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
215 stars 117 forks source link

How to use local (from assets folder) sprites and glyphs #338

Open venomwine opened 10 months ago

venomwine commented 10 months ago

Good library!

When I use maplibre android, I can use local (from assets folder) sprites and glyphs. The keywords of style.json are just like this.

"sprite": "asset://sprites/sprite",
"glyphs": "asset://glyphs/{fontstack}/{range}.pbf",

and all files are under assets folder. image

But with this flutter-maplibre-gl, That way is not working. I try to copy those files to under flutter assets folder and android assets folder. Both way couldn't load asset. image

Is there any way to use local sprites and glyphs ??

Thanks!!

m0nac0 commented 10 months ago

Sorry, the documentation isn't very good about that, and we should investigate approaches and improve the documentation.

One approach that has come up a few times is copying the assets from the asset directory to the file system (in flutter) and then reference that file system location (see e.g. #318).

More as a reference for contributors: we may also be able to add a lookup function in the native code of this library (ref.: e.g. https://stackoverflow.com/questions/56339637/is-there-a-way-to-access-flutter-resources-from-native-code).

venomwine commented 10 months ago

The "asset" keyword is not working.

But "file" keyword is working. "glyphs": "file://appCacheDir/glyphs/{fontstack}/{range}.pbf"

Thanks!

mariusvn commented 8 months ago

To help the people that want to use this in the future, the venomwine answer only includes the final step but you first have to move the glyphs to the cache directory.

Here is the functions I use in my project:

class GlyphsService {
  /// Copies the glyphs files to cache dir to allow native code to access it
  Future<void> copyGlyphsToCacheDir() async {
    var dir = (await getApplicationCacheDirectory()).path;

    /* Check if the files are present */

    var directory = Directory(dir);

    var glyphFilesInDirectory = directory.listSync(recursive: true).where((file) => p.basename(file.path).startsWith('glyph'))
        .map((file) => p.basename(file.path));

    if (glyphFilesInDirectory.isNotEmpty) {
      print('Glyph files directories are already present');
      // Exits here if glyph files already exist
      return;
    }

    /* If not present, copy */

    final List<String> glyphsAssets = await _getGlyphAssets();
    final int glyphAmount = glyphsAssets.length;
    for (var i = 0; i < glyphAmount; i++) {
      final String asset = glyphsAssets[i];
      final String assetPath = p.dirname(asset);
      final String assetDir = p.join(dir, assetPath);
      final String assetFileName = p.basename(asset);

      // Create the directory structure if it's not present
      await Directory(assetDir).create(recursive: true);

      final ByteData data = await rootBundle.load(asset);
      final String path = p.join(assetDir, assetFileName);
      await _writeAssetToFile(data, path);
      print('[${i+1}/$glyphAmount] "$asset" copied to "$path".');
    }
  }

  Future<List<String>> _getGlyphAssets() {
    return rootBundle
        .loadString('AssetManifest.json')
        .then<List<String>>((String manifestJson) {
      Map<String, dynamic> manifestMap = jsonDecode(manifestJson);
      return manifestMap.keys
          .where((String key) => key.contains('assets/glyphs'))
          .toList();
    });
  }

  Future<void> _writeAssetToFile(ByteData data, String path) {
    final buffer = data.buffer;
    return File(path).writeAsBytes(
        buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
  }
}

This code assumes you have the glyphs in the app's directory assets/glyphs like this:

idea64_3MSL8cARDJ

Once the assets files are there, get the paths to the pubspec:


flutter:
  assets:
    - assets/
    - assets/glyphs/Noto Sans Bold/
    - assets/glyphs/Noto Sans Italic/
    - assets/glyphs/Noto Sans Regular/
    - assets/glyphs/Open Sans Regular,Arial Unicode MS Regular/
    - assets/glyphs/Open Sans Semibold/

If you call copyGlyphsToCacheDir it will check if the files are present in the cache dir and if this is not the case, it will copy to yourAppCacheDir/assets/glyphs keeping the directory strucure.

I recommend to call this function when the app is starting.

Once you are done calling this function, you should use a style with this glyph value:

final String cacheDirPath = (await getApplicationCacheDirectory()).path;

// Then, the style should look like this (at least, the glyphs value)

final String style = '''{
  "version": 8,
  "glyphs": "file://$cacheDirPath/assets/glyphs/{fontstack}/{range}.pbf",
  "sources": {},
  "layers": []
}''';

Now this should work with no download :)

EDIT: not that the prints shoud be only on debug. I used a custom logger on my project that does it somewhere else so thats why they are not if-ed.