Get POIs information on click

Get POI information from MapTiler, OpenStreetMap (OSM), and Wikidata data by clicking on the map.

Click on any POI on the map to get the information.

<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Offset the vanishing point using padding</title>
<script src="https://cdn.maptiler.com/maptiler-sdk-js/v3.0.1/maptiler-sdk.umd.min.js"></script>
<link href="https://cdn.maptiler.com/maptiler-sdk-js/v3.0.1/maptiler-sdk.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<style>
  body {margin: 0; padding: 0;}
  #map {position: absolute; top: 0; bottom: 0; width: 100%;}
  .rounded-rect {
        background: white;
        border-radius: 10px;
        box-shadow: 0 0 50px -25px black;
    }

    .flex-center {
        position: absolute;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .flex-center.left {
        left: 0px;
    }

    .sidebar-content-info {
        position: absolute;
        top: 0px;
        font-size: 1rem;
        padding: 16px;
        box-sizing: border-box;
        width: 100%;
        word-break: break-word;
        overflow-y: auto;
        height: 100%;
    }

    .sidebar-content-info img {
      width: 100%;
    }

    .sidebar-content-info h1 {
      line-height: 1em;
    }

    .sidebar-content-info label {
      margin-right: 8px;
      color: #6B7C92;
      font-weight: 600;
    }

    .sidebar-content-info .details-info {
      font-size: 0.8em;
    }

    .sidebar-content {
        position: absolute;
        width: 95%;
        height: 95%;
        font-family: Arial, Helvetica, sans-serif;
        font-size: 32px;
        color: #AEB6C7;
    }

    .sidebar-toggle {
        position: absolute;
        width: 1.3em;
        height: 1.3em;
        overflow: visible;
        display: flex;
        justify-content: center;
        align-items: center;
    }

    .sidebar-toggle.left {
        right: -1.5em;
    }

    .sidebar.left .sidebar-toggle.left .icon {
	    background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ctitle%3Ekeyboard_arrow_left%3C/title%3E%3Cpath fill='currentColor' d='M15.422 16.594l-1.406 1.406-6-6 6-6 1.406 1.406-4.594 4.594z'%3E%3C/path%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: center;

    }

    .sidebar.left.collapsed .sidebar-toggle.left .icon {
	    background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Ctitle%3Ekeyboard_arrow_right%3C/title%3E%3Cpath fill='currentColor' d='M8.578 16.594l4.594-4.594-4.594-4.594 1.406-1.406 6 6-6 6z'%3E%3C/path%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: center;
    }

    .icon {
      display: inline-block;
      width: 70%;
      height: 70%;
      background-size: cover;
    }

    .icon:hover {
      filter: invert(71%) sepia(45%) saturate(7285%) hue-rotate(158deg) brightness(90%) contrast(92%);
    }

    .sidebar-toggle:hover {
        color: #0aa1cf;
        cursor: pointer;
    }

    .sidebar {
        transition: transform 1s;
        z-index: 1;
        width: 300px;
        height: 100%;
    }

    /*
      The sidebar styling has them "expanded" by default, we use CSS transforms to push them offscreen
      The toggleSidebar() function removes this class from the element in order to expand it.
    */
    .left.collapsed {
        transform: translateX(-295px);
    }

    .right.collapsed {
        transform: translateX(295px);
    }
</style>
</head>
<body>
<div id="map">
    <div id="left" class="sidebar flex-center left collapsed">
        <div class="sidebar-content rounded-rect flex-center">
            <div class="sidebar-content-info">Left Sidebar</div>
            <div
                class="sidebar-toggle rounded-rect left"
            >
            <span class="icon"></span>
            </div>
        </div>
    </div>
</div>

<script type="module">
    import queryString from 'https://cdn.jsdelivr.net/npm/query-string@8.1.0/+esm'

    maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
    var center = [-73.986281, 40.74241];
    var map = new maptilersdk.Map({
        container: 'map',
        zoom: 16,
        center: center,
        style: maptilersdk.MapStyle.STREETS
    });
    const marker = new maptilersdk.Marker();

    function toggleSidebar(id) {
        var elem = document.getElementById(id);
        var classes = elem.className.split(' ');
        var collapsed = classes.indexOf('collapsed') !== -1;

        var padding = {};

        if (collapsed) {
            // Remove the 'collapsed' class from the class list of the element, this sets it back to the expanded state.
            classes.splice(classes.indexOf('collapsed'), 1);

            padding[id] = 300; // In px, matches the width of the sidebars set in .sidebar CSS class
            map.easeTo({
                padding: padding,
                duration: 1000 // In ms, CSS transition duration property for the sidebar matches this value
            });
        } else {
            padding[id] = 0;
            // Add the 'collapsed' class to the class list of the element
            classes.push('collapsed');

            map.easeTo({
                padding: padding,
                duration: 1000
            });
        }

        // Update the class list on the element
        elem.className = classes.join(' ');
    }

    function showSidebar(id) {
      var elem = document.getElementById(id);
      var classes = elem.className.split(' ');
      var collapsed = classes.indexOf('collapsed') !== -1;
      var padding = {};
      if (collapsed) {
        // Remove the 'collapsed' class from the class list of the element, this sets it back to the expanded state.
        classes.splice(classes.indexOf('collapsed'), 1);

        padding[id] = 300; // In px, matches the width of the sidebars set in .sidebar CSS class
        map.easeTo({
          padding: padding,
          duration: 1000 // In ms, CSS transition duration property for the sidebar matches this value
        });
        // Update the class list on the element
        elem.className = classes.join(' ');
      }
    }

    map.on('load', function () {
      toggleSidebar('left');
      map.on('click', async function(e) {
        const features = map.queryRenderedFeatures(e.point, {
          layers: ['Public', 'Sport', 'Tourism', 'Culture', 'Education', 'Shopping', 'Food',
          'Transport', 'Park', 'Healthcare', 'Station']
        });
        if (features.length > 0) {
          getInfoFromLngLat(e.lngLat, features[0]);
        }
      });
    });

    async function getInfoFromLngLat(lngLat, feature) {
      marker.setLngLat(lngLat).addTo(map);
      const osmInfo = await getOMSInfo(feature.id);
      const wikidata = await getWikidata(osmInfo?.tags);
      showPoiInfo(feature, osmInfo, wikidata);
    }

    async function getOMSInfo(id) {
      const query = queryString.stringify({
          data: `[out:json][timeout:25];
            node(${id/10});
            out tags;`
        });
      const response = await fetch(`https://overpass-api.de/api/interpreter?${query}`, {
        redirect: 'follow',
        headers: {
          accept: 'application/json'
        },
      });
      const info = await response.json();
      return info?.elements[0] ? info.elements[0] : null;
    }

    function getWikidataId(tags) {
      if (!tags) return null;
      const regexWikidata = /(\w*:)?wikidata/;
      const key = Object.keys(tags).find(key => key.match(regexWikidata));
      return tags[key] ? tags[key] : null;
    }

    function getWikidataImageHash(name) {
      const imageHash = md5(name);
      return imageHash;
    }

    function getWikidataImagePath(image_name) {
      const name = image_name.replace(/\s+/g, '_');
      const hash = getWikidataImageHash(name);
      return `https://upload.wikimedia.org/wikipedia/commons/${hash.substring(0,1)}/${hash.substring(0,2)}/${name}`;
    }

    async function getWikidata(tags) {
      const id = getWikidataId(tags);
      if (!id) return null;
      const response = await fetch(`https://www.wikidata.org/wiki/Special:EntityData/${id}.json?flavor=simple`);
      const info = await response.json();
      let image;
      if (info.entities[id].claims.P154) {
        image = getWikidataImagePath(info.entities[id].claims.P154[0].mainsnak.datavalue.value);
      }else if (info.entities[id].claims.P18) {
        image = getWikidataImagePath(info.entities[id].claims.P18[0].mainsnak.datavalue.value);
      }

      return image ? {...info.entities[id], ...{image}} : info.entities[id];
    }

    function showPoiInfo(feature, osmInfo, wikidata) {
      const textHtml = [];
      if (wikidata?.image) {
        textHtml.push(`<img src="${wikidata?.image}" />`);
      }
      textHtml.push(`<h1>${feature.properties.name}</h1>`);
      if (feature.properties.class !== feature.properties.subclass) {
        textHtml.push(`<h4>${feature.properties.class} (<small>${feature.properties.subclass}</small>)</h4>`);
      } else {
        textHtml.push(`<h4>${feature.properties.class}</h4>`);
      }
      if (osmInfo && osmInfo?.tags) {
        const {opening_hours, "contact:website": contact_website, website, ...tags } = osmInfo.tags;
        if (contact_website || website) {
          let web = website ?? contact_website;
          textHtml.push(`<div><a href="${web}">${web}</a></div>`);
        }
        if (opening_hours) {
          textHtml.push(`<h3>Opening hours</h3>`);
          osmInfo?.tags?.opening_hours.split(",").forEach(element => {
            textHtml.push(`<div>${element.trim()}</div>`);
          });
        }
        if (tags) {
          textHtml.push(`<h3>Details</h3>`);
          textHtml.push(`<div class="details-info">`);
          Object.keys(tags).forEach(element => {
            if (element.includes("email")) {
              textHtml.push(`<div><label>${element}:</label><a href="mailto: ${tags[element]}">${tags[element]}</a></div>`);
            } else if (element.includes("website")) {
              textHtml.push(`<div><label>${element}:</label><a href="${tags[element]}">${tags[element]}</a></div>`);
            } else if (element.includes("wikidata")) {
              textHtml.push(`<div><label>${element}:</label><a href="https://www.wikidata.org/wiki/${tags[element]}">${tags[element]}</a></div>`);
            } else if (element.includes("wikipedia")) {
              const [lang, term] = tags[element].split(":");
              textHtml.push(`<div><label>${element}:</label><a href="https://${lang}.wikipedia.org/wiki/${term}">${tags[element]}</a></div>`);
            } else {
              textHtml.push(`<div><label>${element}:</label>${tags[element]}</div>`);
            }
          });
          textHtml.push(`</div>`);
        }
      }
      document.querySelector(".sidebar-content-info").innerHTML = textHtml.join("");
      showSidebar('left');
    }

    document.querySelector(".sidebar-toggle").addEventListener('click', function() {
      toggleSidebar('left');
    });

</script>
</body>
</html>
HTML
An extension of MapLibre GL JS