关于 mapbox-store-locator-patterns
mapbox-store-locator-patterns 是使用 Mapbox GL JS 构建门店定位器和地点搜索应用的模式指南,面向地图应用开发者。内容涵盖地图标记、筛选、距离计算及列表与地图交互,并提供按地点数量选择标记策略的建议。
主要功能
- 01GeoJSON 地点数据结构
- 02地图标记与聚合模式
- 03地点列表与地图联动
- 04搜索和类别筛选
- 05定位、距离与路线指引
适用场景
- 01在地图上展示门店或餐厅
- 02按类别或距离筛选地点
- 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.mdevals/evals.jsonreferences/geolocation-directions.mdmarkers.mdoptimization-a11y.mdsearch-filter.mdstyling-layout.mdvariations-react.md详细介绍
门店定位器模式技能
使用 Mapbox GL JS 构建门店定位器、餐厅查找器和基于位置的搜索应用的全面模式。涵盖标记显示、筛选、距离计算、交互式列表和路线集成。
何时使用此技能
在构建以下应用时使用此技能:
- 在地图上显示多个地点(商店、餐厅、办公室等)
- 允许用户筛选或搜索地点
- 计算用户位置与地点之间的距离
- 提供与地图标记同步的交互式列表
- 在弹出窗口或侧边面板中显示地点详情
- 集成前往所选地点的路线
依赖项
必需:
- Mapbox GL JS v3.x
- @turf/turf —— 用于空间计算(距离、面积等)
安装:
npm install mapbox-gl @turf/turf
核心架构
模式概览
典型的门店定位器包括:
- 地图显示 —— 将所有地点显示为标记
- 地点数据 —— 包含门店/地点信息的 GeoJSON
- 交互式列表 —— 列出所有地点的侧边面板
- 筛选 —— 搜索、类别筛选、距离筛选
- 详情视图 —— 显示地点详情的弹出窗口或面板
- 用户位置 —— 用于计算距离的地理定位。若要显示蓝点位置指示器,请使用内置的
mapboxgl.GeolocateControl,它比自定义标记更简单。 - 路线 —— 前往所选地点的路线(可选)
数据结构
地点的 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.md | HTML 标记(< 100 个位置)、聚类(> 1000 个位置) |
| 搜索与筛选 | references/search-filter.md | 文本搜索、类别筛选 |
| 地理位置与路线指引 | references/geolocation-directions.md | 用户位置、距离计算、路线指引 |
| 样式与布局 | references/styling-layout.md | 完整的 HTML/CSS 布局、自定义标记 CSS |
| 性能与无障碍 | references/optimization-a11y.md | 防抖搜索、数据管理、错误处理、无障碍支持 |
| 变体与 React | references/variations-react.md | 移动端优先、全屏、仅地图、React 实现 |
资源
- Turf.js - 空间分析库(推荐用于距离计算)
- Mapbox GL JS API
- Interactions API 指南
- GeoJSON 规范
- Directions API
- 门店定位器教程
评论
支持 Markdown