Skip to content Skip to sidebar Skip to footer

Using Leaflet.offline With React?

I'm developing a React app, and I'm trying to implement a Leaflet-map with support for offline download of the map tiles. For this I thought of using leaflet.offline (https://githu

Solution 1:

EDIT: Okay, so the old solution worked perfectly, but it required downloading the files from the Leaflet Offline package directly, and modifying them, which is not ideal. I've now managed to use the Leaflet Offline package with npm together with React. I recommend first reading the old solution posted below, as the new solution builds upon that. Here's what I did:

UPDATED NEW SOLUTION:

1 - Install Leaflet Offline with: npm install leaflet.offline@next

2 - Import the Leaflet Offline package into your React component with:

import L from"leaflet"; // Remember that this must also be importedimport"leaflet.offline";

3 - Now look at step 6 from the old solution. Instead of using the "generateOfflineTilelayer" and "generateControlSavetiles", we now use L.tileLayer.offline and L.control.savetiles respectively. If you're using TypeScript like me, add a ts-ignore as a comment right above the codeline to prevent any errors caused by TypeScript. So, the useEffect code from step 6 now looks like this:

useEffect(() => {
  if(map){

    // @ts-ignoreconst tileLayerOffline = L.tileLayer.offline(
      "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
      {
        attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
        minZoom: 13,
      }
    );

    tileLayerOffline.addTo(map);

    // @ts-ignoreconst controlSaveTiles = L.control.savetiles(
      tileLayerOffline, 
      {
        zoomlevels: [13, 14, 15, 16], // optional zoomlevels to save, default current zoomlevel
      }
    );

    controlSaveTiles.addTo(map!);
  }
}, [map]);

4 - The map should now work as intended. I did encounter one problem with missing declaration files when trying to import leaflet.offline. For me this problem was weirdly only present in one of my components, and not in other components. If you encounter the same problem, you could try creating a declaration file like your IDE may suggest, or you could try to add "// @ts-ignore" right above the import statement. Hopefully this helps someone :)


OLD SOLUTION:

Alright, so I never found anyone online who's got a solution to this, but after some trial-and-error I've got this working. The reason why I wanted to be able to download maps for offline use in Leaflet is because I'm making an app for mobile phones with Ionic, Capacitor and React. It's probably worth mentioning that I'm using typescript as well.

In the interest of others, I'll post a guide below on how I got this working. Hopefully this will prevent others from going through the same frustrations I did :D

So the problem I had with the Leaflet.offline plugin was that Leaflet.tileLayer.offline or Leaflet.control.saveTiles weren't recognized (due to the use of React probably). So here are the steps I took to get offline maps working:

1 - I initially was using leaflet.offline via the npm package. We want to alter the plugin so that we are no longer dependent on the plugin's use of Leaflet.tileLayer.offline or Leaflet.control.saveTiles. What we want is to rather create some methods that returns an instance of these two mentioned classes, and then we can import these methods into our React-components. The simplest way of doing this is to not use the plugin via npm, but downloading the plugin's files directly. So the first thing you should do is to download a copy of the contents of the leaflet.offline/src folder that are found on the plugin's GitHub-page (https://github.com/allartk/leaflet.offline/tree/master/src). The files you should download are ControlSaveTiles.js, TileLayerOffline.js and TileManager.js.

2 - Now create a folder in your React project. I named mine LeafletMapOfflinePlugin, but you can name it whatever you want. Then move the files you downloaded into this folder.

3 - Open the TileLayerOffline.js file, and paste the following code at the bottom of the file. This will export a function that can be used to create a new instance of the TileLayerOffline class, so that we no longer depend on L.tileLayer.offline:

// Export a function that generates an offfline tilelayer-object, // as the above expansion of L.tileLayer.offline doesn't workexportfunctiongenerateOfflineTilelayer(url, options) {
  returnnewTileLayerOffline(url, options)
}

4 - Open the ControlSaveTiles.js file, and paste the following code at the bottom of the file. This will export a function that can be used to create a new instance of the ControlSaveTiles class, so that we no longer depend on L.control.saveTiles:

// Export a function that generates a savetiles-object,// as the above expansion of L.control.savetiles doesn't workexportfunctiongenerateControlSavetiles(baseLayer, options) {
  returnnewControlSaveTiles(baseLayer, options);
}

5 - Now these can be imported into any React-component by using the following imports:

import { generateOfflineTilelayer } from"<path-to-TileLayerOffline.js>";
import { generateControlSavetiles } from"<path-to-ControlSaveTiles.js>";

6 - A simple example of how to use this in a React component (with TypeScript) can be seen below. Here we store the MapContainer object using useState, and uses this in an useEffect to manually set the tileLayer and control:

import L, { Map } from"leaflet";
import { MapContainer } from"react-leaflet";
import { generateOfflineTilelayer } from"<path-to-TileLayerOffline.js>";
import { generateControlSavetiles } from"<path-to-ControlSaveTiles.js>";
importReact, { useState } from"react";


constLeafletMap: React.FC<LeafletMapProps> = () => {
  const [map, setMap] = useState<Map | undefined>();
  
  useEffect(() => {
    if(map){
      const tileLayerOffline = generateOfflineTilelayer(
        "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        {
          attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
          minZoom: 13,
        }
      );

      tileLayerOffline.addTo(map);

      const controlSaveTiles = generateControlSavetiles(
        tileLayerOffline, 
        {
          zoomlevels: [13, 14, 15, 16], // optional zoomlevels to save, default current zoomlevel
        }
      );

      controlSaveTiles.addTo(map!);
    }
  }, [map]);
  
  return(
    <MapContainerstyle={{width: "100vw", height: "20vh" }}
      center={[63.446827,10.421906]}
      zoom={13}scrollWheelZoom={false}whenCreated={setMap}
    ></MapContainer>
  )
}

7 - You should now have a simple Leaflet-map with a control-element that when the "+" (not to be confused with the "+" for zooming. The "+" is the default for the leaflet.offline's control element, and can be changed by looking at leaflet.offline's documentation) is clicked, it downloads and saves the map tiles for the area that is currently visible on the map. If you want, you can inspect the download of these tiles by going to the network-tab of your web browser. When the "-" (again, not to be confused by the button for zooming) is clicked, all the downloaded tiles are deleted. See the image below of how the map should look like when using leaflet.offline's ControlSaveTiles; the control element for saving tiles is located below the the control element for zooming:

Leaflet offline map example

Alright, that about wraps it up. Hopefully this helps someone :D Also a tip related to Leaflet maps; if you're having trouble with only a small piece of the map showing, try using map.invalidateSize() (if your mapcontainer is stored as "map"). This will reset and update the device width known to the leaflet-map, which hopefully makes the map render correctly :D

Post a Comment for "Using Leaflet.offline With React?"