I'm working on a React project where I'm using React-Leaflet and need to display GeoTIFF (.tif) layers on multiple maps within the same page.
Overview: I have several Leaflet maps rendered simultaneously, and each map needs to load multiple TIFF layers. I also have four different metrics, and the displayed TIFF layers depend on the currently selected metric.
To update the layers when the metric changes, I’m unmounting and re-creating the entire MapContainer, assuming this would remove the old layers and load the new ones.
Problem: On the initial load, the TIFF layers display correctly.
However, when I change the metric:
-The new TIFF layers do not appear immediately.
-I still see the old layers partially loaded.
-If I zoom out, the new layers suddenly appear.
-But when I zoom back in, the old and new layers start mixing.
Has anyone encountered this issue or knows how to properly reset/reload GeoTIFF layers in React-Leaflet?
Any guidance would be greatly appreciated.
I'm attaching my code below for reference.
-> Map Container Code
import React, { useEffect, useState } from 'react';
import { MapContainer, TileLayer, Polygon } from 'react-leaflet';
import 'leaflet/dist/leaflet.css';
import parseGeoraster from 'georaster';
import DateLabel from './DateLabel';
import HomeButton from './HomeControl';
import GeoTiffLayer from './TifLayer';
import ThreeDotsLoader from './Loader';
// --- Parse MULTIPOLYGON geometry safely ---
const parseMultiPolygon = (geometry) => {
if (!geometry || !geometry.includes('MULTIPOLYGON')) return [];
const cleaned = geometry.replace(/^SRID=\d+;/, '').trim();
const matches = [...cleaned.matchAll(/\(\(\((.*?)\)\)\)/g)];
return matches
.map((m) => {
const coords = m[1]
.split(',')
.map((pair) => {
const [lon, lat] = pair.trim().split(' ').map(Number);
return [lat, lon];
})
.filter(([lat, lon]) => !Number.isNaN(lat) && !Number.isNaN(lon));
return coords.length > 2 ? coords : null;
})
.filter(Boolean);
};
// --- Compute bounds from all subplots ---
const getBoundsFromSubplots = (subplots) => {
const allCoords = [];
subplots.forEach((s) => {
const polys = parseMultiPolygon(s.geometry);
polys.forEach((p) => allCoords.push(...p));
});
if (!allCoords.length) return [[0, 0], [0, 0]];
const lats = allCoords.map(([lat]) => lat);
const lons = allCoords.map(([, lon]) => lon);
return [
[Math.min(...lats), Math.min(...lons)],
[Math.max(...lats), Math.max(...lons)],
];
};
// --- Main Map Component ---
const DroneRastersMap = ({
subplotsForContract, dateData, date, metric,
}) => {
const bounds = getBoundsFromSubplots(subplotsForContract);
const [loading, setLoading] = useState(true);
const [rasters, setRasters] = useState([]);
useEffect(() => {
const loadRasters = async () => {
setLoading(true);
// Create promises for fetching and parsing
const rasterPromises = dateData.map(async (r) => {
const url = `preUrl/${r.fn}`;
const buffer = await (await fetch(url)).arrayBuffer();
return parseGeoraster(buffer);
});
// Wait for all promises to resolve
try {
const resolvedRasters = await Promise.all(rasterPromises);
setRasters(resolvedRasters);
if (resolvedRasters.length === dateData.length) {
setLoading(false);
}
} catch (error) {
console.error('Error loading rasters:', error);
setLoading(false);
}
};
if (dateData && dateData.length > 0) {
loadRasters();
} else {
setLoading(false);
}
}, [metric]);
if (loading) {
return ;
}
return (
{subplotsForContract.map((subplot, i) => {
const polygons = parseMultiPolygon(subplot.geometry);
return polygons.map((coords, j) => (
));
})}
);
};
export default DroneRastersMap;
-> Tif Layer Code:
import { useEffect, useRef } from 'react';
import { useMap } from 'react-leaflet';
import GeoRasterLayer from 'georaster-layer-for-leaflet';
import chroma from 'chroma-js';
import { METRIC_LEGENDS } from '../shared/constants';
const GeoTiffLayer = ({ rasters, metric }) => {
const map = useMap();
const layerRefs = useRef([]);
useEffect(() => {
layerRefs.current.forEach((layer) => {
if (map.hasLayer(layer)) {
map.removeLayer(layer);
}
});
layerRefs.current = [];
const legends = METRIC_LEGENDS[metric];
const values = legends.map((l) => parseFloat(l.v));
const colors = legends.map((l) => l.c);
const scale = chroma.scale(colors).domain(values);
const getColor = (v) => {
if (v == null || Number.isNaN(v)) return null;
if (v < values[0]) return colors[0];
if (v > values.at(-1)) return colors.at(-1);
return scale(v).hex();
};
rasters.forEach((gr) => {
const layer = new GeoRasterLayer({
georaster: gr,
resolution: 256,
pixelValuesToColorFn: (vals) => getColor(vals[0]),
});
layer.addTo(map);
if (map.hasLayer(layer)) {
layerRefs.current.push(layer);
}
});
// return () => {
// layerRefs.current.forEach((layer) => {
// if (map.hasLayer(layer)) {
// map.removeLayer(layer);
// if (!map.hasLayer(layer)) {
// console.log("Layer Removed: ", layer._layerId);
// }
// }
// });
// layerRefs.current = [];
// };
}, [metric, map]);
return null;
};
export default GeoTiffLayer;