henrythasler / Leaflet.Geodesic

Add-on to draw geodesic lines with leaflet
GNU General Public License v3.0
156 stars 27 forks source link

[Document] Integrating with Next.JS and `react-leaflet` with typescript #97

Closed dccboss closed 1 year ago

dccboss commented 1 year ago

Purpose

This is not an issue This post is to document a react-leaflet wrapper for Leaflet.Geodesic implemented with strict typescript rules. It was troublesome enough that I would have preferred that someone had already done it. There is an existing project at react-leaflet-geodesic, but it was not typescript friendly; and it uses a method that is far enough behind the current version of react-leaflet that it was unusable (for me).

Context

Using Leaflet.Geodesic with react-leaflet in a Next.JS project with typescript.

Versions

    "leaflet": "1.9.3",
    "react-leaflet": "4.2.1",
    "leaflet.geodesic": "2.6.1",

React Component

The following component is what I used to form the wrapper. Important notes:

  1. Don't use useRef, use useState; you need this component to react to changes in the Geodesic Line.
  2. You can use a normal function or a FunctionalComponent, they both work; but FC is a little more verbose to typescript.
  3. The interfaces are for the typing of the props. Unfortunately, i. type GeodesicLineProps = Parameters<typeof geodesic>;, and ii. type GeodesicLineProps = ConstructorParameters<typeof GeodesicLineClass> leave the props encased in an array: which I could not find a way to use as props for the wrapper that was good for use as react props. This might be a candidate to export from the project in future.
  4. Leaflet.Geodesic exports both GeodesicLine (a class to be instantiated with new) and the function geodesic, but geodesic was not recognized as a function by typescript at runtime.
import { PolylineOptions, LatLngExpression, Geodesic } from 'leaflet';
import { GeodesicLine as GeodesicLineClass } from 'leaflet.geodesic';
import { useEffect, useState } from 'react';
import { useMap } from 'react-leaflet';

interface GeodesicOptions extends PolylineOptions {
  wrap?: boolean;
  steps?: number;
  radius?: number;
}

interface GeodesicLineProps {
  positions: LatLngExpression[] | LatLngExpression[][];
  options?: GeodesicOptions;
}

export function GeodesicLine({ positions, options }: GeodesicLineProps) {
  const [geodesic, setGeodesic] = useState<Geodesic>();
  const map = useMap();
  useEffect(() => {
    if (geodesic === undefined) {
      setGeodesic(new GeodesicLineClass(positions, options).addTo(map));
    } else {
      geodesic.setLatLngs(positions);
    }

    return () => {
      geodesic?.remove();
    };
  }, [map, geodesic, positions, options]);

  return null;
}

Usage

          <GeodesicLine
            positions={[railwayApproachLondon, belarusUkraineBorder]}
            options={edgeOptions}
          />

OR

          <GeodesicLine
            positions={[[50.0, 1.0],[51.5, 1.0]]}
            options={edgeOptions}
          />

Where: The above is nested below a MapContainer and the following are imported and defined:

import { GeodesicLine } from '../utils/leaflet-geodesic';   // the code above
import { LatLngLiteral } from 'leaflet';

const railwayApproachLondon: LatLngLiteral = { lat: 51.505, lng: -0.087 };
const belarusUkraineBorder: LatLngLiteral = { lat: 51.5, lng: 30.06 };
const edgeOptions = {
  color: palette.orange,
  fillColor: palette.yellow,
  weight: 3,
  steps: 10,
};