Store Locator
Build a store locator with sidebar list, map pins, and interactive selection Each library has its own approach: here's how they compare.
Quick Recommendations
Leaflet — The better fit for lightweight store locators where DOM-based markers and simple popups keep the implementation straightforward.
MapLibre GL JS — A strong fit when the store locator needs smooth WebGL rendering, fly-to animations, and data-driven marker styling.
OpenLayers — Best when store data comes from OGC services or when you need advanced spatial queries like nearest-feature lookup.
MapTiler SDK — The MapTiler SDK take: MapLibre's store locator pattern, with built-in geocoding for address search.
At a Glance
| Leaflet | MapLibre | OpenLayers | SDK | |
|---|---|---|---|---|
| Coordinates | [lat, lng] | [lng, lat] | fromLonLat([lng, lat]) | [lng, lat] |
| Rendering | DOM + Canvas (2D) | WebGL (GPU, 60fps) | Canvas + WebGL | WebGL (GPU, 60fps) |
| Bundle size | ~42 KB gzipped | ~250 KB gzipped | ~290 KB gzipped (tree-shakeable) | ~280 KB gzipped (includes MapLibre) |
| Learning curve | Easiest (simple API, great tutorials) | Moderate (source/layer model, expressions) | Steepest (projections, OGC standards, verbose API) | Easy (MapLibre API with convenience helpers) |
| 3D support | No native 3D (2D only) | Full: terrain, fill-extrusion, globe, sky | Limited: WebGL layers, no native globe | Full (same as MapLibre) |
| Code lines | 21 | 29 | 52 | 30 |
Code Snippets
Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.
Leaflet21 linesShow code
const stores = [
{ name: 'Store Marais', address: '15 Rue des Francs Bourgeois', lat: 48.8571, lng: 2.3621 },
{ name: 'Store Louvre', address: '2 Rue de Rivoli', lat: 48.8606, lng: 2.3376 },
{ name: 'Store Montmartre', address: '30 Rue Lepic', lat: 48.8847, lng: 2.3334 },
{ name: 'Store Latin Quarter', address: '10 Rue Mouffetard', lat: 48.8430, lng: 2.3499 },
{ name: 'Store Bastille', address: '5 Rue de la Roquette', lat: 48.8534, lng: 2.3715 }
];
const map = L.map('map').setView([48.8600, 2.3500], 13);
L.tileLayer('https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', {
tileSize: 256,
attribution: '© <a href="https://www.maptiler.com/">MapTiler</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);
const panel = document.getElementById('store-panel');
stores.forEach(s => {
const marker = L.marker([s.lat, s.lng]).addTo(map).bindPopup(`<strong>${s.name}</strong><br>${s.address}`);
const item = document.createElement('div');
item.className = 'store-item';
item.innerHTML = `<strong>${s.name}</strong>${s.address}`;
item.addEventListener('click', () => { map.setView([s.lat, s.lng], 16); marker.openPopup(); });
panel.appendChild(item);
});MapLibre GL JS29 linesShow code
const stores = [
{ name: 'Store Marais', address: '15 Rue des Francs Bourgeois', lng: 2.3621, lat: 48.8571 },
{ name: 'Store Louvre', address: '2 Rue de Rivoli', lng: 2.3376, lat: 48.8606 },
{ name: 'Store Montmartre', address: '30 Rue Lepic', lng: 2.3334, lat: 48.8847 },
{ name: 'Store Latin Quarter', address: '10 Rue Mouffetard', lng: 2.3499, lat: 48.8430 },
{ name: 'Store Bastille', address: '5 Rue de la Roquette', lng: 2.3715, lat: 48.8534 }
];
const map = new maplibregl.Map({
container: 'map',
style: 'https://api.maptiler.com/maps/streets-v4/style.json?key=YOUR_MAPTILER_KEY',
center: [2.3500, 48.8600],
zoom: 13
});
const panel = document.getElementById('store-panel');
stores.forEach(s => {
const popup = new maplibregl.Popup({ offset: 25 }).setHTML(`<strong>${s.name}</strong><br>${s.address}`);
const marker = new maplibregl.Marker({ color: '#2196F3' })
.setLngLat([s.lng, s.lat])
.setPopup(popup)
.addTo(map);
const item = document.createElement('div');
item.className = 'store-item';
item.innerHTML = `<strong>${s.name}</strong>${s.address}`;
item.addEventListener('click', () => {
map.flyTo({ center: [s.lng, s.lat], zoom: 16 });
marker.togglePopup();
});
panel.appendChild(item);
});OpenLayers52 linesShow code
var stores = [
{ name: 'Store Marais', address: '15 Rue des Francs Bourgeois', lng: 2.3621, lat: 48.8571 },
{ name: 'Store Louvre', address: '2 Rue de Rivoli', lng: 2.3376, lat: 48.8606 },
{ name: 'Store Montmartre', address: '30 Rue Lepic', lng: 2.3334, lat: 48.8847 },
{ name: 'Store Latin Quarter', address: '10 Rue Mouffetard', lng: 2.3499, lat: 48.8430 },
{ name: 'Store Bastille', address: '5 Rue de la Roquette', lng: 2.3715, lat: 48.8534 }
];
var features = stores.map(function(s) {
var f = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([s.lng, s.lat])), name: s.name, address: s.address });
return f;
});
var vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({ features: features }),
style: new ol.style.Style({
image: new ol.style.Circle({ radius: 8, fill: new ol.style.Fill({ color: '#FF5722' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) })
})
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', attributions: '© <a href="https://www.maptiler.com/copyright/">MapTiler</a>' }) }),
vectorLayer
],
view: new ol.View({ center: ol.proj.fromLonLat([2.3500, 48.8600]), zoom: 13 })
});
var popupEl = document.getElementById('popup');
var overlay = new ol.Overlay({ element: popupEl, positioning: 'bottom-center', offset: [0, -12] });
map.addOverlay(overlay);
map.on('click', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel, function(f) { return f; });
if (feature) {
popupEl.style.display = 'block';
popupEl.innerHTML = '<strong>' + feature.get('name') + '</strong><br>' + feature.get('address');
overlay.setPosition(evt.coordinate);
} else {
popupEl.style.display = 'none';
}
});
var panel = document.getElementById('store-panel');
stores.forEach(function(s) {
var item = document.createElement('div');
item.className = 'store-item';
item.innerHTML = '<strong>' + s.name + '</strong>' + s.address;
item.addEventListener('click', function() {
var center = ol.proj.fromLonLat([s.lng, s.lat]);
map.getView().animate({ center: center, zoom: 16, duration: 600 });
popupEl.style.display = 'block';
popupEl.innerHTML = '<strong>' + s.name + '</strong><br>' + s.address;
overlay.setPosition(center);
});
panel.appendChild(item);
});MapTiler SDK30 linesShow code
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const stores = [
{ name: 'Store Marais', address: '15 Rue des Francs Bourgeois', lng: 2.3621, lat: 48.8571 },
{ name: 'Store Louvre', address: '2 Rue de Rivoli', lng: 2.3376, lat: 48.8606 },
{ name: 'Store Montmartre', address: '30 Rue Lepic', lng: 2.3334, lat: 48.8847 },
{ name: 'Store Latin Quarter', address: '10 Rue Mouffetard', lng: 2.3499, lat: 48.8430 },
{ name: 'Store Bastille', address: '5 Rue de la Roquette', lng: 2.3715, lat: 48.8534 }
];
const map = new maptilersdk.Map({
container: 'map',
style: maptilersdk.MapStyle.STREETS,
center: [2.3500, 48.8600],
zoom: 13
});
const panel = document.getElementById('store-panel');
stores.forEach(s => {
const popup = new maptilersdk.Popup({ offset: 25 }).setHTML(`<strong>${s.name}</strong><br>${s.address}`);
const marker = new maptilersdk.Marker({ color: '#9C27B0' })
.setLngLat([s.lng, s.lat])
.setPopup(popup)
.addTo(map);
const item = document.createElement('div');
item.className = 'store-item';
item.innerHTML = `<strong>${s.name}</strong>${s.address}`;
item.addEventListener('click', () => {
map.flyTo({ center: [s.lng, s.lat], zoom: 16 });
marker.togglePopup();
});
panel.appendChild(item);
});Things to Know
Implementation-specific details that matter for store locator.
Sidebar-to-marker interaction uses simple click handlers; flyTo animation is built-in but less smooth than WebGL libraries.
Markers are DOM-based (or symbol layers for scale); flyTo provides smooth camera transitions; sidebar interaction updates map state.
Store pins are ol.Feature instances in a vector layer; sidebar interaction triggers map.getView().animate() for camera movement.
Same marker and flyTo approach as MapLibre; the SDK's built-in geocoding can enhance the locator with address search.
Which Library to Use?
Build a store locator with a sidebar list and interactive map pins using Leaflet.
The better fit for lightweight store locators where DOM-based markers and simple popups keep the implementation straightforward.
Build a store locator with a sidebar list and interactive map markers using MapLibre GL JS.
A strong fit when the store locator needs smooth WebGL rendering, fly-to animations, and data-driven marker styling.
Build a store locator with a sidebar list, point features, and popup overlays using OpenLayers.
Best when store data comes from OGC services or when you need advanced spatial queries like nearest-feature lookup.
Build a store locator with a sidebar list and interactive map markers using MapTiler SDK.
The MapTiler SDK take: MapLibre's store locator pattern, with built-in geocoding for address search.
Build Maps with AI
Generate code like these examples using mapping library skills in your IDE.