0

I'm trying to build a map with a selectable and dynamic layer URL with the code below.

When I try to change the layer URL by the state it shows the error in the screenshot attached.

I have stored all the tiles in the tiles array variable and I'm trying to change the URL by changing the index of the tiles. The first time I run the project it works fine, but when I click the button and try to change the index of the tile to render another map tile it shows the error in the screenshot. I also should mention that there are no errors shown in the console tab of developer tools chrome. Is there a way to solve this problem? Any idea would be appreciated :)

Screenshot of error:


const LeafletContainer = () => {
  const [baseViewCoords, setBaseViewCoords] = useState([37.715, 44.8611]);
  const [map, setMap] = useState();

  const merged_20181223 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20180924 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190323 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190626 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20190919 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20191218 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20200625 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const merged_20200918 = L.tileLayer(
    "dymmyurl",
    { tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
  );

  const tiles = [
    merged_20190323,
    merged_20191218,
    merged_20181223,
    merged_20180924,
    merged_20190626,
    merged_20190919,
    merged_20200625,
    merged_20200918,
  ];

  const [leftTileIndex, setLeftTileIndex] = useState(0);
  const [rightTileIndex, setTRightTileIndex] = useState(7);

  const changeTileHandler = () => {
    setLeftTileIndex((prev) => {
      if (leftTileIndex === tiles.length - 1) {
        return 0;
      }
      return prev + 1;
    });
  };

  useEffect(() => {
    if (map) {
      L.control
        .splitMap(
          L.tileLayer(tiles[leftTileIndex]._url, {
            tms: true,
            opacity: 1,
            attribution: "",
            minZoom: 1,
            maxZoom: 16,
          }).addTo(map),
          L.tileLayer(tiles[rightTileIndex]._url, {
            tms: true,
            opacity: 1,
            attribution: "",
            minZoom: 1,
            maxZoom: 16,
          })
            .addTo(map)
            .addTo(map)
        )
        .addTo(map);
    }
  }, [map, leftTileIndex, rightTileIndex]);

  useEffect(() => {
    if (map) {
      map.setView(baseViewCoords);
    }
  }, [map, baseViewCoords]);

  return (
    <div style={{ position: "relative" }}>
      <SearchArea
        {...{
          changeTileHandler,
        }}
      />
      <Options />
      <Coordinate {...{ baseViewCoords }} />
      <div id="map">
        <MapContainer
          style={{ height: "100%" }}
          center={baseViewCoords}
          zoom={9}
          scrollWheelZoom={true}
          whenCreated={(map) => {
            setMap(map);
          }}
        >
          <TileLayer
            tms={true}
            minZoom={1}
            maxZoom={16}
            opacity={1}
            attribution=""
            url={tiles[leftTileIndex]._url}
          />

          <TileLayer
            minZoom={1}
            maxZoom={16}
            opacity={1}
            tms={true}
            attribution=""
            url={tiles[rightTileIndex]._url}
          />
        </MapContainer>
      </div>
    </div>
  );
};

export default LeafletContainer;

kboul
  • 13,836
  • 5
  • 42
  • 53
Emad Baqeri
  • 2,333
  • 2
  • 14
  • 29

1 Answers1

2

I'm not sure exactly why this error is getting as deep as leafet's Events class, but I do see one main problem. In your useEffect, every time the user triggers a state change that setLeftTileIndex or setRightTileIndex, a new instance of L.Control.splitmap is added to the map. While the react-leaflet <TileLayer /> component is written to handle the url change and automatically update the underlying L.TileLayer, your version of L.control.splitmap is not - it is simply adding a new splitmap. Meanwhile, the old one is referring to L.TileLayer's that no longer exist. You should use leaflet-splitmap's setLeftLayers and setRightLayers methods:

// initialize this outside the useEffect
const [splitMapAdded, setSplitMapAdded] = useState(false)

const splitMap = L.control.splitMap(
  L.tileLayer("left_side_initial_url", { options }),
  L.tileLayer("right_side_initial_url", { options })
)

useEffect(() => {
  if (map && !splitMapAdded) {
    splitmap.addTo(map);
    setSplitMapAdded()true
  }
}, [map]);

// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
  if (map && splitMapAdded) {
    splitMap.setLeftLayers(
      [L.tileLayer(tiles[leftTileIndex]._url], 
      { options }
    )
  }
}, [map, leftTileIndex])

// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
  if (map && splitMapAdded) {
    splitMap.setRightLayers(
      [L.tileLayer(tiles[rightTileIndex]._url], 
      { options }
    )
  }
}, [map, rightTileIndex])

So now the splitmap control is added on component mount with some initial values. When those state variables change, instead of recreating the control, you use its internal methods to change the urls. Hopefully this will get you started in digging out some of those errors.

Another way:

I also might add that this can be neatly managed by creating a react-leaflet v3 custom component. You can create the component with a create function:

