🗾

Google Mapの埋め込みURLからマップへのリンクを生成する

に公開

Google Mapの埋め込みURLは、直接URLを表示しようとすると

The Google Maps Embed API must be used in an iframe.

と表示され地図が見られません。
このURLをなんとかしてGoogleマップに辿り着きたかった。

まあ埋め込みコードを表示しさえすれば「拡大地図を表示」からマップに飛べるんだけどね

今回試す地図

https://maps.app.goo.gl/6mN8vZxF9Vk3ZWUS6

埋め込みコード・埋め込みURL

<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3751.431566109456!2d139.76448647615422!3d35.68114412997824!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60188bfc40f10f69%3A0x30bde288c0edfb04!2z5p2x5Lqs6aeF5LiA55Wq6KGX!5e1!3m2!1sja!2sjp!4v1750416763037!5m2!1sja!2sjp" width="600" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>

https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3751.431566109456!2d139.76448647615422!3d35.68114412997824!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x60188bfc40f10f69%3A0x30bde288c0edfb04!2z5p2x5Lqs6aeF5LiA55Wq6KGX!5e1!3m2!1sja!2sjp!4v1750416763037!5m2!1sja!2sjp

埋め込みURL パラメータの解析

pbクエリの文字列の中で、!1dとか!1dとか!2zとかから始まっているのがそれぞれパラメータになります。

  • !1d: ズーム係数
  • !2d: 緯度
  • !3d: 経度
  • !1s: %3Aで区切られた後半のほうが場所のID(16進)
    だったりそうじゃなかったりする、なんだこれ
  • !2z: Base64URL化された表示名
  • のこり: わからん……

地図へのリンク

埋め込みコードの「拡大地図を表示」の地図リンクは以下のようになります。
https://maps.google.com/maps?ll=35.682289,139.76849&z=15&t=h&hl=ja&gl=JP&mapclient=embed&cid=3512212361399106308

  • ll: 緯度経度
    (longitude,latitude?)
  • z: ズームレベル
    (0:広域 - 22:詳細)
  • cid: 場所のID

パラメータ変換方法

ll

!2d,!3d

cid

!1sの、%3A以降の16進数(0x...)を10進化する

でときどきいけない時がある。
!2zで表示名拾ってきて、検索クエリqで表示させた方が無難か?
ただし同じ名前が複数あるときは選択されない

q

!2zをBase64UrlEncodeした値

z

!1d, !2d, !3dをいろいろ変えてzの変化を確認したところ

  • !1dのズーム係数にのみ依存
  • 対数スケールになってそう

おそらくこんな感じの式。正確性は保証しません

const BASE = 625.6721325515;
const calculatedZoom = 19 - Math.log2(distance / BASE);
const zoom = Math.floor(calculatedZoom);

コード例

こんな感じ
cidどうしよう、なんもわからん

const convertEmbedToRegularUrl = (embedUrl: string): string | null => {
  try {
    // 1. 施設名を抽出 (!2z - Base64エンコード)
    const placeBase64Regex = /!2z([^!]+)/;
    const placeBase64Match = placeBase64Regex.exec(embedUrl);
    if (!placeBase64Match?.[1]) {
      return null; // 施設名が必須
    }

    // Base64URLからBase64に変換してからデコード
    let base64String = placeBase64Match[1];
    base64String = base64String.replace(/-/g, "+").replace(/_/g, "/");
    // パディングを追加
    while (base64String.length % 4) {
      base64String += "=";
    }
    const base64Decoded = atob(base64String);
    const bytes = new Uint8Array(base64Decoded.length);
    for (let i = 0; i < base64Decoded.length; i++) {
      bytes[i] = base64Decoded.charCodeAt(i);
    }
    const placeName = new TextDecoder("utf-8").decode(bytes);

    // 2. 座標を抽出 (!2d = 経度, !3d = 緯度)
    const lngRegex = /!2d(-?\d+\.\d+)/;
    const latRegex = /!3d(-?\d+\.\d+)/;
    const lngMatch = lngRegex.exec(embedUrl);
    const latMatch = latRegex.exec(embedUrl);

    if (!latMatch?.[1] || !lngMatch?.[1]) {
      return null; // 座標が必須
    }

    const coordinates = `${latMatch[1]},${lngMatch[1]}`;

    // 3. ズームレベルを計算 (!1d値から)
    const distanceRegex = /!1d(\d+(?:\.\d+)?)/;
    const distanceMatch = distanceRegex.exec(embedUrl);
    if (!distanceMatch?.[1]) {
      return null; // ズームレベルが必須
    }

    const distance = parseFloat(distanceMatch[1]);
    const GOOGLE_MAPS_BASE = 625.6721325515;
    const calculatedZoom = 19 - Math.log2(distance / GOOGLE_MAPS_BASE);
    const zoom = Math.floor(calculatedZoom).toString();

    // 4. URLを生成
    const params = new URLSearchParams();
    params.set("q", placeName);
    params.set("ll", coordinates);
    params.set("z", zoom);

    // その他のパラメータを追加(実際のパターンに合わせる)
    params.set("t", "m"); // map type
    params.set("hl", "ja"); // language
    params.set("gl", "JP"); // region
    params.set("mapclient", "embed");

    return `https://www.google.com/maps?${params.toString()}`;
  } catch {
    return null;
  }
};

生成されたURL

https://www.google.com/maps?q=%E6%9D%B1%E4%BA%AC%E9%A7%85%E4%B8%80%E7%95%AA%E8%A1%97&ll=35.682293229915025%2C139.76591537615425&z=16&t=m&hl=ja&gl=JP&mapclient=embed

Discussion