Draw UNL Grid with React Leaflet

This example demonstrates how UNL Core library can be used in a React (with TypeScript) app with React Leaflet library to draw a UNL Grid.

Project setup

This tutorial is written considering you have created a React app with TypeScript:

yarn create react-app my-app --template typescript

And use React Leaflet library for displaying a map:

yarn add leaflet react-leaflet
yarn add @types/leaflet @types/react-leaflet --dev

Install Unl Core

yarn add unl-core
yarn add @types/unl-core --dev

Display Leaflet map

import { Map, TileLayer } from "react-leaflet";
const MapboxMapView = ReactMapboxGl({
accessToken:"<Add your Mapbox token here>",
minZoom: MIN_ZOOM,
});
render() {
return (
<Map
style={{
width: "100%",
height: "100%",
position: "fixed",
}}
center={[52.373391, 4.902277]}
zoom={MIN_GRID_ZOOM}
minZoom={MIN_ZOOM}
maxZoom={MAX_ZOOM}
>
<TileLayer url={"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"} />
</Map>
);
}

Get a Leaflet Map reference

Obtain a Leaflet Map reference, retrieve map bounds and zoom from it after each map move and save them in component state.

interface State {
mapBounds?: LatLngBounds;
mapZoom: number;
}
state = { mapBounds: undefined, mapZoom: MIN_ZOOM };
map = React.createRef<Map>();
handleMapMoveEnd = () => {
const mapBounds = this.map.current!.leafletElement.getBounds();
const mapZoom = this.map.current!.leafletElement.getZoom();
this.setState({ mapBounds, mapZoom });
};
render() {
return (
<Map
ref={this.map}
...
onmoveend={this.handleMapMoveEnd}
>
...
</Map>
);
}

Retrieve UNL Grid lines for current map bounds using UNL Core library

const UNL_CORE_LOCATION_ID_PRECISION = 9;
getGridLines = (mapBounds: LatLngBounds): [number, number][][] => {
const unlCoreBounds = this.latLngBoundsToUnlCoreBounds(mapBounds);
const lngLatGridLines = UnlCore.gridLines(
unlCoreBounds,
UNL_CORE_LOCATION_ID_PRECISION
);
const latLngGridLines = lngLatGridLines.map((lngLatLine) =>
lngLatLine.map((lngLat): [number, number] => [lngLat[1], lngLat[0]])
);
return latLngGridLines;
};
latLngBoundsToUnlCoreBounds = (latLngBounds: LatLngBounds): UnlCore.Bounds => {
return {
sw: this.latLngToUnlCorePoint(latLngBounds.getSouthWest()),
ne: this.latLngToUnlCorePoint(latLngBounds.getNorthEast()),
};
};
latLngToUnlCorePoint = (latLng: LatLng): UnlCore.Point => {
return { lat: latLng.lat, lon: latLng.lng };
};

Display UNL Grid on the map

renderUnlGrid = () => {
const { mapBounds, mapZoom } = this.state;
if (!mapBounds || mapZoom < MIN_GRID_ZOOM) {
return null;
}
const gridLines = this.getGridLines(mapBounds!);
return <Polyline positions={gridLines} weight={0.5} color="#B0B0B0" />;
};
render() {
return (
<Map
...
>
...
{this.renderUnlGrid()}
</Map>
);
}

Complete source code

import React, { Component } from "react";
import { Map, TileLayer, Polyline } from "react-leaflet";
import { LatLng, LatLngBounds } from "leaflet";
import UnlCore from "unl-core";
const MIN_ZOOM = 2;
const MAX_ZOOM = 19;
const MIN_GRID_ZOOM = 18;
const UNL_CORE_LOCATION_ID_PRECISION = 9;
interface Props {}
interface State {
mapBounds?: LatLngBounds;
mapZoom: number;
}
class MapExample extends Component<Props, State> {
state = { mapBounds: undefined, mapZoom: MIN_ZOOM };
map = React.createRef<Map>();
handleMapMoveEnd = () => {
const mapBounds = this.map.current!.leafletElement.getBounds();
const mapZoom = this.map.current!.leafletElement.getZoom();
this.setState({ mapBounds, mapZoom });
};
latLngToUnlCorePoint = (latLng: LatLng): UnlCore.Point => {
return { lat: latLng.lat, lon: latLng.lng };
};
latLngBoundsToUnlCoreBounds = (
latLngBounds: LatLngBounds
): UnlCore.Bounds => {
return {
sw: this.latLngToUnlCorePoint(latLngBounds.getSouthWest()),
ne: this.latLngToUnlCorePoint(latLngBounds.getNorthEast()),
};
};
getGridLines = (mapBounds: LatLngBounds): [number, number][][] => {
const unlCoreBounds = this.latLngBoundsToUnlCoreBounds(mapBounds);
const lngLatGridLines = UnlCore.gridLines(
unlCoreBounds,
UNL_CORE_LOCATION_ID_PRECISION
);
const latLngGridLines = lngLatGridLines.map((lngLatLine) =>
lngLatLine.map((lngLat): [number, number] => [lngLat[1], lngLat[0]])
);
return latLngGridLines;
};
renderUnlGrid = () => {
const { mapBounds, mapZoom } = this.state;
if (!mapBounds || mapZoom < MIN_GRID_ZOOM) {
return null;
}
const gridLines = this.getGridLines(mapBounds!);
return <Polyline positions={gridLines} weight={0.5} color="#B0B0B0" />;
};
render() {
return (
<Map
ref={this.map}
style={{
width: "100%",
height: "100%",
position: "fixed",
}}
center={[52.373391, 4.902277]}
zoom={MIN_GRID_ZOOM}
minZoom={MIN_ZOOM}
maxZoom={MAX_ZOOM}
onmoveend={this.handleMapMoveEnd}
>
<TileLayer url={"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"} />
{this.renderUnlGrid()}
</Map>
);
}
}
export default MapExample;