MapSnippets logoMapSnippets logo

Store Locator with OpenLayers

OpenLayers

Build a store locator with a sidebar list, point features, and popup overlays using OpenLayers.

progress_activityLoading map...
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);
});

How it works

The store locator combines a sidebar list with point features rendered on a vector layer. Each store is represented as an ol.Feature with a Point geometry created via ol.proj.fromLonLat(). Clicking a sidebar item animates the map view to that store and displays a popup overlay.

The popup uses ol.Overlay to anchor an HTML element to a map coordinate. The map click handler detects features at the click pixel with forEachFeatureAtPixel and positions the overlay accordingly.

The sidebar is absolutely positioned and the #map element has a left margin to prevent overlap. Store data drives both the features and the sidebar items in parallel.

Key APIs

API Description Docs
ol.Feature Geographic feature with geometry and properties openlayers.org
ol.Overlay Anchor an HTML popup to map coordinates openlayers.org
view.animate() Animated camera transition openlayers.org
map.forEachFeatureAtPixel() Hit-detect features at a pixel openlayers.org

FAQ

How do I add search/filter to the store list?

Add a text input above the list and filter sidebar DOM elements on keyup. You can also toggle feature visibility by setting a style to null on filtered-out features.

How do I load store data from a GeoJSON file?

Replace the stores array with a fetch() call and use new ol.format.GeoJSON().readFeatures(data, { featureProjection: 'EPSG:3857' }) to parse the response.

Common mistakes

  • Forgetting ol.proj.fromLonLat(): coordinates must be in EPSG:3857 for the map view
  • Not setting margin-left on the map container: the map renders underneath the sidebar
  • Using evt.coordinate for the popup position when clicking sidebar items: use the projected store coordinate instead
auto_awesome

Build maps using OpenLayers with AI

Generate code like this store locator with openlayers example using the OpenLayers skill in your IDE.