maplibre / flutter-maplibre-gl

Customizable, performant and vendor-free vector and raster maps, flutter wrapper for maplibre-native and maplibre-gl-js (fork of flutter-mapbox-gl/maps)
https://pub.dev/packages/maplibre_gl
Other
186 stars 106 forks source link

How to load an mbtiles file #318

Open timautin opened 8 months ago

timautin commented 8 months ago

Hello, I need to be able to display local .mbtiles files. Using mablibre-gl-js this would be done using the addProtocol method. It is unsupported yet in flutter-mapblibre-gl, right? Would you be interested by a merge request for this? If so, do you have any guide for setting up a dev environment?

m0nac0 commented 8 months ago

I actually think it should work at least on Android and iOS (see e.g. https://github.com/maplibre/maplibre-native/issues/17), but I haven't tried it recently.

In your merge request, would you want to expose the addProtocol functionality completely in this library, or would you only specifically want to enable loading mbtiles files?

timautin commented 8 months ago

Thank you for your answer, if mbtiles:// is already working then that's perfect :) ! Is there some documentation somewhere?

I just added an mbtiles file into my resources folder (in my project root) and added this:

  void _addMbtiles() async {

    await _mapController.addSource("mbtiles", const RasterSourceProperties(
        tiles: [ 'mbtiles://resources/map.mbtiles' ],
        tileSize: 256,
        attribution: '[...]'
    ));

    await _mapController.addLayer("mbtiles", "mbtiles", const RasterLayerProperties());
  }

But the app crashes with the following error (indicating that indeed mbtiles support seems there, it's juste that my file is not found): terminating with uncaught exception of type mapbox::sqlite::Exception: unable to open database file

EDIT: alright I got it working putting the file to the external storage rather than in assets. Here's the code:

  Future<String> get _localPath async {
    return (await getExternalStorageDirectory())!.path;
  }

  void _addMbtiles() async {

    await _mapController.addSource("mbtiles", RasterSourceProperties(
        tiles: [ 'mbtiles://${await _localPath}/map.mbtiles' ],
        tileSize: 256,
        attribution: '[...]'
    ));

    await _mapController.addLayer("mbtiles", "mbtiles", const RasterLayerProperties());
  }

Thank you again for your answer :)

m0nac0 commented 8 months ago

That's great to hear! I adjusted the title since this might be relevant for more people in the future. We might also want to add something about your solution to the docs.

timautin commented 8 months ago

Yes indeed an addition to the doc would be nice, that's a common use case I think :) You can close the issue, everything is ok for me (still have to confirm that it works on iOS, will do this next week).

m0nac0 commented 7 months ago

Did you already check if your solution also works on iOS?

timautin commented 7 months ago

It also works on iOS, but it needs some more work (on both platforms):

m0nac0 commented 7 months ago

Thanks for the feedback. That sounds interesting, have you tried playing with the min/max zoom of the style layers?

venomwine commented 7 months ago

I tested another way to load an mbtiles. And It's working. I copied test.mbtiles and style.json files to assets folder.

Future<String> loadAssets() async {
    var dir = (await getApplicationCacheDirectory()).path;

    const filename = 'test.mbtiles';
    var bytes = await rootBundle.load("assets/$filename");
    writeAssetToFile(bytes, '$dir/$filename');

    String loadedStyleString =
        await rootBundle.loadString("assets/style.json");
    loadedStyleString = loadedStyleString.replaceAll(
        '.pbf', '.pbf?key=abcabc');
    loadedStyleString = loadedStyleString.replaceAll(
        "___MBTILES_URI___", "mbtiles:///$dir/$filename");

    return loadedStyleString;
}
Future<void> writeAssetToFile(ByteData data, String path) {
    final buffer = data.buffer;
    return File(path).writeAsBytes(
        buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
}
@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder(
        future: loadAssets(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return MaplibreMap(
              onMapCreated: _onMapCreated,
              initialCameraPosition:
                  const CameraPosition(target: LatLng(0.0, 0.0)),
              onStyleLoadedCallback: _onStyleLoadedCallback,
              styleString: snapshot.data,
            );
          } else {
            return Container(
              color: Colors.red,
            );
          }
        },
      ),
    );
  }

And style.json has this.

"sources": {
        "countries": {
            "type": "vector",
            "tiles": [
                "???/{z}/{x}/{y}.pbf",
                "???/{z}/{x}/{y}.pbf",
                "???/{z}/{x}/{y}.pbf",
                "???/{z}/{x}/{y}.pbf"
            ]
        },
        "test-mbtiles": {
            "type": "vector",
            "url": "___MBTILES_URI___"
        }
    },

Thanks this library!