Using leaflet.offline with React?
Asked Answered
P

1

8

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://github.com/allartk/leaflet.offline), but I'm unsure of how to use this library in React?

How can one use leaflet.offline with React, or are there any React-specific libraries that supports offline download of map tiles using Leaflet?

Thanks for any help!

Phosgene answered 7/9, 2021 at 16:36 Comment(0)
P
10

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 imported
import "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-ignore
    const 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-ignore
    const 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 work
export function generateOfflineTilelayer(url, options) {
  return new TileLayerOffline(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 work
export function generateControlSavetiles(baseLayer, options) {
  return new ControlSaveTiles(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>";
import React, { useState } from "react";


const LeafletMap: 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(
    <MapContainer
      style={{ 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

Phosgene answered 12/9, 2021 at 16:10 Comment(3)
Hi, Thanks for your efforts. I followed the old solution steps and downgrade my react version from 18 to 17 so that I can use the when-created props but the map didn't work offline when I turned off the internet. any advice, please?Pul
Hey! That's strange. Did you remember to add only the offline tile layer to your map?Phosgene
Hey, Yes I did. But now i download the source code for the map for specific area from openstreetmap and generated the tiles offline and it worked perfectly but i had another problem :D the images format that is generated from the tiles is in text/html and it should be in images/png. Still searching for it but I’m wondering why that would happenPul

© 2022 - 2024 — McMap. All rights reserved.