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

Controls — GeoloniaControl / CustomAttributionControl と maplibre 標準

GeoloniaControl (src/lib/controls/geolonia-control.ts, 38 行)

ロゴ + 公式サイトリンクを表示するブランディングコントロール。

公開 API

export class GeoloniaControl implements IControl {
  private container: HTMLDivElement;
  onAdd(): HTMLDivElement;
  onRemove(): void;
  getDefaultPosition(): ControlPosition;
}

onAdd() (geolonia-control.ts:6-29)

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() (geolonia-control.ts:31-33)

onRemove() {
  this.container.parentNode.removeChild(this.container);
}

getDefaultPosition() (geolonia-control.ts:35-37)

getDefaultPosition(): ControlPosition {
  return 'bottom-left';
}

maplibre は addControl(control, position?) で position を省略すると control.getDefaultPosition() を呼ぶ。

geolonia-map.ts での扱い

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 参照)。

CustomAttributionControl (src/lib/CustomAttributionControl.ts, 394 行)

maplibre 標準 AttributionControlShadow DOM 内に描画する亜種。CSS を内蔵し、ホストページの CSS から隔離する。

公開 API

class CustomAttributionControl implements IControl {
  constructor(options?: { compact?: boolean; customAttribution?: string | string[] });
  getDefaultPosition(): ControlPosition;
  onAdd(map): HTMLDivElement;
  onRemove(): void;
}
export default CustomAttributionControl;

Constructor (CustomAttributionControl.ts:41-53)

constructor(options = {}) {
  this.options = options;
  bindAll(['_toggleAttribution', '_updateData', '_updateCompact', '_updateCompactMinimize'], this);
}

bindAll (maplibre-util.ts:130-137) で 4 つのメソッドの this を固定 (fn.bind(context))。

onAdd(map) (CustomAttributionControl.ts:59-244)

要約:

  1. this._container = DOM.create('div') で root div を作成
  2. const shadow = this._container.attachShadow({ mode: 'open' }) で Shadow DOM 化
  3. Shadow 内に以下を構築:
    • <details class="maplibregl-ctrl maplibregl-ctrl-attrib"> (_shadowContainer)
      • <summary class="maplibregl-ctrl-attrib-button"> (_compactButton)
      • <div class="maplibregl-ctrl-attrib-inner"> (_innerContainer)
  4. _compactButton に click リスナー (_toggleAttribution)
  5. <style> 要素を作成、内部に大量の CSS を埋め込み (CustomAttributionControl.ts:84-219)
  6. _updateAttributions() を実行 (初期 attribution セット)
  7. _updateCompact() を実行 (responsive 判定)
  8. map イベントを listen:
    • styledata_updateData
    • sourcedata_updateData
    • terrain_updateData
    • resize_updateCompact
    • drag_updateCompactMinimize
  9. shadow.appendChild(style)shadow.appendChild(_shadowContainer)
  10. window.matchMedia('print') の change イベント listener → 印刷時は強制開く
  11. return this._container

Shadow DOM 内の CSS (CustomAttributionControl.ts:84-219)

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() (CustomAttributionControl.ts:292-358)

_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.0tileManagers が再び有効(PR #482 で対応、issue #487 で議論された経緯あり。詳細は Suspicious-Logic)。

attribution 合成:

  1. customAttribution option
  2. 全 source の source.attribution (used または usedForTerrain な source のみ)
  3. whitespace 除去
  4. substring (長い文字列の一部が短い文字列にすでに含まれているケース) 除去
  5. ' | ' で join

innerHTML = attribHTML で直接挿入 → source.attribution の値は HTML として解釈される (リンク等を許可するため)。XSS リスクが残るかは別途検討必要。

_toggleAttribution() (CustomAttributionControl.ts:268-278)

<summary> クリックで:

  • compact-show 状態 → open 属性付与 + compact-show 削除
  • そうでない → compact-show 追加 + open 削除

<details> の挙動と class の組み合わせで開閉状態を管理。

_updateCompact() (CustomAttributionControl.ts:360-383)

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 で表示

_updateCompactMinimize() (CustomAttributionControl.ts:385-391)

drag (panning) を検知したら compact-show を外す (OSM Foundation の指針: ユーザ操作で attribution を minimize 可能)。

_updateData(e) (CustomAttributionControl.ts:280-290)

styledata / sourcedata / terrain イベントから呼ばれる。

if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' ||
          e.dataType === 'style' || e.type === 'terrain')) {
  this._updateAttributions();
}

特定の event subtype のみ反応 (パフォーマンス対策)。

onRemove() (CustomAttributionControl.ts:246-260)

  • _container を DOM から削除
  • 5 つの map イベントを off
  • インスタンスメンバを undefined に
  • print media query listener を解除

maplibre 標準コントロール

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)。

コントロール追加順 (geolonia-map.ts:200-237)

  1. GeoloniaControl (常に最初に追加、unconditionally)
  2. CustomAttributionControl (常に bottom-right)
  3. FullscreenControl (条件付き)
  4. NavigationControl (条件付き)
  5. GeolocateControl (条件付き)
  6. 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' でも追加) は仕様確認のうえ修正

Clone this wiki locally