نمایش لیستی از مکان‌‌ها بر روی نقشه و نمایش اطلاعات آنها

توی یکی از کارهایی که انجام می‌دادم نیاز شد که لیستی از مکانهای انتخابی ادمین سایت را روی نقشه نمایش دهم ( نمایش لیست مکان‌ها بر روی گوگل مپ ) به طوری که اگر کاربر روی یکی از اون موارد کلیک کرد اطلاعاتی از ان مکان را نمایش دهد از طرف دیگر لیستی از اون مکانها علاوه بر اینکه باید روی نقشه باشد باید در کنار نقشه نمایش داده شود :/

قبلا در رابطه با سفارشی کردن نقشه گوگل مپ مطلبی نوشته بودم که اگر فقط به این مورد نیاز دارید اون مطلب رو مطالعه کنید کافیه، ولی اگر مواردی که بالاتر اشاره کردم براتون جذابه ادامه مطلب رو بخونید 🙂

با استفاده از گوگل عزیز گشت و گذاری داشتم و مطالب زیادی رو پیدا کردم که کاربران نمونه کدها رو توی JSFiddle گذاشته بودند :

همه این موارد کاربرد خودشون رو دارند و از مورد اول میشه برای چیزی که مد نظر دارم استفاده کنم، اما پلاگینی نظرم رو به خودش جلب کرد که ترجیح دادم از اون استفاده کنم.

پلاگین MAPBOX :

MAPBOX Plugin

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

قابلیت جالبی که داره می‌توانیم نقشه موردنظرمون رو به سبکی که دوست داریم دیزاین کنیم و کافیه توی کدها مسیر دیزاین مورد نظر را قرار دهیم تا از این دیزاین استفاده کنه.

design mapbox

همچنین میتوانید آمار بازدید نقشه‌هایی که ایجاد می‌کنید را مشاهده کنید.

mapbox statistics

استفاده از 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 تعریف میکنیم و لیست داده ها را داخل آن قرار میدهیم. ساختار آن به صورت تصویر زیر است که داخل لینکی که بالاتر قراردادم میتوانید لیست این داده ها را دانلود کنید و تست کنید.

JSON data list

بعد از تعریف متغیر و قرار دادن اطلاعات مکان‌ها با استفاده از کد زیر آنها را روی نقشه نمایش می‌دهیم.

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,
    }
  });
});

در صورتیکه تا اینجا قدم به قدم انجام داده باشید همچین دمویی دارید. دمو اولیه نقشه

first demo mapbox

قرار دادن لیست مکان‌ها در کنار نقشه :

در سایدبار کناری تگی با آی‌دی موردنظرمون ( listings ) قرار میدهیم. یعنی ساختار کدهای من تا به اینجای کار به صورت زیر است :

structure html code mapbox

تابع جاوااسکریپت زیر برای ایجاد لیست مکان‌ها را به کدهای اسکریپتی که نوشتیم اضافه میکنیم.

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);

تا به اینجای کار میتوانید دموی مورد نظرتون رو مشاهده کنید.

third demo mapbox - display list locations

نقشه را جذابتر و قابل تعاملتر کنیم!

کاری که میخواهیم انجام دهیم به این صورت است که وقتی بر روی لیست کناری نقشه کلیک شد، نقشه آن مکان را با فوکوس روی آن نمایش دهد.

دو تابع برای قابل تعامل کردن نقشه :

با کلیک کاربر بر روی لیست کنار نقشه ابتدا نقشه به مکان درست هدایت می‌شود و سپس یک پاپ آپ برای نمایش اطلاعات آن مکان نمایش داده می‌شود. هر دو تابع وقتی که کاربر بر روی یکی از مکانها کلیک میکند اجرا می‌شود.

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.

شما میتوانید آخرین ادیت کدهای اسکریپت فوق ( بخش آخر سفارشی شده ) را دانلود و بررسی کنید.

در نهایت من کدهای بالا را بر اساس نیاز خودم تغییر دادم و به تصویر زیر رسیدم، تغییراتی که ایجاد کردم به این صورت بود که با کلیک بر روی هر کدام از مکان ها علاوه بر نمایش اطلاعات، اطلاعات آن لیست در بالاترین قسمت لیست قرار داده میشود.

custom mapbox demo

شما می‌توانید روی گیت‌هاب من دموی کامل را مشاهده نمائید.

در نهایت لینک‌های زیر را برای استفاده بیشتر و مطالعه پیشنهاد میکنم: