How to sync the map with a list of places

This tutorial shows how to sync the map with a list of places.

This tutorial will guide you through the process of creating a compilation of locations and synchronizing it with a map. The map will display markers for each of these locations. By following these steps, you will be able to generate a list of places where selecting an item from the list will automatically select the pin on the map. Similarly, when making changes to the map view, the list of places will be updated to only show the locations that are currently visible on the map. Additionally, selecting a place on the map will highlight and select the corresponding item on the list.

Download the shoe sample icon and the selected shoe sample icon, The icon is from the Maps Icons Collection. Download the shoe shops GeoJSON sample data

NPM module setup

  1. Install the npm package.

    
     npm install --save @maptiler/sdk
    
  2. Include the CSS file.

    If you have a bundler that can handle CSS, you can import the CSS

    
     import "@maptiler/sdk/dist/maptiler-sdk.css";
    

    or include it with a <link /> in the head of the document via the CDN

    
     <link href='https://cdn.maptiler.com/maptiler-sdk-js/v4.1.0/maptiler-sdk.css' rel='stylesheet' />
    
  3. Include the following code in your JavaScript file (Example: app.js).

    
     import * as maptilersdk from '@maptiler/sdk';
    
     maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
     const map = new maptilersdk.Map({
       container: 'map', // container's id or the HTML element to render the map
       style: maptilersdk.MapStyle.STREETS,
          
       center: [-0.0921, 51.5108], // starting position [lng, lat]
       zoom: 11, // starting zoom
          
     });
    
  4. Replace YOUR_MAPTILER_API_KEY_HERE with your own API key. Make sure to secure the key before you publish it.

  5. The next is up to you. You can center your map wherever you desire (modifying the starting position) and set an appropriate zoom level (modifying the starting zoom) to match your users’ needs. Additionally, you can change the map’s look (by updating the source URL); choose from a range of visually appealing map styles from our extensive MapTiler standard maps, or create your own to truly differentiate your application.

  6. We are going to modify the structure of the web page to have two main elements: a sidebar (aside) where we will show the list of shoe stores and the main area (main) where we will display the map.

    
     <aside class="sidebar">
       <div class="sidebar-content-info"></div>
     </aside>
     <main class="main">
       <div id="map">
         <button class="btn hidden">Search in this area</button>
       </div>
     </main>
    
  7. Add the CSS classes to create the simple Flexbox Layout for the Sidebar + Main Content Area

    
     body {
       margin: 0;
       padding: 0;
       min-height: 100vh;
       display: flex;
     }
    
     .sidebar {
       min-width: 450px;
       max-width: 450px;
       display: flex;
     }
    
     .main {
       flex-grow: 1;
       display: flex;
     }
    
     #map {
       position: relative;
       width: 100%;
       height: 100%;
     }
    
     .btn {
       display: block;
       position: relative;
       margin: 8px auto;
       height: 40px;
       padding: 10px;
       border: none;
       border-radius: 3px;
       font-size: 12px;
       text-align: center;
       color: #fff;
       background:#3174FF;
       z-index: 2;
     }
    
     .hidden {
       display: none;
     }
    
     .sidebar-content-info {
       display: flex;
       flex-direction: column;
       font-size: 1rem;
       width: 100%;
       word-break: break-word;
       overflow-y: auto;
       max-height: 100vh;
     }
    
  8. Reload the page. Now you should see the app in your browser.

    Flexbox simple layout sidebar + main

  9. Add an event handler for the map load event. You will add code to create a GeoJSON source and a vector layer in this handler. We will also add other map event handlers.

    
     map.on('load', async function() {
       //your code here
    
       //end load function code
     });
    
  10. Add a custom images to the map. You can use any image or download the shoe image and the selected shoe image we used in the tutorial. In the example, these images are in the same folder as the index.html file

    
     //your code here
     const image = await map.loadImage('./shoes.png');
     map.addImage('pinShoe', image.data);
    
     const imageSelected = await map.loadImage('./shoes_selected.png');
     map.addImage('pinShoeSelected', imageSelected.data);
        
     //end load function code
    
  11. Create GeoJSON source. The following snippet creates GeoJSON source hosted on MapTiler (check out the How to Upload GeoJSON to MapTiler tutorial). Publish the dataset and copy the link to the GeoJSON. Download the shoe shop GeoJSON sample data. In this example we will add a random image (in the photo property) to each of the map elements.

    
     const shops = await maptilersdk.data.get('YOUR_MAPTILER_DATASET_ID_HERE');
    
     function randomIntFromInterval(min = 1, max = 5) { // min and max included 
       return Math.floor(Math.random() * (max - min + 1) + min);
     }
    
     //add a random image to the GeoJSON features
     shops.features.forEach(item => {
       item.properties.photo = `./shoe_shop_${randomIntFromInterval()}.webp`;
     })
    
     map.addSource('shops', {
       type: 'geojson',
       generateId: true,
       data: shops
     });
    
     //end load function code
    
  12. Add the vector layer. In this example, we will use the shoe image to display a point layer using a custom PNG icon as a pin.

    
       map.addLayer({
         'id': 'points',
         'type': 'symbol',
         'source': 'shops',
         'layout': {
           'icon-image': 'pinShoe',
         },
         'paint': {}
       });
        
       //end load function code
    
  13. Now you should see the map with pins.

    Map with shoe shop pins

  14. Change the cursor to a pointer when the mouse is over the points layer.

    
     map.on('mouseenter', 'points', (e) => {
       map.getCanvas().style.cursor = 'pointer';
     });
    
     map.on('mouseleave', 'points', (e) => {
       map.getCanvas().style.cursor = '';
     });
    
     //end load function code
    
  15. Create the list with the elements that are displayed on the map.

    
     map.on('render', createListFromSource);
    
     //end load function code
    
  16. Update the list when the map view is changed. With this event, we will have the map view and the list synchronized. Every time the view is updated (zoomed, panned) the “Search in this area” button is displayed. This will allow us to refresh the list and only show the elements that appear on the map.

    
     map.on('moveend', showRefreshListButton);
    
     //end load function code
    
  17. Get the point layer elements when the user clicks on the map. With the click event, the user will be able to select an element on the map. This will allow us to select the same item in the list.

    
     map.on('click', getPoint);
    
     //end load function code
    
  18. We are done with the functions that are executed when the map is loaded. Now we will create the functions outside of the load event handler.

    
       //end load function code
     });
        
     function createListFromSource() {
         
     }
    
     function showRefreshListButton() {
    
     }
    
     function getPoint(e) {
    
     }
    
  19. Get the elements that are visible in the map view.

    
     function createListFromSource() {
       const features = getRenderedFeatures();
       if (features.length) {
         //stop listening to the map render event
         map.off('render', createListFromSource);
         updateList();
       }
     }
    
     function getRenderedFeatures(point) {
       //if the point is null, it is searched within the bounding box of the map view
       const features = map.queryRenderedFeatures(point, {
         layers: ['points']
       });
       return features;
     }
    
    

