Consuming a Geospatial REST API with React-leaflet and Data Filtering.
- Introduction
In this article, we will create an interactive web map with React-Leaflet, utilizing data retrieved from an API endpoint we’ve developed earlier in Node.js.
- About React-leaflet
React-Leaflet is an open-source library that enables mapping in React applications by allowing you to render Leaflet maps withinyour React-based projects. It leverages OpenStreetMap and provides a convenient way to integrate maps into your web applications.
- Project Definition and Objectives
We will create a web application using Leaflet.js that consumes the REST API we built in our previous article titled “Getting Started: Building Location-Based (GIS) REST APIs with NodeJS, Express, and PostgreSQL.” In this web application, we will achieve the following functionalities:
- Display all Health Facilities on the Map
- Display all Sub Counties on the Map
- Implement a filter to display Health Facilities based on Sub County names
- Prerequisites
Have the node.js installed on your machine, create a react application then install leaflet, react-leaflet and bootstrap.
npm install leaflet react-leaflet bootstrap
Leaflet — Mapping Library for creating interactive maps
React-leaflet — Helps us render maps in react application
Bootstrap — Styling framework
Run your application and make sure it runs successfully.
. Rendering our map
Prepare your application by removing any unnecessary files. Inside the “src” directory, establish a “components” folder, and generate a “Map.js” file to handle map rendering. To display the map, which consists of Leaflet tile layers, you need to employ a “MapContainer” component with essential attributes such as “style,” “center,” and “zoom.”
See code below.
Update the App.js file by importing our Map.js file to it. This is how your file should appear. Then run your application again and have the output below.
import React from 'react';
import Map from './components/Map';
import './App.css';
class App extends React.Component {
render() {return
(<div className='App'>
<Map />
</div>
);}
}
export default App;
.Rendering all the health facilities
We will develop a function within the parent component, “App.js,” to retrieve our health facilities data. Subsequently, we will pass this data as a prop to “Map.js.” for display, see code below; in App.js and Map.js
//Code within App.js
import './App.css';
import React from 'react';
import Map from './components/Map'
class Apps extends React.Component {
constructor(props) {
super(props);
this.state = {
healthCenters:[]
}
}
componentDidMount =() => {
this.getAllHeathFacilities();
}
getAllHeathFacilities = ()=> {
fetch('http://localhost:9000/data/api/nairobihealthfacilities')
.then((response) => response.json())
.then((data) => {
this.setState({ healthCenters: data });
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}
render() {
let { healthCenters} = this.state;
return (
<div>
<Map healthCenters={healthCenters}/>
</div>
);
}
}
export default Apps;
//Code within Map.js
import { React, useState, useEffect } from 'react';
import { MapContainer, TileLayer, Marker, Popup, GeoJSON } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import L from 'leaflet';
import marker from '../assets/images/medicine.png';
const Map = ({healthCenters}) => {
const position = [-1.295761267252445, 36.8605899810791];
//mapcontainer style
const style = {
height: '70vh',
width: '100%',
};
//marker style
const myIcon = new L.Icon({
iconUrl: marker,
iconRetinaUrl: marker,
popupAnchor: [0, 0],
iconSize: [12, 12],
});
return (
<div>
<MapContainer center={position} zoom={11} scrollWheelZoom={true} style={style}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors|| Build by fmuchembi'
url="https://{s}.basemaps.cartocdn.com/rastertiles/dark_all/{z}/{x}/{y}.png"
/>
{healthCenters.map((center) => {
const point = [center.point.coordinates[1], center.point.coordinates[0]];
return (
<Marker position={point} key={center.id} icon={myIcon} >
<Popup>
<span>Name:<br /> {center.name}</span>
</Popup>
</Marker>
);
})}
</MapContainer>
</div>
);
};
export default Map;
our output is as below.
.Rendering all the sub counties
First is to create a function that fetches our data from our API endpoint: See the example below, this is created within the Map.js
const getAllSubcounties = async()=>{
const response = await fetch("http://localhost:9000/data/api/subcounties")
const subcounties = await response.json();
setSubcounties(subcounties)
}
useEffect(()=>{
getAllSubcounties();
}, [])
After successfully implementing our fetch function, the next step is to showcase the fetched data. Our dataset consists of multipolygon features, so we utilize the Ajax syntax <GeoJSON/>. The GeoJSON format includes several attributes: “data” (in this instance, our data is denoted by the variable “subcounties”), “style” (primarily used for customizing the data’s appearance). The following code snippet illustrates how to display the subcounties.
{subcounties&& (
<GeoJSON
data={subcounties}
style={styles}
attribution='nairobi subcounties'
/>
)}
At this point, this is how our output looks like
. Displaying health facilities within a specific sub-county
To begin with, we create a search section in the ‘Map.js’ file that includes a dropdown (select) option to allow us to choose a specific sub-county within Nairobi. This drop-down receives data passed to it as a prop from the Parent component App.js. The code snippet below shows the code update in Map.js
//Updated App.js Component
import './App.css';
import React from 'react';
import Map from './components/Map'
class Apps extends React.Component {
constructor(props) {
super(props);
this.state = {
subCounty:[],
healthCenters:[]
}
}
componentDidMount =() => {
this.getSubcounties();
this.getAllHeathFacilities();
}
getSubcounties = ()=> {
fetch('http://localhost:9000/data/api/nairobisubcounties')
.then((response) => response.json())
.then((data) => {
this.setState({ subCounty: data });
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}
getAllHeathFacilities = ()=> {
fetch('http://localhost:9000/data/api/nairobihealthfacilities')
.then((response) => response.json())
.then((data) => {
this.setState({ healthCenters: data });
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}
render() {
let { subCounty, healthCenters} = this.state;
return (
<div>
<Map subCounty={subCounty} healthCenters={healthCenters}/>
</div>
);
}
}
export default Apps;
//Form section with Map.js
<form className='d-flex'>
<select className="form-select" aria-label="Default select example" value={selectedSubCounty} onChange={handleChange}>
<option value='Nairobi County' className='option'>Nairobi County</option>
{props.subCounty.map((option) => (
<option key={option.id} value={option.value} className='option'>
{option.name}
</option>
))}
</select>
<button type="submit" className="btn btn-danger">Search</button>
</form>
The value of our select is set to ‘selectedSubCounty’ this is initialized in state as the code below;
const [selectedSubCounty, setSelectedSubCounty] = useState('Nairobi County');
We need an onChange event listener to handle our select options, see the code below and the output expected thereafter.
const handleChange = (event) => {
setSelectedSubCounty(event.target.value);
};
Remember it is Within the Parent Component App.js, where we created the function to get our sub county names.
import './App.css';
import React from 'react';
import Map from './components/Maps'
class Apps extends React.Component {
constructor(props) {
super(props);
this.state = {
subCounty:[],
healthCenters:[]
}
}
componentDidMount =() => {
this.getSubcounties();
this.getAllHeathFacilities();
}
getSubcounties = ()=> {
fetch('http://localhost:9000/data/api/nairobisubcounties')
.then((response) => response.json())
.then((data) => {
this.setState({ subCounty: data });
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}
getAllHeathFacilities = ()=> {
fetch('http://localhost:9000/data/api/nairobihealthfacilities')
.then((response) => response.json())
.then((data) => {
this.setState({ healthCenters: data });
})
.catch((error) => {
console.error('Error fetching data:', error);
});
}
render() {
let { subCounty, healthCenters} = this.state;
return (
<div>
<Maps subCounty={subCounty} healthCenters={healthCenters}/>
</div>
);
}
}
export default Apps;After ensuring that our dropdown option functions correctly, we will proceed to update our Map.js file so that it can render health facilities within a certain subcounty once the search is submitted. First we initialize state and set all the health facilities within Nairobi County as the default state, we shall name our state facilities; see code below
const [facilities, setFacilities] = useState(props.healthCenters);
Following the initialization of our state, we will define a function responsible for managing the form upon submission, which will update the state. Let’s name this function ‘onSearchSubmit.’ You can refer to the code snippet below for implementation:
<form className='d-flex' onSubmit={onSearchSubmit}>
<select className="form-select" aria-label="Default select example" value={selectedSubCounty} onChange={handleChange}>
<option value='Nairobi County' className='option'>Nairobi County</option>
{props.subCounty.map((option) => (
<option key={option.id} value={option.value} className='option'>
{option.name}
</option>
))}
</select>
<button type="submit" className="btn btn-danger">Search</button>
</form>
Next, we will create a function that specifies the actions to take when the ‘onSearchSubmit’ function is invoked. We intend for this function to update the state with health facilities that belong to a particular sub county. Refer to the code below for details:
// receives seletectedSubCounty as a parameter and returns health facilities with that boundary and updates our facilities state
const fetchData = async(selectedSubCounty) => {
try {
const response = await fetch(`http://localhost:9000/data/api/nairobihealthfacilities/withinsubcounty/${selectedSubCounty}`);
const data = await response.json();
setFacilities(data);
} catch (error) {
console.error(error);
}
};
//OnsearchSubmit function that executes the fetchData function
const onSearchSubmit = async (event) => {
event.preventDefault();
await fetchData(selectedSubCounty);
};
The provided code enables you to effectively update the state with any selection you make regarding the sub-county. You can observe the results in the snapshot displayed below.
— CONCLUSION
And that concludes our tutorial. Thank you for following along! Happy mapping, and you can find the entire code by following the link provided.