---
title: "Get POIs information on click | JavaScript maps SDK"
description: "Get POI information by clicking the map."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# 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.

### NPM module setup

```bash
npm install --save @maptiler/sdk blueimp-md5
```

#### main.js

```js
import { Map, MapStyle, config } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import md5 from 'blueimp-md5';

import queryString from 'https://cdn.jsdelivr.net/npm/query-string@8.1.0/+esm'

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

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

    const 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) {
  const elem = document.getElementById(id);
  const classes = elem.className.split(' ');
  const collapsed = classes.indexOf('collapsed') !== -1;
  const 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');
});
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Offset the vanishing point using padding</title>
  <link rel="stylesheet" href="./style.css">
</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" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
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);
  }
```

### Basic JavaScript setup

```html
<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/v4.2.0/maptiler-sdk.umd.min.js"></script>
<link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/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';
    const center = [-73.986281, 40.74241];
    const map = new maptilersdk.Map({
        container: 'map',
        zoom: 16,
        center: center,
        style: maptilersdk.MapStyle.STREETS
    });
    const marker = new maptilersdk.Marker();

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

        const 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) {
      const elem = document.getElementById(id);
      const classes = elem.className.split(' ');
      const collapsed = classes.indexOf('collapsed') !== -1;
      const 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>
```

## Related examples

### [Geocoding search for POIs near the user's location](https://docs.maptiler.com/sdk-js/examples/geocoding-filter-types/)

Examples

Filter geocoding search results by specific place types.

### [Display a popup on click](https://docs.maptiler.com/sdk-js/examples/popup-on-click/)

Examples

Display a popup when a user clicks a symbol.

### [Point filtering by property](https://docs.maptiler.com/sdk-js/examples/point-filtering/)

Examples

Filter features in real time by property.

### [Offset the vanishing point using padding](https://docs.maptiler.com/sdk-js/examples/offset-vanishing-point-with-padding/)

Examples

Offset the map vanishing point using padding.
