-
Notifications
You must be signed in to change notification settings - Fork 7
Module Controls
ロゴ + 公式サイトリンクを表示するブランディングコントロール。
export class GeoloniaControl implements IControl {
private container: HTMLDivElement;
onAdd(): HTMLDivElement;
onRemove(): void;
getDefaultPosition(): ControlPosition;
}onAdd() {
this.container = document.createElement('div');
this.container.className = 'maplibregl-ctrl';
const img = document.createElement('img');
img.src = 'https://cdn.geolonia.com/logo/geolonia-symbol_1.png';
img.style.width = '16px';
img.style.height = '16px';
img.style.display = 'block';
img.style.cursor = 'pointer';
img.style.padding = '0';
img.style.margin = '0';
img.style.border = 'none';
img.alt = 'Geolonia';
const link = document.createElement('a');
link.href = 'https://geolonia.com/';
link.appendChild(img);
link.title = 'Powered by Geolonia';
this.container.appendChild(link);
return this.container;
}<div class="maplibregl-ctrl"><a href="..."><img src="...geolonia-symbol_1.png" /></a></div>- 画像 16×16 px、inline style で固定
-
<a>は新規タブを開かない (target 未指定) - title 属性
'Powered by Geolonia'
onRemove() {
this.container.parentNode.removeChild(this.container);
}getDefaultPosition(): ControlPosition {
return 'bottom-left';
}maplibre は addControl(control, position?) で position を省略すると control.getDefaultPosition() を呼ぶ。
const { position: geoloniaControlPosition } = parseControlOption(atts.geoloniaControl);
map.addControl(new GeoloniaControl(), geoloniaControlPosition);parseControlOption の戻り値 position を渡すが、atts.geoloniaControl === 'on' (デフォルト) の場合は position = undefined → maplibre 側で getDefaultPosition() (= 'bottom-left') が使われる。
atts.geoloniaControl === 'off' でも GeoloniaControl は追加される (parseControlOption の戻り値 enabled が false でも、enabled をチェックする if が無いため。詳細は geolonia-map.md の controls 節、Suspicious-Logic 参照)。
maplibre 標準 AttributionControl をShadow DOM 内に描画する亜種。CSS を内蔵し、ホストページの CSS から隔離する。
class CustomAttributionControl implements IControl {
constructor(options?: { compact?: boolean; customAttribution?: string | string[] });
getDefaultPosition(): ControlPosition;
onAdd(map): HTMLDivElement;
onRemove(): void;
}
export default CustomAttributionControl;constructor(options = {}) {
this.options = options;
bindAll(['_toggleAttribution', '_updateData', '_updateCompact', '_updateCompactMinimize'], this);
}bindAll (maplibre-util.ts:130-137) で 4 つのメソッドの this を固定 (fn.bind(context))。
要約:
-
this._container = DOM.create('div')で root div を作成 -
const shadow = this._container.attachShadow({ mode: 'open' })で Shadow DOM 化 - Shadow 内に以下を構築:
-
<details class="maplibregl-ctrl maplibregl-ctrl-attrib">(_shadowContainer)-
<summary class="maplibregl-ctrl-attrib-button">(_compactButton) -
<div class="maplibregl-ctrl-attrib-inner">(_innerContainer)
-
-
-
_compactButtonに click リスナー (_toggleAttribution) -
<style>要素を作成、内部に大量の CSS を埋め込み (CustomAttributionControl.ts:84-219) -
_updateAttributions()を実行 (初期 attribution セット) -
_updateCompact()を実行 (responsive 判定) - map イベントを listen:
-
styledata→_updateData -
sourcedata→_updateData -
terrain→_updateData -
resize→_updateCompact -
drag→_updateCompactMinimize
-
-
shadow.appendChild(style)とshadow.appendChild(_shadowContainer) -
window.matchMedia('print')の change イベント listener → 印刷時は強制開く return this._container
136 行に及ぶ CSS リテラル。詳細は Constants の CSS リテラル節。主なクラス:
-
.maplibregl-ctrl— base -
.maplibregl-ctrl-attrib— attribution コンテナ -
.maplibregl-compact— 折りたたみ状態 -
.maplibregl-compact-show— 折りたたみ展開状態 -
.maplibregl-attrib-empty— 空状態 (display: none) -
.maplibregl-ctrl-attrib-button— トグルボタン (<summary>) -
.maplibregl-ctrl-attrib-inner— 中身
_updateAttributions() {
if (!this._map.style) return;
let attributions = [];
if (this.options.customAttribution) {
// string または string[] を attributions に追加
}
if (this._map.style.stylesheet) {
this.styleOwner = this._map.style.stylesheet.owner;
this.styleId = this._map.style.stylesheet.id;
}
const tileManagers = this._map.style.tileManagers;
for (const id in tileManagers) {
const tileManager = tileManagers[id];
if (tileManager.used || tileManager.usedForTerrain) {
const source = tileManager.getSource();
if (source.attribution && attributions.indexOf(source.attribution) < 0) {
attributions.push(source.attribution);
}
}
}
// whitespace 行除去
attributions = attributions.filter((e) => String(e).trim());
// substring を除去 (長い方優先)
attributions.sort((a, b) => a.length - b.length);
attributions = attributions.filter((attrib, i) => {
for (let j = i + 1; j < attributions.length; j++) {
if (attributions[j].indexOf(attrib) >= 0) {
return false;
}
}
return true;
});
const attribHTML = attributions.join(' | ');
if (attribHTML === this._attribHTML) return;
this._attribHTML = attribHTML;
if (attributions.length) {
this._innerContainer.innerHTML = attribHTML;
this._shadowContainer.classList.remove('maplibregl-attrib-empty');
} else {
this._shadowContainer.classList.add('maplibregl-attrib-empty');
}
this._updateCompact();
this._editLink = null;
}this._map.style.tileManagers が maplibre 5.11.0 以降の API (旧名 sourceCaches)。maplibre-gl@5.19.0 で tileManagers が再び有効(PR #482 で対応、issue #487 で議論された経緯あり。詳細は Suspicious-Logic)。
attribution 合成:
-
customAttributionoption - 全 source の
source.attribution(used または usedForTerrain な source のみ) - whitespace 除去
- substring (長い文字列の一部が短い文字列にすでに含まれているケース) 除去
-
' | 'で join
innerHTML = attribHTML で直接挿入 → source.attribution の値は HTML として解釈される (リンク等を許可するため)。XSS リスクが残るかは別途検討必要。
<summary> クリックで:
- compact-show 状態 → open 属性付与 + compact-show 削除
- そうでない → compact-show 追加 + open 削除
<details> の挙動と class の組み合わせで開閉状態を管理。
if (this._map.getCanvasContainer().offsetWidth <= 640 || this._compact) {
// compact mode
if (this._compact === false) {
this._shadowContainer.setAttribute('open', '');
} else if (
!this._shadowContainer.classList.contains('maplibregl-compact') &&
!this._shadowContainer.classList.contains('maplibregl-attrib-empty')
) {
this._shadowContainer.setAttribute('open', '');
this._shadowContainer.classList.add('maplibregl-compact', 'maplibregl-compact-show');
}
} else {
this._shadowContainer.setAttribute('open', '');
if (this._shadowContainer.classList.contains('maplibregl-compact')) {
this._shadowContainer.classList.remove('maplibregl-compact', 'maplibregl-compact-show');
}
}- 幅 640px 以下 or
compact === trueで compact mode - それ以上で非 compact mode
- 初期は open で表示
drag (panning) を検知したら compact-show を外す (OSM Foundation の指針: ユーザ操作で attribution を minimize 可能)。
styledata / sourcedata / terrain イベントから呼ばれる。
if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' ||
e.dataType === 'style' || e.type === 'terrain')) {
this._updateAttributions();
}特定の event subtype のみ反応 (パフォーマンス対策)。
-
_containerを DOM から削除 - 5 つの map イベントを off
- インスタンスメンバを undefined に
- print media query listener を解除
geolonia-map.ts で条件付きで addControl される:
| Class | 条件 | maplibre import |
|---|---|---|
FullscreenControl |
parseControlOption(atts.fullscreenControl).enabled |
geolonia-map.ts:1-13 |
NavigationControl |
parseControlOption(atts.navigationControl).enabled |
同上 |
GeolocateControl |
parseControlOption(atts.geolocateControl).enabled |
同上 |
ScaleControl |
parseControlOption(atts.scaleControl).enabled |
同上 |
GeolocateControl のみ new GeolocateControl({}) (空オブジェクト)、他は引数なし。
position は parseControlOption().position で渡される (string または undefined)。
- GeoloniaControl (常に最初に追加、unconditionally)
- CustomAttributionControl (常に bottom-right)
- FullscreenControl (条件付き)
- NavigationControl (条件付き)
- GeolocateControl (条件付き)
- ScaleControl (条件付き)
maplibre の addControl は同じ position に複数追加すると後勝ちで重ねる挙動 → GeoloniaControl を最初に置くことで他の control の下層 (またはそれより前) に配置される。
- GeoloniaControl は maps-core (lib) 側に置く (ブランディングなので必須)
- CustomAttributionControl も lib 側
- maplibre 標準コントロールの追加判定 (
parseControlOption) は wrapper 側でやるか、GeoloniaMap内部で自動化するか議論 - CustomAttributionControl の長大 CSS は外部 CSS ファイル化を検討 (ただし Shadow DOM のメリット失わない設計)
-
GeoloniaControlの enabled 無視問題 (atts === 'off' でも追加) は仕様確認のうえ修正