Maplibre-GL Places Layers
Get direct, fast access to global POI data
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/maplibre-gl@5.14.0/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@5.14.0/dist/maplibre-gl.css" rel="stylesheet">
<script src="https://unpkg.com/@turf/turf@7.2.0/turf.min.js"></script>
<script src="https://releases.targomo.com/core/latest.min.js"></script>
<style>
body, html {
margin: 0;
width: 100%;
height: 100%;
}
#map {
width: 100%;
height: 100%;
}
/* Fix for MapLibre 5.14.0: ensure navigation controls stay visible */
.maplibregl-ctrl-top-left,
.maplibregl-ctrl-top-right {
z-index: 1000 !important;
}
</style>
</head>
<body>
<div id="map"></div>
<script>// create targomo client
const client = new tgm.TargomoClient('northamerica', '__targomo_key_here__')
// Coordinates to center the map
const lnglat = [-118.254427, 34.046185]
// add the map and set the initial center
const map = new maplibregl.Map({
container: 'map',
style: client.basemaps.getGLStyleURL("Light"),
zoom: 13,
minZoom: 9,
center: lnglat
})
map.addControl(new maplibregl.NavigationControl())
// disable scroll zoom
map.scrollZoom.disable()
// POI mvt paths for cafes/coffee_shops and restaurants,
const tileSources = [{
type: 'cafe', color: 'black', textColor: 'white',
url: `https://api.targomo.com/pointofinterest/{z}/{x}/{y}.mvt?apiKey=${client.serviceKey}` +
'&group=coffee_shop&group=cafe&loadAllTags=true&layerType=node&layerGeometryDetailPerTile=5'
}, {
type: 'restaurant', color: 'orange', textColor: 'black',
url: `https://api.targomo.com/pointofinterest/{z}/{x}/{y}.mvt?apiKey=${client.serviceKey}` +
'&group=restaurant&loadAllTags=true&layerType=node&layerGeometryDetailPerTile=5'
}]
map.on('load', () => {
// Pre-load all icons synchronously
const icons = ['tgm-cafe', 'tgm-restaurant', 'tgm-circle']
const loadedImages = {}
let loadCount = 0
icons.forEach(icon => {
const img = new Image()
img.crossOrigin = 'anonymous'
img.onload = () => {
loadedImages[icon] = img
loadCount++
if (loadCount === icons.length) {
// All images loaded, add them to the map and then add layers
Object.keys(loadedImages).forEach(id => {
if (!map.hasImage(id)) {
map.addImage(id, loadedImages[id], { sdf: true })
}
})
addLayers()
}
}
img.onerror = () => {
console.error(`Failed to load icon: ${icon}`)
loadCount++
if (loadCount === icons.length) {
addLayers()
}
}
img.src = `https://releases.targomo.com/tgm-icons/images/${icon}.png`
})
function addLayers() {
for (let source of tileSources) {
// add source
map.addSource(`poi-${source.type}-src`,{
type: 'vector',
tiles: [source.url],
minzoom: 9,
attribution: '<a href="https://www.targomo.com/developers/resources/attribution/" target="_blank">© Targomo</a>'
})
// add layer for individual pois
map.addLayer({
id: `poi-${source.type}`,
type: 'symbol',
source: `poi-${source.type}-src`,
'source-layer': 'poi',
layout: {
'icon-allow-overlap': true,
'icon-image': `tgm-${source.type}`,
'icon-size': 0.18
},
filter: ['==', ['get', 'numOfPois'], 1],
paint: {
'icon-color': source.color
}
})
// update cursor for layer
map.on('mouseenter', `poi-${source.type}`, () => { map.getCanvas().style.cursor = 'pointer' })
map.on('mouseleave', `poi-${source.type}`, () => { map.getCanvas().style.cursor = '' })
// add layer for clusters
map.addLayer({
id: `${source.type}-many`,
type: 'symbol',
source: `poi-${source.type}-src`,
'source-layer': 'poi',
layout: {
'icon-allow-overlap': true,
'icon-image': 'tgm-circle',
'icon-size': 0.3,
'text-field': '{numOfPois}',
'text-size': 8
},
filter: ['>', ['get', 'numOfPois'], 1],
paint: {
'icon-color': source.color,
'text-color': source.textColor
}
})
// update cursor for layer
map.on('mouseenter', `${source.type}-many`, () => { map.getCanvas().style.cursor = 'pointer' })
map.on('mouseleave', `${source.type}-many`, () => { map.getCanvas().style.cursor = '' })
}
map.on('click', event => {
let description = ''
// get all singular pois
const poiFeatures = map.queryRenderedFeatures(event.point).filter(feature => (/^poi-/g).test(feature.source))
// get all multiple pois
const poiManyFeatures = map.queryRenderedFeatures(event.point).filter(feature => (/-many$/g).test(feature.layer.id))
if (poiManyFeatures.length > 0) { // only show multiple if overlapping
description += `<strong>Multiple POIs</strong><br><em>${ poiManyFeatures[0].properties.numOfPois }
POIs here,<br>zoom in to see details</em>`
} else { // otherwise show singular
poiFeatures.forEach((f, i) => {
description += `<strong>POI: ${ f.properties.groupId.replace(/^\w/, c => c.toUpperCase()) }</strong><br>`
description += f.properties['tag-name'] ? f.properties['tag-name'] : '<em>Name not listed...</em>'
if (i < poiFeatures.length) { description += '<br>' }
})
}
// show popup
if (description.length > 1) {
new maplibregl.Popup()
.setLngLat(event.lngLat)
.setHTML(description).addTo(map)
}
})
} // end addLayers function
})</script>
</body>
</html>
Copied to clipboard