Skip to content
niryuu edited this page May 25, 2026 · 1 revision

隠れスペック (未文書化機能の規約)

embed 内部または Geolonia 側との間で暗黙の約束になっている挙動。public ドキュメント / TS 型に出てこない仕様。書き直しの際にも維持する必要があり、見落とすと外部利用者の地図が壊れる。

1. 3D Layer Metadata 規約

スタイル JSON の layer.metadata フィールドに以下のキーが任意で設定可能 (geolonia-map.ts:327-356)。

metadata キー 効果 (data-3d="on" 時) 効果 (data-3d="off" 時)
visible-on-3d boolean (true) このレイヤを表示 (visibility: visible) 非表示 (visibility: none)
hide-on-3d boolean (true) 非表示 (visibility: none) 表示 (visibility: visible)

ロジック (geolonia-map.ts:329-355):

style.layers.forEach((layer) => {
  if (atts['3d'] === 'on' && layer.metadata && layer.metadata['visible-on-3d'] === true) {
    map.setLayoutProperty(layer.id, 'visibility', 'visible');
  } else if (atts['3d'] === 'off' && layer.metadata && layer.metadata['visible-on-3d'] === true) {
    map.setLayoutProperty(layer.id, 'visibility', 'none');
  } else if (atts['3d'] === 'on' && layer.metadata && layer.metadata['hide-on-3d'] === true) {
    map.setLayoutProperty(layer.id, 'visibility', 'none');
  } else if (atts['3d'] === 'off' && layer.metadata && layer.metadata['hide-on-3d'] === true) {
    map.setLayoutProperty(layer.id, 'visibility', 'visible');
  }
});

例: 建物 3D extrusion レイヤに metadata: { 'visible-on-3d': true } を付けると、data-3d="on" の時のみ表示。地表の建物 footprint レイヤに metadata: { 'hide-on-3d': true } を付けると、data-3d="on" の時に非表示にする (3D と 2D で対のレイヤを切替)。

この規約は Geolonia スタイル JSON 側でも前提になっている。外部スタイルにこの metadata を仕込まない限り 3D 切替は無効。

書き直し時にレイヤ visibility 制御の方式を変える場合、サーバ側スタイルとの後方互換を維持する必要あり。

2. SimpleStyle GeoJSON Properties 規約

GeoJSON Feature の properties で意味を持つキー。Mapbox の SimpleStyle 仕様準拠だが、embed 固有の挙動もある。

Style properties (simplestyle.ts / simplestyle-vector.ts のスタイル式から抽出)

プロパティ 用途 デフォルト
marker-color hex 色 Point の circle 塗り色 'rgba(255, 0, 0, 0.4)'
marker-size 'small' / 'large' / その他 circle 半径とテキストオフセット 中サイズ (9 / 1.2)
marker-symbol icon ID (string) icon-image プロパティに使用。アイコン無指定 (= 空文字 or undefined) なら circle レイヤに描画 (なし)
fill hex 色 Polygon 塗り backgroundColor ('rgba(255, 0, 0, 0.4)')
fill-opacity 数値 (0-1) Polygon 透明度 1.0
stroke hex 色 LineString 色 / Polygon outline / 円 stroke backgroundColor (線), '#FFFFFF' (円 stroke)
stroke-width 数値 線幅 2 (Linestring), 1 (Circle stroke)
stroke-opacity 数値 (0-1) 線透明度 1.0
text-color hex 色 テキスト塗り '#000000'
text-halo-color hex 色 テキストハロー '#FFFFFF'
title string レイヤラベル (text-field) (空)
description string (HTML) popup 内容 (なし。クリックしても popup 出ない)

marker-size の具体マッピング

GeoJSON 版 (simplestyle.ts:220-225, 260-264):

  • 'small': circle-radius: 7, text-radial-offset: 1
  • 'large': circle-radius: 13, text-radial-offset: 1.6
  • その他: circle-radius: 9, text-radial-offset: 1.2

