🗾
Google Mapの埋め込みURLからマップへのリンクを生成する
Google Mapの埋め込みURLは、直接URLを表示しようとすると
The Google Maps Embed API must be used in an iframe.
と表示され地図が見られません。
このURLをなんとかしてGoogleマップに辿り着きたかった。
まあ埋め込みコードを表示しさえすれば「拡大地図を表示」からマップに飛べるんだけどね
今回試す地図
埋め込みコード・埋め込み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>
埋め込み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;
}
};
Discussion