flutter_map_tile_caching icon indicating copy to clipboard operation
flutter_map_tile_caching copied to clipboard

[FEATURE] Add ability to retrieve raw bytes from internal `ImageProvider` (to enable partial compatibilty with vector tiles)

Open GaelleJoubert opened this issue 2 years ago • 11 comments

What do you want implemented?

I would be able to use vector tiles (.pbf) instead of Raster Tiles (.png). Indeed my company is rendering our map ourselves, and Vector tile takes much less computation power to render.

The idea is to be able to use url links like : "https://tiles.stadiamaps.com/data/openmaptiles/{z}/{x}/{y}.pbf?"

To provide the style in a json like file, and to have all the features the library already provide (cache & bulk dowloading).

What other alternatives are available?

There is already another library that handle Vector tiles, but the way it works I am pretty sure it is not compatible with this one.

I'll be happy to be wrong and that both library could be used at the same time.

The library is : https://github.com/greensopinion/flutter-vector-map-tiles

It does handle caching, but not bulk dowloading.

Can you provide any other information?

No response

Platforms Affected

Android, iOS

Severity

Annoying: Currently have to use workarounds

Requirements

  • [X] I agree to follow this project's Code of Conduct
  • [X] I am using the latest stable version of this package
  • [X] I have checked for similar feature requests which may be duplicates

GaelleJoubert avatar Jan 18 '23 14:01 GaelleJoubert

Hi @GaelleJoubert, That package is a plugin for FM, they are designed to work together.

JaffaKetchup avatar Jan 18 '23 15:01 JaffaKetchup

@JaffaKetchup Thanks you for your answer. I juste don't see yet how I would be able to make flutter-vector-map-tiles & flutter_map-tile-caching work together ... Indeed, from what I see, flutter-vector-map-tiles provide a VectorTileLayer meant to be used instead of a TileLayer this way :

FlutterMap(
            options: MapOptions(
                center: LatLng(49.246292, -123.116226),
                zoom: 10,
                maxZoom: 15),
            children: [
              VectorTileLayer(
                theme: _mapTheme()
                tileProviders: TileProviders(
                    {'openmaptiles': _cachingTileProvider(_urlTemplate())}),
              )
            ],
          ));

But the TileProvider it takes as an argument is a custum object, and not the same object that a TileLayer would take. So I cannot use the FMTC provider that I usually use this way in a TileLayer :

TileLayer(
          tileProvider: storeMap.getTileProvider(FMTCProviderSettings),
         urlTemplate: "https://tile.map.api-k.com/styles/topo/{z}/{x}/{y}.png",
        ),

Do you see a way to use both plugging together ? If yes I'll be happy to know how you would do. Thanks you very much for the help.

GaelleJoubert avatar Jan 18 '23 16:01 GaelleJoubert

Ah my apologies @GaelleJoubert, I didn't see a reference to FMTC, so I assumed you were talking about FM.

Indeed they are incompatible.

JaffaKetchup avatar Jan 18 '23 16:01 JaffaKetchup

Ah, yeah I thought so ... And there is no way, for now, to use Vector tiles with this plugging?

GaelleJoubert avatar Jan 18 '23 16:01 GaelleJoubert

No way for now. PRs are always welcome (use 'next-version' branch if you do) :), but I'm unlikely to work to implement this.

PS. My fault for missing your actual point before, didn't realise this was an issue in my repo instead of FM. Apologies :)

JaffaKetchup avatar Jan 18 '23 17:01 JaffaKetchup

Is there still plan to make this map_tile_caching work with vector_map_tile ?

supagon avatar Mar 29 '24 01:03 supagon

There is nothing in the infrastructure and design that would be incompatible with vector tiles: they are bytes just like any other. Theoretically, all that is required is a compatible tile provider. I'll see what I can do to allow users to define this, because I don't want to add the dependency.

JaffaKetchup avatar Mar 29 '24 08:03 JaffaKetchup

@JaffaKetchup I just tried but it doesn't seem as easy as you make it sound :) A tile provider doesn't provide bytes but an ImageProvider. Bytes are, actually, interpreted here:

https://github.com/JaffaKetchup/flutter_map_tile_caching/blob/dfee86c092fc2541b972632da626191cddbc1317/lib/src/providers/image_provider.dart#L97

To get the bytes from the store, FMTCBackendInternal.readTile() needs to be used - which not only isn't accessible, but would also mean that almost all features of FMTC are gone.

If there was a method getTileBytes(), an adapter could be implemented like this:

import 'dart:typed_data';

import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
import 'package:zs_connect/map/map.dart';

class FmtcVectorTileProvider extends VectorTileProvider {
  final tileProvider = const FMTCStore(mapStoreName).getTileProvider();

  @override
  final int maximumZoom;

  @override
  final int minimumZoom;

