From f1323b9a183bbceb05db65af2eaf6d9f51e7d4e9 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Sun, 28 Aug 2022 15:16:07 +0200 Subject: [PATCH 1/6] working for NetworkTileProvider --- example/lib/main.dart | 2 + example/lib/pages/fallback_url_page.dart | 50 +++++++++++++++++++ example/lib/widgets/drawer.dart | 7 +++ lib/src/layer/tile_layer/tile_layer.dart | 22 ++++---- .../tile_provider/base_tile_provider.dart | 28 ++++++++--- .../tile_provider/network_image_provider.dart | 35 +++++++++---- .../tile_provider/tile_provider_io.dart | 1 + .../tile_provider/tile_provider_web.dart | 4 +- 8 files changed, 118 insertions(+), 31 deletions(-) create mode 100644 example/lib/pages/fallback_url_page.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index f72ec1760..bd0b9537e 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/esri.dart'; +import 'package:flutter_map_example/pages/fallback_url_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; @@ -87,6 +88,7 @@ class MyApp extends StatelessWidget { PointToLatLngPage.route: (context) => const PointToLatLngPage(), LatLngScreenPointTestPage.route: (context) => const LatLngScreenPointTestPage(), + FallbackUrlPage.route: (context) => const FallbackUrlPage(), }, ); } diff --git a/example/lib/pages/fallback_url_page.dart b/example/lib/pages/fallback_url_page.dart new file mode 100644 index 000000000..2a6331987 --- /dev/null +++ b/example/lib/pages/fallback_url_page.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/widgets/drawer.dart'; +import 'package:latlong2/latlong.dart'; + +class FallbackUrlPage extends StatelessWidget { + static const String route = '/fallback_url'; + + const FallbackUrlPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Fallback Url')), + drawer: buildDrawer(context, route), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + const Padding( + padding: EdgeInsets.only(top: 8, bottom: 8), + child: Text( + 'Map with a fake url should, use the fallback, showing (51.5, -0.9).', + ), + ), + Flexible( + child: FlutterMap( + options: MapOptions( + center: LatLng(51.5, -0.09), + zoom: 5, + ), + children: [ + TileLayer( + urlTemplate: + 'https://fake-tile-provider.org/{z}/{x}/{y}.png', + fallbackUrl: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: const ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + tileProvider: NetworkTileProvider(), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/widgets/drawer.dart b/example/lib/widgets/drawer.dart index a7418499e..472ba66b6 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -6,6 +6,7 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/esri.dart'; +import 'package:flutter_map_example/pages/fallback_url_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; @@ -257,6 +258,12 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { PointToLatLngPage.route, currentRoute), _buildMenuItem(context, const Text('LatLng to ScreenPoint'), LatLngScreenPointTestPage.route, currentRoute), + _buildMenuItem( + context, + const Text('Fallback URL'), + FallbackUrlPage.route, + currentRoute, + ), ], ), ); diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 835e1351a..bdcb83731 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -25,7 +25,6 @@ part 'tile_layer_options.dart'; /// https://docs.fleaflet.dev/usage/layers/tile-layer. Some are important to /// avoid issues. class TileLayer extends StatefulWidget { - /// Defines the structure to create the URLs for the tiles. `{s}` means one of /// the available subdomains (can be omitted) `{z}` zoom level `{x}` and `{y}` /// — tile coordinates `{r}` can be used to add "@2x" to the URL to @@ -40,6 +39,10 @@ class TileLayer extends StatefulWidget { /// https://a.tile.openstreetmap.org/12/2177/1259.png final String? urlTemplate; + /// Follows the same structure as [urlTemplate]. If precised, this URL is used + /// only if an error occurs when loading the [urlTemplate]. + final String? fallbackUrl; + /// If `true`, inverses Y axis numbering for tiles (turn this on for /// [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). final bool tms; @@ -240,6 +243,7 @@ class TileLayer extends StatefulWidget { TileLayer({ super.key, this.urlTemplate, + this.fallbackUrl, double tileSize = 256.0, double minZoom = 0.0, double maxZoom = 18.0, @@ -277,7 +281,7 @@ class TileLayer extends StatefulWidget { this.reset, this.tileBounds, String userAgentPackageName = 'unknown', - }) : updateInterval = + }) : updateInterval = updateInterval <= Duration.zero ? null : updateInterval, tileFadeInDuration = tileFadeInDuration <= Duration.zero ? null : tileFadeInDuration, @@ -317,7 +321,6 @@ class TileLayer extends StatefulWidget { } class _TileLayerState extends State with TickerProviderStateMixin { - late Bounds _globalTileRange; Tuple2? _wrapX; Tuple2? _wrapY; @@ -343,7 +346,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { _resetSub = widget.reset?.listen((_) => _resetTiles()); } - //TODO fix + //TODO fix // _initThrottleUpdate(); } @@ -361,8 +364,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { reloadTiles = true; } - reloadTiles |= - !_tileManager.allWithinZoom(widget.minZoom, widget.maxZoom); + reloadTiles |= !_tileManager.allWithinZoom(widget.minZoom, widget.maxZoom); if (oldWidget.updateInterval != widget.updateInterval) { _throttleUpdate?.close(); @@ -371,8 +373,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { } if (!reloadTiles) { - final oldUrl = oldWidget.wmsOptions?._encodedBaseUrl ?? - oldWidget.urlTemplate; + final oldUrl = + oldWidget.wmsOptions?._encodedBaseUrl ?? oldWidget.urlTemplate; final newUrl = widget.wmsOptions?._encodedBaseUrl ?? widget.urlTemplate; final oldOptions = oldWidget.additionalOptions; @@ -584,7 +586,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } - Bounds _getTiledPixelBounds(FlutterMapState map, LatLng center) { final scale = map.getZoomScale(map.zoom, _tileZoom); final pixelCenter = map.project(center, _tileZoom).floor(); @@ -695,7 +696,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { return true; } - Bounds _latLngBoundsToPixelBounds(FlutterMapState map, LatLngBounds bounds, double thisZoom) { + Bounds _latLngBoundsToPixelBounds( + FlutterMapState map, LatLngBounds bounds, double thisZoom) { final swPixel = map.project(bounds.southWest!, thisZoom).floor(); final nePixel = map.project(bounds.northEast!, thisZoom).ceil(); final pxBounds = Bounds(swPixel, nePixel); diff --git a/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart index 345e74fa9..50a22b89e 100644 --- a/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart @@ -22,13 +22,7 @@ abstract class TileProvider { /// Called when the [TileLayerWidget] is disposed void dispose() {} - /// Generate a valid URL for a tile, based on it's coordinates and the current [TileLayerOptions] - String getTileUrl(Coords coords, TileLayer options) { - final urlTemplate = (options.wmsOptions != null) - ? options.wmsOptions! - .getUrl(coords, options.tileSize.toInt(), options.retinaMode) - : options.urlTemplate; - + String _getTileUrl(String urlTemplate, Coords coords, TileLayer options) { final z = _getZoomForUrl(coords, options); final data = { @@ -43,7 +37,25 @@ abstract class TileProvider { } final allOpts = Map.from(data) ..addAll(options.additionalOptions); - return options.templateFunction(urlTemplate!, allOpts); + return options.templateFunction(urlTemplate, allOpts); + } + + /// Generate a valid URL for a tile, based on it's coordinates and the current + /// [TileLayerOptions] + String getTileUrl(Coords coords, TileLayer options) { + final urlTemplate = (options.wmsOptions != null) + ? options.wmsOptions! + .getUrl(coords, options.tileSize.toInt(), options.retinaMode) + : options.urlTemplate; + + return _getTileUrl(urlTemplate!, coords, options); + } + + /// Generates a valid URL for the [fallbackUrl]. + String? getTileFallbackUrl(Coords coords, TileLayer options) { + final urlTemplate = options.fallbackUrl; + if (urlTemplate == null) return null; + return _getTileUrl(urlTemplate, coords, options); } double _getZoomForUrl(Coords coords, TileLayer options) { diff --git a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart index 42596d951..59aa0b1f1 100644 --- a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart @@ -7,6 +7,9 @@ class FMNetworkImageProvider extends ImageProvider { /// The URL from which the image will be fetched. final String url; + /// The fallback URL from which the image will be fetched. + final String? fallbackUrl; + /// The http RetryClient that is used for the requests final RetryClient retryClient; @@ -15,6 +18,7 @@ class FMNetworkImageProvider extends ImageProvider { FMNetworkImageProvider( this.url, { + required this.fallbackUrl, RetryClient? retryClient, this.headers = const {}, }) : retryClient = retryClient ?? RetryClient(Client()); @@ -36,21 +40,30 @@ class FMNetworkImageProvider extends ImageProvider { Future _loadWithRetry( FMNetworkImageProvider key, - DecoderCallback decode, - ) async { + DecoderCallback decode, [ + bool useFallback = false, + ]) async { assert(key == this); + assert(useFallback == false || fallbackUrl != null); - final uri = Uri.parse(url); - final response = await retryClient.get(uri, headers: headers); + try { + final uri = Uri.parse(useFallback ? fallbackUrl! : url); + final response = await retryClient.get(uri, headers: headers); - if (response.statusCode != 200) { - throw NetworkImageLoadException( - statusCode: response.statusCode, uri: uri); - } + if (response.statusCode != 200) { + throw NetworkImageLoadException( + statusCode: response.statusCode, uri: uri); + } - final codec = await decode(response.bodyBytes); - final image = (await codec.getNextFrame()).image; + final codec = await decode(response.bodyBytes); + final image = (await codec.getNextFrame()).image; - return ImageInfo(image: image); + return ImageInfo(image: image); + } catch (e) { + if (!useFallback && fallbackUrl != null) { + return _loadWithRetry(key, decode, true); + } + rethrow; + } } } diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart index 324099ca4..fc2c581be 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart @@ -31,6 +31,7 @@ class NetworkTileProvider extends TileProvider { HttpOverrides.runZoned( () => FMNetworkImageProvider( getTileUrl(coords, options), + fallbackUrl: getTileFallbackUrl(coords, options), headers: headers, retryClient: retryClient, ), diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart index cba3bb93d..17fb23acc 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart @@ -24,6 +24,7 @@ class NetworkTileProvider extends TileProvider { ImageProvider getImage(Coords coords, TileLayer options) => FMNetworkImageProvider( getTileUrl(coords, options), + fallbackUrl: getTileFallbackUrl(coords, options), headers: headers..remove('User-Agent'), ); } @@ -41,8 +42,7 @@ class NetworkNoRetryTileProvider extends TileProvider { } @override - ImageProvider getImage(Coords coords, TileLayer options) => - NetworkImage( + ImageProvider getImage(Coords coords, TileLayer options) => NetworkImage( getTileUrl(coords, options), headers: headers..remove('User-Agent'), ); From 665df8705135f25002d92b87ee6a3eee8db1758b Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Sun, 28 Aug 2022 16:02:58 +0200 Subject: [PATCH 2/6] working for NetworkNoRetryTileProvider --- example/lib/pages/fallback_url_page.dart | 1 - .../network_no_retry_image_provider.dart | 22 ++++++++++++++++--- .../tile_provider/tile_provider_io.dart | 1 + .../tile_provider/tile_provider_web.dart | 8 +++++-- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/example/lib/pages/fallback_url_page.dart b/example/lib/pages/fallback_url_page.dart index 2a6331987..f49635842 100644 --- a/example/lib/pages/fallback_url_page.dart +++ b/example/lib/pages/fallback_url_page.dart @@ -37,7 +37,6 @@ class FallbackUrlPage extends StatelessWidget { 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: const ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - tileProvider: NetworkTileProvider(), ), ], ), diff --git a/lib/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart b/lib/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart index 1abbdeb09..25caa1ba2 100644 --- a/lib/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart @@ -11,6 +11,9 @@ class FMNetworkNoRetryImageProvider /// A valid URL, which is the location of the image to be fetched final String url; + /// The fallback URL from which the image will be fetched. + final String? fallbackUrl; + /// The client which will be used to fetch the image final HttpClient httpClient; @@ -19,6 +22,7 @@ class FMNetworkNoRetryImageProvider FMNetworkNoRetryImageProvider( this.url, { + required this.fallbackUrl, HttpClient? httpClient, this.headers = const {}, }) : httpClient = httpClient ?? HttpClient() @@ -55,11 +59,14 @@ class FMNetworkNoRetryImageProvider required FMNetworkNoRetryImageProvider key, required DecoderCallback decode, required StreamController chunkEvents, + bool useFallback = false, }) async { try { assert(key == this); + assert(useFallback == false || fallbackUrl != null); - final Uri resolved = Uri.base.resolve(key.url); + final Uri resolved = + Uri.base.resolve(useFallback ? key.fallbackUrl! : key.url); final HttpClientRequest request = await httpClient.getUrl(resolved); @@ -87,15 +94,24 @@ class FMNetworkNoRetryImageProvider throw Exception('NetworkImage is an empty file: $resolved'); } + chunkEvents.close(); return decode(bytes); } catch (e) { + if (!useFallback && fallbackUrl != null) { + return _loadAsync( + key: key, + decode: decode, + chunkEvents: chunkEvents, + useFallback: true, + ); + } + scheduleMicrotask(() { _ambiguate(_ambiguate(PaintingBinding.instance)?.imageCache) ?.evict(key); }); - rethrow; - } finally { chunkEvents.close(); + rethrow; } } diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart index fc2c581be..134dbbdc1 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart @@ -60,6 +60,7 @@ class NetworkNoRetryTileProvider extends TileProvider { ImageProvider getImage(Coords coords, TileLayer options) => FMNetworkNoRetryImageProvider( getTileUrl(coords, options), + fallbackUrl: getTileFallbackUrl(coords, options), headers: headers, httpClient: httpClient, ); diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart index 17fb23acc..47f3252e1 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart @@ -1,4 +1,5 @@ import 'package:flutter/widgets.dart'; +import 'package:http/http.dart'; import 'package:http/retry.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -29,7 +30,7 @@ class NetworkTileProvider extends TileProvider { ); } -/// [TileProvider] that uses [NetworkImage] internally +/// [TileProvider] that uses [FMNetworkImageProvider] internally with no retry. /// /// This image provider does not automatically retry any failed requests. This provider is the default and the recommended provider, unless your tile server is especially unreliable. /// @@ -42,9 +43,12 @@ class NetworkNoRetryTileProvider extends TileProvider { } @override - ImageProvider getImage(Coords coords, TileLayer options) => NetworkImage( + ImageProvider getImage(Coords coords, TileLayer options) => + FMNetworkImageProvider( getTileUrl(coords, options), + fallbackUrl: getTileFallbackUrl(coords, options), headers: headers..remove('User-Agent'), + retryClient: RetryClient(Client(), retries: 0), ); } From 9ad282abfd7d3718217c7b22071fcd51b8e6ac96 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Sun, 28 Aug 2022 17:08:26 +0200 Subject: [PATCH 3/6] done AssetTileProvider --- example/lib/main.dart | 8 ++- .../lib/pages/fallback_url/fallback_url.dart | 57 +++++++++++++++++++ .../lib/pages/fallback_url_network_page.dart | 28 +++++++++ .../lib/pages/fallback_url_offline_page.dart | 29 ++++++++++ example/lib/pages/fallback_url_page.dart | 49 ---------------- example/lib/pages/offline_map.dart | 1 - example/lib/widgets/drawer.dart | 13 ++++- lib/flutter_map.dart | 1 + .../tile_provider/asset_tile_provider.dart | 49 ++++++++++++++++ .../tile_provider/network_image_provider.dart | 17 +++--- .../tile_provider/tile_provider_io.dart | 18 +++--- .../tile_provider/tile_provider_web.dart | 7 +-- 12 files changed, 202 insertions(+), 75 deletions(-) create mode 100644 example/lib/pages/fallback_url/fallback_url.dart create mode 100644 example/lib/pages/fallback_url_network_page.dart create mode 100644 example/lib/pages/fallback_url_offline_page.dart delete mode 100644 example/lib/pages/fallback_url_page.dart create mode 100644 lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index bd0b9537e..20f25682c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,7 +6,8 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/esri.dart'; -import 'package:flutter_map_example/pages/fallback_url_page.dart'; +import 'package:flutter_map_example/pages/fallback_url_network_page.dart'; +import 'package:flutter_map_example/pages/fallback_url_offline_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; @@ -88,7 +89,10 @@ class MyApp extends StatelessWidget { PointToLatLngPage.route: (context) => const PointToLatLngPage(), LatLngScreenPointTestPage.route: (context) => const LatLngScreenPointTestPage(), - FallbackUrlPage.route: (context) => const FallbackUrlPage(), + FallbackUrlNetworkPage.route: (context) => + const FallbackUrlNetworkPage(), + FallbackUrlOfflinePage.route: (context) => + const FallbackUrlOfflinePage(), }, ); } diff --git a/example/lib/pages/fallback_url/fallback_url.dart b/example/lib/pages/fallback_url/fallback_url.dart new file mode 100644 index 000000000..619de1e60 --- /dev/null +++ b/example/lib/pages/fallback_url/fallback_url.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/widgets/drawer.dart'; +import 'package:latlong2/latlong.dart'; + +class FallbackUrlPage extends StatelessWidget { + final String route; + final TileLayer tileLayer; + final String title; + final String description; + final double zoom; + final double? maxZoom; + final double? minZoom; + final LatLng center; + + const FallbackUrlPage({ + Key? key, + required this.route, + required this.tileLayer, + required this.title, + required this.description, + required this.center, + this.zoom = 13, + this.maxZoom, + this.minZoom, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + drawer: buildDrawer(context, route), + body: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 8, bottom: 8), + child: Text(description), + ), + Flexible( + child: FlutterMap( + options: MapOptions( + center: center, + zoom: zoom, + maxZoom: maxZoom, + minZoom: minZoom, + ), + children: [tileLayer], + ), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/pages/fallback_url_network_page.dart b/example/lib/pages/fallback_url_network_page.dart new file mode 100644 index 000000000..23c65d7b7 --- /dev/null +++ b/example/lib/pages/fallback_url_network_page.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/pages/fallback_url/fallback_url.dart'; +import 'package:latlong2/latlong.dart'; + +class FallbackUrlNetworkPage extends StatelessWidget { + static const String route = '/fallback_url_network'; + + const FallbackUrlNetworkPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FallbackUrlPage( + route: route, + tileLayer: TileLayer( + urlTemplate: 'https://fake-tile-provider.org/{z}/{x}/{y}.png', + fallbackUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: const ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), + title: 'Fallback URL NetworkTileProvider', + description: + 'Map with a fake url should use the fallback, showing (51.5, -0.9).', + zoom: 5, + center: LatLng(51.5, -0.09), + ); + } +} diff --git a/example/lib/pages/fallback_url_offline_page.dart b/example/lib/pages/fallback_url_offline_page.dart new file mode 100644 index 000000000..6f74ff1a1 --- /dev/null +++ b/example/lib/pages/fallback_url_offline_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map_example/pages/fallback_url/fallback_url.dart'; +import 'package:latlong2/latlong.dart'; + +class FallbackUrlOfflinePage extends StatelessWidget { + static const String route = '/fallback_url_offline'; + + const FallbackUrlOfflinePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return FallbackUrlPage( + route: route, + tileLayer: TileLayer( + tileProvider: AssetTileProvider(), + maxZoom: 14, + urlTemplate: 'assets/fake/tiles/{z}/{x}/{y}.png', + fallbackUrl: 'assets/map/anholt_osmbright/{z}/{x}/{y}.png', + ), + title: 'Fallback URL AssetTileProvider', + description: + 'Map with a fake asset path, should be using the fallback to show Anholt Island, Denmark.', + maxZoom: 14, + minZoom: 12, + center: LatLng(56.704173, 11.543808), + ); + } +} diff --git a/example/lib/pages/fallback_url_page.dart b/example/lib/pages/fallback_url_page.dart deleted file mode 100644 index f49635842..000000000 --- a/example/lib/pages/fallback_url_page.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class FallbackUrlPage extends StatelessWidget { - static const String route = '/fallback_url'; - - const FallbackUrlPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Fallback Url')), - drawer: buildDrawer(context, route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - const Padding( - padding: EdgeInsets.only(top: 8, bottom: 8), - child: Text( - 'Map with a fake url should, use the fallback, showing (51.5, -0.9).', - ), - ), - Flexible( - child: FlutterMap( - options: MapOptions( - center: LatLng(51.5, -0.09), - zoom: 5, - ), - children: [ - TileLayer( - urlTemplate: - 'https://fake-tile-provider.org/{z}/{x}/{y}.png', - fallbackUrl: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: const ['a', 'b', 'c'], - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - ], - ), - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/pages/offline_map.dart b/example/lib/pages/offline_map.dart index 95a969b2a..1345ee136 100644 --- a/example/lib/pages/offline_map.dart +++ b/example/lib/pages/offline_map.dart @@ -28,7 +28,6 @@ class OfflineMapPage extends StatelessWidget { center: LatLng(56.704173, 11.543808), minZoom: 12, maxZoom: 14, - zoom: 13, swPanBoundary: LatLng(56.6877, 11.5089), nePanBoundary: LatLng(56.7378, 11.6644), ), diff --git a/example/lib/widgets/drawer.dart b/example/lib/widgets/drawer.dart index 472ba66b6..2f5e47b1f 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -6,7 +6,8 @@ import 'package:flutter_map_example/pages/custom_crs/custom_crs.dart'; import 'package:flutter_map_example/pages/epsg3413_crs.dart'; import 'package:flutter_map_example/pages/epsg4326_crs.dart'; import 'package:flutter_map_example/pages/esri.dart'; -import 'package:flutter_map_example/pages/fallback_url_page.dart'; +import 'package:flutter_map_example/pages/fallback_url_network_page.dart'; +import 'package:flutter_map_example/pages/fallback_url_offline_page.dart'; import 'package:flutter_map_example/pages/home.dart'; import 'package:flutter_map_example/pages/interactive_test_page.dart'; import 'package:flutter_map_example/pages/latlng_to_screen_point.dart'; @@ -260,8 +261,14 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { LatLngScreenPointTestPage.route, currentRoute), _buildMenuItem( context, - const Text('Fallback URL'), - FallbackUrlPage.route, + const Text('Fallback URL NetworkTileProvider'), + FallbackUrlNetworkPage.route, + currentRoute, + ), + _buildMenuItem( + context, + const Text('Fallback URL AssetTileProvider'), + FallbackUrlOfflinePage.route, currentRoute, ), ], diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 0a798a4e3..5c151525c 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -35,6 +35,7 @@ export 'package:flutter_map/src/layer/tile_layer/tile.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_builder.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart'; +export 'package:flutter_map/src/layer/tile_layer/tile_provider/asset_tile_provider.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart' if (dart.library.html) 'package:flutter_map/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart'; export 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_io.dart' diff --git a/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart new file mode 100644 index 000000000..dea440b76 --- /dev/null +++ b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart @@ -0,0 +1,49 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_map/src/layer/tile_layer/coords.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_layer.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_provider/base_tile_provider.dart'; + +class AssetTileProvider extends TileProvider { + @override + AssetImage getImage(Coords coords, TileLayer options) { + return AssetImage( + getTileUrl(coords, options), + bundle: _FlutterMapAssetBundle( + fallbackKey: getTileFallbackUrl(coords, options), + ), + ); + } +} + +/// Used to load a fallback asset when the main asset is not found. +class _FlutterMapAssetBundle extends CachingAssetBundle { + final String? fallbackKey; + + _FlutterMapAssetBundle({required this.fallbackKey}); + + Future _loadAsset(String key) async { + final Uint8List encoded = + utf8.encoder.convert(Uri(path: Uri.encodeFull(key)).path); + final ByteData? asset = await ServicesBinding + .instance.defaultBinaryMessenger + .send('flutter/assets', encoded.buffer.asByteData()); + return asset; + } + + @override + Future load(String key) async { + final asset = await _loadAsset(key); + if (asset != null && asset.lengthInBytes > 0) return asset; + + if (fallbackKey != null) { + final fallbackAsset = await _loadAsset(fallbackKey!); + if (fallbackAsset != null) return fallbackAsset; + } + + throw FlutterError('_FlutterMapAssetBundle - Unable to load asset: $key'); + } +} diff --git a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart index 59aa0b1f1..7d559062b 100644 --- a/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; -import 'package:http/http.dart'; +import 'package:http/http.dart' as http; import 'package:http/retry.dart'; class FMNetworkImageProvider extends ImageProvider { @@ -10,8 +10,9 @@ class FMNetworkImageProvider extends ImageProvider { /// The fallback URL from which the image will be fetched. final String? fallbackUrl; - /// The http RetryClient that is used for the requests - final RetryClient retryClient; + /// The http client that is used for the requests. Defaults to a [RetryClient] + /// with a [http.Client]. + final http.Client httpClient; /// Custom headers to add to the image fetch request final Map headers; @@ -19,13 +20,15 @@ class FMNetworkImageProvider extends ImageProvider { FMNetworkImageProvider( this.url, { required this.fallbackUrl, - RetryClient? retryClient, + http.Client? httpClient, this.headers = const {}, - }) : retryClient = retryClient ?? RetryClient(Client()); + }) : httpClient = httpClient ?? RetryClient(http.Client()); @override ImageStreamCompleter load( - FMNetworkImageProvider key, DecoderCallback decode) { + FMNetworkImageProvider key, + DecoderCallback decode, + ) { return OneFrameImageStreamCompleter(_loadWithRetry(key, decode), informationCollector: () sync* { yield ErrorDescription('Image provider: $this'); @@ -48,7 +51,7 @@ class FMNetworkImageProvider extends ImageProvider { try { final uri = Uri.parse(useFallback ? fallbackUrl! : url); - final response = await retryClient.get(uri, headers: headers); + final response = await httpClient.get(uri, headers: headers); if (response.statusCode != 200) { throw NetworkImageLoadException( diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart index 134dbbdc1..1b7e72c60 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:flutter/widgets.dart'; -import 'package:http/http.dart'; +import 'package:http/http.dart' as http; import 'package:http/retry.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -18,13 +18,12 @@ import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_no_retry_ class NetworkTileProvider extends TileProvider { NetworkTileProvider({ Map? headers, - RetryClient? retryClient, - }) { + http.Client? httpClient, + }) : httpClient = httpClient ?? RetryClient(http.Client()) { this.headers = headers ?? {}; - this.retryClient = retryClient ?? RetryClient(Client()); } - late final RetryClient retryClient; + final http.Client httpClient; @override ImageProvider getImage(Coords coords, TileLayer options) => @@ -33,7 +32,7 @@ class NetworkTileProvider extends TileProvider { getTileUrl(coords, options), fallbackUrl: getTileFallbackUrl(coords, options), headers: headers, - retryClient: retryClient, + httpClient: httpClient, ), createHttpClient: (c) => _FlutterMapHTTPOverrides().createHttpClient(c), ); @@ -41,9 +40,12 @@ class NetworkTileProvider extends TileProvider { /// [TileProvider] that uses [FMNetworkNoRetryImageProvider] internally /// -/// This image provider does not automatically retry any failed requests. This provider is the default and the recommended provider, unless your tile server is especially unreliable. +/// This image provider does not automatically retry any failed requests. This +/// provider is the default and the recommended provider, unless your tile +/// server is especially unreliable. /// -/// Note that the 'User-Agent' header and the [HttpClient] cannot be changed, on the web platform. +/// Note that the 'User-Agent' header and the [HttpClient] cannot be changed, on +/// the web platform. class NetworkNoRetryTileProvider extends TileProvider { NetworkNoRetryTileProvider({ Map? headers, diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart index 47f3252e1..a2de281e4 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart @@ -1,6 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:http/http.dart'; -import 'package:http/retry.dart'; +import 'package:http/http.dart' as http; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_provider.dart'; @@ -19,8 +18,6 @@ class NetworkTileProvider extends TileProvider { this.headers = headers ?? {}; } - late final RetryClient retryClient; - @override ImageProvider getImage(Coords coords, TileLayer options) => FMNetworkImageProvider( @@ -48,7 +45,7 @@ class NetworkNoRetryTileProvider extends TileProvider { getTileUrl(coords, options), fallbackUrl: getTileFallbackUrl(coords, options), headers: headers..remove('User-Agent'), - retryClient: RetryClient(Client(), retries: 0), + httpClient: http.Client(), ); } From aa2f7a0b028e512684a8accec481be99dfa65e5d Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Sun, 28 Aug 2022 17:09:52 +0200 Subject: [PATCH 4/6] formatted comments --- .../tile_provider/tile_provider_io.dart | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart index 1b7e72c60..fdd4b8a46 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart @@ -10,11 +10,14 @@ import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_no_retry_ /// [TileProvider] that uses [FMNetworkImageProvider] internally /// -/// This image provider automatically retries some failed requests up to 3 times. +/// This image provider automatically retries some failed requests up to 3 +/// times. /// -/// Note that this provider may be slower than [NetworkNoRetryTileProvider] when fetching tiles due to internal reasons. +/// Note that this provider may be slower than [NetworkNoRetryTileProvider] when +/// fetching tiles due to internal reasons. /// -/// Note that the 'User-Agent' header and the [RetryClient] cannot be changed, on the web platform. +/// Note that the 'User-Agent' header and the [RetryClient] cannot be changed, +/// on the web platform. class NetworkTileProvider extends TileProvider { NetworkTileProvider({ Map? headers, @@ -68,9 +71,12 @@ class NetworkNoRetryTileProvider extends TileProvider { ); } -/// A very basic [TileProvider] implementation, that can be extended to create your own provider +/// A very basic [TileProvider] implementation, that can be extended to create +/// your own provider /// -/// Using this method is not recommended any more, except for very simple custom [TileProvider]s. Instead, visit the online documentation at https://docs.fleaflet.dev/plugins/making-a-plugin/creating-new-tile-providers. +/// Using this method is not recommended any more, except for very simple custom +/// [TileProvider]s. Instead, visit the online documentation at +/// https://docs.fleaflet.dev/plugins/making-a-plugin/creating-new-tile-providers. class CustomTileProvider extends TileProvider { final String Function(Coords coors, TileLayer options) customTileUrl; From a65c88709729f0fd0b95645da6489f5c230d9eef Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 13 Sep 2022 08:04:10 +0200 Subject: [PATCH 5/6] fixed wording & removed openstreetmap subdomain --- example/lib/pages/fallback_url_network_page.dart | 2 +- lib/src/layer/tile_layer/tile_layer.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/example/lib/pages/fallback_url_network_page.dart b/example/lib/pages/fallback_url_network_page.dart index 23c65d7b7..a61e2ddbe 100644 --- a/example/lib/pages/fallback_url_network_page.dart +++ b/example/lib/pages/fallback_url_network_page.dart @@ -14,7 +14,7 @@ class FallbackUrlNetworkPage extends StatelessWidget { route: route, tileLayer: TileLayer( urlTemplate: 'https://fake-tile-provider.org/{z}/{x}/{y}.png', - fallbackUrl: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + fallbackUrl: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: const ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index f457487f3..f35a9eeac 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -39,8 +39,8 @@ class TileLayer extends StatefulWidget { /// https://a.tile.openstreetmap.org/12/2177/1259.png final String? urlTemplate; - /// Follows the same structure as [urlTemplate]. If precised, this URL is used - /// only if an error occurs when loading the [urlTemplate]. + /// Follows the same structure as [urlTemplate]. If specified, this URL is + /// used only if an error occurs when loading the [urlTemplate]. final String? fallbackUrl; /// If `true`, inverses Y axis numbering for tiles (turn this on for From 7eb01170ff0519e75f69b8c3bc2ff32e68d14c79 Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 13 Sep 2022 08:05:55 +0200 Subject: [PATCH 6/6] removed AssetTileProvider from tile_provider_web.dart --- .../tile_layer/tile_provider/asset_tile_provider.dart | 1 - .../tile_layer/tile_provider/tile_provider_web.dart | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart index dea440b76..4d8532474 100644 --- a/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart @@ -1,5 +1,4 @@ import 'dart:convert'; -import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart index 6950f0c26..a2de281e4 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart @@ -49,16 +49,6 @@ class NetworkNoRetryTileProvider extends TileProvider { ); } -/// [TileProvider] that uses [AssetImage] internally -class AssetTileProvider extends TileProvider { - AssetTileProvider(); - - @override - ImageProvider getImage(Coords coords, TileLayer options) { - return AssetImage(getTileUrl(coords, options)); - } -} - /// A very basic [TileProvider] implementation, that can be extended to create your own provider /// /// Using this method is not recommended any more, except for very simple custom [TileProvider]s. Instead, visit the online documentation at https://docs.fleaflet.dev/plugins/making-a-plugin/creating-new-tile-providers.