-
Notifications
You must be signed in to change notification settings - Fork 7
Hidden Spec
embed 内部または Geolonia 側との間で暗黙の約束になっている挙動。public ドキュメント / TS 型に出てこない仕様。書き直しの際にも維持する必要があり、見落とすと外部利用者の地図が壊れる。
スタイル 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 制御の方式を変える場合、サーバ側スタイルとの後方互換を維持する必要あり。
GeoJSON Feature の properties で意味を持つキー。Mapbox の SimpleStyle 仕様準拠だが、embed 固有の挙動もある。
| プロパティ | 型 | 用途 | デフォルト |
|---|---|---|---|
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 出ない) |
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]
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 として解釈)
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 参照。
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 自体は残すべき (外部コードが直接読んでいる可能性)。
GeoloniaMap の private instance プロパティ。styledata イベントハンドラの再入防止フラグ。
-
constructor:__styleExtensionLoadRequired = true(geolonia-map.ts:198) - styledata 1 回目: 拡張処理を実行し
falseに (geolonia-map.ts:294-297) -
setStyle()override:trueにリセット (geolonia-map.ts:393) - styledata 2 回目以降: フラグ false なので何もしない
これにより styledata が複数回 fire しても、SimpleStyle/3D 等の拡張処理は 1 回のみ動作。setStyle() で明示的に reset することで再ロード。
map.on('styledata', ...) は style の更新ごとに発火する。constructor 内の super(options) でも内部的に style 読み込みが走るので、その完了後に発火する。 つまり constructor 直後 + setStyle() ごと + その他のスタイル変更ごと。
embed では「最初のスタイル読み込み完了時に SimpleStyle 等を一度だけ追加したい」ので、このフラグで guard。
util.ts:394-403:
sanitizeHtml(description, {
allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']),
allowedAttributes: {
...sanitizeHtml.defaults.allowedAttributes,
'*': ['class'],
},
});(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']
-
imgをallowedTagsに追加 - 全要素 (
'*') でclass属性許可
- popup HTML で
<img><a><table>等利用可能 - CSS のための class 属性を全要素で許可 → カスタムスタイリング可能
-
<script><iframe><style>等は許可されない (XSS リスク低減) - ただし
<img src="javascript:...">等は sanitize-html 側でフィルタされる想定 (詳細は sanitize-html 仕様)
書き直し時に sanitize-html を別ライブラリに変える場合、これらの許可設定を equivalent に再現必要。
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 つは固定。
| 色 | 場所 | 用途 |
|---|---|---|
'#E4402F' |
parse-atts.ts:41, geolonia-marker.ts:40
|
デフォルトマーカー色 (2 箇所定義) |
'#ff0000' |
parse-atts.ts:54, simplestyle.ts:33
|
デフォルトクラスタ色 (2 箇所定義) |
同じ値が複数箇所に literal で書かれているため、一方を変更しても他方が追従しない。書き直し時に共通定数 (DEFAULT_MARKER_COLOR = '#E4402F') に集約推奨。
geolonia-map.ts:359-364 で 402 ステータスを検知すると handleRestrictedMode(map) 呼び出し。
handleRestrictedMode (util.ts:366-374):
-
map._geolonia_restricted_mode_handled = true(再入防止) map.remove()container.innerHTML = ''container.classList.add('geolonia__restricted-mode-image-container')
その後 CSS 側 (docs/style.css 等、リポジトリ外も含む) で背景画像を表示することを期待。
Geolonia API サーバが支払い不足等で 402 を返す状況の運用挙動。書き直し時もサーバ仕様に従って 402 ハンドリングを維持する必要あり。
util.ts:387:
div.innerHTML +=
'<div class="geolonia__error-message-description">地図の初期化に失敗しました。管理者にお問い合わせ下さい。開発者の方は開発者ツールでエラー詳細をご確認下さい。</div>';maplibre.Map constructor 失敗時に表示。日本語固定。i18n の予定があるなら lib 側に置く前に決定したい。
util.ts:294:
const options: MapOptions = {
// ...
localIdeographFontFamily: 'sans-serif',
// ...
};CJK (中日韓) 文字を Glyph として読まずに、ブラウザの sans-serif でレンダリングする maplibre オプション。タイルサイズ削減と日本語表示の互換性向上のため。
書き直し時もデフォルトで sans-serif を残すか、ユーザに設定させるか議論。
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 を直接呼ぶ場合のみ可能。
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 });
}atts は renderSingleMap 時点の 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 を直接読む必要あり。
geolonia-marker.ts:35-44 で SVG 内部の .left と .right クラス要素を querySelector し style.fill を直接書き換える。
assets/marker.svg がこの構造を持っていることが前提。SVG 差し替え時はクラス維持か参照側の修正が必要。
-
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 の併用は想定外。
parse-atts.ts:27 で keyring.parse(container) を呼ぶが、keyring.parse(doc: Document = document) のシグネチャ。HTMLElement を Document として渡すのは型エラー。実用上は container 上に currentScript や getElementsByTagName が無いので何も起きず、前の parse 結果が残る。
render.ts:21 で先に keyring.parse() (引数なし) が呼ばれているので、parse-atts での呼び出しは事実上 no-op。
書き直し時はこの container 引数を削除して keyring.parse() のみに統一推奨。
util.ts:62-105 で Codepen / JSFiddle / CodeSandbox の 3 サイトに対し referrer ベースの特例を持つ。
詳細は Constants の "iframe 許可特例" セクション。新サイトを追加するには util.ts を変更する必要。
書き直し時にホワイトリスト管理方法を改めるか、現状維持するか判断。
-
Data-Attributes— data-* 属性 -
URL-Rewrite-Spec— transformRequest -
Constants— 全定数リスト -
Suspicious-Logic— 怪しいロジック (重複あり) - 各
Module-*ページ — モジュール詳細