diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a55803f..c4b686d5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [2.0.0] - 2022/XX/XX + +Contains the following additions/removals: + +- Added adjustable mouse wheel zoom speed - [#1289](https://github.com/fleaflet/flutter_map/pull/1289) +- Multiple changes - [#1294](https://github.com/fleaflet/flutter_map/pull/1294) + - Added advanced header support, including 'User-Agent' + - Refactored `TileProvider`s + - Resolved multiple TODOs within codebase + - Removed old deprecated code + +Contains the following bug fixes: + +- Fixed unsymmetrical markers disappearing with unusually positioned anchors - [#1291](https://github.com/fleaflet/flutter_map/pull/1291) +- Fixed potential for error 403s due to invalid/blocked 'User-Agent' header - [#1294](https://github.com/fleaflet/flutter_map/pull/1294) + +In other news: + +- None + +Many thanks to these contributors (in no particular order): + +- @mboe +- @aytunch +- @MichalTorma +- ... and all the maintainers + ## [1.1.1] - 2022/06/25 Contains the following additions/removals: diff --git a/example/lib/pages/animated_map_controller.dart b/example/lib/pages/animated_map_controller.dart index afc824657..fbc0ab7f2 100644 --- a/example/lib/pages/animated_map_controller.dart +++ b/example/lib/pages/animated_map_controller.dart @@ -182,9 +182,11 @@ class AnimatedMapControllerPageState extends State minZoom: 3.0), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: markers) ], ), diff --git a/example/lib/pages/circle.dart b/example/lib/pages/circle.dart index 0d2e98189..90fdcd835 100644 --- a/example/lib/pages/circle.dart +++ b/example/lib/pages/circle.dart @@ -40,9 +40,11 @@ class CirclePage extends StatelessWidget { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), CircleLayerOptions(circles: circleMarkers) ], ), diff --git a/example/lib/pages/epsg4326_crs.dart b/example/lib/pages/epsg4326_crs.dart index 2a5052a03..8ad4cbd0f 100644 --- a/example/lib/pages/epsg4326_crs.dart +++ b/example/lib/pages/epsg4326_crs.dart @@ -37,6 +37,7 @@ class EPSG4326Page extends StatelessWidget { baseUrl: 'https://ows.mundialis.de/services/service?', layers: ['TOPO-OSM-WMS'], ), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ) ], ), diff --git a/example/lib/pages/esri.dart b/example/lib/pages/esri.dart index 845332f96..d1130d83c 100644 --- a/example/lib/pages/esri.dart +++ b/example/lib/pages/esri.dart @@ -32,6 +32,7 @@ class EsriPage extends StatelessWidget { TileLayerOptions( urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ], ), diff --git a/example/lib/pages/home.dart b/example/lib/pages/home.dart index 12f3bb9d2..5c949d19b 100644 --- a/example/lib/pages/home.dart +++ b/example/lib/pages/home.dart @@ -63,7 +63,7 @@ class HomePage extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions(markers: markers) ], diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index b853148ec..e0a43cfa4 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -181,6 +181,7 @@ class _InteractiveTestPageState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ], ), diff --git a/example/lib/pages/live_location.dart b/example/lib/pages/live_location.dart index ddf5b3e58..1f32e4095 100644 --- a/example/lib/pages/live_location.dart +++ b/example/lib/pages/live_location.dart @@ -145,10 +145,7 @@ class _LiveLocationPageState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - // For example purposes. It is recommended to use - // TileProvider with a caching and retry strategy, like - // NetworkTileProvider or CachedNetworkTileProvider - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions(markers: markers) ], diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index d76fda483..163fa398d 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -83,6 +83,7 @@ class _ManyMarkersPageState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions( markers: allMarkers.sublist( diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 6df32c2b7..072eba33a 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -162,9 +162,11 @@ class MapControllerPageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: markers) ], ), diff --git a/example/lib/pages/map_inside_listview.dart b/example/lib/pages/map_inside_listview.dart index da1e2a6da..f3b199032 100644 --- a/example/lib/pages/map_inside_listview.dart +++ b/example/lib/pages/map_inside_listview.dart @@ -45,6 +45,7 @@ class MapInsideListViewPage extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ), ], diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart index 68c4ea32c..4efee60b1 100644 --- a/example/lib/pages/marker_anchor.dart +++ b/example/lib/pages/marker_anchor.dart @@ -115,9 +115,11 @@ class MarkerAnchorPageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: markers) ], ), diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart index dd9c43006..40911e8a1 100644 --- a/example/lib/pages/marker_rotate.dart +++ b/example/lib/pages/marker_rotate.dart @@ -140,9 +140,11 @@ class MarkerRotatePageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions( rotate: rotateMarkerLayerOptions, markers: markers, diff --git a/example/lib/pages/max_bounds.dart b/example/lib/pages/max_bounds.dart index feb526ea6..5b29256ed 100644 --- a/example/lib/pages/max_bounds.dart +++ b/example/lib/pages/max_bounds.dart @@ -38,6 +38,7 @@ class MaxBoundsPage extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ], ), diff --git a/example/lib/pages/moving_markers.dart b/example/lib/pages/moving_markers.dart index a33327463..3313c363a 100644 --- a/example/lib/pages/moving_markers.dart +++ b/example/lib/pages/moving_markers.dart @@ -61,9 +61,11 @@ class _MovingMarkersPageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: [_marker!]) ], ), diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart index 2d7635992..cd26ea582 100644 --- a/example/lib/pages/network_tile_provider.dart +++ b/example/lib/pages/network_tile_provider.dart @@ -50,11 +50,14 @@ class NetworkTileProviderPage extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), - child: Wrap(children: const [ - Text('This Provider does not provide caching.'), - Text( - 'For further options about that, check flutter_map\'s README on GitHub.'), - ]), + child: Wrap( + children: const [ + Text( + 'This provider will automatically retry failed requests, unlike the other pages.'), + Text( + 'For further information, check the documentation website.'), + ], + ), ), Flexible( child: FlutterMap( @@ -67,10 +70,8 @@ class NetworkTileProviderPage extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - // For example purposes. It is recommended to use - // TileProvider with a caching and retry strategy, like - // NetworkTileProvider or CachedNetworkTileProvider tileProvider: NetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions(markers: markers) ], diff --git a/example/lib/pages/offline_map.dart b/example/lib/pages/offline_map.dart index 1f796f090..510131e43 100644 --- a/example/lib/pages/offline_map.dart +++ b/example/lib/pages/offline_map.dart @@ -35,7 +35,7 @@ class OfflineMapPage extends StatelessWidget { ), layers: [ TileLayerOptions( - tileProvider: const AssetTileProvider(), + tileProvider: AssetTileProvider(), maxZoom: 14.0, urlTemplate: 'assets/map/anholt_osmbright/{z}/{x}/{y}.png', ), diff --git a/example/lib/pages/on_tap.dart b/example/lib/pages/on_tap.dart index 7e45d4965..85c490057 100644 --- a/example/lib/pages/on_tap.dart +++ b/example/lib/pages/on_tap.dart @@ -87,9 +87,11 @@ class OnTapPageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: markers) ], ), diff --git a/example/lib/pages/overlay_image.dart b/example/lib/pages/overlay_image.dart index b0bd3498f..1eb577cae 100644 --- a/example/lib/pages/overlay_image.dart +++ b/example/lib/pages/overlay_image.dart @@ -38,9 +38,11 @@ class OverlayImagePage extends StatelessWidget { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), OverlayImageLayerOptions(overlayImages: overlayImages) ], ), diff --git a/example/lib/pages/plugin_api.dart b/example/lib/pages/plugin_api.dart index cd6bb1750..d39a0b9cf 100644 --- a/example/lib/pages/plugin_api.dart +++ b/example/lib/pages/plugin_api.dart @@ -29,9 +29,11 @@ class PluginPage extends StatelessWidget { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), ], nonRotatedLayers: [ MyCustomPluginOptions(text: "I'm a plugin!"), diff --git a/example/lib/pages/plugin_scalebar.dart b/example/lib/pages/plugin_scalebar.dart index dce6910d0..c4aaa19f1 100644 --- a/example/lib/pages/plugin_scalebar.dart +++ b/example/lib/pages/plugin_scalebar.dart @@ -33,6 +33,7 @@ class PluginScaleBar extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ], nonRotatedLayers: [ diff --git a/example/lib/pages/plugin_zoombuttons.dart b/example/lib/pages/plugin_zoombuttons.dart index c00d8cf01..051a2989e 100644 --- a/example/lib/pages/plugin_zoombuttons.dart +++ b/example/lib/pages/plugin_zoombuttons.dart @@ -33,7 +33,7 @@ class PluginZoomButtons extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ], nonRotatedLayers: [ diff --git a/example/lib/pages/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index e6b7f787a..54bb75231 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -69,9 +69,11 @@ class PointToLatlngPage extends State { children: [ TileLayerWidget( options: TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'])), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + )), if (latLng != null) MarkerLayerWidget( options: MarkerLayerOptions( diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index 4aac2a0e5..c019f8b83 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -55,9 +55,11 @@ class PolygonPage extends StatelessWidget { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), PolygonLayerOptions(polygons: [ Polygon( points: notFilledPoints, diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index b65db5109..aca0f705f 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -83,9 +83,12 @@ class _PolylinePageState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: + 'dev.fleaflet.flutter_map.example', + ), PolylineLayerOptions( polylines: [ Polyline( diff --git a/example/lib/pages/reset_tile_layer.dart b/example/lib/pages/reset_tile_layer.dart index d24d8837c..6a72203c6 100644 --- a/example/lib/pages/reset_tile_layer.dart +++ b/example/lib/pages/reset_tile_layer.dart @@ -78,9 +78,11 @@ class ResetTileLayerPageState extends State { ), layers: [ TileLayerOptions( - reset: resetController.stream, - urlTemplate: layerToggle ? layer1 : layer2, - subdomains: ['a', 'b', 'c']), + reset: resetController.stream, + urlTemplate: layerToggle ? layer1 : layer2, + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), MarkerLayerOptions(markers: markers) ], ), diff --git a/example/lib/pages/sliding_map.dart b/example/lib/pages/sliding_map.dart index 80938f69c..364c13318 100644 --- a/example/lib/pages/sliding_map.dart +++ b/example/lib/pages/sliding_map.dart @@ -37,7 +37,7 @@ class SlidingMapPage extends StatelessWidget { ), layers: [ TileLayerOptions( - tileProvider: const AssetTileProvider(), + tileProvider: AssetTileProvider(), maxZoom: 14.0, urlTemplate: 'assets/map/anholt_osmbright/{z}/{x}/{y}.png', ), diff --git a/example/lib/pages/stateful_markers.dart b/example/lib/pages/stateful_markers.dart index 43615ff63..1fe7b49db 100644 --- a/example/lib/pages/stateful_markers.dart +++ b/example/lib/pages/stateful_markers.dart @@ -69,10 +69,7 @@ class _StatefulMarkersPageState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - // For example purposes. It is recommended to use - // TileProvider with a caching and retry strategy, like - // NetworkTileProvider or CachedNetworkTileProvider - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions(markers: _markers) ], diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index f51d63e44..40acd30b2 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -51,6 +51,7 @@ class TapToAddPageState extends State { TileLayerOptions( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), MarkerLayerOptions(markers: markers) ], diff --git a/example/lib/pages/tile_builder_example.dart b/example/lib/pages/tile_builder_example.dart index 6c97bfd83..2e382348a 100644 --- a/example/lib/pages/tile_builder_example.dart +++ b/example/lib/pages/tile_builder_example.dart @@ -116,7 +116,7 @@ class _TileBuilderPageState extends State { TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', tileBuilder: tileBuilder, tilesContainerBuilder: darkMode ? darkModeTilesContainerBuilder : null, diff --git a/example/lib/pages/tile_loading_error_handle.dart b/example/lib/pages/tile_loading_error_handle.dart index 7870912a5..ecd893497 100644 --- a/example/lib/pages/tile_loading_error_handle.dart +++ b/example/lib/pages/tile_loading_error_handle.dart @@ -47,7 +47,7 @@ class _TileLoadingErrorHandleState extends State { // For example purposes. It is recommended to use // TileProvider with a caching and retry strategy, like // NetworkTileProvider or CachedNetworkTileProvider - tileProvider: const NonCachingNetworkTileProvider(), + userAgentPackageName: 'dev.fleaflet.flutter_map.example', errorTileCallback: (Tile tile, error) { if (needLoadingError) { WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index c004df44b..11646a743 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -55,6 +55,7 @@ class WidgetsPage extends StatelessWidget { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ), const MovingWithoutRefreshAllMapMarkers(), diff --git a/example/lib/pages/wms_tile_layer.dart b/example/lib/pages/wms_tile_layer.dart index 34bece41b..e71ea9271 100644 --- a/example/lib/pages/wms_tile_layer.dart +++ b/example/lib/pages/wms_tile_layer.dart @@ -35,6 +35,7 @@ class WMSLayerPage extends StatelessWidget { layers: ['s2cloudless-2018_3857'], ), subdomains: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', ) ], ), diff --git a/example/lib/test_app.dart b/example/lib/test_app.dart index d3548703d..9908cc7ab 100644 --- a/example/lib/test_app.dart +++ b/example/lib/test_app.dart @@ -34,9 +34,11 @@ class _TestAppState extends State { ), layers: [ TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + ), ], ), ), diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index a8d90ba3c..7b198a5b3 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -5,6 +5,9 @@ import 'dart:math'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; + import 'package:flutter_map/src/core/center_zoom.dart'; import 'package:flutter_map/src/core/point.dart'; import 'package:flutter_map/src/geo/crs/crs.dart'; @@ -16,8 +19,6 @@ import 'package:flutter_map/src/layer/layer.dart'; import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:flutter_map/src/plugins/plugin.dart'; -import 'package:latlong2/latlong.dart'; -import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; export 'package:flutter_map/src/core/center_zoom.dart'; export 'package:flutter_map/src/core/point.dart'; @@ -38,9 +39,11 @@ export 'package:flutter_map/src/layer/tile_layer/coords.dart'; 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/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.dart'; +export 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_io.dart' + if (dart.library.html) 'package:flutter_map/src/layer/tile_layer/tile_provider/tile_provider_web.dart'; export 'package:flutter_map/src/plugins/plugin.dart'; /// Renders a map composed of a list of layers powered by [LayerOptions]. diff --git a/lib/src/geo/crs/crs.dart b/lib/src/geo/crs/crs.dart index 55b6db150..e7f8a7b3a 100644 --- a/lib/src/geo/crs/crs.dart +++ b/lib/src/geo/crs/crs.dart @@ -130,7 +130,7 @@ class Epsg3857 extends Earth { transformation = const Transformation(_scale, 0.5, -_scale, 0.5), super(); -// TODO Epsg3857 seems to have latitude limits. https://epsg.io/3857 +// Epsg3857 seems to have latitude limits. https://epsg.io/3857 //@override //Tuple2 get wrapLat => const Tuple2(-85.06, 85.06); } diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index cec76503e..48002d46e 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -709,7 +709,6 @@ abstract class MapGestureMixin extends State _doubleTapHoldMaxDelay?.cancel(); final flags = options.interactiveFlags; - // TODO: is this pinchZoom? never seen this fired if (InteractiveFlag.hasFlag(flags, InteractiveFlag.pinchZoom)) { final zoom = mapState.zoom; final focalOffset = details.localFocalPoint; diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 8df75667c..bf1265081 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -67,15 +67,11 @@ class OverlayImageLayer extends StatelessWidget { } Positioned _positionedForOverlay(OverlayImage overlayImage) { - final zoomScale = - map.getZoomScale(map.zoom, map.zoom); // TODO replace with 1? final pixelOrigin = map.getPixelOrigin(); final upperLeftPixel = - map.project(overlayImage.bounds.northWest).multiplyBy(zoomScale) - - pixelOrigin; + map.project(overlayImage.bounds.northWest) - pixelOrigin; final bottomRightPixel = - map.project(overlayImage.bounds.southEast).multiplyBy(zoomScale) - - pixelOrigin; + map.project(overlayImage.bounds.southEast) - pixelOrigin; return Positioned( left: upperLeftPixel.x.toDouble(), top: upperLeftPixel.y.toDouble(), diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 9bc665936..1d442759e 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -201,21 +201,11 @@ class _TileLayerState extends State with TickerProviderStateMixin { tilesToRender, ); - final attributionLayer = - // ignore: deprecated_member_use_from_same_package - widget.options.attributionBuilder?.call(context); - return Opacity( opacity: options.opacity, child: Container( color: options.backgroundColor, - child: Stack( - alignment: widget.options.attributionAlignment, - children: [ - tilesLayer, - if (attributionLayer != null) attributionLayer, - ], - ), + child: tilesLayer, ), ); }, diff --git a/lib/src/layer/tile_layer/tile_layer_options.dart b/lib/src/layer/tile_layer/tile_layer_options.dart index 86ff7b1f6..af76b482d 100644 --- a/lib/src/layer/tile_layer/tile_layer_options.dart +++ b/lib/src/layer/tile_layer/tile_layer_options.dart @@ -20,6 +20,10 @@ typedef ErrorTileCallBack = void Function(Tile tile, dynamic error); /// Describes the needed properties to create a tile-based layer. A tile is an /// image bound to a specific geographical position. +/// +/// You should read up about the options by exploring each one, or visiting +/// https://docs.fleaflet.dev/usage/layers/tile-layer. Some are important to +/// avoid issues. class TileLayerOptions extends LayerOptions { /// 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}` @@ -88,48 +92,48 @@ class TileLayerOptions extends LayerOptions { /// https://c.tile.openstreetmap.org/{z}/{x}/{y}.png final List subdomains; - /// Color shown behind the tiles. + /// Color shown behind the tiles final Color backgroundColor; /// Opacity of the rendered tile final double opacity; - /// Provider to load the tiles. The default is `NonCachingNetworkTileProvider()` which - /// doesn't cache tiles and won't retry the HTTP request. Use `NetworkTileProvider()` for - /// a provider which will retry requests. For the best caching implementations, see the - /// flutter_map readme. + /// Provider with which to load map tiles /// - /// In order to use images from the asset folder set this option to - /// AssetTileProvider() Note that it requires the urlTemplate to target - /// assets, for example: + /// The default is [NetworkNoRetryTileProvider]. Alternatively, use + /// [NetworkTileProvider] for a network provider which will retry requests. /// - /// ```dart - /// urlTemplate: "assets/map/anholt_osmbright/{z}/{x}/{y}.png", - /// ``` + /// Both network providers will use some form of caching, although not reliable. For + /// better options, see https://docs.fleaflet.dev/usage/layers/tile-layer#caching. /// - /// In order to use images from the filesystem set this option to - /// FileTileProvider() Note that it requires the urlTemplate to target the - /// file system, for example: + /// `userAgentPackageName` is a construction parameter, which should be passed + /// the application's correct package name, such as 'com.example.app'. If no + /// value is passed, it defaults to 'unknown'. This parameter is used to form + /// part of the 'User-Agent' header, which is important to avoid blocking by + /// tile servers. Namely, the header is the following 'flutter_map ()'. /// - /// ```dart - /// urlTemplate: "/storage/emulated/0/tiles/some_place/{z}/{x}/{y}.png", - /// ``` + /// Header rules are as follows, after 'User-Agent' is generated as above: + /// + /// * If no provider is specified here, the default will be used with + /// 'User-Agent' header injected (recommended) + /// * If a provider is specified here with no 'User-Agent' header, that + /// provider will be used and the 'User-Agent' header will be injected + /// * If a provider is specified here with a 'User-Agent' header, that + /// provider will be used and the 'User-Agent' header will not be changed to any created here + /// + /// [AssetTileProvider] and [FileTileProvider] are alternatives to network + /// providers, which use the [urlTemplate] as a path instead. + /// For example, 'assets/map/{z}/{x}/{y}.png' or + /// '/storage/emulated/0/map_app/tiles/{z}/{x}/{y}.png'. /// - /// Furthermore you create your custom implementation by subclassing - /// TileProvider - final TileProvider tileProvider; + /// Custom [TileProvider]s can also be used, but these will not follow the header + /// rules above. + late final TileProvider tileProvider; /// When panning the map, keep this many rows and columns of tiles before /// unloading them. final int keepBuffer; - /// Placeholder to show until tile images are fetched by the provider. - /// - /// _`placeholderImage` has been deprecated with no current replacement or workaround. Usage no longer has an effect internally._ - @Deprecated( - '`placeholderImage` has been deprecated with no current replacement or workaround. Usage no longer has an effect internally.') - final ImageProvider? placeholderImage; - /// Tile image to show in place of the tile that failed to load. final ImageProvider? errorImage; @@ -226,15 +230,6 @@ class TileLayerOptions extends LayerOptions { /// When set to `true`, the `tileFadeIn*` options will be ignored. final bool fastReplace; - /// [attributionBuilder] has been deprecated. Usage will continue to work, however not as expected. As an alternative, use [AttributionWidget] inside `nonRotatedChildren`. - @Deprecated( - '`attributionBuilder` has been deprecated. Usage will continue to work, however not as expected. As an alternative, use `AttributionWidget` inside `nonRotatedChildren`.', - ) - final WidgetBuilder? attributionBuilder; - - ///aligment of the attribution text on the map widget - final Alignment attributionAlignment; - /// Stream to notify the [TileLayer] that it needs resetting Stream? reset; @@ -242,13 +237,7 @@ class TileLayerOptions extends LayerOptions { LatLngBounds? tileBounds; TileLayerOptions({ - this.attributionAlignment = Alignment.bottomRight, - @Deprecated( - '`attributionBuilder` has been deprecated. Usage will continue to work, however not as expected. As an alternative, use `AttributionWidget` inside `nonRotatedChildren`.', - ) - this.attributionBuilder, Key? key, - // TODO: make required this.urlTemplate, double tileSize = 256.0, double minZoom = 0.0, @@ -261,25 +250,19 @@ class TileLayerOptions extends LayerOptions { this.subdomains = const [], this.keepBuffer = 2, this.backgroundColor = const Color(0xFFE0E0E0), - @Deprecated('`placeholderImage` has been deprecated with no current replacement or workaround. Usage no longer has an effect internally.') - this.placeholderImage, this.errorImage, - this.tileProvider = const NonCachingNetworkTileProvider(), + TileProvider? tileProvider, this.tms = false, - // ignore: avoid_init_to_null - this.wmsOptions = null, + this.wmsOptions, this.opacity = 1.0, - // Tiles will not update more than once every `updateInterval` milliseconds - // (default 200) when panning. It can be 0 (but it will calculating for - // loading tiles every frame when panning / zooming, flutter is fast) This - // can save some fps and even bandwidth (ie. when fast panning / animating - // between long distances in short time) - // TODO: change to Duration - int updateInterval = 200, - // Tiles fade in duration in milliseconds (default 100). This can be set to - // 0 to avoid fade in - // TODO: change to Duration - int tileFadeInDuration = 100, + + /// Tiles will not update more than once every `updateInterval` milliseconds + /// (default 200) when panning. It can be 0 (but it will calculating for + /// loading tiles every frame when panning / zooming, flutter is fast) This + /// can save some fps and even bandwidth (ie. when fast panning / animating + /// between long distances in short time) + Duration updateInterval = const Duration(milliseconds: 200), + Duration tileFadeInDuration = const Duration(milliseconds: 100), this.tileFadeInStart = 0.0, this.tileFadeInStartWhenOverride = 0.0, this.overrideTilesWhenUrlChanges = false, @@ -293,11 +276,11 @@ class TileLayerOptions extends LayerOptions { this.fastReplace = false, this.reset, this.tileBounds, + String userAgentPackageName = 'unknown', }) : updateInterval = - updateInterval <= 0 ? null : Duration(milliseconds: updateInterval), - tileFadeInDuration = tileFadeInDuration <= 0 - ? null - : Duration(milliseconds: tileFadeInDuration), + updateInterval <= Duration.zero ? null : updateInterval, + tileFadeInDuration = + tileFadeInDuration <= Duration.zero ? null : tileFadeInDuration, assert(tileFadeInStart >= 0.0 && tileFadeInStart <= 1.0), assert(tileFadeInStartWhenOverride >= 0.0 && tileFadeInStartWhenOverride <= 1.0), @@ -315,11 +298,19 @@ class TileLayerOptions extends LayerOptions { tileSize = wmsOptions == null && retinaMode && maxZoom > 0.0 ? (tileSize / 2.0).floorToDouble() : tileSize, - // copy additionalOptions Map if not null, so we can safely compare old - // and new Map inside didUpdateWidget with MapEquality. additionalOptions = additionalOptions == null ? const {} : Map.from(additionalOptions), + tileProvider = tileProvider == null + ? NetworkNoRetryTileProvider( + headers: {'User-Agent': 'flutter_map ($userAgentPackageName)'}, + ) + : (tileProvider + ..headers = { + ...tileProvider.headers, + if (!tileProvider.headers.containsKey('User-Agent')) + 'User-Agent': 'flutter_map ($userAgentPackageName)', + }), super(key: key, rebuild: rebuild); } @@ -327,14 +318,13 @@ class WMSTileLayerOptions { final service = 'WMS'; final request = 'GetMap'; - /// url of WMS service. - /// Ex.: 'http://ows.mundialis.de/services/service?' + /// WMS service's URL, for example 'http://ows.mundialis.de/services/service?' final String baseUrl; - /// list of WMS layers to show + /// List of WMS layers to show final List layers; - /// list of WMS styles + /// List of WMS styles final List styles; /// WMS image format (use 'image/png' for layers with transparency) @@ -343,16 +333,16 @@ class WMSTileLayerOptions { /// Version of the WMS service to use final String version; - /// tile transparency flag + /// Whether to make tiles transparent final bool transparent; /// Encode boolean values as uppercase in request final bool uppercaseBoolValue; - // TODO find a way to implicit pass of current map [Crs] + /// Sets map projection standard final Crs crs; - /// other request parameters + /// Other request parameters final Map otherParameters; late final String _encodedBaseUrl; diff --git a/lib/src/layer/tile_layer/tile_provider/tile_provider.dart b/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart similarity index 54% rename from lib/src/layer/tile_layer/tile_provider/tile_provider.dart rename to lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart index 4ffddef66..9ede51ece 100644 --- a/lib/src/layer/tile_layer/tile_provider/tile_provider.dart +++ b/lib/src/layer/tile_layer/tile_provider/base_tile_provider.dart @@ -1,14 +1,28 @@ import 'package:flutter/widgets.dart'; + import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_with_retry.dart'; +/// The base tile provider implementation, extended by other classes such as [NetworkTileProvider] +/// +/// Visit the online documentation at https://docs.fleaflet.dev/usage/layers/tile-layer/tile-providers for more information. abstract class TileProvider { - const TileProvider(); + /// Custom headers that may be sent with each tile request, if the specific implementation supports it + Map headers; + + /// The base tile provider implementation, extended by other classes such as [NetworkTileProvider] + /// + /// Visit the online documentation at https://docs.fleaflet.dev/usage/layers/tile-layer/tile-providers for more information. + TileProvider({ + this.headers = const {}, + }); + /// Retrieve a tile as an image, based on it's coordinates and the current [TileLayerOptions] ImageProvider getImage(Coords coords, TileLayerOptions options); + /// 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, TileLayerOptions options) { final urlTemplate = (options.wmsOptions != null) ? options.wmsOptions! @@ -46,6 +60,7 @@ abstract class TileProvider { return ((1 << z) - 1) - y; } + /// Get a subdomain value for a tile, based on it's coordinates and the current [TileLayerOptions] String getSubdomain(Coords coords, TileLayerOptions options) { if (options.subdomains.isEmpty) { return ''; @@ -54,44 +69,3 @@ abstract class TileProvider { return options.subdomains[index]; } } - -class NetworkTileProvider extends TileProvider { - @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { - return NetworkImageWithRetry(getTileUrl(coords, options)); - } -} - -class NonCachingNetworkTileProvider extends TileProvider { - const NonCachingNetworkTileProvider(); - - @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { - return NetworkImage(getTileUrl(coords, options)); - } -} - -class AssetTileProvider extends TileProvider { - const AssetTileProvider(); - - @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { - return AssetImage(getTileUrl(coords, options)); - } -} - -class CustomTileProvider extends TileProvider { - final String Function(Coords coors, TileLayerOptions options) customTileUrl; - - const CustomTileProvider({required this.customTileUrl}); - - @override - String getTileUrl(Coords coords, TileLayerOptions options) { - return customTileUrl(coords, options); - } - - @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { - return AssetImage(getTileUrl(coords, options)); - } -} diff --git a/lib/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart b/lib/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart index 564345245..0085d4236 100644 --- a/lib/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart +++ b/lib/src/layer/tile_layer/tile_provider/file_tile_provider_io.dart @@ -3,9 +3,9 @@ import 'dart:io'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -/// FileTileProvider +/// [TileProvider] that uses [FileImage] internally on platforms other than web class FileTileProvider extends TileProvider { - const FileTileProvider(); + FileTileProvider(); @override ImageProvider getImage(Coords coords, TileLayerOptions options) { diff --git a/lib/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart b/lib/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart index 31da2b069..0c842f4c1 100644 --- a/lib/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart +++ b/lib/src/layer/tile_layer/tile_provider/file_tile_provider_web.dart @@ -1,10 +1,12 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -/// FileTileProvider - +/// [TileProvider] that uses [NetworkImage] internally on the web +/// +/// Note that this is not recommended, as important headers cannot be passed. +/// Use [NetworkNoRetryTileProvider] if you know the platform is the web. class FileTileProvider extends TileProvider { - const FileTileProvider(); + FileTileProvider(); @override ImageProvider getImage(Coords coords, TileLayerOptions 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 new file mode 100644 index 000000000..42596d951 --- /dev/null +++ b/lib/src/layer/tile_layer/tile_provider/network_image_provider.dart @@ -0,0 +1,56 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; +import 'package:http/http.dart'; +import 'package:http/retry.dart'; + +class FMNetworkImageProvider extends ImageProvider { + /// The URL from which the image will be fetched. + final String url; + + /// The http RetryClient that is used for the requests + final RetryClient retryClient; + + /// Custom headers to add to the image fetch request + final Map headers; + + FMNetworkImageProvider( + this.url, { + RetryClient? retryClient, + this.headers = const {}, + }) : retryClient = retryClient ?? RetryClient(Client()); + + @override + ImageStreamCompleter load( + FMNetworkImageProvider key, DecoderCallback decode) { + return OneFrameImageStreamCompleter(_loadWithRetry(key, decode), + informationCollector: () sync* { + yield ErrorDescription('Image provider: $this'); + yield ErrorDescription('Image key: $key'); + }); + } + + @override + Future obtainKey(ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + Future _loadWithRetry( + FMNetworkImageProvider key, + DecoderCallback decode, + ) async { + assert(key == this); + + final uri = Uri.parse(url); + final response = await retryClient.get(uri, headers: headers); + + if (response.statusCode != 200) { + throw NetworkImageLoadException( + statusCode: response.statusCode, uri: uri); + } + + final codec = await decode(response.bodyBytes); + final image = (await codec.getNextFrame()).image; + + return ImageInfo(image: image); + } +} diff --git a/lib/src/layer/tile_layer/tile_provider/network_image_with_retry.dart b/lib/src/layer/tile_layer/tile_provider/network_image_with_retry.dart deleted file mode 100644 index 7689413a1..000000000 --- a/lib/src/layer/tile_layer/tile_provider/network_image_with_retry.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/painting.dart'; -import 'package:http/http.dart'; -import 'package:http/retry.dart'; - -class NetworkImageWithRetry extends ImageProvider { - /// The URL from which the image will be fetched. - final String url; - - /// The scale to place in the [ImageInfo] object of the image. - final double scale; - - /// The http RetryClient that is used for the requests - final RetryClient retryClient = RetryClient(Client()); - - NetworkImageWithRetry(this.url, {this.scale = 1.0}); - - @override - ImageStreamCompleter load(NetworkImageWithRetry key, DecoderCallback decode) { - return OneFrameImageStreamCompleter(_loadWithRetry(key, decode), - informationCollector: () sync* { - yield ErrorDescription('Image provider: $this'); - yield ErrorDescription('Image key: $key'); - }); - } - - @override - Future obtainKey(ImageConfiguration configuration) { - return SynchronousFuture(this); - } - - Future _loadWithRetry( - NetworkImageWithRetry key, DecoderCallback decode) async { - assert(key == this); - - final uri = Uri.parse(url); - final response = await retryClient.get(uri); - final codec = await decode(response.bodyBytes); - final image = (await codec.getNextFrame()).image; - - return ImageInfo( - image: image, - scale: key.scale, - ); - } -} 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 new file mode 100644 index 000000000..059add3bf --- /dev/null +++ b/lib/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart @@ -0,0 +1,100 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/painting.dart'; + +class FMNetworkNoRetryImageProvider + extends ImageProvider { + /// A valid URL, which is the location of the image to be fetched + final String url; + + /// The client which will be used to fetch the image + final HttpClient httpClient; + + /// Custom headers to add to the image fetch request + final Map headers; + + FMNetworkNoRetryImageProvider( + this.url, { + HttpClient? httpClient, + this.headers = const {}, + }) : httpClient = httpClient ?? HttpClient() + ..userAgent = null; + + @override + ImageStreamCompleter load( + FMNetworkNoRetryImageProvider key, + DecoderCallback decode, + ) { + //ignore: close_sinks + final StreamController chunkEvents = + StreamController(); + + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key: key, decode: decode, chunkEvents: chunkEvents), + chunkEvents: chunkEvents.stream, + scale: 1, + debugLabel: key.url, + informationCollector: () => [ + DiagnosticsProperty('Image provider', this), + DiagnosticsProperty('Image key', key), + ], + ); + } + + @override + Future obtainKey( + ImageConfiguration configuration) { + return SynchronousFuture(this); + } + + Future _loadAsync({ + required FMNetworkNoRetryImageProvider key, + required DecoderCallback decode, + required StreamController chunkEvents, + }) async { + try { + assert(key == this); + + final Uri resolved = Uri.base.resolve(key.url); + + final HttpClientRequest request = await httpClient.getUrl(resolved); + + headers.forEach((String name, String value) { + request.headers.add(name, value); + }); + + final HttpClientResponse response = await request.close(); + if (response.statusCode != HttpStatus.ok) { + await response.drain>([]); + throw NetworkImageLoadException( + statusCode: response.statusCode, uri: resolved); + } + + final Uint8List bytes = await consolidateHttpClientResponseBytes( + response, + onBytesReceived: (int cumulative, int? total) { + chunkEvents.add(ImageChunkEvent( + cumulativeBytesLoaded: cumulative, + expectedTotalBytes: total, + )); + }, + ); + if (bytes.lengthInBytes == 0) { + throw Exception('NetworkImage is an empty file: $resolved'); + } + + return decode(bytes); + } catch (e) { + scheduleMicrotask(() { + PaintingBinding.instance.imageCache.evict(key); + }); + rethrow; + } finally { + chunkEvents.close(); + } + } +} 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 new file mode 100644 index 000000000..f1e126af6 --- /dev/null +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_io.dart @@ -0,0 +1,123 @@ +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:http/http.dart'; +import 'package:http/retry.dart'; + +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_provider.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_no_retry_image_provider.dart'; + +/// [TileProvider] that uses [FMNetworkImageProvider] internally +/// +/// 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 the 'User-Agent' header and the [RetryClient] cannot be changed, on the web platform. +class NetworkTileProvider extends TileProvider { + NetworkTileProvider({ + Map? headers, + RetryClient? retryClient, + }) { + this.headers = headers ?? {}; + this.retryClient = retryClient ?? RetryClient(Client()); + } + + late final RetryClient retryClient; + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + HttpOverrides.runZoned( + () => FMNetworkImageProvider( + getTileUrl(coords, options), + headers: headers, + retryClient: retryClient, + ), + createHttpClient: (c) => _FlutterMapHTTPOverrides().createHttpClient(c), + ); +} + +/// [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. +/// +/// Note that the 'User-Agent' header and the [HttpClient] cannot be changed, on the web platform. +class NetworkNoRetryTileProvider extends TileProvider { + NetworkNoRetryTileProvider({ + Map? headers, + HttpClient? httpClient, + }) { + this.headers = headers ?? {}; + this.httpClient = httpClient ?? HttpClient() + ..userAgent = null; + } + + late final HttpClient httpClient; + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + FMNetworkNoRetryImageProvider( + getTileUrl(coords, options), + headers: headers, + httpClient: httpClient, + ); +} + +/// Deprecated due to internal refactoring. The name is misleading, as the internal [ImageProvider] always caches, and this is recommended by most tile servers anyway. For the same functionality, migrate to [NetworkNoRetryTileProvider] before the next minor update. +@Deprecated( + '`NonCachingNetworkTileProvider` has been deprecated due to internal refactoring. The name is misleading, as the internal `ImageProvider` always caches, and this is recommended by most tile servers anyway. For the same functionality, migrate to `NetworkNoRetryTileProvider` before the next minor update.') +class NonCachingNetworkTileProvider extends TileProvider { + NonCachingNetworkTileProvider({ + Map? headers, + HttpClient? httpClient, + }) { + this.headers = headers ?? {}; + this.httpClient = httpClient ?? HttpClient() + ..userAgent = null; + } + + late final HttpClient httpClient; + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + NetworkNoRetryTileProvider( + headers: headers, + httpClient: httpClient, + ).getImage(coords, options); +} + +class AssetTileProvider extends TileProvider { + AssetTileProvider(); + + @override + ImageProvider getImage(Coords coords, TileLayerOptions 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. +class CustomTileProvider extends TileProvider { + final String Function(Coords coors, TileLayerOptions options) customTileUrl; + + CustomTileProvider({required this.customTileUrl}); + + @override + String getTileUrl(Coords coords, TileLayerOptions options) { + return customTileUrl(coords, options); + } + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) { + return AssetImage(getTileUrl(coords, options)); + } +} + +class _FlutterMapHTTPOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context)..userAgent = null; + } +} 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 new file mode 100644 index 000000000..b4ed8cd47 --- /dev/null +++ b/lib/src/layer/tile_layer/tile_provider/tile_provider_web.dart @@ -0,0 +1,94 @@ +import 'package:flutter/widgets.dart'; +import 'package:http/retry.dart'; + +import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/layer/tile_layer/tile_provider/network_image_provider.dart'; + +/// [TileProvider] that uses [FMNetworkImageProvider] internally +/// +/// 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 the 'User-Agent' header and the `RetryClient` cannot be changed, on the web platform. +class NetworkTileProvider extends TileProvider { + NetworkTileProvider({ + Map? headers, + }) { + this.headers = headers ?? {}; + } + + late final RetryClient retryClient; + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + FMNetworkImageProvider( + getTileUrl(coords, options), + headers: headers..remove('User-Agent'), + ); +} + +/// [TileProvider] that uses [NetworkImage] 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. +/// +/// Note that the 'User-Agent' header and the `HttpClient` cannot be changed, on the web platform. +class NetworkNoRetryTileProvider extends TileProvider { + NetworkNoRetryTileProvider({ + Map? headers, + }) { + this.headers = headers ?? {}; + } + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + NetworkImage( + getTileUrl(coords, options), + headers: headers..remove('User-Agent'), + ); +} + +/// Deprecated due to internal refactoring. The name is misleading, as the internal [ImageProvider] always caches, and this is recommended by most tile servers anyway. For the same functionality, migrate to [NetworkNoRetryTileProvider] before the next minor update. +@Deprecated( + '`NonCachingNetworkTileProvider` has been deprecated due to internal refactoring. The name is misleading, as the internal `ImageProvider` always caches, and this is recommended by most tile servers anyway. For the same functionality, migrate to `NetworkNoRetryTileProvider` before the next minor update.') +class NonCachingNetworkTileProvider extends TileProvider { + NonCachingNetworkTileProvider({ + Map? headers, + }) { + this.headers = headers ?? {}; + } + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) => + NetworkNoRetryTileProvider( + headers: headers, + ).getImage(coords, options); +} + +class AssetTileProvider extends TileProvider { + AssetTileProvider(); + + @override + ImageProvider getImage(Coords coords, TileLayerOptions 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. +class CustomTileProvider extends TileProvider { + final String Function(Coords coors, TileLayerOptions options) customTileUrl; + + CustomTileProvider({required this.customTileUrl}); + + @override + String getTileUrl(Coords coords, TileLayerOptions options) { + return customTileUrl(coords, options); + } + + @override + ImageProvider getImage(Coords coords, TileLayerOptions options) { + return AssetImage(getTileUrl(coords, options)); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 476bed06c..17d0cc92d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_map description: A versatile mapping package for Flutter, based off leaflet.js, that's simple and easy to learn, yet completely customizable and configurable. -version: 1.1.1 +version: 2.0.0 repository: https://github.com/fleaflet/flutter_map documentation: https://docs.fleaflet.dev