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

GIS react

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:

  1. Creating a div> element into which OpenLayers will be loaded.
  2. 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
view raw MapWrapper.js hosted with ❤ by GitHub

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
}
view raw MapWrapper.js hosted with ❤ by GitHub

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
}
view raw MapWrapper.js hosted with ❤ by GitHub

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
}
view raw MapWrapper.js hosted with ❤ by GitHub

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
view raw MapWrapper.js hosted with ❤ by GitHub

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.

Hire the author: Martin K