Vector 版 (simplestyle-vector.ts:177-182, 213-220):

  • 'small': circle-radius: 7, text-offset: [0, 1]
  • 'large': circle-radius: 13, text-offset: [0, 1.6]
  • その他: circle-radius: 9, text-offset: [0, 1.2]

description 処理

simplestyle.ts:280-285 / simplestyle-vector.ts:235-241:

if (description) {
  const sanitizedDescription = await sanitizeDescription(description);
  new maplibregl.Popup()
    .setLngLat(center)
    .setHTML(sanitizedDescription)
    .addTo(map);
}
  • description が truthy のみ popup 表示
  • sanitize-html でフィルタ後、setHTML で挿入 (HTML として解釈)

3. geolonia:// URL プロトコル

embed 固有の URL スキーム。data-simple-vector 属性経由で受け取り、内部で実 URL に展開する。

形式

形式 意味
geolonia://tiles/custom/{customtileId} カスタムベクトルタイル
geolonia://tiles/{username}/{customtileId} (transformRequest が match する正規表現。実用上は上と同じ意味で username は捨てられる)

変換ロジック

Step 1: parseSimpleVector (util.ts:358-364):

export const parseSimpleVector = (attributeValue) => {
  if (/^(https?|geolonia):\/\//.test(attributeValue)) {
    return attributeValue;
  } else {
    return `geolonia://tiles/custom/${attributeValue}`;
  }
};

data-simple-vector="myid"geolonia://tiles/custom/myid に変換。

Step 2: transformRequest (geolonia-map.ts:139-146):

if (url.startsWith('geolonia://')) {
  const tilesMatch = url.match(
    /^geolonia:\/\/tiles\/(?<username>.+)\/(?<customtileId>.+)/,
  );
  if (tilesMatch) {
    transformedUrl = `https://tileserver.geolonia.com/customtiles/${tilesMatch.groups.customtileId}/tiles.json`;
  }
}

https://tileserver.geolonia.com/customtiles/{customtileId}/tiles.json に展開。

: regex match の username キャプチャは捨てられるcustomtileId のみ使われる。ユーザ名で識別する設計だったが、実装上は ID で十分というケース。

詳細は URL-Rewrite-Spec 参照。

4. container.geoloniaMap シングルトンキャッシュ

HTMLElement に動的プロパティを生やして重複生成を防ぐ仕組み。

設定

geolonia-map.ts:366:

container.geoloniaMap = map;

参照

geolonia-map.ts:86-88:

if (container.geoloniaMap) {
  return container.geoloniaMap;
}

解除

geolonia-map.ts:400-405:

remove(): void {
  const container = this.getContainer();
  super.remove.call(this);
  delete (container as HTMLElement & { geoloniaMap: GeoloniaMap }).geoloniaMap;
}

含意

  • 同一 container に対する new GeoloniaMap()シングルトン
  • remove() 後は新規生成可能
  • React 等の hot-reload で同じ container が再 mount されると、前の map が remaining で memory leak の可能性 (cleanup タイミングに依存)
  • TS の型システムで保護されていない (as Container で動的に通している)

書き直し時は WeakMap 等で外部管理することも検討可能だが、後方互換のため container.geoloniaMap 自体は残すべき (外部コードが直接読んでいる可能性)。

5. __styleExtensionLoadRequired フラグ

GeoloniaMap の private instance プロパティ。styledata イベントハンドラの再入防止フラグ。

用途

  1. constructor: __styleExtensionLoadRequired = true (geolonia-map.ts:198)
  2. styledata 1 回目: 拡張処理を実行し false に (geolonia-map.ts:294-297)
  3. setStyle() override: true にリセット (geolonia-map.ts:393)
  4. styledata 2 回目以降: フラグ false なので何もしない

これにより styledata が複数回 fire しても、SimpleStyle/3D 等の拡張処理は 1 回のみ動作。setStyle() で明示的に reset することで再ロード。

maplibre の styledata 挙動

map.on('styledata', ...) は style の更新ごとに発火する。constructor 内の super(options) でも内部的に style 読み込みが走るので、その完了後に発火する。 つまり constructor 直後 + setStyle() ごと + その他のスタイル変更ごと。

