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> は 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.mdHTML マーカー(100 か所未満)、クラスタリング(1000 か所超)
検索とフィルターreferences/search-filter.mdテキスト検索、カテゴリーフィルター
位置情報と経路案内references/geolocation-directions.mdユーザーの位置、距離の計算、経路案内
スタイルとレイアウトreferences/styling-layout.mdHTML/CSS の全体的なレイアウト、カスタムマーカーの CSS
パフォーマンスとアクセシビリティreferences/optimization-a11y.md検索のデバウンス、データ管理、エラー処理、アクセシビリティ
バリエーションと Reactreferences/variations-react.mdモバイルファースト、フルスクリーン、地図のみ、React の実装

リソース

コメント