0

I have a map component that loads data from a geoJSON file and displays each datapoint on an openstreetmap map. The library is use for that is the 'react-leaflet' library. And I also use the 'react-leaflet-cluster' library to cluster the markers.

In the dataset I use it is common for multiple records to have the same exact location. When that is the case I want to change the coordinates of the markers so that the markers are neatly placed next to each other in a row:

export function MapOld() {
const [jsonContent, setJsonContent] = useState()
const [isMapReady, setIsMapReady] = useState(false)
const mapRef = useRef()
const geojsonRef = useRef()
const markersRef = useRef([])

useEffect(() => {
    useFetchGeoJson()
        .then(data => setJsonContent(data))
        .catch(err => console.log(`error reading data ${err}`)) // eslint-disable-line no-console
}, [])

useEffect(() => {
    if (mapRef.current && jsonContent) {
        mapRef.current.fitBounds(geojsonRef.current?.getBounds())
        geojsonRef.current?.eachLayer(layer => layer.openPopup())
    }
}, [isMapReady, jsonContent])

const pointToLayer = useCallback((feature, latlng) => {
    if (feature.properties && feature.properties.icon) {
        return L.marker(latlng, {
            icon: L.icon(feature.properties && feature.properties.icon),
        })
    }

    const existingMarkers = markersRef.current
    const samePositionMarkers = existingMarkers.filter(marker =>
        marker.getLatLng().equals(latlng)
    )

    const newIcon = L.divIcon({
        html: ReactDOMServer.renderToString(<SignEmpty />),
        iconSize: [25, 25],
        iconAnchor: [12, 12],
    })

    if (samePositionMarkers.length > 0) {
        // const lastMarker = samePositionMarkers[samePositionMarkers.length - 1]
        const offsetX = 40 * samePositionMarkers.length
        const newLatLng = L.latLng(latlng.lat, latlng.lng + offsetX)

        const newMarker = L.marker(newLatLng, {
            icon: newIcon,
        })
        newMarker.addTo(mapRef.current)
        newMarker.setLatLng(newLatLng)
        newMarker.bindPopup(feature.properties.name)
        console.log(newMarker) // eslint-disable-line no-console

        existingMarkers.push(newMarker)

        return null // returning null here prevents Leaflet from creating a marker for the original latlng
    }
    const marker = L.marker(latlng, {
        icon: newIcon,
    })
    marker.bindPopup(feature.properties.name)
    // marker.on('click', () => onMarkerClick(marker))

    existingMarkers.push(marker)

    return marker
}, [])

const onEachFeature = useCallback((feature, layer) => {
    if (feature.properties && feature.properties.popupContent) {
        layer.bindPopup(feature.properties.popupContent, {
            autoClose: false,
        })
    }
}, [])

return (
    <MapContainer
        className="geoMapContainer"
        style={{
            position: 'absolute',
            height: 'Calc(100vh - 75px)',
            width: 'Calc(100% - 30px)',
            left: '30px',
            top: '50px',
            zIndex: -1,
        }}
        ref={mapRef}
        zoom={7}
        center={[52.04627, 4.51326]}
        whenReady={() => setIsMapReady(true)}
    >
        <TileLayer
            url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
            attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
            maxNativeZoom={18}
            maxZoom={21}
        />
        <MarkerClusterGroup
            spiderLegPolylineOptions={{ weight: 0 }}
            disableClusteringAtZoom={18}
        >
            {jsonContent && (
                <GeoJSON
                    data={jsonContent}
                    pointToLayer={pointToLayer}
                    onEachFeature={onEachFeature}
                    ref={geojsonRef}
                />
            )}
        </MarkerClusterGroup>
    </MapContainer>
)

}

The problem is that the pages crashes, when I try to open it. The browser shows an alert prompt saying 'page does not respond'. I'm suspecting an infinte loop or something in my code, that completely uses up the memory.

So my question is, is there something wrong with my code and how can I fix this? Or maybe (which is what I suspect) this isn't the correct way of achieving the desired result. In that case can you please tell me what the correct way of doing it?

1
  • From what I can see, every time you add a marker to the map, your pointToLayer is called. pointToLayer then adds a marker, which calls pointToLayer, which adds a marker, etc. What you probably want is to apply the offset to your points outside of your pointToLayer, in your jsonContent before passing it to <GeoJSON/> Commented Apr 24, 2023 at 16:01

1 Answer 1

0

Here a jsFiddle demo, which checks if a position is already occupied and moves the new marker to the right a bit:

animated map

My Javascript code adds markers to a L.layerGroup, but first checks if the position is occupied already:

'use strict';

var positions = [
  [51.46, 7.244], // 4 markers have same position
  [51.46, 7.244],
  [51.46, 7.244],
  [51.46, 7.244],
  [51.47, 7.245], // 3 markers have same position
  [51.47, 7.245],
  [51.47, 7.245]
];

var map = L.map('map').setView(positions[0], 14);
var markersGroup = L.layerGroup();
map.addLayer(markersGroup);

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);

for (var i = 0; i < positions.length; i++) {
  var latLng = L.latLng(positions[i]);
  
  var markers = markersGroup.getLayers();
  var latLngs = markers.map(marker => marker.getLatLng());
  // check if the positions is already occupied
  while (latLngs.find(pos => pos.equals(latLng))) {
      // adjust the position of the new marker
      latLng.lng += 0.001;
  }
  
  var marker = L.marker(latLng)
    .bindPopup('Marker #' + i + '<br>' + latLng)
    .addTo(markersGroup);
}
html,
body {
  padding: 0;
  margin: 0;
  height: 100%;
}

body {
  display: flex;
  flex-direction: column;
}

#map {
  flex-grow: 1;
}
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet.min.css">
<script src="https://cdn.jsdelivr.net/npm/leaflet@1/dist/leaflet-src.min.js"></script>

<div id="map"></div>

Not the answer you're looking for? Browse other questions tagged or ask your own question.