Guide to Adding Polylines to Mapbox View
Polylines are simply lines that connects two or more points on the map. The map provided by Mapbox doesn't have a Polyline component by default but we can leverage on other components that gives us the same result as what we get from Google Maps Polylines. To add a polyline on our map, we would be using the ShapeSource and LineLayer component provided by Mapbox. Let's get into it. From our previous article, we looked at how to add a point annotation to our map. We would be building off that project in this article.
To get started, we would open the ReactNativeMapbox project from the previous article.
Next, open App.js file. You should see the following content
import React from "react";
import { View } from "react-native";
import MapboxGL from "@react-native-mapbox-gl/maps";
MapboxGL.setAccessToken('MAPBOX_TOKEN_GOES_HERE');
export default App = () => {
const renderAnnotations = () => {
return (
<MapboxGL.PointAnnotation
key="pointAnnotation"
id="pointAnnotation"
coordinate={[3.3362400, 6.5790100]}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}} />
</MapboxGL.PointAnnotation>
);
}
return (
<View style={{flex: 1, height: "100%", width: "100%" }}>
<MapboxGL.MapView
styleURL={MapboxGL.StyleURL.Street}
zoomLevel={16}
centerCoordinate={[3.3362400, 6.5790100]}
style={{flex: 1}}>
<MapboxGL.Camera
zoomLevel={16}
centerCoordinate={[3.3362400, 6.5790100]}
animationMode={'flyTo'}
animationDuration={0}
>
</MapboxGL.Camera>
{renderAnnotations()}
</MapboxGL.MapView>
</View>
)
}
Next, run the app on the emulator to verify the map still shows up. Run the following command on your terminal to run the app on your ios simulator.
yarn ios
You should see the app running on your ios simulator.
Next, we would be installing two packages that would help us achieve our objective of adding a polyline to our map. The first package is mapbox-sdk and the second is turf helpers. The mapbox sdk would be used to get the direction between the two points we would be having on our map. The direction comes with coordinates which would allow us connect our start and end points together nicely. The turf helper would help us create a lineString geoJSON reponse from an array of coordinates(In this case, from our Mapbox direction API). It is important to note that since we are connecting the two points on our map with a line, we need to create a geoJSON variable with a feature type of lineString.
To install the mapbox sdk and turf helpers package, run the following command on the terminal
yarn add @mapbox/mapbox-sdk @turf/helpers
Next, import both libraries in the App.js file, your file should look like this
import React from "react";
import { View } from "react-native";
import MapboxGL from "@react-native-mapbox-gl/maps";
import {lineString as makeLineString} from '@turf/helpers';
import MapboxDirectionsFactory from '@mapbox/mapbox-sdk/services/directions';
From the code snippet, we are using the directions API from the mapbox sdk.
Next, we initialize the class object with our Mapbox token as shown below
const directionsClient = MapboxDirectionsFactory({accessToken});
Note the MapboxDirectionFactory takes an object as a parameter like this {accessToken: 'MAPBOX_TOKEN_GOES_HERE'}. For short, you can declare a variable
const accessToken = 'MAPBOX_TOKEN_GOES_HERE';
and then just use it like this
const directionsClient = MapboxDirectionsFactory({accessToken});
Next, we need to add a second point on the map. For the purpose of this article, we would make it as simple as possible. We would need another set of coordinate to indicate our destination point. We already have a start point from our previous article. The destination point would look like this
const destinationPoint = [ 3.3750014, 6.5367877 ];
We would also create a variable for our startPoint so we can reuse it across the App.js component. Like this
const startingPoint = [3.3362400, 6.5790100];
const destinationPoint = [ 3.3750014, 6.5367877 ];
Next, we are going to replace lines in our App.js component that have the [3.3362400, 6.5790100]
with just startingPoint
. This is what your file should look like
const renderAnnotations = () => {
return (
<MapboxGL.PointAnnotation
key="pointAnnotation"
id="pointAnnotation"
coordinate={startingPoint}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}} />
</MapboxGL.PointAnnotation>
);
}
return (
<View style={{flex: 1, height: "100%", width: "100%" }}>
<MapboxGL.MapView
styleURL={MapboxGL.StyleURL.Street}
zoomLevel={16}
centerCoordinate={startingPoint}
style={{flex: 1}}>
<MapboxGL.Camera
zoomLevel={16}
centerCoordinate={startingPoint}
animationMode={'flyTo'}
animationDuration={0}
>
</MapboxGL.Camera>
{renderAnnotations()}
</MapboxGL.MapView>
</View>
)
Next, we need to create another Annotation component for the destination point in the renderAnnotations
function. Like this,
const renderAnnotations = () => {
return (
<>
<MapboxGL.PointAnnotation
key="startPointAnnotation"
id="startPointAnnotation"
coordinate={startingPoint}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}} />
</MapboxGL.PointAnnotation>
<MapboxGL.PointAnnotation
key="destinationPointAnnotation"
id="destinationPointAnnotation"
coordinate={destinationPoint}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}} />
</MapboxGL.PointAnnotation>
</>
);
}
From the code snippet above, I added another component to render the destination point and I changed the id and key on both components to be unique. Let's make this prettier. We can have a 2-dimensional array of our start and destination point and loop through it to generate both components like this
// const startDestinationPoints = [[3.3362400, 6.5790100], [3.3750014, 6.5367877 ]]
better still this,
const startDestinationPoints = [startingPoint, destinationPoint]
const renderAnnotations = () => {
return (
startDestinationPoints.map((point, index) => (
<MapboxGL.PointAnnotation
key={`${index}-PointAnnotation`}
id={`${index}-PointAnnotation`}
coordinate={point}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}}
/>
</MapboxGL.PointAnnotation>
))
);
}
Next, check your simulator, you would still see a single point at glance, don't worry. The reason is because of the zoomLevel. As mentioned in my first article, The zoomLevel shows more map detail by zooming into a specific area. To display both points on the map, reduce the zoomLevel prop value from 16 to 11 for all lines in the App.js file. Doing this would bring both points closer. (Note that this adjustment is manual for now, we would definitely need a way to do it dynamically depending on the start and destination coordinates)
check the simulator again, you should see both point now. Like this
let's get back to adding our polyline to connect the two points, earlier we had this
const directionsClient = MapboxDirectionsFactory({accessToken});
Next, we would create a function called fetchRoute
to get the direction between the start and destination points. To do that we need to import react useEffect and useState. The import line should look like this
import React, { useState, useEffect } from "react";
Next, we need add useEffect hook in our component like this
useEffect(() => {
})
Next, we need to create a route state because we would be updating the route state with the resolution from our fetchRoute function. The line should look like this
const [route, setRoute] = useState(null);
Next, let's write the fetchRoute function.
const fetchRoute = async () => {
const reqOptions = {
waypoints: [
{coordinates: startingPoint},
{coordinates: destinationPoint},
],
profile: 'driving-traffic',
geometries: 'geojson',
};
const res = await directionsClient.getDirections(reqOptions).send();
console.log(res.body.routes[0].geometry.coordinates);
const newRoute = makeLineString(res.body.routes[0].geometry.coordinates);
setRoute(newRoute);
};
From the code snippet above, we are sending a request to the getDirections API provided by the mapbox sdk to extract the coordinates needed to plot our polyline. Why is this important? As explained earlier, Mapbox does not have an explicit polyline component as what we see in google map package. Also, using just the start and destination point to plot the polyline would give a straight line between points which in most cases is not the desired result.
Looking at the reqOptions object, we have a key called profile. The Direction API provides us with different profiles which includes, driving, cycling, driving-traffic and walking. To get a list of mapbox direction profiles and how Directions API work, check out this link. We use the makeLineString to generate the geoJSON coordinate response which is set in the route state.
Next, we call the fetchRoute function in our useEffect hook so that once the App component mounts, we fetch the direction route as shown below:
useEffect(() => {
fetchRoute();
})
Next, we render our route on the map using the ShapeSource and LineLayer component from Mapbox as shown in the snippet code below
route && (
<MapboxGL.ShapeSource id='shapeSource' shape={route}>
<MapboxGL.LineLayer id='lineLayer' style={{lineWidth: 5, lineJoin: 'bevel', lineColor: '#ff0000'}} />
</MapboxGL.ShapeSource>
)
From the code snippet above, we pass our route response as the shape property value and then we specify how our linelayer should be rendered using the style property.
Putting everything together, this is the entire App.js component with point annotations and polyline.
import React, {useState, useEffect } from "react";
import { View } from "react-native";
import MapboxGL from "@react-native-mapbox-gl/maps";
import {lineString as makeLineString} from '@turf/helpers';
import MapboxDirectionsFactory from '@mapbox/mapbox-sdk/services/directions';
const accessToken = 'MAPBOX_TOKEN_GOES_HERE';
MapboxGL.setAccessToken(accessToken);
const directionsClient = MapboxDirectionsFactory({accessToken});
export default App = () => {
const startingPoint = [3.3362400, 6.5790100];
const destinationPoint = [ 3.3750014, 6.5367877 ];
const [route, setRoute] = useState(null);
const startDestinationPoints = [startingPoint, destinationPoint]
useEffect(() => {
fetchRoute();
})
const fetchRoute = async () => {
const reqOptions = {
waypoints: [
{coordinates: startingPoint},
{coordinates: destinationPoint},
],
profile: 'driving-traffic',
geometries: 'geojson',
};
const res = await directionsClient.getDirections(reqOptions).send();
const newRoute = makeLineString(res.body.routes[0].geometry.coordinates);
setRoute(newRoute);
};
const renderAnnotations = () => {
return (
startDestinationPoints.map((point, index) => (
<MapboxGL.PointAnnotation
key={`${index}-PointAnnotation`}
id={`${index}-PointAnnotation`}
coordinate={point}>
<View style={{
height: 30,
width: 30,
backgroundColor: '#00cccc',
borderRadius: 50,
borderColor: '#fff',
borderWidth: 3
}}
/>
</MapboxGL.PointAnnotation>
))
);
}
return (
<View style={{flex: 1, height: "100%", width: "100%" }}>
<MapboxGL.MapView
styleURL={MapboxGL.StyleURL.Street}
zoomLevel={11}
centerCoordinate={startingPoint}
style={{flex: 1}}>
<MapboxGL.Camera
zoomLevel={11}
centerCoordinate={startingPoint}
animationMode={'flyTo'}
animationDuration={0}
>
</MapboxGL.Camera>
{renderAnnotations()}
{
route && (
<MapboxGL.ShapeSource id='shapeSource' shape={route}>
<MapboxGL.LineLayer id='lineLayer' style={{lineWidth: 5, lineJoin: 'bevel', lineColor: '#ff0000'}} />
</MapboxGL.ShapeSource>
)
}
</MapboxGL.MapView>
</View>
)
}
The map on our simulator should look like this
And that is it!!!. š š š š š š. In my next article, I would be showing you how to get the current location of a user on the map.
To check out the previous article, Click here.
PART I Ultimate Guide to integrating Mapbox with React Native
Hy, Can you help me for mapmatching in mapbox react native ?
I am so sorry Jay. I am going through my old post and seeing this. Let me know if you need any help
Gracias por el post Bro :)
Hello this is really good tutorial. can you animate the line from origin to destination.
I am so sorry Dildar. I am going through my old post and seeing this. Let me know if you need any help