3
3

More than 3 years have passed since last update.

LeafletプラグインをReact+TypeScriptで使えるように拡張

Posted at

はじめに

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 コンポーネントの中身がどのようになっているかを見る。

react-leaflet/esm/Marker.js
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 に関する公式ドキュメントには以下のように書かれている。

createLayerComponent.png

Arguments にはインスタンスを作成して返す関数 createElement、インスタンスの内容を更新する関数 updateElement の2つを使用している。(Type parameters と Returns は一旦置いておく。)
この構成をもとに先ほどの react-leaflet/esm/Marker.js を見ていくと、以下のような構成になっていることがわかる。

  • createMarker : Marker インスタンスを作成し、そのインスタンスを返す。(context とかは不明)
  • updateMarker : position, icon, zIndexOffset, opacity, draggable が変更されたときに更新する。

上記を理解した上で、マーカーの回転ができるコンポーネント RotatableMarker を作成する。

コンポーネントの作成

作成したコンポーネントのコードを以下に示す。(全型のエラーを除去しきれなかったため一部 any を使用している。)

RotatableMarker.tsx
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度回転して表示させた結果が以下。

RotatableMarker.png

おわりに

leaflet プラグインの拡張ができた。ただ完全理解はできていない、かつ型定義も一部しきれていないので修正していきたい。わかる人がいればコメントください。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3