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> は 1 つで、地点ごとの DOM 要素はありません |
| 1,000 超 | クラスタリング | 大規模な表示で視覚的な混雑を軽減します |
HTML マーカーは地点ごとに DOM 要素を 1 つ作成します。場所が約 100 か所を超えると、ブラウザーはレイアウトや描画に時間をかけすぎます。シンボルレイヤーは DOM を完全に介さず、GPU が WebGL の 1 回の描画呼び出しですべての地点を描画します。
シンボルレイヤーの実装(場所が 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
- インタラクション API ガイド
- GeoJSON 仕様
- Directions API
- 店舗検索チュートリアル