Render local .mbtiles from file in maplibre-react-native

90 Views Asked by At

I am wondering if it's possible to overlay a mbtiles file using a VectorSource and FillLayer using maplibre-react-native. Currently my setup works with a remotely hosted dataset, but not when using a .mbtiles file with a path from the documents filesystem path.

I am using Expo workflow with the dev-client, so I'd be best if it could be done without touching native files.

const downloadMbtiles = async () => {
  const remoteUrl = 'my-url';
  const localPath = FileSystem.documentDirectory + 'test.mbtiles';
  const { status, uri } = await FileSystem.downloadAsync(remoteUrl, localPath);
  console.warn(status, uri); // 200 ok
};

return (
  <MapLibreGL.VectorSource
    key={'maps-overlay'}
    id={'maps-source'}
    url={FileSystem.documentDirectory + 'test.mbtiles'}
  >
    <MapLibreGL.FillLayer
      id={'test-overlay'}
      sourceLayerID={'test-123'}
      style={{
        fillOpacity: 0.8,
        fillColor: [
          'interpolate',
          ['linear'],
          ['get', 'v'],
          0,
          'gold',
          2,
          'red',
        ],
      }}
    />
  </MapLibreGL.VectorSource>
);

1

There are 1 best solutions below

1
VonC On

To use a local .mbtiles file with maplibre-react-native in an Expo workflow without touching native code, you could try and serve the .mbtiles file over HTTP using a local server within your app.
That would involve using a library like expo-server (or any other suitable library) to serve files locally: start a local server, serve the .mbtiles file, and then use the local server's URL as the source URL in your VectorSource.

I see an @expo/server package, published a month ago (with a fairly recent changelog).

Try instead mbtiles-server or the more recent tileserver-gl (npm install -g tileserver-gl).

A tileserver-gl path/to/your/test.mbtiles should start a local server, usually accessible at http://localhost:8080, serving tiles from your .mbtiles file. The console output will provide the exact URL.

With your tile server running, you will need to update your maplibre-react-native component to fetch tiles from your local tileserver-gl instance. Replace the url prop in the VectorSource component with the URL provided by tileserver-gls:

const LOCAL_TILESERVER_URL = "http://localhost:8080/data/v3/{z}/{x}/{y}.pbf";

return (
  <MapLibreGL.VectorSource
    key={'maps-overlay'}
    id={'maps-source'}
    url={LOCAL_TILESERVER_URL} // Use the URL provided by tileserver-gl
  >
    <MapLibreGL.FillLayer
      id={'test-overlay'}
      sourceLayerID={'test-123'}
      style={{
        fillOpacity: 0.8,
        fillColor: [
          'interpolate',
          ['linear'],
          ['get', 'v'],
          0,
          'gold',
          2,
          'red',
        ],
      }}
    />
  </MapLibreGL.VectorSource>
);

Since tileserver-gl runs outside the Expo environment, there should be no compatibility issues. However, make sure your mobile device or simulator can access the local server. If testing on a physical device, your device and the server must be on the same network, and you may need to use your machine's IP address instead of localhost.


If I were to host a tiling server, I could better host my own and make this much easier. My idea is to do this locally without any servers. I thought it must be doable since there exists a mbtiles:// protocol in the gl-js library.

That would mean a more integrated and seamless solution within the Expo and maplibre-react-native environment. Unfortunately, the mbtiles:// protocol implementation in maplibre-gl-js is designed for direct access to .mbtiles files, which is not directly supported in a React Native (and by extension, Expo) environment due to the sandboxed nature of mobile app file systems and the React Native runtime.

One theoretical approach to achieve direct .mbtiles usage would be to develop a custom native module for React Native that implements .mbtiles file reading and tile serving functionality directly within the app. That module could expose a method to React Native that returns tile images given x, y, and z parameters, mimicking a tile server's response but locally.
But: that would require ejecting from Expo's managed workflow to add custom native code, which is what you wanted to avoid.

Another approach, not fully aligned with avoiding server setups, involves extracting the tiles from the .mbtiles file and bundling them with the app. That method, while also somewhat cumbersome, would allow for using the tiles directly from the filesystem. Tools like mbutil or custom scripts could automate the extraction process.
The main drawback is the significant increase in app size and the static nature of the tile data.

Hence, the first proposed approach, running a local server within the app (or using an embedded server that starts with the app) and accessing this server through a WebView component. It uses tileserver-gl or a similar tool, running it in a way that is encapsulated within the app's environment.
While this still involves a server, it is local to the app and does not require external hosting.