# پروژه اولیه
پروژه اولیه را از
[این لینک](/contest/assignments/35059/download_problem_initial_project/125899/)
دانلود کنید.
<details class="green">
<summary>
ساختار فایلهای این پروژه به این صورت است
</summary>
```
snapp-shekarstoon
├── package.json
├── package-lock.json
├── public
│ └── index.html
├── server
│ ├── data
│ │ └── data.json
│ ├── index.js
│ ├── utils
│ │ └── router.js
│ └── views
│ └── index.html
└── src
├── API
│ └── <mark>index.js</mark>
├── App.js
├── Components
│ ├── Map
│ │ ├── <mark>Map.jsx</mark>
│ │ ├── Relative.jsx
│ │ └── Way.jsx
│ ├── MapWidget
│ │ ├── ClearButton.jsx
│ │ ├── <mark>Distance.jsx</mark>
│ │ ├── <mark>MapWidget.jsx</mark>
│ │ └── Row.jsx
│ ├── Marker.jsx
│ ├── Node
│ │ ├── Absolute.jsx
│ │ └── Node.jsx
│ └── UI
│ └── Title.jsx
├── hooks
│ ├── <mark>useAPI.jsx</mark>
│ ├── <mark>useMap.jsx</mark>
│ ├── <mark>useMarker.jsx</mark>
│ └── <mark>useNavigator.jsx</mark>
├── index.js
├── styles
│ ├── absolute.scss
│ ├── btn.scss
│ ├── marker.scss
│ ├── relative.scss
│ ├── row.scss
│ └── title.scss
└── utils
├── <mark>calcDistance.js</mark>
└── <mark>calcPositionCSS.js</mark>
```
</details>
<details class="brown">
<summary>
راه اندازی پروژه
</summary>
- ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
- سپس با اجرای دستور `npm i` پکیج های مورد نیاز را نصب نمایید
- با اجرای دستور `node server/index.js` سرور را اجرا نمایید.
- با اجرای دستور `npm start` پروژه را اجرا نمایید
</details>
# جزئیات
![sample_gif](https://quera.ir/qbox/view/GfQRWosdgA/map_shekar.gif)
شکرستون شهری است به شدت رو به رشد به همین جهت مهیار تصمیم گرفته است که به این شهر نقل مکان کند که اندک سهمی در رشد این شهر نیز داشته باشد. اما در این میان خبری بسیار مسرت بخش به مهیار رسید که بازیگر افسانه ای نقش ژنرال کنوبی در سری فیلم های جنگ ستارگان قصد سفر و بازدید به شکرستون را کرده است برای همین مهیار تصمیم گرفت که شغلش را از از برنامه نویس به به برنامه ریز تورهای سیاحتی تغییر دهد. اما قبل از این تصمیم کلان مهیار متوجه مشکل زیرساخت تکنولوژیکی شدیدی در شکرستون شد که حتی گریبانگیر تیم اسنپ نیز شده بود.
اسنپ در این شهر برخلاف سایر شهرها هیچ نقشه ای از آن ندارد و مردم شکرستون صرفا با وارد کردن نام محله های مبدا و مقصد درخواست تاکسی میکنند. فلذا مهیار از بچه های عمران و شهرسازی دانشگاه صنعتی شکرستون درخواست کرد که در سریع ترین زمان ممکن نقشه ای از راه های سطح شهر تهیه کنند تا تاکسی اینترنتی خودش را به بازار شکرستون عرضه کند. در گام بعدی مهیار API شکرستون را بر اساس اطلاعات جمع آوری شده، طراحی کرده است که اطلاعات زیر را بر میگرداند:
- نقاط ضروری برای رسم راه ها برحسب طول و عرض جغرافیایی (بر حسب درجه) که از طریق api/node قابل دسترس است.
- مربع در برگیرنده شکرستون که شامل بیشترین و کمترین، طول و عرض جغرافیایی این شهر می باشد و از طریق `api/bounds` قابل دسترسی است.
- همچنین شیوه اتصال نقاط در `api/way` قابل دریافت است که در قالب آرایه ای از شی های برگردانده می شود.
وسیله نقلی اصلیه شکرستون برای مقابله با اثرات زیست محیطی، زیباسازی شهر و آوردن خنده بر لب مردم شکرستون و جذب توریست های داخلی و خارجی، بر خلاف بسیاری از کلانشهرا که ماشین های لاکچری را ترجیح میدهند، حیوان دوست داشتنی خر می باشد، اما تیم اسنپ با خرها مشکلی اساسی داشت که البتی ناشی از عدم وجود نقشه نیز بود، آن هم این بود که در اکثر مواقع با وجود اینکه خرها مسافر را به مقصد میرساندند اما از هر مسیری که دلشان میخواست حرکت میکردند که نه تنها کوتاه ترین مسیر نبود بلکه طولانی ترین مسیر نیز بود. نکته جالب دیگر شکرستون این است که تمامی راه های موجود در این شهر دوطرفه هستند. به همین جهت مهیار تصمیم گرفته است نام استارتاپش را "خرنپ" بگذارد.
مهیار در ابتدا به اشتباه گمان میکرد پیاده سازی و اجرای الگوریتم های مسیریابی در سمت مرورگر کار درست و عقلانی است اما پس از صحبتهایی مفصل با علی آقا که از دانشجویان ممتاز دانشگاه صنعتی شکرستون می باشند، متوجه شد که کار اشتباهی است و تصمیم گرفت پیشنهاد علی آقا را مبنی بر اینکه مسیریابی در سمت سرور انجام بگیرد را پذیرفت. لکن برای پیدا کردن یک الگوریتم مسیریابی خوب تصمیم گرفت پیش یکی از اساتید پیشگام در حوزه گراف دانشگاه شکرستون یعنی جناب دکتر دیکسترا برود که متاسفانه ایشان به مسافرت رفته بود، به همین جهت او به الگوریتم BFS بسنده کرد و جهت استفاده از آن آدرس `api/route` را به آن اختصاص داد که با فرستادن شناسه مبدا و مقصد در قالب متد `POST`، کوتاه ترین مسیر (یا حداقل از مسیر خرها بسیار کوتاه تر) را در قالب آرایه ای شامل شناسه Node ها را بر میگرداند.
همچنین او از شما خواسته سیستم برای گذاشتن نشان (Marker) روی نقشه نیز برنامه را به نحوی طراحی کنید تا بتواند مبدا و مقاصد مورد نظر را به راحتی تعیین نماید که در جلوتر به صورت مفصل آن را توضیح خواهد داد.
از آنجایی که ژنرال کنوبی راس ساعت 6:05PM به شکرستون میرسد مهیار از شما می خواهد تا این زمان برنامه را تکیمل کنید. هرچند طبق گفته های مهیار اسنپ قول داده است که اگر این برنامه در راس زمان مقرر تکیمل بشود، استارتاپ خرنپ را با قیمت خوبی بخرد و شما را نیز به عنوان یک توسعه دهند قابل و توانا استخدام نماید.
ازآنجایی که مهیار درگیر تدارک دیدن خوش آمد گویی ژنرال کنوبی است در گام اول از شما خواسته است که اطلاعات دریافتی از API را در قالب یک نقشه گرافیکی با استفاده از کتابخانه react طراحی نمایید. (قابل به ذکر است که مهیار چند کامپوننت از قبل آماده شده برای راحت تر شدن استایل دهی پروژه و همچنین تسریع در روند توسعه آن تدارک دیده است که جزییات هریک را در قالب یک داکیومنت در اختیار شما می گذارد).
همچنین مهیار با توجه به ضیق وقت، سلیب را که از تیم لیدهای بسیار خوب در توسعه نرم افزار، دادن نقشه راه و روند طراحی نرم افزار است را استخدام کرده که نرم افزار نهایی علاوه بر دیپلوی شدن در وقت موعد، maintainable و قابل توسعه بیشتر در آینده نه چندان دور باشد و هم چنین روند توسعه درست و اصولی را برای شما مشخص نماید.
# نکات سیستم داوری
برای ارزیابی درست تمارین شما رعایت موارد زیر الزامی است:
- برای دریافت مکان نشانگر mouse حتما از clientX و clientY استفاده کنید.
- هوک useMap به طور مستقیم ارزیابی نمی شود و فقط جهت سهولت در تکمیل تمرین می باشد
- کامپوننت MapWidget به طور مستقیم ارزیابی نمی شود و فقط جهت سهولت در تکمیل تمرین می باشد.
# موارد خواسته شده
<details class="yellow">
<summary>
تابع ``utils/calcDistance.js``
</summary>
در این فایل یک تابع هم نام با فایل وجود دارد که وظیفه تکمیل آن نیز بر عهده شما می باشد.به عنوان ورودی دو نقطه را که هر نقطه شامل طول و عرض جغرافیایی هستند را دریافت می کند و فاصله آن ها را محاسبه کرده و بر میگرداند. برای محاسبه فواصل درون شهری با دقت نسبتا خوبی میتوان از فرمول هاورسین (Haversian) استفاده نمود، که بیان ریاضی این به شرح زیر است.
https://quera.ir/contest/assignments/36266/problems/125390
$$ R = 6371000 $$
$$ \phi = latitude\hspace{5mm}\lambda = longitude $$
$$ \Delta\phi = \phi_2 - \phi_1 $$
$$ \Delta\lambda = \lambda_2 - \lambda_1 $$
$$ a = sin^2(\Delta\phi/2) + cos(\phi_1).cos(\phi_2).sin^2(\Delta\lambda/2) $$
$$ c = 2 . atan2(\sqrt{a}.\sqrt{1- a}) $$
$$ d = R.c $$
که R بیانگر شعاع زمین برحسب متر و d بیانگر فاصله بین دو نقطه برحسب متر می باشد.
نکته: دقت شود که atan2 مفهوم کاملا متفاوتی نسب به تانژات معکوس دارد( فی الواقع بیان میزان اختلاف درجه نقطه ای دلخواه بر روی صفحه با محور افقی می باشد)
</details>
<details class="yellow">
<summary>
تابع ``utils/calcPositionCss.js``
</summary>
در این فایل یک تابع هم نام با فایل وجود دارد که وظیفه تکمیل آن نیز بر عهده شما می باشد.به عنوان ورودی node, bound را میگیرد و مکان node را برحسب درصد نسبت به یکی left/right در راستای محور افقی و top/bottom در راستای محور عمودی بیان میکند.(دقت کنید که شکل نهایی خروجی باید مطابق گیف باشد و دارای هیچ چرخش اضافه ای نباشد)
راهنمایی I : اگر بخواهیم مولفه های موقعیت جغرافییایی را در قالب دستگاه کارتزین تصور کنیم، عرض جغرافیایی (longitude) معادل x و طول جفرافیایی (latitude) معادل y می شود.
راهنمایی II : راه های زیادی برای این نگاشت وجود دارد که بر حسب فرمول های ریاضی پیچیده با در نظر گرفتن کره ای بودن زمین می باشد، اما چون وقت محدود است مهیار بهبود این امر را به آینده موکول کرده و از دوست ریاضیدانش جان فون نیومان درخواست کمک کرده است که در فاز بعدی بهبود این ضعف نیز بر طرف شود، اما علی الحساب جان فون نیومان پیشنهاد کرده است که برای تخمین مکان نقطه بر روی صفحه، این امر محاسبه شود که این نقطه چه کسری از اضلاع مستطیل در برگیرنده شکرستون می باشد، برای مثال:
![desc](https://quera.ir/qbox/view/9lGpeeY3OU/dist.png)
</details>
<details class="green">
<summary>
فایل `api/index.js`
</summary>
در این فایل شما باید 4 تابع را به نحوی پیاده سازی کنید که اطلاعات هر مسیر را از سرور دریافت کند
- تابع :`getBounds` برای دریافت اطلاعات bounds شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
```json
{
"minlat": "123.987",
"minlon": "5642.12354",
"maxlat": "123.12321",
"maxlon": "1245.456"
}
```
- تابع `getNodes`: برای دریافت موقعیت های جغرافیایی شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
```json
[
{
"id": "942401067",
"lat": "33.5031685",
"lon": "51.9268632"
},
{
"id": "942401068",
"lat": "33.5101294",
"lon": "51.9202060"
}
]
```
- تابع `getWays`: برای دریافت اطلاعات مسیرهای شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
```json
[
{
"id": "80818771",
"nd": [
"942401300",
"942401130",
"5202757684",
"3539769753",
"3539769746",
"5202757588",
"5202757591",
"5202757579",
"5202704676",
"942401196",
"942401359",
"2989992895"
]
}
]
```
- تابع :`getPath` که با فرستادن شناسه(id) مبدا و مقصد در قالب یک درخواست از نوع POST اطلاعات مسیر را دریافت می کند.
```json
// REQUEST POST
{
"src": "5202757608", // SOURCE NODE ID
"dst": "5202757524" // DESTINATION NODE ID
}
// RESPONSE
{
"path": [
"5202757608",
"5202757609",
"5202757610",
"5202757611",
"5202757630",
"5202757629",
"5202757541",
"5202757540",
"5202757539",
"5202757538",
"5202757550",
"5202757537",
"5202757536",
"5202757542",
"5202757535",
"5202757547",
"5202757534",
"942401095",
"5202757522",
"942401261",
"942401291",
"5202757524"
]
}
```
</details>
<details class="blue">
<summary>
هوک `useAPI`
</summary>
هدف از این هوک استفاده از توابع ذکر شده و ذخیره مقادیر دریافتی در سه state مجزا با نام های nodes, ways, bounds می باشد، در نهایت این هوک تنها این مقادیر این سه state را بر میگرداند.
</details>
<details class="blue">
<summary>
هوک `useMarker`
</summary>
هدف از طراحی این هوک به شرح زیر است:
- گذاشتن نشان روی نقشه
- محاسبه نزدیک ترین node به نشان گذاشته شده
- نمایش دادن نشان ها بر روی صفحه
این هوک به عنوان ورودی یک reference و اطلاعات نقشه را دریافت می کند و دارای دو state تحت عنوانه:
- `marker` برای ذخیره مکان نشان ها می باشد
- `Cords` برای ذخیره نزدیک ترین node به نشان وارد شده
و به عنوان خروجی توابع و استیت زیر را بر میگرداند:
- `addMarker` که بعنوان ورودی شی event را دریافت کرده و یک marker, cords جدید اضافه می کند
- `deleteMarker` هیچ ورودی نمی گیرد و صرفا state ها را خالی میکند
- `renderMarker` نشان ها را با استفاده از کامپوننت`Marker.jsx` در قالب یک لیست جهت نمایش بر میگرداند، کامپوننت مارکر به عنوان Attribute دو ورودی x, y را میگیرد که بیانگر موفعیت آن در صفحه متناسبش می باشد.
- `cords` که شامل نزدیک ترین Node به هر نشان است.
- نکته: محدودیتی در تعداد نشان ها وجود ندارد.
</details>
<details class="blue">
<summary>
هوک `useNavigator `
</summary>
این هوک به عنوان ورودی اطلاعات نقشه و اطلاعات جغرافیایی نشان ها را دریافت میکند و بعنوان خروجی تالعی تحت عنوان `renderWays` را بر میگرداند که وظیفه اصلی آن نمایش راه هاست. سپس با استفاده از `API` طراحی شده در بخش قبل و با استفاده از نشان های دریافتی مسیر متناسب گذرنده میان نشان ها را دریافت میکند. برای نمایش مسیر شما باید در صورتی که راه در در حال رسم بخشی از مسیر پیش بینی شده بود پراپرتی مناسب را به آن پاس دهید.
همچنین تابع دیگری تحت عنوان `getDistance` نیز برمیگرداند که مسافت طی شده را بر حسب متر با استفاده از تابع `calcDistance` محاسبه کرده و حاصل آن را بر میگرداند.
</details>
<details class="blue">
<summary>
هوک `useMap`
</summary>
هدف از طراحی این هوک استفاده از سه هوک طراحی شده قبلی میباشد و به عنوان ورودی یک `reference` دریافت میکند و توابع زیر را به عنوان خروجی بر میگرداند.
- تابعی تحت عنوان `renderMap` برای رندر کردن تمام المان ها
- تابع اضافه کردن نشان بر روی نقشه
- تابع حذف نشان های روی نقشه
</details>
</details>
<details class="red">
<summary>
کامپوننت `<Distance />`
</summary>
هدف از طراحی این کامپوننت نشان دادن مسافت پیموده شده با دقت دو رقم اعشار، نشان ها میباشد که باید درون یک تگ ```span``` به صورت زیر نشان داده شود:
```
distance = 1.25 km
```
در صورتی که فاصله قابل تبدیل به km بود باید به صورت km ای نشان داده شود (یعنی اگر بزرگتر از 1000 متر بود) در غیر این صورت به صورت همان متر نشان داده شود:
```
distance = 924.56 m
```
که مقدار مسافت پیموده شده برحسب متر در قالب prop ای تحت عنوان distance پاس داده میشود:
```jsx
<Distance distance={123.56} />
```
</details>
</details>
<details class="red">
<summary>
کامپوننت `<MapWidget />`
</summary>
این کامپوننت دو پراپ تحت عنوان های deleteMarkers که یک تابع است که عمل پاک کردن مارکر ها را انجام میدهد و دیگری فاصله بر حسب متر یا همان distance می باشد.
حال شما باید این کامپوننت را به نحوی تکمیل کنید که پراپها به نحو درست به کامپوننت های ```<Distance />``` و ```<ClearButton />``` پاس داده شوند و هردوی آنها درون ```<Row> </Row>``` قرار بگیرند
</details>
</details>
<details class="red">
<summary>
کامپوننت `<Map />`
</summary>
در گام آخر در این کامپوننت شما باید با استفاده از هوک ```useMap``` طراحی شده نقشه را در درون ```<Relative/>``` با کلاس ```map``` نمایش دهید و همچنین در صورتی که بر روی آن کلیک شد باید یک نشان به مجموعه نشان ها اضافه شود.
و یک کامپوننت دیگر یعنی همان ```<MapWidget />``` با پراپ های مطلوب نمایش داده شود.
و همچنین هردوی این کامپوننتها باید درون تگ ```<div>``` قرار بگیرند
</details>
# موارد پیاده شده
</details>
<details class="blue">
<summary>
کامپوننت `<Marker/>`
</summary>
به عنوان پراپهای ورودی متغییرهای x و y را که بیانگر فاصله از سمت چپ و بالا می باشند دریافت میکند، و یک المان به صورت absolute به عنوان marker را نمایش میدهد
</details>
</details>
<details class="green">
<summary>
کامپوننت `<Row/>`
</summary>
یک ردیف ایجاد می کنید و هیچ ورودی دریافت نمی کند
</details>
</details>
<details class="green">
<summary>
کامپوننت `<ClearButton />`
</summary>
به عنوان ورودی یک تابع onClick میگیرد
</details>
</details>
<details class="red">
<summary>
کامپوننت `<Relative/>`
</summary>
یک المان با استایل relative نمایش میدهد
</details>
</details>
<details class="red">
<summary>
کامپوننت `<Way />`
</summary>
به عنوان ورودی شناسه src و dst را دریافت کرده و متغیر bool تحت عنوان isPath نیز دریافت میکند که بیانگر مسیر بودن یا نبودن می باشد
</details>
</details>
<details class="yellow">
<summary>
کامپوننت `<Node />`
</summary>
به عنوان ورودی طول و عرض جفرافیایی را در قالب lat و lon دریافت کرده علاوه بر آن مربع در برگیرنده را تحت عنوان bounds دریافت کرده و همچنین شناسه موقعیت جغرافیایی را نیز دریافت میکند.
</details>