embed では「最初のスタイル読み込み完了時に SimpleStyle 等を一度だけ追加したい」ので、このフラグで guard。

6. sanitize-html 許可設定

util.ts:394-403:

sanitizeHtml(description, {
  allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
  allowedAttributes: {
    ...sanitizeHtml.defaults.allowedAttributes,
    '*': ['class'],
  },
});

sanitize-html defaults

(sanitize-html パッケージのデフォルト。具体は version 依存だが概ね):

  • allowedTags: address, article, aside, footer, header, h1-h6, hgroup, main, nav, section, blockquote, dd, div, dl, dt, figcaption, figure, hr, li, main, ol, p, pre, ul, a, abbr, b, bdi, bdo, br, cite, code, data, dfn, em, i, kbd, mark, q, rb, rp, rt, rtc, ruby, s, samp, small, span, strong, sub, sup, time, u, var, wbr, caption, col, colgroup, table, tbody, td, tfoot, th, thead, tr
  • allowedAttributes: a: ['href', 'name', 'target'], img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading']

追加

  • imgallowedTags に追加
  • 全要素 ('*') で class 属性許可

含意

  • popup HTML で <img> <a> <table> 等利用可能
  • CSS のための class 属性を全要素で許可 → カスタムスタイリング可能
  • <script> <iframe> <style> 等は許可されない (XSS リスク低減)
  • ただし <img src="javascript:..."> 等は sanitize-html 側でフィルタされる想定 (詳細は sanitize-html 仕様)

書き直し時に sanitize-html を別ライブラリに変える場合、これらの許可設定を equivalent に再現必要。

7. cluster 閾値 (simplestyle.ts:91-92)

this.map.addSource(`${this.options.id}-points`, {
  type: 'geojson',
  data: { type: 'FeatureCollection', features: points },
  cluster: this.options.cluster,
  clusterMaxZoom: 14,
  clusterRadius: 50,
});

clusterMaxZoom: 14: zoom 14 までクラスタ化、それ以上は個別ピン展開。 clusterRadius: 50: cluster 半径 (px)。

両方ハードコード。data-cluster-color でクラスタ色は変えられるが、これら 2 つは固定。

8. デフォルト色の二重・三重定義

場所 用途
'#E4402F' parse-atts.ts:41, geolonia-marker.ts:40 デフォルトマーカー色 (2 箇所定義)
'#ff0000' parse-atts.ts:54, simplestyle.ts:33 デフォルトクラスタ色 (2 箇所定義)

同じ値が複数箇所に literal で書かれているため、一方を変更しても他方が追従しない。書き直し時に共通定数 (DEFAULT_MARKER_COLOR = '#E4402F') に集約推奨。

9. HTTP 402 → restricted mode

geolonia-map.ts:359-364 で 402 ステータスを検知すると handleRestrictedMode(map) 呼び出し。

handleRestrictedMode (util.ts:366-374):

  1. map._geolonia_restricted_mode_handled = true (再入防止)
  2. map.remove()
  3. container.innerHTML = ''
  4. container.classList.add('geolonia__restricted-mode-image-container')

その後 CSS 側 (docs/style.css 等、リポジトリ外も含む) で背景画像を表示することを期待。

Geolonia API サーバが支払い不足等で 402 を返す状況の運用挙動。書き直し時もサーバ仕様に従って 402 ハンドリングを維持する必要あり。

10. handleErrorMode 日本語固定メッセージ

util.ts:387:

div.innerHTML +=
  '<div class="geolonia__error-message-description">地図の初期化に失敗しました。管理者にお問い合わせ下さい。開発者の方は開発者ツールでエラー詳細をご確認下さい。</div>';

maplibre.Map constructor 失敗時に表示。日本語固定。i18n の予定があるなら lib 側に置く前に決定したい。

11. localIdeographFontFamily: 'sans-serif'

util.ts:294:

const options: MapOptions = {
  // ...
  localIdeographFontFamily: 'sans-serif',
  // ...
};

