I am working on a React component using OpenLayers to render a map with GeoJSON features and track the user's geolocation. The map initializes, but:
The GeoJSON features do not render.
Clicking "Track Position" does not update the map with the user's location.
I have checked the GeoJSON format and ensured the vectorSource
and vectorLayer
are set up correctly.
// ShareMap.js - OpenLayers in React
import React, { Component, createRef } from 'react';
import { Feature, Map, View } from 'ol';
import TileLayer from 'ol/layer/WebGLTile';
import { OSM, Vector as VectorSource } from 'ol/source';
import { Vector as VectorLayer } from 'ol/layer';
import { defaults as defaultInteraction } from 'ol/interaction';
import GeoJSON from 'ol/format/GeoJSON';
import { fromLonLat } from 'ol/proj';
import { Point } from 'ol/geom';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';
import 'ol/ol.css';
import 'ol-ext/dist/ol-ext.min.css';
import { Crosshair2Icon } from 'lucide-react';
class ShareMap extends Component {
mapRef = createRef();
vectorSource = new VectorSource({ wrapX: false });
vectorLayer = new VectorLayer({
source: this.vectorSource,
style: () => new Style({
image: new CircleStyle({
radius: 6,
fill: new Fill({ color: 'blue' }),
stroke: new Stroke({ color: 'white', width: 2 }),
}),
}),
});
constructor(props) {
super(props);
this.state = { isLoading: true, isTrack: false };
}
renderGeoLocation = async () => {
const map = this.mapRef.current?.map;
if (!map && !this.state.isTrack) return;
const pointStyle = new Style({
image: new CircleStyle({
radius: 8,
fill: new Fill({ color: '#3399CC' }),
stroke: new Stroke({ color: '#fff', width: 2 }),
}),
});
const userFeature = new Feature();
userFeature.setStyle(pointStyle);
const getUserCoords = () => {
return new Promise((resolve) => {
if (!navigator?.geolocation) return resolve([]);
navigator.geolocation.getCurrentPosition(
(pos) => resolve([pos.coords.longitude, pos.coords.latitude]),
(error) => {
alert(`ERROR: ${error.message}`);
resolve([]);
},
{ enableHighAccuracy: true }
);
});
};
const coords = await getUserCoords();
if (!coords.length) return;
const transformedCoords = fromLonLat(coords);
userFeature.setGeometry(new Point(transformedCoords));
this.vectorSource.clear();
this.vectorSource.addFeature(userFeature);
this.setState({ isTrack: false });
};
renderGeoJsonData = () => {
try {
const map = this.mapRef?.current?.map;
if (this.props.geoJson && map) {
this.vectorSource.clear();
const features = new GeoJSON().readFeatures(JSON.parse(this.props.geoJson));
if (features.length > 0) {
this.vectorSource.addFeatures(features);
}
}
this.vectorSource.changed();
} catch (err) {
console.error(err);
}
};
mapRender = () => {
const map = new Map({
interactions: defaultInteraction(),
target: 'mapShare',
layers: [new TileLayer({ source: new OSM() }), this.vectorLayer],
view: new View({ center: fromLonLat([0, 0]), zoom: 5 }),
});
this.mapRef.current = { map };
this.renderGeoLocation();
this.renderGeoJsonData();
};
componentDidMount() {
if (!this.mapRef?.current?.map) this.mapRender();
}
componentDidUpdate(prevProps, prevState) {
if (prevState.isTrack !== this.state.isTrack) {
this.state.isTrack && this.renderGeoLocation();
}
}
trackPosition = () => {
this.setState({ isTrack: true });
};
render() {
return (
<div className="h-full w-full relative flex items-center justify-center">
<button
type="button"
className="flex items-center space-x-2 absolute right-4 top-5 rounded-lg bg-white p-2 shadow z-50 hover:bg-gray-50"
onClick={this.trackPosition}
>
<Crosshair2Icon />
<span className="text-sm">Track Position</span>
</button>
<div id="mapShare" className="h-full w-full"></div>
</div>
);
}
}
export default ShareMap;
What I Have Tried:
Verified GeoJSON structure using an online validator.
Ensured
vectorSource.addFeatures()
is called with valid features.Called
vectorSource.changed()
to force a re-render.We need to both render at same time when both location is same