</code></pre>

  1. Select a map element when the user clicks on it.

    
     function getPoint(e) {
       const features = getRenderedFeatures(e.point);
       if (features.length) {
         const element = features[0];
         selectedItem = element.id;
         map.setLayoutProperty('points', 'icon-image',
           [
             'match',
             ['id'], // get the feature id (make sure your data has an id set or use generateIds for GeoJSON sources
             element.id, 'pinShoeSelected', //image when id is the clicked feature id
             'pinShoe' // default
           ]
         );
         selectMapToList(element);
       } else {
         cleanSelection();
       }
     }
    
     function cleanSelection() {
    
     }
    
     function selectMapToList(element) {
    
     }
    
    

</code></pre>

  1. Create a list of places.

    
     let selectedItem;
    
     function updateList() {
       const features = getRenderedFeatures();
       const listItems = features.map(item => {
         return `<div class='list-item ${item.id === selectedItem ? "selected" : ""}' 
           data-id="${item.id}" data-lnglat="${item.geometry.coordinates.join()}">
             ${createListItemContent(item)}
           </div>`;
       });
       const listContainer = document.querySelector('.sidebar-content-info');
       listContainer.scrollTop = 0;
       listContainer.innerHTML = listItems.join('');
     }
    
     function createListItemContent(item) {
       const html = [`<img src="${item.properties.photo}" alt="shoe shop">`];
       if (item.properties.name) {
         html.push(`<h2>${item.properties.name}</h2>`);  
       }
       if (item.properties.operator) {
         html.push(`<h4>${item.properties.operator}</h4>`);  
       }
       if (item.properties["addr:street"]) {
         html.push(`<div class="addr">${item.properties["addr:street"]}, ${item.properties["addr:housenumber"]}</div>`);
       }
       if (item.properties.opening_hours) {
           html.push(`<h5>Opening hours</h5>`);
           item.properties.opening_hours.split(",").forEach(element => {
             html.push(`<div>${element.trim()}</div>`);
           });
       }
       if (item.properties.phone) {
         html.push(`<div><a href="tel:${item.properties.phone}" target="_blank" rel="noopener noreferrer">${item.properties.phone}</a></div>`);
       }
       if (item.properties.website) {
         html.push(`<div><a href="${item.properties.website}" target="_blank" rel="noopener noreferrer">${item.properties.website}</a></div>`);
       }
       return html.join('');
     }
    
  2. Create the list style. Add the list style to your stylesheet.

    
     .list-item {
       border-bottom: 1px solid #333333;
       padding: 16px;
     }
    
     .list-item:hover {
       cursor: pointer;
       background-color: #E4ECFF;
     }
    
     .list-item h2 {
       margin: 0.23em 0;
     }
    
     .list-item.selected {
       background-color: #05D0DF;
     }
    
     .list-item img {
       width: 100%;
     }
    
  3. With everything done up until now, you should be able to see your map and the list of places in your browser.

    Map with a list of places

  4. Next, we are going to add some events to link the list with the map. These events will ensure that when a list item is clicked on, the corresponding marker on the map is selected and centered on the map view.

    
     let listContainer = document.querySelector('.sidebar-content-info');
    
     listContainer.addEventListener('click', (e) => {
       cleanSelection();
       const li = e.target.closest('.list-item');
       li.classList.toggle('selected');
       if (li.classList.contains('selected')) {
         selectedItem = parseInt(li.dataset.id);
         selectListToMap(li);
       }
     });
    
     function selectListToMap(item) {
       map.setLayoutProperty('points', 'icon-image',
         [
           'match',
           ['id'], // get the feature id (make sure your data has an id set or use generateIds for GeoJSON sources
           parseInt(item.dataset.id), 'pinShoeSelected', //image when id is the clicked feature id
           'pinShoe' // default
         ]
       );
       map.setCenter(item.dataset.lnglat.split(','));
     }
    
  5. We have already made it so that selecting an item from the list selects it on the map. Now we will do the other way, when selecting an element on the map it is selected in the list.

    
     function selectMapToList(element) {
       cleanListSelection();
       const listSelected = document.querySelector(`.list-item[data-id="${element.id}"]`);
       listSelected.classList.add('selected');
       listSelected.scrollIntoView({behavior: 'smooth', block: 'nearest'});
     }
    
  6. At this point we already have the list linked and synchronized with the map. Now we will create some functions to toggle the selected elements so that we have only one element selected in both the list and the map.

    
     function cleanSelection() {
       selectedItem = null;
       map.setLayoutProperty('points', 'icon-image', 'pinShoe');
       cleanListSelection();
     }
    
     function cleanListSelection() {
       const listSelected = document.querySelector(`.list-item.selected`);
       if (listSelected) {
         listSelected.classList.remove('selected');
       }
     }
    
  7. Finally, we need to show the “Search in this area” button when the map view is updated and update the list when the user clicks the button.

    
     function showRefreshListButton() {
       document.querySelector('.btn').classList.remove('hidden');
     }
    
     document.querySelector('.btn').addEventListener('click', (e) => {
       e.target.classList.add('hidden');
       cleanSelection();
       updateList();
     });
    
  8. Congratulations! You have your map with place markers synchronized with the list of places.

Show point data from GeoJSON on the map

GeoJSON point layer

Tutorials

Add a GeoJSON point overlay to your map application.

Get POIs information on click

Get POIs information on click

Examples

Get POI information by clicking the map.

How to add a custom icon (PNG) to a point layer

Add custom icon (PNG) to a point layer

Examples

Display a point layer using custom PNG icons.

Offset the vanishing point using padding

Offset the vanishing point using padding

Examples

Offset the map vanishing point using padding.

Was this helpful?