Skip to content

Commit

Permalink
Add Fallback URLs (for #1203) (#1348)
Browse files Browse the repository at this point in the history
* working for NetworkTileProvider

* working for NetworkNoRetryTileProvider

* done AssetTileProvider

* formatted comments

* fixed wording & removed openstreetmap subdomain

* removed AssetTileProvider from tile_provider_web.dart
  • Loading branch information
TesteurManiak authored Sep 17, 2022
1 parent 44d570d commit a56b2b3
Show file tree
Hide file tree
Showing 14 changed files with 287 additions and 64 deletions.
6 changes: 6 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +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_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';
Expand Down Expand Up @@ -87,6 +89,10 @@ class MyApp extends StatelessWidget {
PointToLatLngPage.route: (context) => const PointToLatLngPage(),
LatLngScreenPointTestPage.route: (context) =>
const LatLngScreenPointTestPage(),
FallbackUrlNetworkPage.route: (context) =>
const FallbackUrlNetworkPage(),
FallbackUrlOfflinePage.route: (context) =>
const FallbackUrlOfflinePage(),
},
);
}
Expand Down
57 changes: 57 additions & 0 deletions example/lib/pages/fallback_url/fallback_url.dart
Original file line number Diff line number Diff line change
@@ -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],
),
),
],
),
),
);
}
}
28 changes: 28 additions & 0 deletions example/lib/pages/fallback_url_network_page.dart
Original file line number Diff line number Diff line change
@@ -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://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),
);
}
}
29 changes: 29 additions & 0 deletions example/lib/pages/fallback_url_offline_page.dart
Original file line number Diff line number Diff line change
@@ -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),
);
}
}
1 change: 0 additions & 1 deletion example/lib/pages/offline_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
),
Expand Down
14 changes: 14 additions & 0 deletions example/lib/widgets/drawer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +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_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';
Expand Down Expand Up @@ -257,6 +259,18 @@ 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 NetworkTileProvider'),
FallbackUrlNetworkPage.route,
currentRoute,
),
_buildMenuItem(
context,
const Text('Fallback URL AssetTileProvider'),
FallbackUrlOfflinePage.route,
currentRoute,
),
],
),
);
Expand Down
1 change: 1 addition & 0 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
8 changes: 8 additions & 0 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,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 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
/// [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
final bool tms;
Expand Down Expand Up @@ -239,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,
Expand Down Expand Up @@ -340,6 +345,9 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {
if (widget.reset != null) {
_resetSub = widget.reset?.listen((_) => _resetTiles());
}

//TODO fix
// _initThrottleUpdate();
}

@override
Expand Down
48 changes: 48 additions & 0 deletions lib/src/layer/tile_layer/tile_provider/asset_tile_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:convert';

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<num> 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<ByteData?> _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<ByteData> 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');
}
}
28 changes: 20 additions & 8 deletions lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <String, String>{
Expand All @@ -43,7 +37,25 @@ abstract class TileProvider {
}
final allOpts = Map<String, String>.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) {
Expand Down
42 changes: 28 additions & 14 deletions lib/src/layer/tile_layer/tile_provider/network_image_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,29 @@ import 'dart:ui';

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<FMNetworkImageProvider> {
/// The URL from which the image will be fetched.
final String url;

/// The http RetryClient that is used for the requests
final RetryClient retryClient;
/// The fallback URL from which the image will be fetched.
final String? fallbackUrl;

/// 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<String, String> headers;

FMNetworkImageProvider(
this.url, {
RetryClient? retryClient,
required this.fallbackUrl,
http.Client? httpClient,
this.headers = const {},
}) : retryClient = retryClient ?? RetryClient(Client());
}) : httpClient = httpClient ?? RetryClient(http.Client());

@override
ImageStreamCompleter loadBuffer(
Expand All @@ -38,22 +43,31 @@ class FMNetworkImageProvider extends ImageProvider<FMNetworkImageProvider> {

Future<ImageInfo> _loadWithRetry(
FMNetworkImageProvider key,
DecoderBufferCallback decode,
) async {
DecoderBufferCallback 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 httpClient.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(await ImmutableBuffer.fromUint8List(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;
}
}
}
Loading

0 comments on commit a56b2b3

Please sign in to comment.