Recipe: custom map UI with your own map library
Use Yatmo for the data, Leaflet / MapLibre / Mapbox GL / Google Maps for the rendering. Same POIs as the JS Map plugin, your own visual identity.
Approach
- Once on app startup, fetch /categories to know the POI type IDs you want to render.
- On every map render / pan / zoom, call /points with the current bounding box.
- Render the returned POIs using your map library’s native marker API.
1. Load categories once
# Run this once on startup; cache the response.
curl -H 'LicenseKey: YOUR_KEY' \
'https://be.yatmo.com/categories?language=EN' > categories.json
const categories = await (await fetch(
'https://be.yatmo.com/categories?language=EN',
{ headers: { LicenseKey: 'YOUR_KEY' } }
)).json();
// Pick the POI type ids you actually want to show.
// Each category has .l (label), .id (POI type id), .c (sub-categories).
const wantedIds = categories
.filter(c => ['Transports', 'Shopping', 'Education'].includes(c.l))
.map(c => c.id);
$ch = curl_init('https://be.yatmo.com/categories?language=EN');
curl_setopt($ch, CURLOPT_HTTPHEADER, ['LicenseKey: YOUR_KEY']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$categories = json_decode(curl_exec($ch), true);
curl_close($ch);
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("LicenseKey", "YOUR_KEY");
var json = await http.GetStringAsync("https://be.yatmo.com/categories?language=EN");
2. Fetch POIs for the visible map
When the user pans or zooms, get the new bounding box from your map library and call /points:
async function fetchVisiblePois(map) {
const b = map.getBounds(); // Leaflet-style API; adapt to your lib
const bound1 = `${b.getSouth()},${b.getWest()}`;
const bound2 = `${b.getNorth()},${b.getEast()}`;
const qs = new URLSearchParams({
bound1, bound2,
language: 'EN',
groupSamePositions: 'true',
poiTypesIds: wantedIds.join(',')
});
const res = await fetch(`https://be.yatmo.com/points?${qs}`, {
headers: { LicenseKey: 'YOUR_KEY' }
});
return await res.json(); // array of compact POI objects (n, la, ln, p, ...)
}
Check the Nz response header: when it’s true, the result was truncated — tell the user to zoom in.
3. Render on Leaflet (concrete example)
const map = L.map('map').setView([50.8520525, 4.3442926], 14);
L.tileLayer('https://{your-tile-server}/{z}/{x}/{y}.png').addTo(map);
let layer = L.layerGroup().addTo(map);
async function refresh() {
const pois = await fetchVisiblePois(map);
layer.clearLayers();
pois.forEach(p => {
L.marker([p.la, p.ln], { title: p.n })
.bindPopup(`<strong>${p.n}</strong>`)
.addTo(layer);
});
}
map.on('moveend', refresh);
refresh();
Notes
- Debounce
moveendby 200-300ms. The user dragging a map fires it constantly; you don’t need a POI fetch every frame. - Cache by bounding box. If the visible area hasn’t changed enough to bring new POIs into view, skip the fetch.
- Don’t request huge boxes. The API rejects bounding boxes that are too large — show a “zoom in to see POIs” message at low zoom levels rather than hitting 400.
- Need travel-time on click? Combine with /route from a fixed origin (the property) to the clicked POI.