はじめに
leaflet の基本的な機能は、react-leaflet を用いることで容易に React で扱うことができる。しかし、少しでも複雑なことを行うには react-leaflet だけでは不十分であり、leaflet プラグインを React+TypeScript で扱えるように拡張することが必要となる。本記事ではマーカーの回転を容易にできるようにする leaflet-marker-rotation を React に対応できるよう拡張する。なお、このプラグインでは、すでに型定義ファイル leaflet-marker-rotation/src/index.d.ts が存在しているため、TypeScript 対応は自身で行う必要がない。また react-leaflet はインストールされている前提で話を進める。
公式ドキュメント Core API を参考にした。
leaflet-marker-rotation のインストール
まず元となるプラグイン leaflet-marker-rotation をインストールする。
$ npm install leaflet-marker-rotation
leaflet-marker-rotation/src/index.d.ts を見ると、leaflet の Marker に対して rotationAngle と rotationOrigin の2つのオプションを拡張して作成されていることがわかる。(今回はプラグインを React で使用できることが目的なので、leaflet-marker-rotation/src/rotatedMarker.js のような具体的な中身の理解はしない。)
react-leaflet を参考にコンポーネント内容の確認
急に拡張するといっても難しいので、まずは react-leaflet でどのように拡張されているのかを確認して参考にする。今回は Marker に関する拡張なので、react-leaflet 内で定義されている Marker コンポーネントの中身がどのようになっているかを見る。
import { createLayerComponent } from '@react-leaflet/core';
import { Marker as LeafletMarker } from 'leaflet';
export const Marker = createLayerComponent(function createMarker({
position,
...options
}, ctx) {
const instance = new LeafletMarker(position, options);
return {
instance,
context: { ...ctx,
overlayContainer: instance
}
};
}, function updateMarker(marker, props, prevProps) {
if (props.position !== prevProps.position) {
marker.setLatLng(props.position);
}
if (props.icon != null && props.icon !== prevProps.icon) {
marker.setIcon(props.icon);
}
if (props.zIndexOffset != null && props.zIndexOffset !== prevProps.zIndexOffset) {
marker.setZIndexOffset(props.zIndexOffset);
}
if (props.opacity != null && props.opacity !== prevProps.opacity) {
marker.setOpacity(props.opacity);
}
if (marker.dragging != null && props.draggable !== prevProps.draggable) {
if (props.draggable === true) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
}
});
まず上記をなんとなく理解する。コンポーネント作成に重要な役割を果たしている createLayerComponent に関する公式ドキュメントには以下のように書かれている。
Arguments にはインスタンスを作成して返す関数 createElement、インスタンスの内容を更新する関数 updateElement の2つを使用している。(Type parameters と Returns は一旦置いておく。)
この構成をもとに先ほどの react-leaflet/esm/Marker.js を見ていくと、以下のような構成になっていることがわかる。
- createMarker : Marker インスタンスを作成し、そのインスタンスを返す。(context とかは不明)
- updateMarker : position, icon, zIndexOffset, opacity, draggable が変更されたときに更新する。
上記を理解した上で、マーカーの回転ができるコンポーネント RotatableMarker を作成する。
コンポーネントの作成
作成したコンポーネントのコードを以下に示す。(全型のエラーを除去しきれなかったため一部 any
を使用している。)
import { createLayerComponent, LeafletContextInterface } from "@react-leaflet/core";
import { LatLngLiteral, MarkerOptions } from "leaflet";
import { RotatedMarker } from "leaflet-marker-rotation";
interface RotatedMarkerOptions extends MarkerOptions {
/*
* Rotation angle, in degrees, clockwise. Defaults to 0.
*/
rotationAngle?: number;
/*
* Rotation angle, in degrees, clockwise. Defaults to 'bottom center'
*/
rotationOrigin?: string;
}
interface RotatedMarkerProps {
latlng: LatLngLiteral;
options?: RotatedMarkerOptions;
}
function createRotatableMarker(props: any, context: LeafletContextInterface) {
const instance = new RotatedMarker(props.latlng, props.options);
return {
instance,
context: { ...context, overlayContainer: instance },
};
}
function updateRotatableMarker(marker:any, props: RotatedMarkerProps, prevProps: RotatedMarkerProps) {
if (props.latlng !== prevProps.latlng) {
marker.setLatLng(props.latlng);
}
// add angle
if (props.options?.rotationAngle !== prevProps.options?.rotationAngle) {
marker.setRotationAngle(props.options?.rotationAngle || 0);
}
// add origin
if (props.options?.rotationOrigin !== prevProps.options?.rotationOrigin) {
marker.setRotationOrigin(props.options?.rotationOrigin || 'bottom center');
}
if (props.options?.icon != null && props.options?.icon !== prevProps.options?.icon) {
marker.setIcon(props.options?.icon);
}
if (
props.options?.zIndexOffset != null &&
props.options?.zIndexOffset !== prevProps.options?.zIndexOffset
) {
marker.setZIndexOffset(props.options?.zIndexOffset);
}
if (props.options?.opacity != null && props.options?.opacity !== prevProps.options?.opacity) {
marker.setOpacity(props.options?.opacity);
}
if (marker.dragging != null && props.options?.draggable !== prevProps.options?.draggable) {
if (props.options?.draggable === true) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
}
}
const RotatableMarker = createLayerComponent<any, any>(createRotatableMarker, updateRotatableMarker);
export default RotatableMarker;
createRotatableMarker にて、leaflet-marker-rotation プラグインから読み込んだ RotatedMarker を使ってインスタンスを作成、updateRotatableMarker にて追加された rotationAngel と rotationOrigin の2つを更新するように修正した。200度回転して表示させた結果が以下。
おわりに
leaflet プラグインの拡張ができた。ただ完全理解はできていない、かつ型定義も一部しきれていないので修正していきたい。わかる人がいればコメントください。