mcpField
M
mapbox-store-locator-patterns

mapbox-store-locator-patterns

github.com/mapbox开发831.7k 次安装4 周前更新
代码仓库

关于 mapbox-store-locator-patterns

mapbox-store-locator-patterns 是使用 Mapbox GL JS 构建门店定位器和地点搜索应用的模式指南,面向地图应用开发者。内容涵盖地图标记、筛选、距离计算及列表与地图交互,并提供按地点数量选择标记策略的建议。

主要功能

  1. 01GeoJSON 地点数据结构
  2. 02地图标记与聚合模式
  3. 03地点列表与地图联动
  4. 04搜索和类别筛选
  5. 05定位、距离与路线指引

适用场景

  1. 01在地图上展示门店或餐厅
  2. 02按类别或距离筛选地点
  3. 03显示所选地点详情并规划路线

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mapbox/mapbox-agent-skills --skill mapbox-store-locator-patterns

安装后在 Claude Code 中输入/mapbox-store-locator-patterns确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
evals/
evals.json
references/
geolocation-directions.md
markers.md
optimization-a11y.md
search-filter.md
styling-layout.md
variations-react.md

详细介绍

门店定位器模式技能

使用 Mapbox GL JS 构建门店定位器、餐厅查找器和基于位置的搜索应用的全面模式。涵盖标记显示、筛选、距离计算、交互式列表和路线集成。

何时使用此技能

在构建以下应用时使用此技能:

  • 在地图上显示多个地点(商店、餐厅、办公室等)
  • 允许用户筛选或搜索地点
  • 计算用户位置与地点之间的距离
  • 提供与地图标记同步的交互式列表
  • 在弹出窗口或侧边面板中显示地点详情
  • 集成前往所选地点的路线

依赖项

必需:

  • Mapbox GL JS v3.x
  • @turf/turf —— 用于空间计算(距离、面积等)

安装:

npm install mapbox-gl @turf/turf

核心架构

模式概览

典型的门店定位器包括:

  1. 地图显示 —— 将所有地点显示为标记
  2. 地点数据 —— 包含门店/地点信息的 GeoJSON
  3. 交互式列表 —— 列出所有地点的侧边面板
  4. 筛选 —— 搜索、类别筛选、距离筛选
  5. 详情视图 —— 显示地点详情的弹出窗口或面板
  6. 用户位置 —— 用于计算距离的地理定位。若要显示蓝点位置指示器,请使用内置的 mapboxgl.GeolocateControl,它比自定义标记更简单。
  7. 路线 —— 前往所选地点的路线(可选)
数据结构

地点的 GeoJSON 格式:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [-77.034084, 38.909671]
      },
      "properties": {
        "id": "store-001",
        "name": "Downtown Store",
        "address": "123 Main St, Washington, DC 20001",
        "phone": "(202) 555-0123",
        "hours": "Mon-Sat: 9am-9pm, Sun: 10am-6pm",
        "category": "retail",
        "website": "https://example.com/downtown"
      }
    }
  ]
}

关键属性:

  • id —— 每个地点的唯一标识符
  • name —— 显示名称
  • address —— 用于显示和地理编码的完整地址
  • coordinates —— [longitude, latitude] 格式
  • category —— 用于筛选(零售、餐厅、办公室等)
  • 根据需要添加自定义属性(营业时间、电话、网站等)

基本门店定位器实现

步骤 1:初始化地图和数据
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';

// Store locations data
const stores = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [-77.034084, 38.909671]
      },
      properties: {
        id: 'store-001',
        name: 'Downtown Store',
        address: '123 Main St, Washington, DC 20001',
        phone: '(202) 555-0123',
        category: 'retail'
      }
    }
    // ... more stores
  ]
};

const map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/standard',
  center: [-77.034084, 38.909671],
  zoom: 11
});
步骤 2:向地图添加标记

按地点数量选择标记策略:

数量策略原因
少于 100 个HTML 标记可全面控制 DOM/CSS;DOM 节点数量易于管理
100–1,000 个符号图层(默认)通过 WebGL 在 GPU 上渲染——一个 <canvas>,每个点无需单独的 DOM 元素
多于 1,000 个聚合在大范围视图中减少视觉杂乱

HTML 标记会为每个点创建一个 DOM 元素。地点超过约 100 个后,浏览器会花费过多时间进行布局/绘制。符号图层完全绕过 DOM,GPU 会在一次 WebGL 绘制调用中绘制所有点。

符号图层实现(最适合 100–1,000 个地点)。对于 HTML 标记(少于 100 个地点)或聚合(多于 1,000 个地点),请参阅 references/markers.md。

