پروژه اولیه
پروژه اولیه را از این لینک دانلود کنید.
ساختار فایلهای این پروژه به این صورت است
snapp-shekarstoon
├── package.json
├── package-lock.json
├── public
│ └── index.html
├── server
│ ├── data
│ │ └── data.json
│ ├── index.js
│ ├── utils
│ │ └── router.js
│ └── views
│ └── index.html
└── src
├── API
│ └── index.js
├── App.js
├── Components
│ ├── Map
│ │ ├── Map.jsx
│ │ ├── Relative.jsx
│ │ └── Way.jsx
│ ├── MapWidget
│ │ ├── ClearButton.jsx
│ │ ├── Distance.jsx
│ │ ├── MapWidget.jsx
│ │ └── Row.jsx
│ ├── Marker.jsx
│ ├── Node
│ │ ├── Absolute.jsx
│ │ └── Node.jsx
│ └── UI
│ └── Title.jsx
├── hooks
│ ├── useAPI.jsx
│ ├── useMap.jsx
│ ├── useMarker.jsx
│ └── useNavigator.jsx
├── index.js
├── styles
│ ├── absolute.scss
│ ├── btn.scss
│ ├── marker.scss
│ ├── relative.scss
│ ├── row.scss
│ └── title.scss
└── utils
├── calcDistance.js
└── calcPositionCSS.js
راه اندازی پروژه
-
ابتدا پروژهی اولیه را دانلود و از حالت فشرده خارج کنید.
-
سپس با اجرای دستور
npm i
پکیج های مورد نیاز را نصب نمایید -
با اجرای دستور
node server/index.js
سرور را اجرا نمایید. -
با اجرای دستور
npm start
پروژه را اجرا نمایید
جزئیات
شکرستون شهری است به شدت رو به رشد به همین جهت مهیار تصمیم گرفته است که به این شهر نقل مکان کند که اندک سهمی در رشد این شهر نیز داشته باشد. اما در این میان خبری بسیار مسرت بخش به مهیار رسید که بازیگر افسانه ای نقش ژنرال کنوبی در سری فیلم های جنگ ستارگان قصد سفر و بازدید به شکرستون را کرده است برای همین مهیار تصمیم گرفت که شغلش را از از برنامه نویس به به برنامه ریز تورهای سیاحتی تغییر دهد. اما قبل از این تصمیم کلان مهیار متوجه مشکل زیرساخت تکنولوژیکی شدیدی در شکرستون شد که حتی گریبانگیر تیم اسنپ نیز شده بود.
اسنپ در این شهر برخلاف سایر شهرها هیچ نقشه ای از آن ندارد و مردم شکرستون صرفا با وارد کردن نام محله های مبدا و مقصد درخواست تاکسی میکنند. فلذا مهیار از بچه های عمران و شهرسازی دانشگاه صنعتی شکرستون درخواست کرد که در سریع ترین زمان ممکن نقشه ای از راه های سطح شهر تهیه کنند تا تاکسی اینترنتی خودش را به بازار شکرستون عرضه کند. در گام بعدی مهیار API شکرستون را بر اساس اطلاعات جمع آوری شده، طراحی کرده است که اطلاعات زیر را بر میگرداند:
-
نقاط ضروری برای رسم راه ها برحسب طول و عرض جغرافیایی (بر حسب درجه) که از طریق api/node قابل دسترس است.
-
مربع در برگیرنده شکرستون که شامل بیشترین و کمترین، طول و عرض جغرافیایی این شهر می باشد و از طریق
api/bounds
قابل دسترسی است. -
همچنین شیوه اتصال نقاط در
api/way
قابل دریافت است که در قالب آرایه ای از شی های برگردانده می شود.
وسیله نقلی اصلیه شکرستون برای مقابله با اثرات زیست محیطی، زیباسازی شهر و آوردن خنده بر لب مردم شکرستون و جذب توریست های داخلی و خارجی، بر خلاف بسیاری از کلانشهرا که ماشین های لاکچری را ترجیح میدهند، حیوان دوست داشتنی خر می باشد، اما تیم اسنپ با خرها مشکلی اساسی داشت که البتی ناشی از عدم وجود نقشه نیز بود، آن هم این بود که در اکثر مواقع با وجود اینکه خرها مسافر را به مقصد میرساندند اما از هر مسیری که دلشان میخواست حرکت میکردند که نه تنها کوتاه ترین مسیر نبود بلکه طولانی ترین مسیر نیز بود. نکته جالب دیگر شکرستون این است که تمامی راه های موجود در این شهر دوطرفه هستند. به همین جهت مهیار تصمیم گرفته است نام استارتاپش را "خرنپ" بگذارد.
مهیار در ابتدا به اشتباه گمان میکرد پیاده سازی و اجرای الگوریتم های مسیریابی در سمت مرورگر کار درست و عقلانی است اما پس از صحبتهایی مفصل با علی آقا که از دانشجویان ممتاز دانشگاه صنعتی شکرستون می باشند، متوجه شد که کار اشتباهی است و تصمیم گرفت پیشنهاد علی آقا را مبنی بر اینکه مسیریابی در سمت سرور انجام بگیرد را پذیرفت. لکن برای پیدا کردن یک الگوریتم مسیریابی خوب تصمیم گرفت پیش یکی از اساتید پیشگام در حوزه گراف دانشگاه شکرستون یعنی جناب دکتر دیکسترا برود که متاسفانه ایشان به مسافرت رفته بود، به همین جهت او به الگوریتم BFS بسنده کرد و جهت استفاده از آن آدرس api/route
را به آن اختصاص داد که با فرستادن شناسه مبدا و مقصد در قالب متد POST
، کوتاه ترین مسیر (یا حداقل از مسیر خرها بسیار کوتاه تر) را در قالب آرایه ای شامل شناسه Node ها را بر میگرداند.
همچنین او از شما خواسته سیستم برای گذاشتن نشان (Marker) روی نقشه نیز برنامه را به نحوی طراحی کنید تا بتواند مبدا و مقاصد مورد نظر را به راحتی تعیین نماید که در جلوتر به صورت مفصل آن را توضیح خواهد داد.
از آنجایی که ژنرال کنوبی راس ساعت 6:05PM به شکرستون میرسد مهیار از شما می خواهد تا این زمان برنامه را تکیمل کنید. هرچند طبق گفته های مهیار اسنپ قول داده است که اگر این برنامه در راس زمان مقرر تکیمل بشود، استارتاپ خرنپ را با قیمت خوبی بخرد و شما را نیز به عنوان یک توسعه دهند قابل و توانا استخدام نماید.
ازآنجایی که مهیار درگیر تدارک دیدن خوش آمد گویی ژنرال کنوبی است در گام اول از شما خواسته است که اطلاعات دریافتی از API را در قالب یک نقشه گرافیکی با استفاده از کتابخانه react طراحی نمایید. (قابل به ذکر است که مهیار چند کامپوننت از قبل آماده شده برای راحت تر شدن استایل دهی پروژه و همچنین تسریع در روند توسعه آن تدارک دیده است که جزییات هریک را در قالب یک داکیومنت در اختیار شما می گذارد).
همچنین مهیار با توجه به ضیق وقت، سلیب را که از تیم لیدهای بسیار خوب در توسعه نرم افزار، دادن نقشه راه و روند طراحی نرم افزار است را استخدام کرده که نرم افزار نهایی علاوه بر دیپلوی شدن در وقت موعد، maintainable و قابل توسعه بیشتر در آینده نه چندان دور باشد و هم چنین روند توسعه درست و اصولی را برای شما مشخص نماید.
نکات سیستم داوری
برای ارزیابی درست تمارین شما رعایت موارد زیر الزامی است:
- برای دریافت مکان نشانگر mouse حتما از clientX و clientY استفاده کنید.
- هوک useMap به طور مستقیم ارزیابی نمی شود و فقط جهت سهولت در تکمیل تمرین می باشد
- کامپوننت MapWidget به طور مستقیم ارزیابی نمی شود و فقط جهت سهولت در تکمیل تمرین می باشد.
موارد خواسته شده
تابع utils/calcDistance.js
utils/calcDistance.js
در این فایل یک تابع هم نام با فایل وجود دارد که وظیفه تکمیل آن نیز بر عهده شما می باشد.به عنوان ورودی دو نقطه را که هر نقطه شامل طول و عرض جغرافیایی هستند را دریافت می کند و فاصله آن ها را محاسبه کرده و بر میگرداند. برای محاسبه فواصل درون شهری با دقت نسبتا خوبی میتوان از فرمول هاورسین (Haversian) استفاده نمود، که بیان ریاضی این به شرح زیر است.
$$ 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 مفهوم کاملا متفاوتی نسب به تانژات معکوس دارد( فی الواقع بیان میزان اختلاف درجه نقطه ای دلخواه بر روی صفحه با محور افقی می باشد)
تابع utils/calcPositionCss.js
utils/calcPositionCss.js
در این فایل یک تابع هم نام با فایل وجود دارد که وظیفه تکمیل آن نیز بر عهده شما می باشد.به عنوان ورودی node, bound را میگیرد و مکان node را برحسب درصد نسبت به یکی left/right در راستای محور افقی و top/bottom در راستای محور عمودی بیان میکند.(دقت کنید که شکل نهایی خروجی باید مطابق گیف باشد و دارای هیچ چرخش اضافه ای نباشد)
راهنمایی I : اگر بخواهیم مولفه های موقعیت جغرافییایی را در قالب دستگاه کارتزین تصور کنیم، عرض جغرافیایی (longitude) معادل x و طول جفرافیایی (latitude) معادل y می شود.
راهنمایی II : راه های زیادی برای این نگاشت وجود دارد که بر حسب فرمول های ریاضی پیچیده با در نظر گرفتن کره ای بودن زمین می باشد، اما چون وقت محدود است مهیار بهبود این امر را به آینده موکول کرده و از دوست ریاضیدانش جان فون نیومان درخواست کمک کرده است که در فاز بعدی بهبود این ضعف نیز بر طرف شود، اما علی الحساب جان فون نیومان پیشنهاد کرده است که برای تخمین مکان نقطه بر روی صفحه، این امر محاسبه شود که این نقطه چه کسری از اضلاع مستطیل در برگیرنده شکرستون می باشد، برای مثال:
فایل api/index.js
api/index.js
در این فایل شما باید 4 تابع را به نحوی پیاده سازی کنید که اطلاعات هر مسیر را از سرور دریافت کند
- تابع :
getBounds
برای دریافت اطلاعات bounds شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
{
"minlat": "123.987",
"minlon": "5642.12354",
"maxlat": "123.12321",
"maxlon": "1245.456"
}
- تابع
getNodes
: برای دریافت موقعیت های جغرافیایی شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
[
{
"id": "942401067",
"lat": "33.5031685",
"lon": "51.9268632"
},
{
"id": "942401068",
"lat": "33.5101294",
"lon": "51.9202060"
}
]
- تابع
getWays
: برای دریافت اطلاعات مسیرهای شهر شکرستون که درخواستی از نوع GET به آدرس مورد نظر ارسال میکند. نمونه داده دریافتی به شرح زیر است:
[
{
"id": "80818771",
"nd": [
"942401300",
"942401130",
"5202757684",
"3539769753",
"3539769746",
"5202757588",
"5202757591",
"5202757579",
"5202704676",
"942401196",
"942401359",
"2989992895"
]
}
]
- تابع :
getPath
که با فرستادن شناسه(id) مبدا و مقصد در قالب یک درخواست از نوع POST اطلاعات مسیر را دریافت می کند.
// 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"
]
}
هوک useAPI
useAPI
هدف از این هوک استفاده از توابع ذکر شده و ذخیره مقادیر دریافتی در سه state مجزا با نام های nodes, ways, bounds می باشد، در نهایت این هوک تنها این مقادیر این سه state را بر میگرداند.
هوک useMarker
useMarker
هدف از طراحی این هوک به شرح زیر است:
-
گذاشتن نشان روی نقشه
-
محاسبه نزدیک ترین node به نشان گذاشته شده
-
نمایش دادن نشان ها بر روی صفحه
این هوک به عنوان ورودی یک reference و اطلاعات نقشه را دریافت می کند و دارای دو state تحت عنوانه:
-
marker
برای ذخیره مکان نشان ها می باشد -
Cords
برای ذخیره نزدیک ترین node به نشان وارد شده
و به عنوان خروجی توابع و استیت زیر را بر میگرداند:
-
addMarker
که بعنوان ورودی شی event را دریافت کرده و یک marker, cords جدید اضافه می کند -
deleteMarker
هیچ ورودی نمی گیرد و صرفا state ها را خالی میکند -
renderMarker
نشان ها را با استفاده از کامپوننتMarker.jsx
در قالب یک لیست جهت نمایش بر میگرداند، کامپوننت مارکر به عنوان Attribute دو ورودی x, y را میگیرد که بیانگر موفعیت آن در صفحه متناسبش می باشد. -
cords
که شامل نزدیک ترین Node به هر نشان است. -
نکته: محدودیتی در تعداد نشان ها وجود ندارد.
هوک useNavigator
useNavigator
این هوک به عنوان ورودی اطلاعات نقشه و اطلاعات جغرافیایی نشان ها را دریافت میکند و بعنوان خروجی تالعی تحت عنوان renderWays
را بر میگرداند که وظیفه اصلی آن نمایش راه هاست. سپس با استفاده از API
طراحی شده در بخش قبل و با استفاده از نشان های دریافتی مسیر متناسب گذرنده میان نشان ها را دریافت میکند. برای نمایش مسیر شما باید در صورتی که راه در در حال رسم بخشی از مسیر پیش بینی شده بود پراپرتی مناسب را به آن پاس دهید.
همچنین تابع دیگری تحت عنوان getDistance
نیز برمیگرداند که مسافت طی شده را بر حسب متر با استفاده از تابع calcDistance
محاسبه کرده و حاصل آن را بر میگرداند.
هوک useMap
useMap
هدف از طراحی این هوک استفاده از سه هوک طراحی شده قبلی میباشد و به عنوان ورودی یک reference
دریافت میکند و توابع زیر را به عنوان خروجی بر میگرداند.
-
تابعی تحت عنوان
renderMap
برای رندر کردن تمام المان ها -
تابع اضافه کردن نشان بر روی نقشه
-
تابع حذف نشان های روی نقشه
کامپوننت <Distance />
<Distance />
هدف از طراحی این کامپوننت نشان دادن مسافت پیموده شده با دقت دو رقم اعشار، نشان ها میباشد که باید درون یک تگ span
به صورت زیر نشان داده شود:
distance = 1.25 km
در صورتی که فاصله قابل تبدیل به km بود باید به صورت km ای نشان داده شود (یعنی اگر بزرگتر از 1000 متر بود) در غیر این صورت به صورت همان متر نشان داده شود:
distance = 924.56 m
که مقدار مسافت پیموده شده برحسب متر در قالب prop ای تحت عنوان distance پاس داده میشود:
<Distance distance={123.56} />
کامپوننت <MapWidget />
<MapWidget />
این کامپوننت دو پراپ تحت عنوان های deleteMarkers که یک تابع است که عمل پاک کردن مارکر ها را انجام میدهد و دیگری فاصله بر حسب متر یا همان distance می باشد.
حال شما باید این کامپوننت را به نحوی تکمیل کنید که پراپها به نحو درست به کامپوننت های <Distance />
و <ClearButton />
پاس داده شوند و هردوی آنها درون <Row> </Row>
قرار بگیرند
کامپوننت <Map />
<Map />
در گام آخر در این کامپوننت شما باید با استفاده از هوک useMap
طراحی شده نقشه را در درون <Relative/>
با کلاس map
نمایش دهید و همچنین در صورتی که بر روی آن کلیک شد باید یک نشان به مجموعه نشان ها اضافه شود.
و یک کامپوننت دیگر یعنی همان <MapWidget />
با پراپ های مطلوب نمایش داده شود.
و همچنین هردوی این کامپوننتها باید درون تگ <div>
قرار بگیرند
موارد پیاده شده
کامپوننت <Marker/>
<Marker/>
به عنوان پراپهای ورودی متغییرهای x و y را که بیانگر فاصله از سمت چپ و بالا می باشند دریافت میکند، و یک المان به صورت absolute به عنوان marker را نمایش میدهد
کامپوننت <Row/>
<Row/>
یک ردیف ایجاد می کنید و هیچ ورودی دریافت نمی کند
کامپوننت <ClearButton />
<ClearButton />
به عنوان ورودی یک تابع onClick میگیرد
کامپوننت <Relative/>
<Relative/>
یک المان با استایل relative نمایش میدهد
کامپوننت <Way />
<Way />
به عنوان ورودی شناسه src و dst را دریافت کرده و متغیر bool تحت عنوان isPath نیز دریافت میکند که بیانگر مسیر بودن یا نبودن می باشد
کامپوننت <Node />
<Node />
به عنوان ورودی طول و عرض جفرافیایی را در قالب lat و lon دریافت کرده علاوه بر آن مربع در برگیرنده را تحت عنوان bounds دریافت کرده و همچنین شناسه موقعیت جغرافیایی را نیز دریافت میکند.
ارسال پاسخ برای این سؤال