Displaying/Rendering multiple TIFF/GeoRaster layers on React-Leaflet Map
12:35 24 Nov 2025

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;
reactjs leaflet tiff react-leaflet geotiff