توی یکی از کارهایی که انجام میدادم نیاز شد که لیستی از مکانهای انتخابی ادمین سایت را روی نقشه نمایش دهم ( نمایش لیست مکانها بر روی گوگل مپ ) به طوری که اگر کاربر روی یکی از اون موارد کلیک کرد اطلاعاتی از ان مکان را نمایش دهد از طرف دیگر لیستی از اون مکانها علاوه بر اینکه باید روی نقشه باشد باید در کنار نقشه نمایش داده شود :/
قبلا در رابطه با سفارشی کردن نقشه گوگل مپ مطلبی نوشته بودم که اگر فقط به این مورد نیاز دارید اون مطلب رو مطالعه کنید کافیه، ولی اگر مواردی که بالاتر اشاره کردم براتون جذابه ادامه مطلب رو بخونید 🙂
با استفاده از گوگل عزیز گشت و گذاری داشتم و مطالب زیادی رو پیدا کردم که کاربران نمونه کدها رو توی JSFiddle گذاشته بودند :
- لیستی از مکانها همراه نمایش اطلاعات – Location list display data – داده ها به صورت json دریافت میشوند.
- نحوه نماش مارکر روی گوگل مپ – display location on google map ,
- Place details ( نیاز به فیلترشکن داره 🙂 )
- Multiple markers on google maps using json and placeid
همه این موارد کاربرد خودشون رو دارند و از مورد اول میشه برای چیزی که مد نظر دارم استفاده کنم، اما پلاگینی نظرم رو به خودش جلب کرد که ترجیح دادم از اون استفاده کنم.
پلاگین MAPBOX :
Mapbox is the location data platform for mobile and web applications. We provide building blocks to add location features like maps, search, and navigation into any experience you create.
We’re changing the way people move around cities and explore our world. Through the apps Mapbox powers, we reach more than 300 million people each month.
برای استفاده از این نقشه ابتدا باید توی وبسایت ثبت نام کنید و بعد Accrss Token رو دریافت کنید تا بتونید توی پروژه از آن استفاده کنید.
ویژگی فوق العاده ای که داره میتونیم نقشه رو برای پروژه روی دیوایسهای مختلف هم اینتگریت کنیم :
قابلیت جالبی که داره میتوانیم نقشه موردنظرمون رو به سبکی که دوست داریم دیزاین کنیم و کافیه توی کدها مسیر دیزاین مورد نظر را قرار دهیم تا از این دیزاین استفاده کنه.
همچنین میتوانید آمار بازدید نقشههایی که ایجاد میکنید را مشاهده کنید.
استفاده از MapBox :
برای استفاده از این پلاگین مطابق مراحل این لینک پیش برید.
پس از قرار دادن کدهای کتابخانه mapbox از طریق کد زیر در صفحه پروژه میتونید نقشه mapbox رو فعال کنید.
mapboxgl.accessToken = 'کد توکن را اینجا قرار دهید.'; // This adds the map to your page var map = new mapboxgl.Map({ // container id specified in the HTML container: 'map', // style URL style: 'mapbox://styles/mapbox/light-v9', // initial position in [lon, lat] format center: [-77.034084, 38.909671], // initial zoom zoom: 14 });
نمایش لیست مکانها :
برای نمایش لیست مکانها به یک لیست با فرمت JSON نیاز داریم که داخل متغیری قرار گیرد تا این مقادیر را توی لایه قرار دهیم که روی نقشه قرار گیرند.
متغیری به نام stores تعریف میکنیم و لیست داده ها را داخل آن قرار میدهیم. ساختار آن به صورت تصویر زیر است که داخل لینکی که بالاتر قراردادم میتوانید لیست این داده ها را دانلود کنید و تست کنید.
بعد از تعریف متغیر و قرار دادن اطلاعات مکانها با استفاده از کد زیر آنها را روی نقشه نمایش میدهیم.
map.on('load', function(e) { // Add the data to your map as a layer map.addLayer({ id: 'locations', type: 'symbol', // Add a GeoJSON source containing place coordinates and information. source: { type: 'geojson', data: stores }, layout: { 'icon-image': 'restaurant-15', 'icon-allow-overlap': true, } }); });
در صورتیکه تا اینجا قدم به قدم انجام داده باشید همچین دمویی دارید. دمو اولیه نقشه
قرار دادن لیست مکانها در کنار نقشه :
در سایدبار کناری تگی با آیدی موردنظرمون ( listings ) قرار میدهیم. یعنی ساختار کدهای من تا به اینجای کار به صورت زیر است :
تابع جاوااسکریپت زیر برای ایجاد لیست مکانها را به کدهای اسکریپتی که نوشتیم اضافه میکنیم.
function buildLocationList(data) { // Iterate through the list of stores for (i = 0; i < data.features.length; i++) { var currentFeature = data.features[i]; // Shorten data.feature.properties to just `prop` so we're not // writing this long form over and over again. var prop = currentFeature.properties; // Select the listing container in the HTML and append a div // with the class 'item' for each store var listings = document.getElementById('listings'); var listing = listings.appendChild(document.createElement('div')); listing.className = 'item'; listing.id = 'listing-' + i; // Create a new link with the class 'title' for each store // and fill it with the store address var link = listing.appendChild(document.createElement('a')); link.href = '#'; link.className = 'title'; link.dataPosition = i; link.innerHTML = prop.address; // Create a new div with the class 'details' for each store // and fill it with the city and phone number var details = listing.appendChild(document.createElement('div')); details.innerHTML = prop.city; if (prop.phone) { details.innerHTML += ' · ' + prop.phoneFormatted; } } }
در واقع این فانکشن یک مقداری دریافت میکنه و با توجه به اون مقدار لیستی ایجاد میکنه و به تگ لیست ما اضافه میکنه. اگر متغیر stores رو یادتون باشه که دادهها رو به صورت JSON ذخیره کرده بود را باید به این فانکشن پاس بدیم. کافیه در کدهایی که ما لایه را ایجاد کردیم ( addLayer ) بعد از آن فانکشن را فراخوانی و مقدار sores را بهش پاس بدیم.
buildLocationList(stores);
تا به اینجای کار میتوانید دموی مورد نظرتون رو مشاهده کنید.
نقشه را جذابتر و قابل تعاملتر کنیم!
کاری که میخواهیم انجام دهیم به این صورت است که وقتی بر روی لیست کناری نقشه کلیک شد، نقشه آن مکان را با فوکوس روی آن نمایش دهد.
دو تابع برای قابل تعامل کردن نقشه :
با کلیک کاربر بر روی لیست کنار نقشه ابتدا نقشه به مکان درست هدایت میشود و سپس یک پاپ آپ برای نمایش اطلاعات آن مکان نمایش داده میشود. هر دو تابع وقتی که کاربر بر روی یکی از مکانها کلیک میکند اجرا میشود.
function flyToStore(currentFeature) { map.flyTo({ center: currentFeature.geometry.coordinates, zoom: 15 }); }
function createPopUp(currentFeature) {
var popUps = document.getElementsByClassName('mapboxgl-popup');
// Check if there is already a popup on the map and if so, remove it
if (popUps[0]) popUps[0].remove();
var popup = new mapboxgl.Popup({ closeOnClick: false })
.setLngLat(currentFeature.geometry.coordinates)
.setHTML('<h3>Sweetgreen</h3>' +
'<h4>' + currentFeature.properties.address + '</h4>')
.addTo(map);
}
میتوانید به پاپآپی که بر روی نقشه نمایش داده میشود استایل دهید. بر روی این لینک کلیک کنید تا کدهای آن را مشاهده نمائید.
برای اینکه نقشه به درستی روی مرورگرهای قدیمی نیز کار کنه لازمه کد زیر رو در ابتدای اسکریپت اضافه کنید تا تابع remove() به درستی کار کند.
// This will let you use the .remove() function later on
if (!('remove' in Element.prototype)) {
Element.prototype.remove = function() {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
};
}
نکته : توابعی که تا اینجا معرفی شدند برای اینکه متغیرهای map, link شناسایی شوند باید داخل تابع buildLocationList که بالاتر این تابع رو اضافه کرده بودیم اضافه شوند.
پس از اینکه توابع فوق را اضافه کردیم باید آنها را فراخوانی کنیم. در دو حالت این فراخوانی ایجاد میشود، در حالت اول وقتی روی لینک سایدبار کلیک میشود و در حالت دوم وقتی روی آیکون رو نقشه کلیک میشود. در هر دوحالت حرکت نقشه و نمایش پاپآپ را خواهیم داشت. پس Event های زیر را به انتهای تابع buildLocationList اضافه میکنیم.
وقتی که روی لینک سایدبار کلیک میشود :
// Add an event listener for the links in the sidebar listing link.addEventListener('click', function(e) { // Update the currentFeature to the store associated with the clicked link var clickedListing = data.features[this.dataPosition]; // ۱. Fly to the point associated with the clicked link flyToStore(clickedListing); // ۲. Close all other popups and display popup for clicked store createPopUp(clickedListing); // ۳. Highlight listing in sidebar (and remove highlight for all other listings) var activeItem = document.getElementsByClassName('active'); if (activeItem[0]) { activeItem[0].classList.remove('active'); } this.parentNode.classList.add('active'); });
وقتی روی آیکون روی نقشه کلیک میشود :
// Add an event listener for when a user clicks on the map map.on('click', function(e) { // Query all the rendered points in the view var features = map.queryRenderedFeatures(e.point, { layers: ['locations'] }); if (features.length) { var clickedPoint = features[0]; // ۱. Fly to the point flyToStore(clickedPoint); // ۲. Close all other popups and display popup for clicked store createPopUp(clickedPoint); // ۳. Highlight listing in sidebar (and remove highlight for all other listings) var activeItem = document.getElementsByClassName('active'); if (activeItem[0]) { activeItem[0].classList.remove('active'); } // Find the index of the store.features that corresponds to the clickedPoint that fired the event listener var selectedFeature = clickedPoint.properties.address; for (var i = 0; i < stores.features.length; i++) { if (stores.features[i].properties.address === selectedFeature) { selectedFeatureIndex = i; } } // Select the correct list item using the found index and add the active class var listing = document.getElementById('listing-' + selectedFeatureIndex); listing.classList.add('active'); } });
نکته : addEventListenerی که خود داکیومنت سایت mapbox نوشته بود درست کار نمیکرد و با توجه به اینکه من توی پروژه از jQuery استفاده کرده بودم به صورت زیر وقتی روی لینک کلیک میشود تابع را فراخوانی میکنم.
$('.item > a').on('click',function(){ // Update the currentFeature to the store associated with the clicked link var clickedListing = data.features[this.dataPosition]; // ۱. Fly to the point associated with the clicked link flyToStore(clickedListing); // ۲. Close all other popups and display popup for clicked store createPopUp(clickedListing); // ۳. Highlight listing in sidebar (and remove highlight for all other listings) var activeItem = document.getElementsByClassName('active'); if (activeItem[0]) { activeItem[0].classList.remove('active'); } this.parentNode.classList.add('active'); });
سفارش کردن نهایی و تغییر مارکر ( آیکون روی نقشه ) :
تاکنون ما بر اساس استایلهایی که داشتیم و فانکشنهایی که معرفی کردیم نقشه را ایجاد کردیم و تمام بخش های آن واضح و روشن است. اکنون برای اینکه ما مارکر سفارشی خود را قرار دهیم. لازم است تغییراتی در کدهای بالا ایجاد کنیم.
اولین مورد باید لایه ای که در ابتدا ساختیم ( map.addLayer ) و مارکر را مشخص کردیم حذف کنیم و کد زیر را جایگزین کنیم.
// This adds the data to the map map.on('load', function (e) { // This is where your '.addLayer()' used to be, instead add only the source without styling a layer map.addSource("places", { "type": "geojson", "data": stores }); // Initialize the list buildLocationList(stores); });
همچنین EventListenerهایی که برای دو فانکشن نوشتیم را حذف میکنیم و با استفاده از آبجکت mapboxgl.Marker() کار را انجام میدهیم. داخل کد جدیدی که مینویسیم کلاس marker به آن اختصاص میدهیم پس به کدهای css خود کد زیر را اضافه میکنیم و آدرس مارکر موردنظرمون رو مشخص میکنیم.
.marker {
border: none;
cursor: pointer;
height: 56px;
width: 56px;
background-image: url(marker.png);
background-color: rgba(0, 0, 0, 0);
}
در نهایت کد زیر را اضافه میکنیم که Event Listner جدید را نیز در آن قرار داده ایم :
// This is where your interactions with the symbol layer used to be // Now you have interactions with DOM markers instead stores.features.forEach(function(marker, i) { // Create an img element for the marker var el = document.createElement('div'); el.id = "marker-" + i; el.className = 'marker'; // Add markers to the map at all points new mapboxgl.Marker(el, {offset: [0, -23]}) .setLngLat(marker.geometry.coordinates) .addTo(map); el.addEventListener('click', function(e){ // ۱. Fly to the point flyToStore(marker); // ۲. Close all other popups and display popup for clicked store createPopUp(marker); // ۳. Highlight listing in sidebar (and remove highlight for all other listings) var activeItem = document.getElementsByClassName('active'); e.stopPropagation(); if (activeItem[0]) { activeItem[0].classList.remove('active'); } var listing = document.getElementById('listing-' + i); listing.classList.add('active'); }); });
مشاهده دموی نهایی نقشه mapbox.
شما میتوانید آخرین ادیت کدهای اسکریپت فوق ( بخش آخر سفارشی شده ) را دانلود و بررسی کنید.
در نهایت من کدهای بالا را بر اساس نیاز خودم تغییر دادم و به تصویر زیر رسیدم، تغییراتی که ایجاد کردم به این صورت بود که با کلیک بر روی هر کدام از مکان ها علاوه بر نمایش اطلاعات، اطلاعات آن لیست در بالاترین قسمت لیست قرار داده میشود.
شما میتوانید روی گیتهاب من دموی کامل را مشاهده نمائید.
در نهایت لینکهای زیر را برای استفاده بیشتر و مطالعه پیشنهاد میکنم: