All skills
mapbox avatar

/mapbox-store-locator-patterns

@9077a82 official
by mapboxmapbox/mapbox-agent-skills80 stars
17

Common patterns for building store locators, restaurant finders, and location-based search applications with Mapbox. Covers marker display, filtering, distance calculation, and interactive lists.

Use this Skill: https://skilld.dev/gh/mapbox/mapbox-agent-skills/mapbox-store-locator-patterns

This session only. Nothing lands on disk.

referencesmarkers.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Markers: HTML Markers, Symbol Layers & Clustering

Choosing the Right Marker Strategy

Location Count Strategy Why
Fewer than 100 HTML Markers Full DOM/CSS control; manageable DOM node count
100–1,000 Symbol Layer (recommended default) Renders on the GPU via WebGL — no DOM elements created, so performance stays smooth even with hundreds of points
More than 1,000 Clustering + Symbol Layer Reduces visual clutter and keeps interaction snappy at large scale

Key insight: Each HTML Marker creates a real DOM element. At 150+ markers that means 150+ nodes the browser must lay out, paint, and composite every frame. A symbol layer, by contrast, is drawn entirely on the GPU through WebGL — the browser sees only the single <canvas> element regardless of point count.

Option 1: HTML Markers (fewer than 100 locations)

const markers = {};

stores.features.forEach((store) => {
  // Create marker element
  const el = document.createElement('div');
  el.className = 'marker';
  el.style.backgroundImage = 'url(/marker-icon.png)';
  el.style.width = '30px';
  el.style.height = '40px';
  el.style.backgroundSize = 'cover';
  el.style.cursor = 'pointer';

  // Create marker
  const marker = new mapboxgl.Marker(el)
    .setLngLat(store.geometry.coordinates)
    .setPopup(
      new mapboxgl.Popup({ offset: 25 }).setHTML(
        `<h3>${store.properties.name}</h3>
         <p>${store.properties.address}</p>
         <p>${store.properties.phone}</p>`
      )
    )
    .addTo(map);

  // Store reference for later access
  markers[store.properties.id] = marker;

  // Handle marker click
  el.addEventListener('click', () => {
    flyToStore(store);
    createPopup(store);
    highlightListing(store.properties.id);
  });
});

Option 2: Symbol Layer (100–1,000 locations) — see SKILL.md Step 2 for full implementation.

Option 3: Clustering (more than 1,000 locations)

map.on('load', () => {
  map.addSource('stores', {
    type: 'geojson',
    data: stores,
    cluster: true,
    clusterMaxZoom: 14,
    clusterRadius: 50
  });

  // Cluster circles
  map.addLayer({
    id: 'clusters',
    type: 'circle',
    source: 'stores',
    filter: ['has', 'point_count'],
    paint: {
      'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 10, '#f1f075', 30, '#f28cb1'],
      'circle-radius': ['step', ['get', 'point_count'], 20, 10, 30, 30, 40]
    }
  });

  // Cluster count labels
  map.addLayer({
    id: 'cluster-count',
    type: 'symbol',
    source: 'stores',
    filter: ['has', 'point_count'],
    layout: {
      'text-field': '{point_count_abbreviated}',
      'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'],
      'text-size': 12
    }
  });

  // Unclustered points
  map.addLayer({
    id: 'unclustered-point',
    type: 'circle',
    source: 'stores',
    filter: ['!', ['has', 'point_count']],
    paint: {
      'circle-color': '#11b4da',
      'circle-radius': 8,
      'circle-stroke-width': 1,
      'circle-stroke-color': '#fff'
    }
  });

  // Zoom on cluster click
  map.on('click', 'clusters', (e) => {
    const features = map.queryRenderedFeatures(e.point, {
      layers: ['clusters']
    });
    const clusterId = features[0].properties.cluster_id;
    map.getSource('stores').getClusterExpansionZoom(clusterId, (err, zoom) => {
      if (err) return;

      map.easeTo({
        center: features[0].geometry.coordinates,
        zoom: zoom
      });
    });
  });

  // Show popup on unclustered point click
  map.on('click', 'unclustered-point', (e) => {
    const coordinates = e.features[0].geometry.coordinates.slice();
    const props = e.features[0].properties;

    new mapboxgl.Popup()
      .setLngLat(coordinates)
      .setHTML(
        `<h3>${props.name}</h3>
         <p>${props.address}</p>`
      )
      .addTo(map);
  });
});

Source: SKILL.md on GitHub

1 warning14d5 checks · Risk SAFE
  • Gen Agent Trust Hub14d

    The skill provides patterns for building map-based applications using Mapbox. It includes minor security risks related to how external location data is rendered in the user interface, which could allow for script execution if the data source is untrusted.

  • Socket14d

    No alerts

  • Snyk14d

    Risk: LOW · No issues

  • Runlayer7mo

    2/2 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 9077a82. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 hours ago.

Activeupdated 2 weeks ago
  • mapbox
  • store-locator
  • geojson
  • markers
  • filtering
  • geolocation
  • turf
  • javascript
  • interactive-map

README badge

README badge for mapbox/mapbox-agent-skills/mapbox-store-locator-patterns

Provides patterns for building store locators, restaurant finders, and location-based search apps with Mapbox GL JS, covering marker display (symbol layers for 100–1000 locations, HTML markers for fewer), filtering, distance calculation, and interactive location lists. Includes data structure templates, popup and directions integration, and references for geolocation, search, clustering, and React implementations.

Generated from the current SKILL.md.

Does this skill work with HTML markers or only symbol layers?
The main implementation uses symbol layers for 100–1,000 locations. HTML markers and clustering patterns are covered in the `references/markers.md` reference file for smaller or larger datasets.
What libraries are required besides Mapbox GL JS?
Turf.js (@turf/turf) is required for spatial calculations like distance. It's listed as a dependency and installed via npm.
Does this skill handle user geolocation and directions?
Yes. The skill references geolocation and directions patterns in `references/geolocation-directions.md`, including distance calculation and route integration.
Is there a React implementation included?
React patterns and variations are documented in `references/variations-react.md`, along with mobile-first and fullscreen layouts.
What's the expected performance threshold for marker count?
Symbol layers scale to 100–1,000 locations efficiently on the GPU. Below 100, HTML markers work fine; above 1,000, clustering is recommended.

Generated from the current SKILL.md. These answers refresh after source changes.