MENU

問い合わせ


    【NuxtとGoogle Mapsで作る情報集約マップ 第2回】Advanced Markersでカテゴリ別の色付きピンを立てる

    Google Maps API で社内向けの情報集約マップを作る連載の第2回です。前回(第1回)では、APIキーとMap IDを用意して、Nuxt 4 で東京駅を中心にした地図を1枚表示しました。今回はその地図の上に、店舗・物件・現場・点検の4カテゴリのサンプル地点20件を、色分けしたピン(マーカー)で表示します。

    「地図にピンを立てるだけなら簡単そうだけど、古いやり方と新しいやり方があると聞いた。どちらで作ればいいの?」

    結論として、新しく作るなら Advanced Markers(AdvancedMarkerElement と PinElement)一択です。以前の google.maps.Marker は2024年2月に非推奨になっています。この回では Advanced Markers でカテゴリごとに色と文字を変えたピンを立て、今後の回で使う「地点データの型」も決めます。

    目次

    Google Maps のマーカーは「Advanced Markers」が現在の標準

    📰 出典:Google Maps Platform「Deprecations」

    公式の非推奨一覧では、google.maps.Marker は Maps JavaScript API のバージョン3.56(2024年2月)で非推奨とされています。執筆時点(2026年9月)では提供終了の予定は発表されておらず、終了する場合は少なくとも12か月前に告知するとされていますが、推奨は AdvancedMarkerElement です。

    項目旧 MarkerAdvanced Markers
    クラスgoogle.maps.Markergoogle.maps.marker.AdvancedMarkerElement
    状態非推奨(2024年2月〜)推奨
    見た目の変更アイコン画像を差し替えPinElement で色・文字を指定、または任意のHTML
    Map ID不要必須(開発中は DEMO_MAP_ID で可)

    📰 出典:Google Maps Platform「Advanced Markers overview」

    Advanced Markers は Map ID が必須です。第1回の MapView.vue で mapId を渡していたのは、この回のためです。

    最新の書き方で変わった点

    @types/google.maps 3 系の型定義を確認すると、Advanced Markers まわりでもいくつかの書き方が非推奨になっています。古い解説記事のコードをそのまま使うと警告の原因になるので、次の表で押さえておきましょう。

    旧い書き方(非推奨)現在の書き方
    content: pin.elementPinElement 自体が HTML 要素なので marker.append(pin)
    new PinElement({ glyph: '店' })glyphText: '店'(画像なら glyphSrc)
    marker.addListener('click', …)marker.addEventListener('gmp-click', …)(第4回で使用)

    📰 出典:Google Maps Platform「Basic marker customization」

    公式のカスタマイズ例も PinElement を作って marker.append(pin) で付ける形になっています。

    実装:地点データの型とサンプル20件を用意する

    この回で追加・変更したファイルは次のとおりです。

    ファイル役割
    app/types/point.ts地点の型とカテゴリの定義(色・表示名)
    app/data/sample-points.tsサンプル20地点(第3回でDBに置き換え)
    app/composables/useMarkers.ts地点データからマーカーを作る
    app/components/CategoryLegend.vueカテゴリの凡例
    app/pages/index.vue地図の準備ができたらマーカーを描く

    手順1:地点の型とカテゴリの定義

    app/types/point.ts

    export const CATEGORIES = ['store', 'property', 'site', 'inspection'] as const
    export type Category = (typeof CATEGORIES)[number]
    
    export interface Point {
      id: number
      name: string
      category: Category
      lat: number
      lng: number
      memo: string
      updatedAt: string // ISO 8601
    }
    
    // カテゴリごとの表示名・ピンの色・ピン内の文字
    export const CATEGORY_META: Record<
      Category,
      { label: string; color: string; border: string; glyph: string }
    > = {
      store: { label: '店舗', color: '#1a73e8', border: '#0b4fb3', glyph: '店' },
      property: { label: '物件', color: '#188038', border: '#0d5a26', glyph: '物' },
      site: { label: '現場', color: '#e37400', border: '#a35300', glyph: '現' },
      inspection: { label: '点検', color: '#a142f4', border: '#7127b5', glyph: '点' },
    }

    カテゴリを as const の配列で定義しておくと、型(Category)・凡例の並び順・次回のサーバー側の入力チェックで同じ定義を使い回せます。カテゴリの色と文字を1か所にまとめておけば、「点検の色を変えたい」という要望にも1行の修正で対応できます。

    色だけで区別すると、色の見分けがつきにくい方には判別しにくくなります。そのため、ピンの中に「店」「物」などの文字も入れています。

    手順2:サンプルデータ

    app/data/sample-points.ts(抜粋)

    import type { Point } from '~/types/point'
    
    // 第2回用のサンプル地点(東京駅周辺・架空のデータ)。第3回で DB に置き換える。
    const updatedAt = '2026-08-01T09:00:00+09:00'
    
    export const samplePoints: Point[] = [
      { id: 1, name: '丸の内店', category: 'store', lat: 35.6812, lng: 139.7644, memo: '', updatedAt },
      { id: 6, name: '京橋ビル(空室あり)', category: 'property', lat: 35.6770, lng: 139.7708, memo: '', updatedAt },
      { id: 11, name: '八重洲二丁目 改修工事', category: 'site', lat: 35.6788, lng: 139.7728, memo: '', updatedAt },
      { id: 16, name: '呉服橋 消火設備点検', category: 'inspection', lat: 35.6830, lng: 139.7715, memo: '', updatedAt },
      // …各カテゴリ5件ずつ、計20件(全体はサンプルコード参照)
    ]

    名称と座標は東京駅周辺に置いた架空のデータです。緯度経度は自分で決めた値なので、Google の API から取得したデータに関する保存期間の制限(第6回で解説)は関係しません。

    手順3:マーカーを作る composable

    app/composables/useMarkers.ts

    import { CATEGORY_META, type Point } from '~/types/point'
    
    export function useMarkers() {
      const { load } = useGoogleMaps()
      const markers = new Map<number, google.maps.marker.AdvancedMarkerElement>()
    
      async function render(map: google.maps.Map, points: Point[]) {
        const { AdvancedMarkerElement, PinElement } = await load('marker')
        clear()
    
        for (const point of points) {
          const meta = CATEGORY_META[point.category]
          // カテゴリ色のピン(PinElement は HTMLElement なのでそのまま子要素にする)
          const pin = new PinElement({
            background: meta.color,
            borderColor: meta.border,
            glyphText: meta.glyph,
            glyphColor: '#fff',
          })
          const marker = new AdvancedMarkerElement({
            map,
            position: { lat: point.lat, lng: point.lng },
            title: `${meta.label}: ${point.name}`, // ツールチップ兼スクリーンリーダー用
          })
          marker.append(pin)
          markers.set(point.id, marker)
        }
      }
    
      function clear() {
        for (const marker of markers.values()) marker.map = null
        markers.clear()
      }
    
      // コンポーネントが破棄されたらマーカーも地図から外す
      onScopeDispose(clear)
    
      return { render, clear, markers }
    }

    ポイントは3つです。

    • load('marker') で marker ライブラリを読み込みます。第1回の useGoogleMaps を通すので、キー未設定時のエラー処理も共通です
    • マーカーは Vue の ref に入れず、プレーンな Map(id → マーカー)で管理します。第1回の地図インスタンスと同じく、Vue の変更監視で重くならないようにするためです。id で引けるようにしておくと、第4回の「クリックした地点の詳細表示」や第8回の「一覧と地図の連動」でそのまま使えます
    • マーカーを地図から外すには marker.map = null とします。描き直しの前とコンポーネント破棄時に必ず外し、古いピンが残らないようにします

    title を指定すると、マウスを重ねたときのツールチップになり、スクリーンリーダー向けの読み上げテキストにもなります。

    手順4:ページで地図の準備を待ってから描く

    app/pages/index.vue(script 部分)

    <script setup lang="ts">
    import { samplePoints } from '~/data/sample-points'
    
    const { render } = useMarkers()
    
    async function onMapReady(map: google.maps.Map) {
      await render(map, samplePoints)
    }
    </script>

    テンプレートでは、第1回の <ClientOnly> 内の <MapView> に @ready="onMapReady" を付け、上に凡例(<CategoryLegend />)を置いています。MapView は地図の作成が終わった時点で ready イベントを出すので、「地図がまだないのにマーカーを作ろうとする」順序の問題を避けられます。

    動作確認の方法

    1. .env にブラウザ用キーを設定し、npm run dev → http://localhost:3000 を開く
    2. 東京駅周辺に20個のピンが、青(店舗)・緑(物件)・オレンジ(現場)・紫(点検)で表示されることを確認
    3. ピンにマウスを重ねると「店舗: 丸の内店」のようなツールチップが出ることを確認
    4. DevTools のコンソールに、Map ID が無いことによる警告や、Marker の非推奨警告が出ていないことを確認
    5. npm run build と npm run typecheck が通ることを確認

    筆者の環境では npm run build と npm run typecheck の成功、ビルド後のサーバーが返すHTMLに凡例(店舗・物件・現場・点検)が入っていることまでを確認しました。実際のピンの表示とコンソールの警告の有無は、執筆環境に本物のAPIキーがないため確認できていません。 ご自身のキーを .env に設定して確認してください。

    つまずきやすい点

    • Map ID を渡し忘れる:Advanced Markers は Map ID が無いと表示されません。DEMO_MAP_ID は開発用なので、本番では自分のプロジェクトで Map ID を発行します
    • 同じ PinElement を複数のマーカーに使い回す:要素は複製されず「移動」するため、前のマーカーが空になります。ピンはマーカーごとに作ります
    • 古い解説のコードを混ぜる:pin.element や glyph、addListener('click') は型定義上で非推奨です。TypeScript の型チェックを通しておくと、エディタ上で取り消し線や警告として気づけます
    • 件数が多い場合:20件ならこの方法で十分ですが、数千件になると1つずつ地図に載せるのは重くなります。第9回でクラスタリング(近いピンをまとめて表示)と表示範囲だけの読み込みを扱います

    発注者向けメモ

    • 既存システムの改修では「Marker → Advanced Markers 移行」が隠れ工数になりやすい。旧 Marker は今すぐ動かなくなるわけではありませんが、非推奨です。改修の見積りで地図部分に触れる場合は、移行を含めるかどうかを確認しておくと後で揉めません
    • Map ID の発行は発注者のプロジェクトで。Map ID は Google Cloud のプロジェクトに紐づくため、第1回のキーと同じく発注者名義のプロジェクトで作るのが原則です
    • ピンを立てること自体に追加のAPI費用はかからない構造です。地図の費用は主に地図の読み込み回数で決まり、マーカーは地図の中で描画されます。ただし「ピンの見た目を凝る(写真入り・吹き出し型など)」要望は、デザインと実装の工数に効いてきます
    • カテゴリの種類と色は、業務の言葉で先に決めておく。後から増減すると、凡例・絞り込み(第8回)・CSV取り込み(第11回)にも影響します

    開発会社への確認に使える質問例です。

    • 「マーカーは Advanced Markers で実装しますか?既存の旧 Marker からの移行は見積りに入っていますか?」
    • 「本番用の Map ID は、どのプロジェクトで誰が発行しますか?」
    • 「色の見分けがつきにくい利用者にも区別できるよう、ピンに文字やアイコンを入れられますか?」

    まとめと次回予告

    この回では、非推奨の旧 Marker ではなく Advanced Markers を使い、カテゴリごとに色と文字を変えたピンを20件表示しました。PinElement は marker.append(pin) で付ける、文字は glyphText で指定する、マーカーは Vue のリアクティブにせず id で引ける Map で管理する、の3点が押さえどころです。

    次回は「地点データをNuxt server API + SQLiteに保存する」です。今回ファイルに直書きしたサンプル地点を、Nuxt のサーバーAPIとSQLiteに移し、登録・更新・削除ができる土台を作ります。

    この連載の記事一覧

    この記事は連載「NuxtとGoogle Mapsで作る情報集約マップ」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      コメント

      コメント一覧 (1件)

      目次