Senior Software Engineer
Published on

Interactive Mapping with React and Mapbox:Drawing Polygons and Checking Point Locations with Turf.js

cover

Introduction: Overview of Mapbox and Turf.js

Mapbox is a popular mapping platform that allows you to create customizable maps for your web and mobile applications. It provides a variety of tools and features to enhance your maps, such as geolocation, custom markers, and interactive layers.

Turf.js, on the other hand, is a JavaScript library that provides advanced geospatial analysis tools. It allows you to perform complex spatial operations, such as buffering, clustering, and geocoding, all within the browser.

In this tutorial, we will explore how to use Mapbox and Turf.js to draw polygons on a map and check if a given point is inside the polygon. We will start by setting up a basic Mapbox React component and then gradually add features to achieve our goal.

Setting Up the Mapbox React Component

To get started, we will create a basic Mapbox React component that displays a map centered on a specific location. We will use the react-map-gl library to render the map, which provides a simple and intuitive API for working with Mapbox in React.

First, we will import the necessary dependencies:

import * as React from 'react';
import { useState, useCallback, useEffect } from 'react';
import { render } from 'react-dom';
import MapGL from 'react-map-gl';
import 'mapbox-gl/dist/mapbox-gl.css';

Next, we will define our App component, which will contain the map:

export default function App() {
  const [viewport, setViewport] = useState({
    longitude: -91.874,
    latitude: 42.76,
    zoom: 12
  });

  return (
    <div>
      <MapGL
        {...viewport}
        width="100%"
        height="100%"
        mapStyle="mapbox://styles/mapbox/light-v10"
        onViewportChange={setViewport}
        mapboxApiAccessToken={process.env.REACT_APP_MAPBOX_ACCESS_TOKEN}
      />
    </div>
  );
}

Here, we define a state variable viewport that stores the current position and zoom level of the map. We also create a MapGL component that renders the map, using the viewport state to set the initial position and zoom level.

Note that we provide a mapboxApiAccessToken prop to the MapGL component, which should contain your Mapbox API access token. You can obtain an access token by signing up for a free Mapbox account and creating a new project.

Finally, we render the App component to the DOM using the renderToDom function:

export function renderToDom(container) {
  render(<App />, container);
}

This will display a basic Mapbox map centered on the coordinates -91.874 (longitude) and 42.76 (latitude), with a zoom level of 12.

Drawing Polygons with MapboxDraw

Drawing polygons on a Mapbox map can be a complex task, but MapboxDraw makes it easy. In this part of the tutorial, we'll use MapboxDraw to enable users to draw a polygon on the map. First, we'll add the MapboxDraw control to our map and set it up to only allow polygon drawing. We'll also add event listeners to capture when the polygon is created, updated, or deleted, so we can update our state accordingly.

const mapRef = useCallback(map => {
  if (map) {
    const draw = new MapboxDraw({
      displayControlsDefault: false,
      controls: {
        polygon: true,
        trash: true
      }
    });
    map.addControl(draw, 'top-left');
    map.on('draw.create', e => setFeatures(e.features));
    map.on('draw.update', e => setFeatures(e.features));
    map.on('draw.delete', e => setFeatures(e.features));
  }
}, []);

Next, we'll use the features state to capture the drawn polygon and use the turf library to determine if a given point is inside the polygon. When the user clicks the "Check" button, we'll create a point object with the latitude and longitude values from the input fields, and use the booleanPointInPolygon function from turf to check if the point is inside the polygon. We'll then display an alert message to the user to indicate whether the point is inside or outside the polygon.

const handleCheck = () => {
  const point = turf.point([parseFloat(lng), parseFloat(lat)]);
  const polygon = features[Object.keys(features)[0]];
  const inside = turf.booleanPointInPolygon(point, polygon.geometry);
  alert(inside ? 'Inside' : 'Outside');
};

Now we have a fully functional map that allows users to draw a polygon and check if a given point is inside that polygon.

Adding User Input

we will add two text fields for latitude and longitude, and a button that will check whether a point is inside or outside the drawn polygon using the Turf library. To accomplish this, we will add the following code to our App component:

const handleLatChange = e => setLat(e.target.value);
const handleLngChange = e => setLng(e.target.value);

const handleCheck = () => {
 const point = turf.point([parseFloat(lng), parseFloat(lat)]);
 const polygon = features[Object.keys(features)[0]];
 const inside = turf.booleanPointInPolygon(point, polygon.geometry);
 alert(inside ? 'Inside' : 'Outside');
};

return (
 <div>
   <MapGL
     //...viewport, mapboxAccessToken, mapStyle, etc.
   />
   <div className="flex flex-row space-x-4 mt-4">
     <div className="flex flex-col">
       <label className="text-gray-700 font-bold mb-2" htmlFor="latitude">
         Latitude:
       </label>
       <input
         className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
         id="latitude"
         type="text"
         value={lat}
         onChange={handleLatChange}
         placeholder="Enter latitude"
       />
     </div>
     <div className="flex flex-col">
       <label className="text-gray-700 font-bold mb-2" htmlFor="longitude">
         Longitude:
       </label>
       <input
         className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
         id="longitude"
         type="text"
         value={lng}
         onChange={handleLngChange}
         placeholder="Enter longitude"
       />
     </div>
     <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" onClick={handleCheck}>
       Check
     </button>
   </div>
 </div>
);

Here, we define two event handlers for the latitude and longitude text fields that update their corresponding state values. We also define a handleCheck function that creates a Turf point from the entered latitude and longitude, retrieves the first drawn polygon from the features state, and uses the booleanPointInPolygon function to determine whether the point is inside or outside the polygon. Finally, we render the text fields and button inside a div using Tailwind CSS classes to style them.

With this code, users can enter a latitude and longitude and click the button to check whether the point is inside or outside the drawn polygon.