map.on('load', () => {
  // Add store data as source
  map.addSource('stores', {
    type: 'geojson',
    data: stores
  });

  // Add custom marker image
  map.loadImage('/marker-icon.png', (error, image) => {
    if (error) throw error;
    map.addImage('custom-marker', image);

    // Add symbol layer
    map.addLayer({
      id: 'stores-layer',
      type: 'symbol',
      source: 'stores',
      layout: {
        'icon-image': 'custom-marker',
        'icon-size': 0.8,
        'icon-allow-overlap': true,
        'text-field': ['get', 'name'],
        'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
        'text-offset': [0, 1.5],
        'text-anchor': 'top',
        'text-size': 12
      }
    });
  });

  // Handle marker clicks using Interactions API (recommended)
  map.addInteraction('store-click', {
    type: 'click',
    target: { layerId: 'stores-layer' },
    handler: (e) => {
      const store = e.feature;
      flyToStore(store);
      createPopup(store);
    }
  });

  // Or using traditional event listener:
  // map.on('click', 'stores-layer', (e) => {
  //   const store = e.features[0];
  //   flyToStore(store);
  //   createPopup(store);
  // });
  //
  // For why addInteraction is preferred over map.on(), and how to pair it with
  // setFeatureState/appearances for hover and selection styling, see the
  // "interactions" reference file in the mapbox-style-patterns skill.

  // Change cursor on hover
  map.on('mouseenter', 'stores-layer', () => {
    map.getCanvas().style.cursor = 'pointer';
  });

  map.on('mouseleave', 'stores-layer', () => {
    map.getCanvas().style.cursor = '';
  });
});
步骤 3:构建交互式地点列表
function buildLocationList(stores) {
  const listingContainer = document.getElementById('listings');

  stores.features.forEach((store, index) => {
    const listing = listingContainer.appendChild(document.createElement('div'));
    listing.id = `listing-${store.properties.id}`;
    listing.className = 'listing';

    const link = listing.appendChild(document.createElement('a'));
    link.href = '#';
    link.className = 'title';
    link.id = `link-${store.properties.id}`;
    link.innerHTML = store.properties.name;

    const details = listing.appendChild(document.createElement('div'));
    details.innerHTML = `
      <p>${store.properties.address}</p>
      <p>${store.properties.phone || ''}</p>
    `;

    // Handle listing click
    link.addEventListener('click', (e) => {
      e.preventDefault();
      flyToStore(store);
      createPopup(store);
      highlightListing(store.properties.id);
    });
  });
}

function flyToStore(store) {
  map.flyTo({
    center: store.geometry.coordinates,
    zoom: 15,
    duration: 1000
  });
}

function createPopup(store) {
  const popups = document.getElementsByClassName('mapboxgl-popup');
  // Remove existing popups
  if (popups[0]) popups[0].remove();

  new mapboxgl.Popup({ closeOnClick: true })
    .setLngLat(store.geometry.coordinates)
    .setHTML(
      `<h3>${store.properties.name}</h3>
       <p>${store.properties.address}</p>
       <p>${store.properties.phone}</p>
       ${store.properties.website ? `<a href="${store.properties.website}" target="_blank">Visit Website</a>` : ''}`
    )
    .addTo(map);
}

// IMPORTANT: highlightListing MUST include scrollIntoView — without it,
// selecting a marker on the map won't scroll the sidebar to the listing.
function highlightListing(id) {
  // Remove existing highlights
  const activeItem = document.getElementsByClassName('active');
  if (activeItem[0]) {
    activeItem[0].classList.remove('active');
  }

  // Add highlight to selected listing
  const listing = document.getElementById(`listing-${id}`);
  listing.classList.add('active');

  // Scroll the selected listing into view (critical UX requirement)
  listing.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

// Build the list on load
map.on('load', () => {
  buildLocationList(stores);
});

参考文件

根据需要加载这些参考文件,了解更多模式:

参考内容文件内容
HTML 标记与聚类references/markers.mdHTML 标记(< 100 个位置)、聚类(> 1000 个位置)
搜索与筛选references/search-filter.md文本搜索、类别筛选
地理位置与路线指引references/geolocation-directions.md用户位置、距离计算、路线指引
样式与布局references/styling-layout.md完整的 HTML/CSS 布局、自定义标记 CSS
性能与无障碍references/optimization-a11y.md防抖搜索、数据管理、错误处理、无障碍支持
变体与 Reactreferences/variations-react.md移动端优先、全屏、仅地图、React 实现

资源

评论

支持 Markdown