MapSnippets logoMapSnippets logo

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

LeafletMapLibreOpenLayersSDK
Coordinates[lat, lng][lng, lat]fromLonLat([lng, lat])[lng, lat]
RenderingDOM + Canvas (2D)WebGL (GPU, 60fps)Canvas + WebGLWebGL (GPU, 60fps)
Bundle size~42 KB gzipped~250 KB gzipped~290 KB gzipped (tree-shakeable)~280 KB gzipped (includes MapLibre)
Learning curveEasiest (simple API, great tutorials)Moderate (source/layer model, expressions)Steepest (projections, OGC standards, verbose API)Easy (MapLibre API with convenience helpers)
3D supportNo native 3D (2D only)Full: terrain, fill-extrusion, globe, skyLimited: WebGL layers, no native globeFull (same as MapLibre)
Code lines21295230

Code Snippets

Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.

Leaflet21 linesShow code
arrow_forwardFull example
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: '&copy; <a href="https://www.maptiler.com/">MapTiler</a> &copy; <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
arrow_forwardFull example
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
arrow_forwardFull example
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: '&copy; <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
arrow_forwardFull example
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.

Leaflet

Sidebar-to-marker interaction uses simple click handlers; flyTo animation is built-in but less smooth than WebGL libraries.

MapLibre GL JS

Markers are DOM-based (or symbol layers for scale); flyTo provides smooth camera transitions; sidebar interaction updates map state.

OpenLayers

Store pins are ol.Feature instances in a vector layer; sidebar interaction triggers map.getView().animate() for camera movement.

MapTiler SDK

Same marker and flyTo approach as MapLibre; the SDK's built-in geocoding can enhance the locator with address search.

Which Library to Use?

auto_awesome

Build Maps with AI

Generate code like these examples using mapping library skills in your IDE.