const createSplitMap = (props, context) => {

  const instance = L.control.splitmap(
    L.tileLayer(props.leftTileLayerUrl, leftTileLayerOptions),
    L.tileLayer(props.rightTileLayerUrl, rightTileLayerOptions)
  )

  return { instance, context }

}

Then your update function might look like this:

const updateSplitMap = (instance, props, prevProps) => {

  if (prevProps.leftTileLayerUrl !== props.leftTileLayerUrl){
    instance.setLeftLayers(L.tileLayer(props.leftTileLayerUrl))
  }

  if (prevProps.rightTileLayerUrl !== props.rightTileLayerUrl){
    instance.setLeftLayers(L.tileLayer(props.rightTileLayerUrl))
  }

}

To put it all together, you can use the createLayerComponent factory function:

const SplitMap = createLayerComponent(createSplitMap, updateSplitMap);
export SplitMap

Now in your map, you can use this component:

<MapContainer {...mapContainerProps}>
  <SplitMap 
    rightTileLayerUrl={tiles[rightTileIndex]._url}
    leftTileLayerUrl={tiles[leftTileIndex]._url}
  />
  <TileLayer {...tileLayer1Props} />
  <TileLayer {...tileLayer2Props} />
</MapContainer>

I haven't tested this, but this is the general pattern you would use to create a react-leaflet custom component for the splitmap.

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78
  • I implemented the first solution but this time I got an error from split-map. [Screen shot of the error](https://ucarecdn.com/1434190b-bc08-44e0-8dbf-a6a7c5ceb395/Screenshotfrom20210301170050.png) – Emad Baqeri Mar 01 '21 at 22:04
  • Is there any way to implement the leaflet without react-leaflet map and use the bare bone leaflet and it's plugins? It's so complicated using react-leaflet I think :( – Emad Baqeri Mar 01 '21 at 22:08
  • 1
    Hmmm you may need to feed `setLeftLayers` and `setRightLayers` an array of layers instead of just a single layer. I'll update my answer, that might help. You've got to learn to track down these bugs line by line. As far as react-leaflet goes, it is a react wrapper for the leaflet library. Why are you using react-leaflet if you just want to do this in vanilla leaflet? It does indeed add a layer of complexity that may be difficult if you're just starting out – Seth Lutske Mar 01 '21 at 23:32
  • I have eddited my code as you have told me but the error is not going off. I should better to use leaflet package to do this job. – Emad Baqeri Mar 01 '21 at 23:58
  • 1
    Its sort of hard to track things like this down without a working example. I would recommend making a codesandbox that reproduces the issue, then we can play with it and see what's really going on. – Seth Lutske Mar 02 '21 at 00:00
  • 1
    I have done it and this is [codesandbox](https://codesandbox.io/s/relaxed-dewdney-25q5n?file=/src/App.js) project that reproduces the error I have in my project. – Emad Baqeri Mar 02 '21 at 21:05
  • 1
    Great! This allowed me to try to debug the issue. Unfortunately, the problem doesn't seem to be with react-leaflet, but with the plugin itself. I get the same error with [this vanilla leaflet codesandbox](https://codesandbox.io/s/leaflet-splitmap-ux7w8?file=/src/index.js). I dug into the source code a bit, and I can't see clearly what is wrong with it. Though there is a warning in the console `Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead`, ... – Seth Lutske Mar 02 '21 at 22:02
  • 1
    which makes me think that the splitmap library is depending on an outdated version of leaflet. I see you opened an issue on that repo, which is what I would have done too. You're either out of luck, or you can try your hand at rewriting / updating that plugin to be compatible with more modern versions of leaflet – Seth Lutske Mar 02 '21 at 22:03
  • Do you have any idea about [leaflet-side-by-side](https://github.com/digidem/leaflet-side-by-side). I think this is the same as splitMap and finally I must right it on my own. – Emad Baqeri Mar 02 '21 at 23:25
  • 1
    Despite leaflet-side-by-side being more out of date, it does have more stars, and @kboul has [show how to implement this in react-leaflet](https://stackoverflow.com/questions/59374092/react-leaflet-side-by-side) before. I recommend following his example and using it as a starting point. Though that example is using react-leaflet version 2, you should still be able to implement in RLV3. – Seth Lutske Mar 02 '21 at 23:36
  • This is the [sandbox project](https://codesandbox.io/s/friendly-williams-6q20c?file=/src/App.js:2594-2805) that I have createad, there is no error in this map but there is bug that I don't know why is that happeing. both the tiles are redering on top of eachother and it seems that we have just one tile. And I'm trying to change the tile it happens in the code and console but renders nothing – Emad Baqeri Mar 04 '21 at 13:13
  • I have solved the issue in [this codesandbox](https://codesandbox.io/s/condescending-bird-gz351) but there is an other issue that I have submitted in [this stackoverflow question](https://stackoverflow.com/questions/66506799/how-to-change-tile-layers-in-leftlet-sidebyside-or-leaflet-splitmap). Do you have any idea @seth-lutske? – Emad Baqeri Mar 06 '21 at 14:38