CJK (中日韓) 文字を Glyph として読まずに、ブラウザの sans-serif でレンダリングする maplibre オプション。タイルサイズ削減と日本語表示の互換性向上のため。

書き直し時もデフォルトで sans-serif を残すか、ユーザに設定させるか議論。

12. MAP_PLATFORM_STAGE の build-time 注入

webpack.config.js:6-8:

new DefinePlugin({
  'process.env.MAP_PLATFORM_STAGE': JSON.stringify(process.env.MAP_PLATFORM_STAGE || 'dev'),
})
  • ビルド時に process.env.MAP_PLATFORM_STAGE を読んで JS 中に静的置換
  • ランタイムで上書き不可
  • keyring.ts:73 で読まれる → keyring.stage プロパティに

npm scripts (package.json:18,21,22):

  • start: MAP_PLATFORM_STAGE=v1 webpack serve
  • build:embed: MAP_PLATFORM_STAGE=v1 webpack --mode production
  • analyze: ANALYZE=true MAP_PLATFORM_STAGE=v1 webpack --mode production

公式 build はすべて v1 固定。'dev' ビルドは webpack を直接呼ぶ場合のみ可能。

13. プラグインに渡される atts は parse-atts 結果のスナップショット

render.ts:48-53:

const atts = parseAtts(target);
if (isDOMContentLoaded && !map[isRemoved]) {
  plugins.forEach((plugin) => plugin(map, target, atts));
} else {
  alreadyRenderedMaps.push({ map, target: target, atts });
}

attsrenderSingleMap 時点の parseAtts 結果。プラグイン実行時には DOM の data-* が変更されていてもこの atts は古いまま。

DOMContentLoaded 後の plugin 実行時 (render.ts:58-62) も古い atts を渡す:

alreadyRenderedMaps.forEach(({ map, target, atts }) => {
  if (!map[isRemoved]) {
    plugins.forEach((plugin) => plugin(map, target, atts));
  }
});

つまりプラグインは「マップ生成時の atts」を見る。動的変更を見たいプラグインは target.dataset を直接読む必要あり。

14. SVG マーカーの .left / .right クラス要求

geolonia-marker.ts:35-44 で SVG 内部の .left.right クラス要素を querySelectorstyle.fill を直接書き換える。

assets/marker.svg がこの構造を持っていることが前提。SVG 差し替え時はクラス維持か参照側の修正が必要。

15. embed.js 経由の pmtiles プロトコル登録タイミング

  • embed-core.ts:11-12: import 時 (module top-level) に登録
  • embed.ts: renderGeoloniaMap() 内 (render.ts:13-14) で登録 — 実行は embed.ts:49 の呼び出しで起きる

両方 import すると 二重登録になる可能性 (maplibre は同じ protocol を二度 add すると挙動が未定義)。embed.js と embed-core.js の併用は想定外。

16. keyring.parse(container) の container 引数

parse-atts.ts:27keyring.parse(container) を呼ぶが、keyring.parse(doc: Document = document) のシグネチャ。HTMLElement を Document として渡すのは型エラー。実用上は container 上に currentScriptgetElementsByTagName が無いので何も起きず、前の parse 結果が残る。

render.ts:21 で先に keyring.parse() (引数なし) が呼ばれているので、parse-atts での呼び出しは事実上 no-op。

書き直し時はこの container 引数を削除して keyring.parse() のみに統一推奨。

17. iframe 許可特例リスト

util.ts:62-105Codepen / JSFiddle / CodeSandbox の 3 サイトに対し referrer ベースの特例を持つ。

詳細は Constants の "iframe 許可特例" セクション。新サイトを追加するには util.ts を変更する必要

書き直し時にホワイトリスト管理方法を改めるか、現状維持するか判断。

参照

  • Data-Attributes — data-* 属性
  • URL-Rewrite-Spec — transformRequest
  • Constants — 全定数リスト
  • Suspicious-Logic — 怪しいロジック (重複あり)
  • Module-* ページ — モジュール詳細

Clone this wiki locally