Using OpenLayers 7.1.0 and React 18.2.0, I have a map with some region boxes displayed on a vector layer using GeoJSON data handed in as a prop to my VectorLayer
component.
When the features
prop changes, a useEffect is fired which is supposed to be clearing the old features off the map and rendering only the new ones. New features are rendered as expected, but they appear on top of the old ones which are not being removed.
After attempting to remove the old features by calling clear()
and refresh()
on the source and renderSync()
on the map, and before adding the new features to the source, console.log(vectorLayer.GetSource().GetFeatures())
shows an empty array but the old features are still visible. I've tried creating a whole new source and even tried creating a whole new layer without success.
I referenced this example to set up the structure of the map context and the basic layers: https://medium.com/swlh/how-to-incorporate-openlayers-maps-into-react-65b411985744
VectorLayer.js:
import {useContext, useEffect, useState} from 'react';
import MapContext from "../MapContext";
import OLVectorLayer from 'ol/Layer/Vector';
import VectorSource from "ol/source/Vector";
import {createBox} from "ol/interaction/Draw";
import {Draw} from "ol/intraction";
import {GeoJSON} from "ol/format";
const VectorLayer = ({source, style, zIndex = 0, canDraw, onShapeDrawn, features}) => {
const {map} = useContext(MapContext);
//const [vectorLayer, setVectorLayer] = useState(null);
let vectorSource = new VectorSource({wrapX: true});
let vectorLayer = new OLVectorLayer({
source: vectorSource,
style
});
const drawBox = new Draw({
source: vectorSource,
type: 'Circle',
geometryFunction: createBox(),
});
// "Constructor"
useEffect(() => {
if (!map) return;
// let vectorLayer = new OLVectorLayer({
// source: vectorSource,
// style
// });
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
if( canDraw )
{
map.addInteraction(drawBox);
if( onShapeDrawn != null ) {
drawBox.on('drawend', (e) => {
onShapeDrawn(e);
})
}
}
//setVectorLayer(vectorLayer);
return () => {
if (map) {
map.removeLayer(vectorLayer);
}
}
}, [map]);
// update map if features prop changes
useEffect( () => {
console.log('new # of features');
console.log(features?.features?.length);
if (features?.features?.length) { // may be empty on first render
// vectorLayer.getSource().clear();
// vectorLayer.getSource().refresh();
// map.renderSync();
// console.log('old source features after clear and refresh');
// console.log(vectorLayer.getSource().getFeatures());
map.removeLayer(vectorLayer);
// let featureSource = new VectorSource({
// wrapX: true
// })
// featureSource.addFeatures(new GeoJSON().readFeatures(features,
// {dataProjection:'EPSG:4326', featureProjection:'EPSG:4326'}));
// vectorLayer = new OLVectorLayer({
// source: featureSource,
// style
// });
vectorSource.clear();
vectorSource.refresh()
vectorSource.addFeatures(new GeoJSON().readFeatures(features,
{dataProjection:'EPSG:4326', featureProjection:'EPSG:4326'}));
// set features to map
// vectorLayer.setSource(featureSource);
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
map.renderSync();
}
},[features])
// Enabled/Disabled draw interaction when canDraw bool prop changes
useEffect(() => {
if (!map) return;
console.log(`vector layer canDraw: ${canDraw}`);
if( canDraw )
{
map.removeLayer(vectorLayer);
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
map.addInteraction(drawBox);
if( onShapeDrawn != null ) {
drawBox.on('drawend', (e) => {
onShapeDrawn(e);
})
}
} else {
console.log('removing draw interaction');
map.getInteractions().pop();
}
}, [canDraw]);
return null;
}
export default VectorLayer;
I've just left in the commented-out code to show a few different things I have tried. If it's a bit confusing to look through I'd be happy to clean it up some more.
Sorry for the late reply, thought I had already come back and updated this!
I was able to resolve the issue by restructuring the code of VectorLayer.js
to ensure that the source, layer, and interactions were only being created once. The issue was caused by their ol_uid
being regenerated on new renders.
VectorLayer.js
import {useContext, useEffect, useState} from 'react';
import MapContext from "../MapContext";
import OLVectorLayer from 'ol/Layer/Vector';
import VectorSource from "ol/source/Vector";
import {createBox} from "ol/interaction/Draw";
import {Draw} from "ol/interaction";
import {GeoJSON} from "ol/format";
const VectorLayer = (props) => {
const {style, zIndex = 0, canDraw, onShapeDrawn, featureCollection} = props;
const {map} = useContext(MapContext);
const [isInstantiated, setIsInstantiated] = useState(false);
const [vectorLayer, setVectorLayer] = useState(null);
const [vectorSource, setVectorSource] = useState(null);
const [drawBox, setDrawBox] = useState(null);
useEffect(() => {
if (!map || isInstantiated) return;
setVectorSource(new VectorSource({wrapX: true}));
setIsInstantiated(true);
},[map]);
useEffect(() => {
if(!map || !vectorSource) return;
setDrawBox(new Draw({
source: vectorSource,
type: 'Circle',
geometryFunction: createBox(),
}));
setVectorLayer(new OLVectorLayer({
source: vectorSource,
style
}));
}, [vectorSource]);
useEffect(() => {
if(!map || !vectorLayer) return;
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
if( canDraw )
{
map.addInteraction(drawBox);
if( onShapeDrawn != null ) {
drawBox.on('drawend', (e) => {
onShapeDrawn(e);
})
}
}
return () => {
if (map) {
map.removeLayer(vectorLayer);
}
}
}, [vectorLayer]);
// update map if features prop changes
useEffect( () => {
if(!map || !vectorLayer) return;
map.removeLayer(vectorLayer);
vectorSource.clear();
if (featureCollection?.features?.length) { // may be empty on first render
vectorSource.addFeatures(new GeoJSON().readFeatures(featureCollection,
{dataProjection:'EPSG:4326', featureProjection:'EPSG:4326'}));
}
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
}, [featureCollection])
// Enable/Disable draw interaction when canDraw bool prop changes
useEffect(() => {
if (!map || !vectorLayer || !drawBox) return;
console.log(`vector layer canDraw: ${canDraw}`);
if( canDraw )
{
map.removeLayer(vectorLayer);
map.addInteraction(drawBox);
if( onShapeDrawn != null ) {
drawBox.on('drawend', (e) => {
onShapeDrawn(e);
})
}
map.addLayer(vectorLayer);
vectorLayer.setZIndex(zIndex);
} else {
console.log('removing draw interaction');
//map.getInteractions().pop();
map.removeInteraction(drawBox);
}
}, [canDraw]);
return null;
}
export default VectorLayer;
Thanks for the help everyone!