Draw UNL Grid with Mapbox Maps SDK for React Native

This example demonstrates how UNL Core library can be used in a React Native (with TypeScript) app with Mapbox Maps SDK for React Native library to draw a UNL Grid.

Project setup

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

npx react-native init my-app --template react-native-template-typescript

And use Mapbox Maps SDK for React Native library for displaying a map:

yarn add @react-native-mapbox-gl/maps
yarn add @turf/helpers

Install Unl Core

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

Display Mapbox map

For displaying a MapboxGL map you need to get a token from Mapbox.

import MapboxGL from "@react-native-mapbox-gl/maps";
import { StyleSheet } from "react-native";
MapboxGL.setAccessToken("<Add your Mapbox token here>");
const styles = StyleSheet.create({
mapView: {
flex: 1,
},
});
render() {
return (
<MapboxGL.MapView
style={styles.mapView}
styleURL={"mapbox://styles/mapbox/streets-v11"}
>
<MapboxGL.Camera
zoomLevel={MIN_ZOOM}
minZoomLevel={MIN_ZOOM}
/>
</MapboxGL.MapView>
);
}

Get a MapboxGL Map reference

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

interface State {
mapBounds?: GeoJSON.Position[];
mapZoom: number;
}
state = { mapBounds: undefined, mapZoom: MIN_ZOOM };
map = React.createRef<MapboxGL.MapView>();
handleRegionDidChange = async () => {
const mapBounds = await this.map.current!.getVisibleBounds();
const mapZoom = await this.map.current!.getZoom();
this.setState({ mapBounds, mapZoom });
};
render() {
return (
<MapboxGL.MapView
ref={this.map}
...
onRegionDidChange={this.handleRegionDidChange}
>
...
</MapboxGL.MapView>
);
}

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

const UNL_CORE_LOCATION_ID_PRECISION = 9;
getGridLines = (mapBounds: GeoJSON.Position[]): [number, number][][] => {
const unlCoreBounds = this.geoJsonBoundsToUnlCoreBounds(mapBounds);
return UnlCore.gridLines(unlCoreBounds, UNL_CORE_LOCATION_ID_PRECISION);
};
geoJsonBoundsToUnlCoreBounds = (
geoJsonBounds: GeoJSON.Position[]
): UnlCore.Bounds => {
return {
sw: this.geoJsonPositionToUnlCorePoint(geoJsonBounds[1]),
ne: this.geoJsonPositionToUnlCorePoint(geoJsonBounds[0]),
};
};
geoJsonPositionToUnlCorePoint = (
geoJsonPosition: GeoJSON.Position
): UnlCore.Point => {
return { lat: geoJsonPosition[1], lon: geoJsonPosition[0] };
};

Display UNL Grid on the map

getGridFeatures = () => {
const { mapBounds, mapZoom } = this.state;
let gridLines =
mapBounds && mapZoom >= MIN_GRID_ZOOM
? this.getGridLines(mapBounds!)
: [];
const features = gridLines.map((gridLine) => lineString(gridLine));
return features;
};
renderUnlGrid = () => {
return (
<MapboxGL.ShapeSource
id={"gridPolyline"}
shape={featureCollection(this.getGridFeatures())}
>
<MapboxGL.LineLayer
id={"gridPolyline_lineLayer"}
style={{
lineWidth: 0.5,
lineColor: "#C0C0C0",
}}
minZoomLevel={MIN_GRID_ZOOM}
/>
</MapboxGL.ShapeSource>
);
};
render() {
return (
<MapboxGL.MapView
...
>
...
{this.renderUnlGrid()}
</MapboxGL.MapView>
);
}

Complete source code

import React from "react";
import MapboxGL from "@react-native-mapbox-gl/maps";
import { StyleSheet } from "react-native";
import { featureCollection, lineString } from "@turf/helpers";
import UnlCore from "unl-core";
const MIN_ZOOM = 2;
const MIN_GRID_ZOOM = 18;
const MAP_ANIMATION_DURATION = 2000;
const UNL_CORE_LOCATION_ID_PRECISION = 9;
const styles = StyleSheet.create({
mapView: {
flex: 1,
},
});
MapboxGL.setAccessToken("<Add your Mapbox token here>");
interface Props {}
interface State {
mapBounds?: GeoJSON.Position[];
mapZoom: number;
}
class Map extends React.Component<Props, State> {
state = {
mapBounds: undefined,
mapZoom: MIN_ZOOM,
};
map = React.createRef<MapboxGL.MapView>();
camera?: MapboxGL.Camera;
setCamera = (camera: MapboxGL.Camera) => {
this.camera = camera;
let cameraSettings = { animationDuration: MAP_ANIMATION_DURATION };
cameraSettings["centerCoordinate"] = [4.902277, 52.373391];
cameraSettings["zoomLevel"] = MIN_GRID_ZOOM;
camera?.setCamera(cameraSettings);
};
handleRegionDidChange = async () => {
const mapBounds = await this.map.current!.getVisibleBounds();
const mapZoom = await this.map.current!.getZoom();
this.setState({ mapBounds, mapZoom });
};
geoJsonPositionToUnlCorePoint = (
geoJsonPosition: GeoJSON.Position
): UnlCore.Point => {
return { lat: geoJsonPosition[1], lon: geoJsonPosition[0] };
};
geoJsonBoundsToUnlCoreBounds = (
geoJsonBounds: GeoJSON.Position[]
): UnlCore.Bounds => {
return {
sw: this.geoJsonPositionToUnlCorePoint(geoJsonBounds[1]),
ne: this.geoJsonPositionToUnlCorePoint(geoJsonBounds[0]),
};
};
getGridLines = (mapBounds: GeoJSON.Position[]): [number, number][][] => {
const unlCoreBounds = this.geoJsonBoundsToUnlCoreBounds(mapBounds);
return UnlCore.gridLines(unlCoreBounds, UNL_CORE_LOCATION_ID_PRECISION);
};
getGridFeatures = () => {
const { mapBounds, mapZoom } = this.state;
let gridLines =
mapBounds && mapZoom >= MIN_GRID_ZOOM
? this.getGridLines(mapBounds!)
: [];
if (gridLines.length > 2000) gridLines = [];
const features = gridLines.map((gridLine) => lineString(gridLine));
return features;
};
renderUnlGrid = () => {
return (
<MapboxGL.ShapeSource
id={"gridPolyline"}
shape={featureCollection(this.getGridFeatures())}
>
<MapboxGL.LineLayer
id={"gridPolyline_lineLayer"}
style={{
lineWidth: 0.5,
lineColor: "#C0C0C0",
}}
minZoomLevel={MIN_GRID_ZOOM}
/>
</MapboxGL.ShapeSource>
);
};
render() {
return (
<MapboxGL.MapView
ref={this.map}
style={styles.mapView}
styleURL={"mapbox://styles/mapbox/streets-v11"}
onRegionDidChange={this.handleRegionDidChange}
>
<MapboxGL.Camera
ref={this.setCamera}
zoomLevel={MIN_ZOOM}
minZoomLevel={MIN_ZOOM}
/>
{this.renderUnlGrid()}
</MapboxGL.MapView>
);
}
}
export default Map;