Implementing a custom ImageProvider that generates a tile client side for the flutter/dart flutter_map package #1574
-
First of all, thank you for this great library, highly appreciate the ease it was to get my first osm map tiles up and running in a flutter app. Now for my project, I would like to generate my own tiles on-the-fly as a tilelayer for the flutter_map package used in a flutter project. The idea is to overlay these tiles over the base open streetmaps tilelayer. This is my implementation which throws a "_Exception (Exception: Invalid image data)" error at point of instantiateImageCodec(imageBytes): import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/plugin_api.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
class CustomTileProvider extends TileProvider {
@override
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) {
return CustomImageProvider(coordinates, options);
}
}
class CustomImageProvider extends ImageProvider<CustomImageProvider> {
final TileCoordinates coordinates;
final TileLayer options;
CustomImageProvider(this.coordinates, this.options);
@override
Future<CustomImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<CustomImageProvider>(this);
}
@override
ImageStreamCompleter load(CustomImageProvider key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(),
scale: 1.0,
);
}
Future<Codec> _loadAsync() async {
final Uint8List imageBytes = await generateTile();
return await instantiateImageCodec(imageBytes);
}
}
Future<Uint8List> generateTile() async {
final int tileSize = 256;
final int width = tileSize;
final int height = tileSize;
final Uint8List bytes = Uint8List(width * height * 4);
// Fill the entire tile with red color
for (int i = 0; i < bytes.length; i += 4) {
bytes[i] = 255; // Red
bytes[i + 1] = 0; // Green
bytes[i + 2] = 0; // Blue
bytes[i + 3] = 255; // Alpha (fully opaque)
}
return bytes;
}
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Column (
children: [
Flexible(
child: FlutterMap(
options: MapOptions(center: LatLng(54.0,4.9), zoom: 8, maxZoom: 22), // , maxZoom: 18
children: [
TileLayer(urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.example.app', maxZoom: 22, maxNativeZoom: 18),
TileLayer(tileProvider: CustomTileProvider(), maxZoom: 24),
],
),
)
],)
),
)
);
}
} Any help on how I can get the above custom tile provider to work is highly appreciated! I sense that I am overlooking something trivial but cannot find out what it is. |
Beta Was this translation helpful? Give feedback.
Replies: 8 comments 1 reply
-
I suspect its because it's not actually an image format that's being returned, and just some bytes, or am I misreading it ? |
Beta Was this translation helpful? Give feedback.
-
Thank you for your response, I think you may be right. It is definitely not an image I'm handing over to the decoder. This brings me to the idea to use MemoryImage instead. It seems this class can accept a UInt8List. |
Beta Was this translation helpful? Give feedback.
-
I'm not entirely sure that will work either...but it's a while since I looked at that type of stuff. It's not clear if you have any actual image bytes in an image format, or just a set of bytes...(I think the memoryimage will still need an image set of bytes) What are you actually trying to convert into an image..what is the data and where did it come from ? |
Beta Was this translation helpful? Give feedback.
-
I would like to generate several custom tile layers on the fly. An example would be a shadow map based on a digital elevation model and datetime. I also will implement a simple eulerian fluid model and generate tiles based on the outcome of this calculation. I am not necessarily trying to convert anything to an image, I thought the list of UInt8 bytes along with dimensions should be enough. The main problem I am trying to solve here is that I want flutter_map to be able to visualize the tiles that I generate. "the data" is really just a placeholder here, I will implement more interesting feature layers, but for now I am trying to wrap my head around getting this simple example to work. |
Beta Was this translation helpful? Give feedback.
-
Again, I may be wrong, but I suspect you may want to be either looking at something like https://pub.dev/packages/image or using a Canvas picturerecorder https://api.flutter.dev/flutter/dart-ui/PictureRecorder-class.html, depending on how the data is calculated/drawn/whatever (i.e whether its pixels from maths, or pixels from drawing a shape etc). I think the disconnect is in the encoding aspect (i.e a png is/may be compressed data), but I'm still a bit unclear if thats been taken into account (and I'm not very strong on that stuff). |
Beta Was this translation helpful? Give feedback.
-
@ibrierley your counter questions helped me solve the problem! I had to directly use ImageDescriptor, as it allows for specifying the dimensions of your raw UInt8List image buffer, which was 256x256 and in rgba8888 format. Working code below: import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'package:flutter_map/plugin_api.dart';
import 'dart:async';
import 'package:flutter/foundation.dart';
class CustomTileProvider extends TileProvider {
@override
ImageProvider getImage(TileCoordinates coordinates, TileLayer options) {
return CustomImageProvider(coordinates, options);
}
}
class CustomImageProvider extends ImageProvider<CustomImageProvider> {
final TileCoordinates coordinates;
final TileLayer options;
CustomImageProvider(this.coordinates, this.options);
@override
Future<CustomImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<CustomImageProvider>(this);
}
@override
ImageStreamCompleter load(CustomImageProvider key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(),
scale: 1.0,
);
}
Future<Codec> _loadAsync() async {
final Uint8List imageBytes = await generateTile();
final ImmutableBuffer buffer = await ImmutableBuffer.fromUint8List(imageBytes);
final imageDescriptorVal = ImageDescriptor.raw(buffer, width:256, height:256, pixelFormat:PixelFormat.rgba8888);
return imageDescriptorVal.instantiateCodec();
}
}
Future<Uint8List> generateTile() async {
final int tileSize = 256;
final int width = tileSize;
final int height = tileSize;
final Uint8List bytes = Uint8List(width * height * 4);
// Fill the entire tile with red color
for (int i = 0; i < bytes.length; i += 4) {
bytes[i] = 255; // Red
bytes[i + 1] = 0; // Green
bytes[i + 2] = 0; // Blue
bytes[i + 3] = 255; // Alpha
}
return bytes;
}
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Column (
children: [
Flexible(
child: FlutterMap(
options: MapOptions(center: LatLng(54.0,4.9), zoom: 8, maxZoom: 22), // , maxZoom: 18
children: [
TileLayer(urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'com.example.app', maxZoom: 22, maxNativeZoom: 18),
TileLayer(tileProvider: CustomTileProvider(), minZoom:19, maxZoom: 24),
],
),
)
],)
),
)
);
}
} Thanks a lot for your help! |
Beta Was this translation helpful? Give feedback.
-
And to make both osm and my customly generated tile visible specify the backgroundColor of the TileLayer: |
Beta Was this translation helpful? Give feedback.
-
Great stuff, that may well be of help to others as well, so thanks for sharing the solution. |
Beta Was this translation helpful? Give feedback.
@ibrierley your counter questions helped me solve the problem! I had to directly use ImageDescriptor, as it allows for specifying the dimensions of your raw UInt8List image buffer, which was 256x256 and in rgba8888 format. Working code below: