Hire the author: Martin K
GIS react citation here
GitHub link for this project GitHub
Introduction
At work, I recently had the challenge of integrating OpenLayers into our React application. I found an existing npm package that did most of what I needed for maps, but not entirely. In addition, the package didn’t have a lot of downloads, and the project didn’t appear to be regularly supported.
Given all of these considerations, I chose to create my own OpenLayers map components to gain the flexibility I required right away. This is a basic build-your-own vs. use-something-that-exists dilemma that developers encounter on a regular basis.
While I typically advocate “don’t reinvent the wheel,” there are times when you know you’re going to hit a brick wall in terms of flexibility if you try to fit your use case into something that currently exists. So, with that out of the way, I’ll walk you through the components I built to make this work exactly how I wanted it to while also leaving plenty of room for future extension.
The source code of the example will be available on Github.
Photo Preview

Glossary
What is GIS?
A geographic information system (GIS) is a piece of software that lets you produce, organize, analyze, and map various sorts of information. By combining location data (where objects are) with other types of descriptive data, GIS connects data to a map (what things are like there). This establish the foundation for mapping and analysis use in science and nearly every industry. Users can use GIS to better understand trends, relationships, and their surroundings. Better management and decision-making, as well as improved communication and efficiency, are all benefits.
What is OpenLayers?
A dynamic map may be easy to use in any web page using OpenLayers. It can use any source to display map tiles, vector data, and markers. OpenLayers encourages the usage of all types of geographic data. OpenLayers is an Open Source JavaScript framework that’s entirely free and under the BSD License’s two clauses (also known as the FreeBSD).
Step by Step Procedure
Step 1: Creating a Wrapper for a Functional Component
To begin, we’ll develop a “wrapper” component that serves as a bridge between the React ecosystem and OpenLayers. This component has two key characteristics:
- Creating a div> element into which OpenLayers will be loaded.
- In the component state, keeping a reference to the OpenLayers Map and Layer objects.
import React, { useState, useRef } from 'react'; | |
function MapWrapper(props) { | |
// set intial state - used to track references to OpenLayers | |
// objects for use in hooks, event handlers, etc. | |
const [ map, setMap ] = useState() | |
const [ featuresLayer, setFeaturesLayer ] = useState() | |
const [ selectedCoord , setSelectedCoord ] = useState() | |
// get ref to div element - OpenLayers will render into this div | |
const mapElement = useRef() | |
return ( | |
<div ref={mapElement} className="map-container"></div> | |
) | |
} | |
export default MapWrapper |
Step 2: Getting the Map Started
Then, after the first render, we’ll create a hook to initialize the OpenLayers map. To ensure that the hook only executes once, we’ll use a useEffect hook with an empty dependency array.(see line 49). This logic would have been in a componentDidMount() function in the past.
On lines 46-47, the OpenLayers Map and VectorLayer objects are in the React Component state. Hooks and event handlers will use these objects later.
import React, { useState, useRef } from 'react'; | |
import Map from 'ol/Map' | |
import View from 'ol/View' | |
import TileLayer from 'ol/layer/Tile' | |
import VectorLayer from 'ol/layer/Vector' | |
import VectorSource from 'ol/source/Vector' | |
import XYZ from 'ol/source/XYZ' | |
function MapWrapper(props) { | |
// state and ref setting logic eliminated for brevity | |
// initialize map on first render - logic formerly put into componentDidMount | |
useEffect( () => { | |
// create and add vector source layer | |
const initalFeaturesLayer = new VectorLayer({ | |
source: new VectorSource() | |
}) | |
// create map | |
const initialMap = new Map({ | |
target: mapElement.current, | |
layers: [ | |
// USGS Topo | |
new TileLayer({ | |
source: new XYZ({ | |
url: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', | |
}) | |
}), | |
initalFeaturesLayer | |
], | |
view: new View({ | |
projection: 'EPSG:3857', | |
center: [0, 0], | |
zoom: 2 | |
}), | |
controls: [] | |
}) | |
// save map and vector layer references to state | |
setMap(initialMap) | |
setFeaturesLayer(initalFeaturesLayer) | |
},[]) | |
// return/render logic eliminated for brevity | |
} |
Step 3: Adding to the Map’s Features
To add features to the map, we can utilize another useEffect hook. When using React props to offer features, inserting ‘props.features’ in the dependencies array (line 24) ensures that the hook only fires when the features property changes. This logic would have been in a componentDidUpdate() function in the past.
function MapWrapper(props) { | |
// state, ref, and initialization logic eliminated for brevity | |
// update map if features prop changes - logic formerly put into componentDidUpdate | |
useEffect( () => { | |
if (props.features.length) { // may be empty on first render | |
// set features to map | |
featuresLayer.setSource( | |
new VectorSource({ | |
features: props.features // make sure features is an array | |
}) | |
) | |
// fit map to feature extent (with 100px of padding) | |
map.getView().fit(featuresLayer.getSource().getExtent(), { | |
padding: [100,100,100,100] | |
}) | |
} | |
},[props.features]) | |
// return/render logic eliminated for brevity | |
} |
Step 4: Inside Event Handlers, Using State
From within an OpenLayers event handler, we can quickly update the React component’s state using the setMap(), setFeaturesLayer(), and setSelectedCoord() functions. Reading State in this situation, however, is more difficult.
When an event handler is set inside of a hook, a closure is created, which means any state we try to access will be stale. Fortunately, we can retrieve the “current” state using a React Ref. Lines 8-9 create and set the ref, which is later accesses the OpenLayers Map object on line 25.
function MapWrapper(props) { | |
// other state, ref, and initialization logic eliminated for brevity | |
const [ map, setMap ] = useState() | |
// create state ref that can be accessed in OpenLayers onclick callback function | |
// https://stackoverflow.com/a/60643670 | |
const mapRef = useRef() | |
mapRef.current = map | |
// initialize map on first render - logic formerly put into componentDidMount | |
useEffect( () => { | |
// placed at the bottom of the initialization hook | |
// (other function content elimintated for brevity) | |
initialMap.on('click', handleMapClick) | |
},[]) | |
// map click handler | |
const handleMapClick = (event) => { | |
// get clicked coordinate using mapRef to access current React state inside OpenLayers callback | |
// https://stackoverflow.com/a/60643670 | |
const clickedCoord = mapRef.current.getCoordinateFromPixel(event.pixel); | |
// transform coord to EPSG 4326 standard Lat Long | |
const transormedCoord = transform(clickedCoord, 'EPSG:3857', 'EPSG:4326') | |
// set React state | |
setSelectedCoord( transormedCoord ) | |
} | |
// return/render logic eliminated for brevity | |
} |
Step 5: Final MapWrapper.jsx file
React’s new Functional Components and Hooks have a lot of advantages. I enjoy how simple it is to make sure hooks are only called when the select state and props change, which eliminates the need for complicated evaluations in shouldComponentUpdate() routines. I’m excited to use them again in the future!
The whole MapWrapper component is shown below, and it is also accessible on GitHub.
// react | |
import React, { useState, useEffect, useRef } from 'react'; | |
// openlayers | |
import Map from 'ol/Map' | |
import View from 'ol/View' | |
import TileLayer from 'ol/layer/Tile' | |
import VectorLayer from 'ol/layer/Vector' | |
import VectorSource from 'ol/source/Vector' | |
import XYZ from 'ol/source/XYZ' | |
import {transform} from 'ol/proj' | |
import {toStringXY} from 'ol/coordinate'; | |
function MapWrapper(props) { | |
// set intial state | |
const [ map, setMap ] = useState() | |
const [ featuresLayer, setFeaturesLayer ] = useState() | |
const [ selectedCoord , setSelectedCoord ] = useState() | |
// pull refs | |
const mapElement = useRef() | |
// create state ref that can be accessed in OpenLayers onclick callback function | |
// https://stackoverflow.com/a/60643670 | |
const mapRef = useRef() | |
mapRef.current = map | |
// initialize map on first render - logic formerly put into componentDidMount | |
useEffect( () => { | |
// create and add vector source layer | |
const initalFeaturesLayer = new VectorLayer({ | |
source: new VectorSource() | |
}) | |
// create map | |
const initialMap = new Map({ | |
target: mapElement.current, | |
layers: [ | |
// USGS Topo | |
new TileLayer({ | |
source: new XYZ({ | |
url: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', | |
}) | |
}), | |
// Google Maps Terrain | |
/* new TileLayer({ | |
source: new XYZ({ | |
url: 'http://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}', | |
}) | |
}), */ | |
initalFeaturesLayer | |
], | |
view: new View({ | |
projection: 'EPSG:3857', | |
center: [0, 0], | |
zoom: 2 | |
}), | |
controls: [] | |
}) | |
// set map onclick handler | |
initialMap.on('click', handleMapClick) | |
// save map and vector layer references to state | |
setMap(initialMap) | |
setFeaturesLayer(initalFeaturesLayer) | |
},[]) | |
// update map if features prop changes - logic formerly put into componentDidUpdate | |
useEffect( () => { | |
if (props.features.length) { // may be null on first render | |
// set features to map | |
featuresLayer.setSource( | |
new VectorSource({ | |
features: props.features // make sure features is an array | |
}) | |
) | |
// fit map to feature extent (with 100px of padding) | |
map.getView().fit(featuresLayer.getSource().getExtent(), { | |
padding: [100,100,100,100] | |
}) | |
} | |
},[props.features]) | |
// map click handler | |
const handleMapClick = (event) => { | |
// get clicked coordinate using mapRef to access current React state inside OpenLayers callback | |
// https://stackoverflow.com/a/60643670 | |
const clickedCoord = mapRef.current.getCoordinateFromPixel(event.pixel); | |
// transform coord to EPSG 4326 standard Lat Long | |
const transormedCoord = transform(clickedCoord, 'EPSG:3857', 'EPSG:4326') | |
// set React state | |
setSelectedCoord( transormedCoord ) | |
} | |
// render component | |
return ( | |
<div> | |
<div ref={mapElement} className="map-container"></div> | |
<div className="clicked-coord-label"> | |
<p>{ (selectedCoord) ? toStringXY(selectedCoord, 5) : '' }</p> | |
</div> | |
</div> | |
) | |
} | |
export default MapWrapper |
Future Directions
When all is done, the future of GIS is pretty bright. As more companies realize how much business value geospatial data can provide. It’s primed for even more widespread adoption than we’ve seen in recent decades. We should expect geographic information science and technology to not only be altered. But also to help change the way enterprises use these technologies as trends. For example, data analytics, mobility, AR, and IoT continue to take hold around the world.
You’ve probably heard a lot about historical trends in Geographic Information Science (GIS) research and application. But what about where GIS is headed in the future? While predicting the future of an industry or technology is difficult. There are numerous signs that point to and provide a glimpse of GIS’s future. The future of GIS could very well be in augmented and virtual reality.
As customers become more familiar with AR and VR, more developers will integrate GIS capabilities into their products. Anyone from architects to oil workers to municipal agencies might use these GIS-enabled AR apps. To visualize the location and orientation of anything underground. Game developers will almost certainly use GIS in the future to construct virtual settings. They will act as a playground for the creative imagination.
This is likely to be one of the areas where 3D and mobile GIS will converge. Mobile GIS applications will be able to generate immersive experiences wherever in the field thanks to 3D geographical data. Popular games like Pokémon Go are early instances of this trend, but the potential for immersive AR representations is enormous.
Learning Strategies and Tools
As previously said, GIS application development necessitates the use of specific GIS tools in addition to regular app development techniques. Fortunately, there are several solutions available, including Web AppBuilder for ArcGIS, Leaflet.js, Turf.js, and OpenLayers.
The development of the GIS react app would not have been feasible without the use of OpenLayers. Its is an open-source JavaScript package that is simple to use. It makes it simple to integrate a map into our apps. Also create layers and import data from a variety of sources. We’ll be able to use OpenLayers to mimic different instances needed for certain projects in the future, thanks to the information gathered from the previous technologies.
Reflective Analysis
I’m sure you know a lot about OpenLayers and React Functional Components, as well as how to design a GIS app in general. Most importantly, you should have learned a lot about React, how components operate, how to map all the different components to write effectively and avoid duplicating code, and how to break everything into components in general.
GIS applications benefit a variety of tasks, but in general, if the primary function of your product or service requires geographical spatial data (rather than just mapping), GIS technology can be quite useful. GIS technologies and frameworks enable us to construct the most varied and functional apps for various objectives, from finding the nearest cab to planning civil engineering projects.
Conclusion
If you find yourself in a situation similar to mine where you need to use OpenLayers in a React project, I hope this presentation is helpful. Please feel free to look through the code and contact me if you have any questions. Thanks for taking the time to read what I’ve written.
Well structured article which is easy to follow and understand.
Also I liked the way you gave a clear direction in the README of the code’s github repository. This made it easy to set up and run the project.
In the future, you may look into how the map can load exactly current user location as the default.