  final TileLayer tileLayer;

  FmtcVectorTileProvider({
    required String urlTemplate,
    this.maximumZoom = 16,
    this.minimumZoom = 1,
  }) : tileLayer = TileLayer(urlTemplate: urlTemplate);

  @override
  Future<Uint8List> provide(TileIdentity tile) async {
    final coordinates = TileCoordinates(tile.x, tile.y, tile.z);
    // If there was a method that returned bytes, it could be called here
    tileProvider.getTileBytes(coordinates, tileLayer);
  }
}

What do you think? Did I miss something?

micheljung avatar May 01 '24 12:05 micheljung

@micheljung You can try following something like this for now: https://github.com/Baseflow/flutter_cached_network_image/issues/714#issuecomment-1072493110. This will load the bytes from the ImageProvider. There's probably also other ways to do similar things. Not sure how efficient/performant this is though!

I can look into adding a getBytes method to the provider, it might make sense.

JaffaKetchup avatar May 01 '24 12:05 JaffaKetchup

Thank you for your quick reaction @JaffaKetchup. I tried this solution, but it seems like the Image detour corrupts the bytes so they can't be parsed as vector data anymore.

I'll give up at this point for now. For anyone interested, here's what I tried.

import 'dart:async';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
import 'package:vector_map_tiles/vector_map_tiles.dart';
import 'package:zs_connect/map/map.dart';

class FmtcVectorTileProvider extends VectorTileProvider {
  final tileProvider = const FMTCStore(mapStoreName).getTileProvider();

  final VectorTileProvider delegate;

  @override
  int get maximumZoom => delegate.maximumZoom;

  @override
  int get minimumZoom => delegate.minimumZoom;

  final TileLayer tileLayer;

  FmtcVectorTileProvider({
    required this.delegate,
    required String urlTemplate,
  }) : tileLayer = TileLayer(urlTemplate: urlTemplate);

  @override
  Future<Uint8List> provide(TileIdentity tile) async {
    final coordinates = TileCoordinates(tile.x, tile.y, tile.z);
    return await tileProvider.getImage(coordinates, tileLayer).getBytes();
  }
}

extension ImageTool on ImageProvider {
  Future<Uint8List> getBytes(
       // also tried rawRgba
      {ImageByteFormat format = ImageByteFormat.rawUnmodified}) async {
    final Completer<Uint8List> completer = Completer<Uint8List>();
    final ImageStreamListener listener = ImageStreamListener(
      (imageInfo, synchronousCall) async {
        final bytes = await imageInfo.image.toByteData(format: format);
        if (!completer.isCompleted) {
          completer.complete(bytes?.buffer.asUint8List());
        }
      },
    );
    final imageStream = resolve(ImageConfiguration.empty);
    imageStream.addListener(listener);
    final imageBytes = await completer.future;
    imageStream.removeListener(listener);
    return imageBytes;
  }
}
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: InvalidProtocolBufferException: CodedBufferReader encountered a malformed varint.
#0      CodedBufferReader._readRawVarint32 (package:protobuf/src/protobuf/coded_buffer_reader.dart:232:5)
#1      CodedBufferReader.readUint32 (package:protobuf/src/protobuf/coded_buffer_reader.dart:122:23)
#2      CodedBufferReader.readTag (package:protobuf/src/protobuf/coded_buffer_reader.dart:161:16)
#3      _mergeFromCodedBufferReader (package:protobuf/src/protobuf/coded_buffer.dart:37:23)
#4      GeneratedMessage.mergeFromBuffer (package:protobuf/src/protobuf/generated_message.dart:192:5)
#5      new VectorTile.fromBuffer (package:vector_tile/raw/proto/vector_tile.pb.dart:340:128)
#6      VectorTile.fromBytes (package:vector_tile/vector_tile.dart:24:33)
#7      VectorTileReader.read.<anonymous closure> (package:vector_tile_renderer/src/vector_tile_reader.dart:10:25)
#8      Timeline.timeSync (dart:developer/timeline.dart:173:22)
#9      profileSync (package:vector_tile_renderer/src/profiling.dart:16:19)
#10     VectorTileReader.read (package:vector_tile_renderer/src/vector_tile_reader.dart:9:12)
#11     _createTile (package:vector_map_tiles/src/cache/vector_tile_loading_cache.dart:139:44)
#12     _Job.apply (package:executor_lib/src/queue_executor.dart:97:59)
#13     QueueExecutor._runOneAndReschedule (package:executor_lib/src/queue_executor.dart:52:34)
#14     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#15     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)

micheljung avatar May 01 '24 13:05 micheljung

I suppose that makes sense. I've re-opened this, as your suggestion would be relatively easy and shouldn't be exposing too much of the internals.

JaffaKetchup avatar May 01 '24 22:05 JaffaKetchup