応用
ハウツー 地図インスタンス(maps-core)に直接アクセスし、MapLibre GL JS の機能を利用するには
embed / maps-suite / maps-react はどれも、土台に maps-core の GeoloniaMap を持っています。GeoloniaMap は MapLibre GL JS の Map を継承したクラスなので、これを取り出せば on()、flyTo()、queryRenderedFeatures() といった MapLibre GL JS の API がそのまま使えます。
各ライブラリが用意している機能で足りないときの逃げ道がこれです。取り出し方だけがライブラリごとに違います。
コード
下のデモはどれも同じことをしています。地図を動かすと、左上の枠に現在のズームと中心座標が出ます。ズームや中心を読む API は各ライブラリの表側にはないので、土台のインスタンスから取っています。
map.getGeoloniaMap() が土台の GeoloniaMap を返します。読み込みの完了を待ってから呼びます。
import "maplibre-gl/dist/maplibre-gl.css";
import "@geolonia/maps-core/css";
import { geolonia } from "@geolonia/maps-suite";
const container = document.getElementById("map");
container.style.height = "100vh";
const readout = document.createElement("div");
readout.style.cssText =
"position:absolute;top:8px;left:8px;z-index:1;background:#fff;padding:6px 10px;border-radius:4px;font:13px/1.5 system-ui,sans-serif";
readout.textContent = "読み込み中";
document.body.appendChild(readout);
const map = new geolonia.maps.Map(container, {
apiKey: "YOUR-API-KEY",
center: { lat: 35.68, lng: 139.75 },
zoom: 11,
});
await map.whenReady();
const impl = map.getGeoloniaMap();
const show = () => {
const c = impl.getCenter();
readout.textContent =
`zoom ${impl.getZoom().toFixed(2)} / ${c.lat.toFixed(4)}, ${c.lng.toFixed(4)}`;
};
impl.on("move", show);
show();maps-suite の Map には getZoom() や getCenter() もあるので、この例に限れば土台に降りる必要はありません。ソースやレイヤの追加、投影法の変更、コントロールの付け外しのように、maps-suite の側に用意がないものが本来の用途です。
whenReady() を待つのは、スタイルの読み込みが終わる前にレイヤを触るとエラーになるためです。getCenter() のように読むだけなら待たなくても動きますが、揃えておくと安全です。
Marker や Polyline、Circle などのオーバーレイには getMap() がありますが、これが返すのはそのオーバーレイが載っている maps-suite の Map で、土台の GeoloniaMap ではありません。土台まで降りるには、返ってきた Map に対してもう一度 getGeoloniaMap() を呼びます。
marker.getMap().getGeoloniaMap();Map の ref が GeoloniaMap そのものです。ただし地図はマウント後に作られるので、useRef で受けて useEffect で読むと、まだ null です。インスタンスが入った瞬間に動きたいので、関数を ref に渡します(コールバック ref)。React が要素を繋いだときに引数で呼んでくれます。
import "maplibre-gl/dist/maplibre-gl.css";
import "@geolonia/maps-core/css";
import { useCallback, useState } from "react";
import { createRoot } from "react-dom/client";
import { Map } from "@geolonia/maps-react";
function App() {
const [text, setText] = useState("読み込み中");
const mapRef = useCallback((map) => {
if (!map) return;
const show = () => {
const c = map.getCenter();
setText(
`zoom ${map.getZoom().toFixed(2)} / ${c.lat.toFixed(4)}, ${c.lng.toFixed(4)}`,
);
};
map.on("move", show);
show();
return () => {
map.off("move", show);
};
}, []);
return (
<div style={{ position: "relative", height: "100vh" }}>
<div
style={{
position: "absolute", top: 8, left: 8, zIndex: 1,
background: "#fff", padding: "6px 10px", borderRadius: 4,
font: "13px/1.5 system-ui, sans-serif",
}}
>
{text}
</div>
<Map
ref={mapRef}
apiKey="YOUR-API-KEY"
center={[139.75, 35.68]}
zoom={11}
containerStyle={{ width: "100%", height: "100%" }}
/>
</div>
);
}
createRoot(document.getElementById("root")).render(<App />);インスタンスが手に入った瞬間に呼ばれるので、待つ仕組みが要りません。後片付けも戻り値で書けます。上の例では、返した関数の中で off("move", show) してリスナを外しています。
コールバック ref から後片付けの関数を返せるのは React 19 以降です。18 以前では戻り値が無視され、代わりに要素が外れるときに null を引数にもう一度呼ばれます。18 で書くなら、map が渡されたときに購読して、null で呼ばれたときに解除する形にしてください。
あとから任意のタイミングで触りたいだけなら、受け取ったインスタンスを自分で持っておきます。
const mapRef = useRef(null);
<Map
ref={(map) => { mapRef.current = map; }}
apiKey="YOUR-API-KEY"
center={[139.75, 35.68]}
zoom={11}
/>
<button onClick={() => mapRef.current?.flyTo({ center: [135.5, 34.7], zoom: 12 })}>
大阪へ
</button>スタイルの読み込みを待つ必要があるとき
ref が呼ばれるのは地図を作った直後で、スタイルの読み込み完了は待ちません。レイヤやソースを足す操作は読み込み後でないとエラーになるので、isStyleLoaded() で見て、まだなら once("load") で待ちます。
const mapRef = useCallback((map) => {
if (!map) return;
const addLayer = () => {
map.addSource("spots", { type: "geojson", data: "/data/sample-spots.geojson" });
map.addLayer({ id: "spots", type: "circle", source: "spots" });
};
if (map.isStyleLoaded()) addLayer();
else map.once("load", addLayer);
}, []);Map には onLoad プロパティもあり、こちらは読み込み完了後に GeoloniaMap を引数で渡してきます。一度だけの初期化なら短く書けますが、リスナを外す先がないので、on() で購読するならコールバック ref にしてください。
React らしく書けるものは props で済みます。地図の移動を拾うだけなら onMove プロパティがありますし、データの描画は <Source> と <Layer> で書けます。土台のインスタンスを触るのは、props に用意がない操作をするときです。
結果
上のコードは、それぞれのタブの下でそのまま動いています。地図をドラッグしたりズームすると、左上の数値が追従します。値を出しているのは土台の GeoloniaMap で、MapLibre の getZoom() と getCenter()、それに move イベントを使っています。
取り出したインスタンスでできることは、MapLibre GL JS の Map にできること全部です。書ける API の一覧は MapLibre GL JS のドキュメントにあります。Geolonia が足した部分は maps-core のリファレンスを参照してください。
- embed で
geoloniaMapがundefined→ 地図がまだ作られていません。embed は要素が画面に入ったときに作ります。new geolonia.Map()で取り直してください。 - embed で「No HTML elements found matching」→ 渡したセレクタに合う要素がありません。スクリプトを要素より前に置いている場合も起きます。
- 「Style is not done loading」→ スタイルの読み込み前にレイヤを触っています。maps-suite なら
whenReady()を待ち、maps-react ならisStyleLoaded()を見てonce("load")で待ってください。 - maps-react で
useRef+useEffectにしたらnullだった → 地図はマウント後に作られるので、最初のuseEffectの時点ではまだ入っていません。関数をrefに渡して、呼ばれたときに動く形にしてください。 - インスタンスを取ったのに操作が反映されない → maps-suite が管理している値(中心・ズーム・傾き・向き)を土台側から直接変えた場合、動作は保証されません。これらは maps-suite のメソッドで変えてください。