【React/Leaflet】MarkerClusterGroupを使おうとして苦戦した
React v18&react-leaflet v4&TypeScriptな環境でMarkerClusterGroupを使おうとして苦戦した話。
概要
今回の記事では、React 18&react-leaflet v4&TypeScriptな環境でMarkerClusterGroupを使う手順を掲載する。
仕様書
環境
- react 18.2.0
- react-leaflet 4.2.1
- @changey/react-leaflet-markercluster 4.0.0-rc1
- typescript 5.2.2
手順書
react v18&react-leaflet v4の環境でreact-leaflet-markerclusterのMarkerClusterGroup
を使うとエラーになっちゃう。相性の問題?
この問題を回避したバージョンがあって下記のコマンドでインストールできる。
npm install @changey/react-leaflet-markercluster
JavaScriptな環境ならこれでreact v18&react-leaflet v4な環境でもMarkerClusterGroup
が使えるようになる。(はず)
んで、TypeScriptな環境だともうひと手間必要でreact-leaflet-markerclusterの型定義ファイルを用意する必要あり。@changey/react-leaflet-markerclusterにはの型定義ファイルが含まれてないので本家のreact-leaflet-markerclusterから型定義ファイルnode_modules\@type\react-leaflet-markercluster\index.d.ts
を拝借する。
プロジェクトのsrc
にreact-leaflet-markercluster.d.ts
を作って、node_modules\@type\react-leaflet-markercluster\index.d.ts
の中身を全部コピペする。
これを@changey/react-leaflet-markerclusterから使えるように加筆する。加筆後は下記のような感じ。
declare module '@changey/react-leaflet-markercluster' {
/// <reference types="leaflet.markercluster" />
import { DivIcon, Icon, MarkerCluster, Point, PolylineOptions } from "leaflet";
import { ComponentType } from "react";
interface MarkerClusterGroupProps {
/**
* showCoverageOnHover
*
* When you mouse over a cluster it shows the bounds of its markers.
*/
showCoverageOnHover?: boolean | undefined;
/**
* zoomToBoundsOnClick
*
* When you click a cluster we zoom to its bounds.
*/
zoomToBoundsOnClick?: boolean | undefined;
/**
* spiderfyOnMaxZoom
*
* When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its
* markers.
*
* Note: the spiderfy occurs at the current zoom level if all items within the cluster are
* still clustered at the maximum zoom level or at zoom specified by disableClusteringAtZoom
* option.
*/
spiderfyOnMaxZoom?: boolean | undefined;
/**
* removeOutsideVisibleBounds
*
* Clusters and markers too far from the viewport are removed from the map for performance.
*/
removeOutsideVisibleBounds?: boolean | undefined;
/**
* animate
*
* Smoothly split / merge cluster children when zooming and spiderfying.
* If L.DomUtil.TRANSITION is false, this option has no effect (no animation is possible).
*/
animate?: boolean | undefined;
/**
* animateAddingMarkers
*
* If set to true (and animate option is also true) then adding individual markers
* to the MarkerClusterGroup after it has been added to the map will add the marker
* and animate it into the cluster.
* Defaults to false as this gives better performance when bulk adding markers.
* addLayers does not support this, only addLayer with individual Markers.
*/
animateAddingMarkers?: boolean | undefined;
/**
* disableClusteringAtZoom
*
* If set, at this zoom level and below, markers will not be clustered.
* This defaults to disabled.
*
* Note: you may be interested in disabling spiderfyOnMaxZoom option when using
* disableClusteringAtZoom.
*/
disableClusteringAtZoom?: number | undefined;
/**
* maxClusterRadius
*
* The maximum radius that a cluster will cover from the central marker (in pixels).
* Default 80.
* Decreasing will make more, smaller clusters.
* You can also use a function that accepts the current map zoom
* and returns the maximum cluster radius in pixels.
*/
maxClusterRadius?: number | ((zoom: number) => number) | undefined;
/**
* polygonOptions
*
* Options to pass when creating the L.Polygon(points, options) to show the bounds of a cluster.
* Defaults to empty, which lets Leaflet use the default Path options.
*/
polygonOptions?: PolylineOptions | undefined;
/**
* singleMarkerMode
*
* If set to true, overrides the icon for all added markers to make them appear as a 1 size cluster.
* Note: the markers are not replaced by cluster objects, only their icon is replaced.
* Hence they still react to normal events, and option disableClusteringAtZoom does not restore
* their previous icon (see #391).
*/
singleMarkerMode?: boolean | undefined;
/**
* spiderLegPolylineOptions
*
* Allows you to specify PolylineOptions to style spider legs.
* By default, they are { weight: 1.5, color: '#222', opacity: 0.5 }.
*/
spiderLegPolylineOptions?: PolylineOptions | undefined;
/**
* spiderfyDistanceMultiplier
*
* Increase from 1 to increase the distance away from the center
* that spiderfied markers are placed.
* Use if you are using big marker icons (Default: 1).
*/
spiderfyDistanceMultiplier?: number | undefined;
/**
* iconCreateFunction
*
* Function used to create the cluster icon.
*/
iconCreateFunction?: ((cluster: MarkerCluster) => Icon | DivIcon) | undefined;
/**
* spiderfyShapePositions
*
* Function used to override spiderfy default shape positions.
*/
spiderfyShapePositions?: ((count: number, centerPt: Point) => Point[]) | undefined;
/**
* clusterPane
*
* Map pane where the cluster icons will be added.
* Defaults to L.Marker's default (currently 'markerPane').
*/
clusterPane?: string | undefined;
/**
* chunkedLoading
*
* Boolean to split the addLayers processing in to small intervals
* so that the page does not freeze.
*/
chunkedLoading?: boolean | undefined;
/**
* chunkInterval
*
* Time interval (in ms) during which addLayers works before pausing
* to let the rest of the page process.
* In particular, this prevents the page from freezing while adding a lot of markers.
* Defaults to 200ms.
*/
chunkInterval?: number | undefined;
/**
* chunkDelay
*
* Time delay (in ms) between consecutive periods of processing for addLayers.
* Default to 50ms.
*/
chunkDelay?: number | undefined;
/**
* chunkProgress
*
* Callback function that is called at the end of each chunkInterval.
* Typically used to implement a progress indicator.
* Defaults to null.
*/
chunkProgress?: ((processed: number, total: number, elapsed: number) => void) | null;
//子のコンポーネントを使えるようにする
children: React.ReactNode;
//Markerと同じような感じでrefを使えるようにする
ref: (ref: any) => void;
}
declare const MarkerClusterGroup: ComponentType<MarkerClusterGroupProps>;
export default MarkerClusterGroup;
}
@changey/react-leaflet-markerclusterから使えるようにdeclare module '@changey/react-leaflet-markercluster'
の中に全体を含めるのと、MarkerClusterGroup
はMarker
コンポーネントを子にして使うんだけども何故かこのままだとPropsにchildren: React.ReactNode
が含まれておらず子のコンポーネントを使おうとするとエラーになるのでinterface MarkerClusterGroupProps
の中にchildren: React.ReactNode;
を加筆する。
MarkerClusterGroup
でMarker
と同じような感じでuseRef
を使いたい場合は、ref: (ref: any) => void;
も加筆しておく。
import MarkerClusterGroup from '@changey/react-leaflet-markercluster';
インポートはこんな感じで
<MarkerClusterGroup spiderfyOnMaxZoom={true}>
<Marker position={[lat, lng]}></Marker>
</MarkerClusterGroup>
レンダーの部分はこんな感じで使えるようになる。レンダーの方は本家のreact-leaflet-markerclusterと同じように使えるらしい。(本家を使ったことがないので、らしい)
まとめ(感想文)
MarkerClusterGroup
を使うだけのために半日使ったよね…。