From ad72887a2fef83868468581dd8ebe82500fac96d Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 18:35:59 -0500 Subject: [PATCH 01/32] bump dart 2.17; remove layeroptions --- lib/flutter_map.dart | 18 ---------- lib/src/layer/layer.dart | 11 ------- lib/src/map/flutter_map_state.dart | 53 ------------------------------ pubspec.yaml | 2 +- 4 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 lib/src/layer/layer.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index afbeb2329..0ef4f4a8c 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -15,7 +15,6 @@ import 'package:flutter_map/src/geo/latlng_bounds.dart'; import 'package:flutter_map/src/gestures/interactive_flag.dart'; import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; -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'; @@ -30,7 +29,6 @@ export 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; export 'package:flutter_map/src/layer/attribution_layer.dart'; export 'package:flutter_map/src/layer/circle_layer.dart'; export 'package:flutter_map/src/layer/group_layer.dart'; -export 'package:flutter_map/src/layer/layer.dart'; export 'package:flutter_map/src/layer/marker_layer.dart'; export 'package:flutter_map/src/layer/overlay_image_layer.dart'; export 'package:flutter_map/src/layer/polygon_layer.dart'; @@ -52,20 +50,6 @@ export 'package:flutter_map/src/plugins/plugin.dart'; /// /// Through [MapOptions] map's callbacks and properties can be defined. class FlutterMap extends StatefulWidget { - /// A set of layers' options to used to create the layers on the map. - /// - /// Usually a list of [TileLayerOptions], [MarkerLayerOptions] and - /// [PolylineLayerOptions]. - /// - /// These layers will render above [children] - final List layers; - - /// These layers won't be rotated. - /// Usually these are plugins which are floating above [layers] - /// - /// These layers will render above [nonRotatedChildren] - final List nonRotatedLayers; - /// A set of layers' widgets to used to create the layers on the map. final List children; @@ -85,8 +69,6 @@ class FlutterMap extends StatefulWidget { const FlutterMap({ Key? key, required this.options, - this.layers = const [], - this.nonRotatedLayers = const [], this.children = const [], this.nonRotatedChildren = const [], this.mapController, diff --git a/lib/src/layer/layer.dart b/lib/src/layer/layer.dart deleted file mode 100644 index 8863b8041..000000000 --- a/lib/src/layer/layer.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/foundation.dart'; - -/// Common type between all LayerOptions. -/// -/// All LayerOptions have access to a stream that notifies when the map needs -/// rebuilding. -class LayerOptions { - final Key? key; - final Stream? rebuild; - LayerOptions({this.key, this.rebuild}); -} diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index e953f8308..52bf759e0 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'package:async/async.dart'; import 'package:flutter/gestures.dart'; @@ -63,16 +62,6 @@ class FlutterMapState extends MapGestureMixin super.dispose(); } - Stream _merge(LayerOptions options) { - if (options.rebuild == null) return mapState.onMoved; - - final group = StreamGroup(); - group.add(mapState.onMoved); - group.add(options.rebuild!); - groups.add(group); - return group.stream; - } - @override Widget build(BuildContext context) { _disposeStreamGroups(); @@ -178,10 +167,6 @@ class FlutterMapState extends MapGestureMixin child: Stack( children: [ if (widget.children.isNotEmpty) ...widget.children, - if (widget.layers.isNotEmpty) - ...widget.layers.map( - (layer) => _createLayer(layer, options.plugins), - ) ], ), ), @@ -190,10 +175,6 @@ class FlutterMapState extends MapGestureMixin children: [ if (widget.nonRotatedChildren.isNotEmpty) ...widget.nonRotatedChildren, - if (widget.nonRotatedLayers.isNotEmpty) - ...widget.nonRotatedLayers.map( - (layer) => _createLayer(layer, options.plugins), - ) ], ), ], @@ -201,40 +182,6 @@ class FlutterMapState extends MapGestureMixin ); } - Widget _createLayer(LayerOptions options, List plugins) { - for (final plugin in plugins) { - if (plugin.supportsLayer(options)) { - return plugin.createLayer(options, mapState, _merge(options)); - } - } - if (options is TileLayerOptions) { - return TileLayer( - options: options, mapState: mapState, stream: _merge(options)); - } - if (options is MarkerLayerOptions) { - return MarkerLayer(options, mapState, _merge(options)); - } - if (options is PolylineLayerOptions) { - return PolylineLayer(options, mapState, _merge(options)); - } - if (options is PolygonLayerOptions) { - return PolygonLayer(options, mapState, _merge(options)); - } - if (options is CircleLayerOptions) { - return CircleLayer(options, mapState, _merge(options)); - } - if (options is GroupLayerOptions) { - return GroupLayer(options, mapState, _merge(options)); - } - if (options is OverlayImageLayerOptions) { - return OverlayImageLayer(options, mapState, _merge(options)); - } - throw (StateError(""" -Can't find correct layer for $options. Perhaps when you create your FlutterMap you need something like this: - - options: new MapOptions(plugins: [MyFlutterMapPlugin()])""")); - } - @override bool get wantKeepAlive => options.keepAlive; } diff --git a/pubspec.yaml b/pubspec.yaml index 96f37111a..cbf394a05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ repository: https://github.com/fleaflet/flutter_map documentation: https://docs.fleaflet.dev environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=2.0.0" dependencies: From ecaafed986e29a1230de44e720431d9f6e4b3932 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:01:26 -0500 Subject: [PATCH 02/32] basic migration of built in layers. Optimizations can be done --- lib/flutter_map.dart | 1 - lib/src/layer/circle_layer.dart | 34 +--- lib/src/layer/group_layer.dart | 69 --------- lib/src/layer/marker_layer.dart | 146 ++++++++---------- lib/src/layer/overlay_image_layer.dart | 46 ++---- lib/src/layer/polygon_layer.dart | 39 +---- lib/src/layer/polyline_layer.dart | 40 +---- lib/src/layer/tile_layer/tile_layer.dart | 98 ++++++------ .../layer/tile_layer/tile_layer_options.dart | 7 +- 9 files changed, 148 insertions(+), 332 deletions(-) delete mode 100644 lib/src/layer/group_layer.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 0ef4f4a8c..767d39b08 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -28,7 +28,6 @@ export 'package:flutter_map/src/gestures/map_events.dart'; export 'package:flutter_map/src/gestures/multi_finger_gesture.dart'; export 'package:flutter_map/src/layer/attribution_layer.dart'; export 'package:flutter_map/src/layer/circle_layer.dart'; -export 'package:flutter_map/src/layer/group_layer.dart'; export 'package:flutter_map/src/layer/marker_layer.dart'; export 'package:flutter_map/src/layer/overlay_image_layer.dart'; export 'package:flutter_map/src/layer/polygon_layer.dart'; diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index bf790980f..7bccf9441 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -1,15 +1,12 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; -class CircleLayerOptions extends LayerOptions { +class CircleLayerOptions { final List circles; CircleLayerOptions({ - Key? key, this.circles = const [], - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); + }); } class CircleMarker { @@ -33,22 +30,7 @@ class CircleMarker { class CircleLayerWidget extends StatelessWidget { final CircleLayerOptions options; - - const CircleLayerWidget({Key? key, required this.options}) : super(key: key); - - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return CircleLayer(options, mapState, mapState.onMoved); - } -} - -class CircleLayer extends StatelessWidget { - final CircleLayerOptions circleOpts; - final MapState map; - final Stream? stream; - CircleLayer(this.circleOpts, this.map, this.stream) - : super(key: circleOpts.key); + const CircleLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { @@ -61,11 +43,9 @@ class CircleLayer extends StatelessWidget { } Widget _build(BuildContext context, Size size) { - return StreamBuilder( - stream: stream, // a Stream or null - builder: (BuildContext context, _) { - final circleWidgets = []; - for (final circle in circleOpts.circles) { + final map = MapState.maybeOf(context)!; + final circleWidgets = []; + for (final circle in options.circles) { circle.offset = map.getOffsetFromOrigin(circle.point); if (circle.useRadiusInMeter) { @@ -85,8 +65,6 @@ class CircleLayer extends StatelessWidget { return Stack( children: circleWidgets, ); - }, - ); } } diff --git a/lib/src/layer/group_layer.dart b/lib/src/layer/group_layer.dart deleted file mode 100644 index 319bfef40..000000000 --- a/lib/src/layer/group_layer.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/map.dart'; - -/// [LayerOptions] that describe a layer composed by multiple built-in layers. -class GroupLayerOptions extends LayerOptions { - List group = []; - - GroupLayerOptions({ - Key? key, - this.group = const [], - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); -} - -class GroupLayerWidget extends StatelessWidget { - final GroupLayerOptions options; - - const GroupLayerWidget({Key? key, required this.options}) : super(key: key); - - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return GroupLayer(options, mapState, mapState.onMoved); - } -} - -class GroupLayer extends StatelessWidget { - final GroupLayerOptions groupOpts; - final MapState map; - final Stream stream; - - GroupLayer(this.groupOpts, this.map, this.stream) : super(key: groupOpts.key); - - @override - Widget build(BuildContext context) { - return StreamBuilder( - stream: stream, - builder: (BuildContext context, _) { - final layers = [ - for (var options in groupOpts.group) _createLayer(options) - ]; - - return Stack( - children: layers, - ); - }, - ); - } - - Widget _createLayer(LayerOptions options) { - if (options is MarkerLayerOptions) { - return MarkerLayer(options, map, options.rebuild); - } - if (options is CircleLayerOptions) { - return CircleLayer(options, map, options.rebuild); - } - if (options is PolylineLayerOptions) { - return PolylineLayer(options, map, options.rebuild); - } - if (options is PolygonLayerOptions) { - return PolygonLayer(options, map, options.rebuild); - } - if (options is OverlayImageLayerOptions) { - return OverlayImageLayer(options, map, options.rebuild); - } - throw Exception('Unknown options type for GeometryLayer: $options'); - } -} diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 26b2992b3..c86e8d972 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -5,7 +5,7 @@ import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; /// Configuration for marker layer -class MarkerLayerOptions extends LayerOptions { +class MarkerLayerOptions { final List markers; /// Toggle marker position caching. Enabling will improve performance, but may introducen @@ -37,14 +37,12 @@ class MarkerLayerOptions extends LayerOptions { final AlignmentGeometry? rotateAlignment; MarkerLayerOptions({ - Key? key, this.markers = const [], this.rotate = false, this.rotateOrigin, this.rotateAlignment = Alignment.center, this.usePxCache = true, - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); + }); } class Anchor { @@ -169,22 +167,21 @@ class Marker { class MarkerLayerWidget extends StatelessWidget { final MarkerLayerOptions options; - const MarkerLayerWidget({Key? key, required this.options}) : super(key: key); + const MarkerLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { final mapState = MapState.maybeOf(context)!; - return MarkerLayer(options, mapState, mapState.onMoved); + return MarkerLayer(key: key, markerLayerOptions: options, map: mapState); } } class MarkerLayer extends StatefulWidget { final MarkerLayerOptions markerLayerOptions; final MapState map; - final Stream? stream; - MarkerLayer(this.markerLayerOptions, this.map, this.stream) - : super(key: markerLayerOptions.key); + const MarkerLayer( + {super.key, required this.markerLayerOptions, required this.map}); @override State createState() => _MarkerLayerState(); @@ -240,75 +237,68 @@ class _MarkerLayerState extends State { @override Widget build(BuildContext context) { - return StreamBuilder( - stream: widget.stream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - final layerOptions = widget.markerLayerOptions; - final map = widget.map; - final usePxCache = layerOptions.usePxCache; - final markers = []; - final sameZoom = map.zoom == lastZoom; - - final cacheUpdated = updatePxCacheIfNeeded(); - - for (var i = 0; i < layerOptions.markers.length; i++) { - final marker = layerOptions.markers[i]; - - // Decide whether to use cached point or calculate it - final pxPoint = usePxCache && (sameZoom || cacheUpdated) - ? _pxCache[i] - : map.project(marker.point); - if (!sameZoom && usePxCache) { - _pxCache[i] = pxPoint; - } - - // See if any portion of the Marker rect resides in the map bounds - // If not, don't spend any resources on build function. - // This calculation works for any Anchor position whithin the Marker - // Note that Anchor coordinates of (0,0) are at bottom-right of the Marker - // unlike the map coordinates. - final rightPortion = marker.width - marker.anchor.left; - final leftPortion = marker.anchor.left; - final bottomPortion = marker.height - marker.anchor.top; - final topPortion = marker.anchor.top; - - final sw = - CustomPoint(pxPoint.x + leftPortion, pxPoint.y - bottomPortion); - final ne = - CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion); - - if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { - continue; - } - - final pos = pxPoint - map.getPixelOrigin(); - final markerWidget = (marker.rotate ?? layerOptions.rotate ?? false) - // Counter rotated marker to the map rotation - ? Transform.rotate( - angle: -map.rotationRad, - origin: marker.rotateOrigin ?? layerOptions.rotateOrigin, - alignment: - marker.rotateAlignment ?? layerOptions.rotateAlignment, - child: marker.builder(context), - ) - : marker.builder(context); - - markers.add( - Positioned( - key: marker.key, - width: marker.width, - height: marker.height, - left: pos.x - rightPortion, - top: pos.y - bottomPortion, - child: markerWidget, - ), - ); - } - lastZoom = map.zoom; - return Stack( - children: markers, - ); - }, + final layerOptions = widget.markerLayerOptions; + final map = widget.map; + final usePxCache = layerOptions.usePxCache; + final markers = []; + final sameZoom = map.zoom == lastZoom; + + final cacheUpdated = updatePxCacheIfNeeded(); + + for (var i = 0; i < layerOptions.markers.length; i++) { + final marker = layerOptions.markers[i]; + + // Decide whether to use cached point or calculate it + final pxPoint = usePxCache && (sameZoom || cacheUpdated) + ? _pxCache[i] + : map.project(marker.point); + if (!sameZoom && usePxCache) { + _pxCache[i] = pxPoint; + } + + // See if any portion of the Marker rect resides in the map bounds + // If not, don't spend any resources on build function. + // This calculation works for any Anchor position whithin the Marker + // Note that Anchor coordinates of (0,0) are at bottom-right of the Marker + // unlike the map coordinates. + final rightPortion = marker.width - marker.anchor.left; + final leftPortion = marker.anchor.left; + final bottomPortion = marker.height - marker.anchor.top; + final topPortion = marker.anchor.top; + + final sw = + CustomPoint(pxPoint.x + leftPortion, pxPoint.y - bottomPortion); + final ne = CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion); + + if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { + continue; + } + + final pos = pxPoint - map.getPixelOrigin(); + final markerWidget = (marker.rotate ?? layerOptions.rotate ?? false) + // Counter rotated marker to the map rotation + ? Transform.rotate( + angle: -map.rotationRad, + origin: marker.rotateOrigin ?? layerOptions.rotateOrigin, + alignment: marker.rotateAlignment ?? layerOptions.rotateAlignment, + child: marker.builder(context), + ) + : marker.builder(context); + + markers.add( + Positioned( + key: marker.key, + width: marker.width, + height: marker.height, + left: pos.x - rightPortion, + top: pos.y - bottomPortion, + child: markerWidget, + ), + ); + } + lastZoom = map.zoom; + return Stack( + children: markers, ); } } diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 49dfdac64..4411cc564 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -1,19 +1,15 @@ -import 'dart:async'; - import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:flutter_map/src/core/bounds.dart'; import 'package:latlong2/latlong.dart'; -class OverlayImageLayerOptions extends LayerOptions { +class OverlayImageLayerOptions { final List overlayImages; OverlayImageLayerOptions({ - Key? key, this.overlayImages = const [], - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); + }); } /// Base class for all overlay images. @@ -145,40 +141,20 @@ class RotatedOverlayImage extends BaseOverlayImage { } class OverlayImageLayerWidget extends StatelessWidget { - final OverlayImageLayerOptions options; - - const OverlayImageLayerWidget({Key? key, required this.options}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return OverlayImageLayer(options, mapState, mapState.onMoved); - } -} - -class OverlayImageLayer extends StatelessWidget { final OverlayImageLayerOptions overlayImageOpts; - final MapState map; - final Stream? stream; - OverlayImageLayer(this.overlayImageOpts, this.map, this.stream) - : super(key: overlayImageOpts.key); + const OverlayImageLayerWidget({super.key, required this.overlayImageOpts}); @override Widget build(BuildContext context) { - return StreamBuilder( - stream: stream, - builder: (BuildContext context, _) { - return ClipRect( - child: Stack( - children: [ - for (var overlayImage in overlayImageOpts.overlayImages) - overlayImage.buildPositionedForOverlay(map), - ], - ), - ); - }, + final map = MapState.maybeOf(context)!; + return ClipRect( + child: Stack( + children: [ + for (var overlayImage in overlayImageOpts.overlayImages) + overlayImage.buildPositionedForOverlay(map), + ], + ), ); } } diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index daaccb812..dc2c603fe 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -6,17 +6,15 @@ import 'package:flutter_map/src/layer/label.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI -class PolygonLayerOptions extends LayerOptions { +class PolygonLayerOptions { final List polygons; final bool polygonCulling; /// screen space culling of polygons based on bounding box PolygonLayerOptions({ - Key? key, this.polygons = const [], this.polygonCulling = false, - Stream? rebuild, - }) : super(key: key, rebuild: rebuild) { + }) { if (polygonCulling) { for (final polygon in polygons) { polygon.boundingBox = LatLngBounds.fromPoints(polygon.points); @@ -68,38 +66,16 @@ class Polygon { } class PolygonLayerWidget extends StatelessWidget { - final PolygonLayerOptions options; - const PolygonLayerWidget({Key? key, required this.options}) : super(key: key); - - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return PolygonLayer(options, mapState, mapState.onMoved); - } -} - -class PolygonLayer extends StatelessWidget { final PolygonLayerOptions polygonOpts; - final MapState map; - final Stream? stream; - PolygonLayer(this.polygonOpts, this.map, this.stream) - : super(key: polygonOpts.key); + const PolygonLayerWidget({super.key, required this.polygonOpts}); @override Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { + final map = MapState.maybeOf(context)!; final size = Size(bc.maxWidth, bc.maxHeight); - return _build(context, size); - }, - ); - } - - Widget _build(BuildContext context, Size size) { - return StreamBuilder( - stream: stream, // a Stream or null - builder: (BuildContext context, _) { final polygons = []; for (final polygon in polygonOpts.polygons) { @@ -117,13 +93,13 @@ class PolygonLayer extends StatelessWidget { continue; } - _fillOffsets(polygon.offsets, polygon.points); + _fillOffsets(polygon.offsets, polygon.points, map); if (null != polygon.holePointsList) { final len = polygon.holePointsList!.length; for (var i = 0; i < len; ++i) { _fillOffsets( - polygon.holeOffsetsList![i], polygon.holePointsList![i]); + polygon.holeOffsetsList![i], polygon.holePointsList![i], map); } } @@ -142,7 +118,8 @@ class PolygonLayer extends StatelessWidget { ); } - void _fillOffsets(final List offsets, final List points) { + void _fillOffsets( + final List offsets, final List points, MapState map) { final len = points.length; for (var i = 0; i < len; ++i) { final point = points[i]; diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index e925d8762..9a50e56f4 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -6,7 +6,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; -class PolylineLayerOptions extends LayerOptions { +class PolylineLayerOptions { /// List of polylines to draw. final List polylines; @@ -23,12 +23,10 @@ class PolylineLayerOptions extends LayerOptions { final bool saveLayers; PolylineLayerOptions({ - Key? key, this.polylines = const [], this.polylineCulling = false, - Stream? rebuild, this.saveLayers = false, - }) : super(key: key, rebuild: rebuild) { + }) { if (polylineCulling) { for (final polyline in polylines) { polyline.boundingBox = LatLngBounds.fromPoints(polyline.points); @@ -65,41 +63,18 @@ class Polyline { }); } -class PolylineLayerWidget extends StatelessWidget { - final PolylineLayerOptions options; - - const PolylineLayerWidget({Key? key, required this.options}) - : super(key: key); - - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return PolylineLayer(options, mapState, mapState.onMoved); - } -} -class PolylineLayer extends StatelessWidget { +class PolylineLayerWidget extends StatelessWidget { final PolylineLayerOptions polylineOpts; - final MapState map; - final Stream? stream; - PolylineLayer(this.polylineOpts, this.map, this.stream) - : super(key: polylineOpts.key); + const PolylineLayerWidget({super.key, required this.polylineOpts}); @override Widget build(BuildContext context) { + final map = MapState.maybeOf(context)!; return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); - return _build(context, size); - }, - ); - } - - Widget _build(BuildContext context, Size size) { - return StreamBuilder( - stream: stream, // a Stream or null - builder: (BuildContext context, _) { final polylines = []; for (final polylineOpt in polylineOpts.polylines) { @@ -111,7 +86,7 @@ class PolylineLayer extends StatelessWidget { continue; } - _fillOffsets(polylineOpt.offsets, polylineOpt.points); + _fillOffsets(polylineOpt.offsets, polylineOpt.points, map); polylines.add(CustomPaint( painter: PolylinePainter(polylineOpt, polylineOpts.saveLayers), @@ -126,7 +101,8 @@ class PolylineLayer extends StatelessWidget { ); } - void _fillOffsets(final List offsets, final List points) { + void _fillOffsets( + final List offsets, final List points, MapState map) { final len = points.length; for (var i = 0; i < len; ++i) { final point = points[i]; diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index ff246fd93..ee6286f07 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -26,10 +26,8 @@ class TileLayerWidget extends StatelessWidget { @override Widget build(BuildContext context) { final mapState = MapState.maybeOf(context)!; - return TileLayer( mapState: mapState, - stream: mapState.onMoved, options: options, ); } @@ -38,13 +36,12 @@ class TileLayerWidget extends StatelessWidget { class TileLayer extends StatefulWidget { final TileLayerOptions options; final MapState mapState; - final Stream stream; - TileLayer({ + const TileLayer({ + super.key, required this.options, required this.mapState, - required this.stream, - }) : super(key: options.key); + }); @override State createState() => _TileLayerState(); @@ -77,7 +74,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { _tileSize = CustomPoint(options.tileSize, options.tileSize); _resetView(); _update(null); - _moveSub = widget.stream.listen((_) => _handleMove()); + _moveSub = widget.mapState.onMoved.listen((_) => _handleMove()); if (options.reset != null) { _resetSub = options.reset?.listen((_) => _resetTiles()); @@ -163,52 +160,47 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - return StreamBuilder( - stream: widget.stream, - builder: (context, snapshot) { - final tilesToRender = _tileZoom == null - ? _tileManager.all() - : _tileManager.sortedByDistanceToZoomAscending( - options.maxZoom, _tileZoom!); - final Map zoomToTransformation = {}; - - final tileWidgets = [ - for (var tile in tilesToRender) - TileWidget( - tile: tile, - size: _tileSize, - tileTransformation: zoomToTransformation[tile.coords.z] ?? - (zoomToTransformation[tile.coords.z] = - _transformationCalculator.transformationFor( - tile.coords.z, - map, - )), - errorImage: options.errorImage, - tileBuilder: options.tileBuilder, - key: ValueKey(tile.coordsKey), - ) - ]; - - final tilesContainer = Stack( - children: tileWidgets, - ); - - final tilesLayer = options.tilesContainerBuilder == null - ? tilesContainer - : options.tilesContainerBuilder!( - context, - tilesContainer, - tilesToRender, - ); - - return Opacity( - opacity: options.opacity, - child: Container( - color: options.backgroundColor, - child: tilesLayer, - ), - ); - }, + final tilesToRender = _tileZoom == null + ? _tileManager.all() + : _tileManager.sortedByDistanceToZoomAscending( + options.maxZoom, _tileZoom!); + final Map zoomToTransformation = {}; + + final tileWidgets = [ + for (var tile in tilesToRender) + TileWidget( + tile: tile, + size: _tileSize, + tileTransformation: zoomToTransformation[tile.coords.z] ?? + (zoomToTransformation[tile.coords.z] = + _transformationCalculator.transformationFor( + tile.coords.z, + map, + )), + errorImage: options.errorImage, + tileBuilder: options.tileBuilder, + key: ValueKey(tile.coordsKey), + ) + ]; + + final tilesContainer = Stack( + children: tileWidgets, + ); + + final tilesLayer = options.tilesContainerBuilder == null + ? tilesContainer + : options.tilesContainerBuilder!( + context, + tilesContainer, + tilesToRender, + ); + + return Opacity( + opacity: options.opacity, + child: Container( + color: options.backgroundColor, + 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 af76b482d..7fdcb32e2 100644 --- a/lib/src/layer/tile_layer/tile_layer_options.dart +++ b/lib/src/layer/tile_layer/tile_layer_options.dart @@ -24,7 +24,7 @@ typedef ErrorTileCallBack = void Function(Tile tile, dynamic error); /// 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 { +class TileLayerOptions { /// 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 @@ -237,7 +237,6 @@ class TileLayerOptions extends LayerOptions { LatLngBounds? tileBounds; TileLayerOptions({ - Key? key, this.urlTemplate, double tileSize = 256.0, double minZoom = 0.0, @@ -268,7 +267,6 @@ class TileLayerOptions extends LayerOptions { this.overrideTilesWhenUrlChanges = false, this.retinaMode = false, this.errorTileCallback, - Stream? rebuild, this.templateFunction = util.template, this.tileBuilder, this.tilesContainerBuilder, @@ -310,8 +308,7 @@ class TileLayerOptions extends LayerOptions { ...tileProvider.headers, if (!tileProvider.headers.containsKey('User-Agent')) 'User-Agent': 'flutter_map ($userAgentPackageName)', - }), - super(key: key, rebuild: rebuild); + }); } class WMSTileLayerOptions { From 4fc999c62377fab0f5b1ae12a6a36335ee2e0337 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:02:29 -0500 Subject: [PATCH 03/32] remove plugins because they are useless now --- lib/flutter_map.dart | 4 ---- lib/src/plugins/plugin.dart | 9 --------- 2 files changed, 13 deletions(-) delete mode 100644 lib/src/plugins/plugin.dart diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 767d39b08..c94766c7f 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -17,7 +17,6 @@ import 'package:flutter_map/src/gestures/map_events.dart'; import 'package:flutter_map/src/gestures/multi_finger_gesture.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'; export 'package:flutter_map/src/core/center_zoom.dart'; export 'package:flutter_map/src/core/point.dart'; @@ -41,7 +40,6 @@ export 'package:flutter_map/src/layer/tile_layer/tile_provider/file_tile_provide 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' 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]. /// @@ -257,7 +255,6 @@ class MapOptions { final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; final MapCreatedCallback? onMapCreated; - final List plugins; final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; @@ -316,7 +313,6 @@ class MapOptions { this.onPointerHover, this.onPositionChanged, this.onMapCreated, - this.plugins = const [], this.slideOnBoundaries = false, this.adaptiveBoundaries = false, this.screenSize, diff --git a/lib/src/plugins/plugin.dart b/lib/src/plugins/plugin.dart deleted file mode 100644 index 04062082e..000000000 --- a/lib/src/plugins/plugin.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/layer/layer.dart'; -import 'package:flutter_map/src/map/map.dart'; - -abstract class MapPlugin { - bool supportsLayer(LayerOptions options); - Widget createLayer( - LayerOptions options, MapState mapState, Stream stream); -} From d2455b93e929a1eed4b7cf5959be179d07a61eac Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:41:29 -0500 Subject: [PATCH 04/32] migrate example app to new plugin --- example/lib/main.dart | 2 - .../lib/pages/animated_map_controller.dart | 8 +- example/lib/pages/circle.dart | 8 +- example/lib/pages/custom_crs/custom_crs.dart | 6 +- example/lib/pages/epsg3413_crs.dart | 16 ++-- example/lib/pages/epsg4326_crs.dart | 6 +- example/lib/pages/esri.dart | 6 +- example/lib/pages/home.dart | 18 ++-- example/lib/pages/interactive_test_page.dart | 6 +- example/lib/pages/latlng_to_screen_point.dart | 6 +- example/lib/pages/live_location.dart | 10 ++- example/lib/pages/many_markers.dart | 10 +-- example/lib/pages/map_controller.dart | 8 +- example/lib/pages/map_inside_listview.dart | 12 +-- example/lib/pages/marker_anchor.dart | 10 ++- example/lib/pages/marker_rotate.dart | 10 +-- example/lib/pages/max_bounds.dart | 6 +- example/lib/pages/moving_markers.dart | 8 +- example/lib/pages/network_tile_provider.dart | 8 +- example/lib/pages/offline_map.dart | 6 +- example/lib/pages/on_tap.dart | 10 ++- example/lib/pages/overlay_image.dart | 16 ++-- example/lib/pages/plugin_api.dart | 82 ------------------- example/lib/pages/plugin_scalebar.dart | 27 +++--- example/lib/pages/plugin_zoombuttons.dart | 26 +++--- example/lib/pages/polygon.dart | 12 +-- example/lib/pages/polyline.dart | 24 +++--- example/lib/pages/reset_tile_layer.dart | 8 +- .../lib/pages/scale_layer_plugin_option.dart | 52 ++---------- example/lib/pages/sliding_map.dart | 6 +- example/lib/pages/stateful_markers.dart | 8 +- example/lib/pages/tap_to_add.dart | 8 +- example/lib/pages/tile_builder_example.dart | 10 +-- .../lib/pages/tile_loading_error_handle.dart | 6 +- example/lib/pages/widgets.dart | 16 ++-- example/lib/pages/wms_tile_layer.dart | 6 +- .../lib/pages/zoombuttons_plugin_option.dart | 28 +------ example/lib/test_app.dart | 6 +- example/lib/widgets/drawer.dart | 7 -- example/pubspec.yaml | 2 +- lib/src/layer/overlay_image_layer.dart | 6 +- lib/src/layer/polygon_layer.dart | 8 +- lib/src/layer/polyline_layer.dart | 10 +-- lib/src/layer/tile_layer/tile_layer.dart | 2 +- 44 files changed, 207 insertions(+), 349 deletions(-) delete mode 100644 example/lib/pages/plugin_api.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index d540dbc0c..f72ec1760 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -21,7 +21,6 @@ import 'package:flutter_map_example/pages/network_tile_provider.dart'; import 'package:flutter_map_example/pages/offline_map.dart'; import 'package:flutter_map_example/pages/on_tap.dart'; import 'package:flutter_map_example/pages/overlay_image.dart'; -import 'package:flutter_map_example/pages/plugin_api.dart'; import 'package:flutter_map_example/pages/plugin_scalebar.dart'; import 'package:flutter_map_example/pages/plugin_zoombuttons.dart'; import 'package:flutter_map_example/pages/point_to_latlng.dart'; @@ -61,7 +60,6 @@ class MyApp extends StatelessWidget { AnimatedMapControllerPage.route: (context) => const AnimatedMapControllerPage(), MarkerAnchorPage.route: (context) => const MarkerAnchorPage(), - PluginPage.route: (context) => const PluginPage(), PluginScaleBar.route: (context) => const PluginScaleBar(), PluginZoomButtons.route: (context) => const PluginZoomButtons(), OfflineMapPage.route: (context) => const OfflineMapPage(), diff --git a/example/lib/pages/animated_map_controller.dart b/example/lib/pages/animated_map_controller.dart index 1c661f193..537da904d 100644 --- a/example/lib/pages/animated_map_controller.dart +++ b/example/lib/pages/animated_map_controller.dart @@ -179,14 +179,14 @@ class AnimatedMapControllerPageState extends State zoom: 5, maxZoom: 10, minZoom: 3), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/circle.dart b/example/lib/pages/circle.dart index 9b40c8fcf..9befece8d 100644 --- a/example/lib/pages/circle.dart +++ b/example/lib/pages/circle.dart @@ -37,14 +37,14 @@ class CirclePage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 11, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - CircleLayerOptions(circles: circleMarkers) + )), + CircleLayerWidget(options: CircleLayerOptions(circles: circleMarkers)), ], ), ), diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart index ade2ec8fa..e730a97db 100644 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ b/example/lib/pages/custom_crs/custom_crs.dart @@ -137,8 +137,8 @@ class _CustomCrsPageState extends State { point = proj4.Point(x: p.latitude, y: p.longitude); }), ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( opacity: 1, backgroundColor: Colors.transparent, wmsOptions: WMSTileLayerOptions( @@ -150,7 +150,7 @@ class _CustomCrsPageState extends State { 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', layers: ['gebco_north_polar_view'], ), - ), + )), ], ), ), diff --git a/example/lib/pages/epsg3413_crs.dart b/example/lib/pages/epsg3413_crs.dart index 14718d9ce..8e70513b5 100644 --- a/example/lib/pages/epsg3413_crs.dart +++ b/example/lib/pages/epsg3413_crs.dart @@ -133,8 +133,9 @@ class _EPSG3413PageState extends State { zoom: 3, maxZoom: maxZoom, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( opacity: 1, backgroundColor: Colors.transparent, wmsOptions: WMSTileLayerOptions( @@ -145,8 +146,9 @@ class _EPSG3413PageState extends State { 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', layers: ['gebco_north_polar_view'], ), - ), - OverlayImageLayerOptions( + )), + OverlayImageLayerWidget( + options: OverlayImageLayerOptions( overlayImages: [ OverlayImage( bounds: LatLngBounds( @@ -157,11 +159,11 @@ class _EPSG3413PageState extends State { 'map/epsg3413/amsr2.png', ).image, ) - ], + ]), ), - CircleLayerOptions( + CircleLayerWidget(options: CircleLayerOptions( circles: circles, - ), + )), ], ), ), diff --git a/example/lib/pages/epsg4326_crs.dart b/example/lib/pages/epsg4326_crs.dart index 0f4d3103c..24054c7b3 100644 --- a/example/lib/pages/epsg4326_crs.dart +++ b/example/lib/pages/epsg4326_crs.dart @@ -29,15 +29,15 @@ class EPSG4326Page extends StatelessWidget { center: LatLng(0, 0), zoom: 0, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( wmsOptions: WMSTileLayerOptions( crs: const Epsg4326(), 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 417dff46f..691c3a98e 100644 --- a/example/lib/pages/esri.dart +++ b/example/lib/pages/esri.dart @@ -27,12 +27,12 @@ class EsriPage extends StatelessWidget { center: LatLng(45.5231, -122.6765), zoom: 13, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: 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 f12ef4e49..33d2999e9 100644 --- a/example/lib/pages/home.dart +++ b/example/lib/pages/home.dart @@ -57,21 +57,21 @@ class HomePage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'], - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) - ], nonRotatedChildren: [ AttributionWidget.defaultWidget( source: 'OpenStreetMap contributors', onSourceTapped: () {}, ), ], + children: [ + TileLayerWidget(options: TileLayerOptions( + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + ], ), ), ], diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index f04ad46db..12dd6bde3 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -175,13 +175,13 @@ class _InteractiveTestPageState extends State { zoom: 11, interactiveFlags: flags, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( 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/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index e68fe4de0..a904b8730 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -64,13 +64,13 @@ class _LatLngScreenPointTestPageState extends State { zoom: 11, rotation: 0, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( 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 ecc77089f..5e884811a 100644 --- a/example/lib/pages/live_location.dart +++ b/example/lib/pages/live_location.dart @@ -139,14 +139,16 @@ class _LiveLocationPageState extends State { zoom: 5, interactiveFlags: interActiveFlags, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget( + options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index 8a39e45c2..72c98b388 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -77,16 +77,16 @@ class _ManyMarkersPageState extends State { zoom: 5, interactiveFlags: InteractiveFlag.all - InteractiveFlag.rotate, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions( + )), + MarkerLayerWidget(options: MarkerLayerOptions( markers: allMarkers.sublist( - 0, min(allMarkers.length, _sliderVal))), + 0, min(allMarkers.length, _sliderVal)))), ], ), ), diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 98ac8c024..3d2990e6f 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -159,14 +159,14 @@ class MapControllerPageState extends State { maxZoom: 5, minZoom: 3, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/map_inside_listview.dart b/example/lib/pages/map_inside_listview.dart index e4b57527d..bce110299 100644 --- a/example/lib/pages/map_inside_listview.dart +++ b/example/lib/pages/map_inside_listview.dart @@ -25,20 +25,16 @@ class MapInsideListViewPage extends StatelessWidget { options: MapOptions( center: LatLng(51.5, -0.09), zoom: 5, - plugins: [ - ZoomButtonsPlugin(), - ], ), - layers: [ - ZoomButtonsPluginOption( + children: [ + //TODO test order + ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( minZoom: 4, maxZoom: 19, mini: true, padding: 10, alignment: Alignment.bottomLeft, - ) - ], - children: [ + )), TileLayerWidget( options: TileLayerOptions( urlTemplate: diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart index 3017f1106..db7c74072 100644 --- a/example/lib/pages/marker_anchor.dart +++ b/example/lib/pages/marker_anchor.dart @@ -112,14 +112,16 @@ class MarkerAnchorPageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget( + options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart index 1e5431b56..f46c8a788 100644 --- a/example/lib/pages/marker_rotate.dart +++ b/example/lib/pages/marker_rotate.dart @@ -137,17 +137,17 @@ class MarkerRotatePageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions( + )), + MarkerLayerWidget(options: MarkerLayerOptions( rotate: rotateMarkerLayerOptions, markers: markers, - ) + )) ], ), ), diff --git a/example/lib/pages/max_bounds.dart b/example/lib/pages/max_bounds.dart index 0701cfaf0..dbb660a2a 100644 --- a/example/lib/pages/max_bounds.dart +++ b/example/lib/pages/max_bounds.dart @@ -30,14 +30,14 @@ class MaxBoundsPage extends StatelessWidget { maxBounds: LatLngBounds(LatLng(-90, -180), LatLng(90, 180)), screenSize: MediaQuery.of(context).size, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( maxZoom: 15, 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 ecb7d6673..8e59cecae 100644 --- a/example/lib/pages/moving_markers.dart +++ b/example/lib/pages/moving_markers.dart @@ -58,14 +58,14 @@ class _MovingMarkersPageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: [_marker!]) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: [_marker!])), ], ), ), diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart index 3f957dc7b..42db66dc5 100644 --- a/example/lib/pages/network_tile_provider.dart +++ b/example/lib/pages/network_tile_provider.dart @@ -64,15 +64,15 @@ class NetworkTileProviderPage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], tileProvider: NetworkTileProvider(), userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)) ], ), ), diff --git a/example/lib/pages/offline_map.dart b/example/lib/pages/offline_map.dart index cef4ca252..30010ef6f 100644 --- a/example/lib/pages/offline_map.dart +++ b/example/lib/pages/offline_map.dart @@ -32,12 +32,12 @@ class OfflineMapPage extends StatelessWidget { swPanBoundary: LatLng(56.6877, 11.5089), nePanBoundary: LatLng(56.7378, 11.6644), ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( tileProvider: AssetTileProvider(), maxZoom: 14, 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 8c9798605..291a67eda 100644 --- a/example/lib/pages/on_tap.dart +++ b/example/lib/pages/on_tap.dart @@ -84,14 +84,16 @@ class OnTapPageState extends State { maxZoom: 5, minZoom: 3, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget( + options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/overlay_image.dart b/example/lib/pages/overlay_image.dart index 41b8bac6c..a76c515c3 100644 --- a/example/lib/pages/overlay_image.dart +++ b/example/lib/pages/overlay_image.dart @@ -46,15 +46,19 @@ class OverlayImagePage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 6, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - OverlayImageLayerOptions(overlayImages: overlayImages), - MarkerLayerOptions(markers: [ + )), + OverlayImageLayerWidget( + options: OverlayImageLayerOptions( + overlayImages: overlayImages)), + MarkerLayerWidget( + options: MarkerLayerOptions(markers: [ Marker( point: topLeftCorner, builder: (context) => const _Circle( @@ -67,7 +71,7 @@ class OverlayImagePage extends StatelessWidget { point: bottomRightCorner, builder: (context) => const _Circle( color: Colors.redAccent, label: "BR")), - ]) + ])) ], ), ), diff --git a/example/lib/pages/plugin_api.dart b/example/lib/pages/plugin_api.dart deleted file mode 100644 index 6b25f3424..000000000 --- a/example/lib/pages/plugin_api.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_map/plugin_api.dart'; -import 'package:flutter_map_example/widgets/drawer.dart'; -import 'package:latlong2/latlong.dart'; - -class PluginPage extends StatelessWidget { - static const String route = 'plugins'; - - const PluginPage({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar(title: const Text('Plugins')), - drawer: buildDrawer(context, PluginPage.route), - body: Padding( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Flexible( - child: FlutterMap( - options: MapOptions( - center: LatLng(51.5, -0.09), - zoom: 5, - plugins: [ - MyCustomPlugin(), - ], - ), - layers: [ - TileLayerOptions( - 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!"), - ], - ), - ), - ], - ), - ), - ); - } -} - -class MyCustomPluginOptions extends LayerOptions { - final String text; - MyCustomPluginOptions({ - Key? key, - this.text = '', - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); -} - -class MyCustomPlugin implements MapPlugin { - @override - Widget createLayer( - LayerOptions options, MapState mapState, Stream stream) { - if (options is MyCustomPluginOptions) { - const style = TextStyle( - fontWeight: FontWeight.bold, - fontSize: 24, - color: Colors.red, - ); - return Text( - options.text, - key: options.key, - style: style, - ); - } - throw Exception('Unknown options type for MyCustom' - 'plugin: $options'); - } - - @override - bool supportsLayer(LayerOptions options) { - return options is MyCustomPluginOptions; - } -} diff --git a/example/lib/pages/plugin_scalebar.dart b/example/lib/pages/plugin_scalebar.dart index d2139506b..9104aaf6c 100644 --- a/example/lib/pages/plugin_scalebar.dart +++ b/example/lib/pages/plugin_scalebar.dart @@ -23,26 +23,25 @@ class PluginScaleBar extends StatelessWidget { options: MapOptions( center: LatLng(51.5, -0.09), zoom: 5, - plugins: [ - ScaleLayerPlugin(), - ], ), - layers: [ - TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'], - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - ], - nonRotatedLayers: [ - ScaleLayerPluginOption( + nonRotatedChildren: [ + ScaleLayerWidget( + options: ScaleLayerPluginOption( lineColor: Colors.blue, lineWidth: 2, textStyle: const TextStyle(color: Colors.blue, fontSize: 12), padding: const EdgeInsets.all(10), - ), + )), + ], + children: [ + TileLayerWidget( + options: TileLayerOptions( + 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/plugin_zoombuttons.dart b/example/lib/pages/plugin_zoombuttons.dart index e8509daf9..f1f093e2f 100644 --- a/example/lib/pages/plugin_zoombuttons.dart +++ b/example/lib/pages/plugin_zoombuttons.dart @@ -23,28 +23,24 @@ class PluginZoomButtons extends StatelessWidget { options: MapOptions( center: LatLng(51.5, -0.09), zoom: 5, - plugins: [ - ZoomButtonsPlugin(), - ], ), - layers: [ - TileLayerOptions( - urlTemplate: - 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'], - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - ], - nonRotatedLayers: [ - ZoomButtonsPluginOption( + nonRotatedChildren: [ + ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( minZoom: 4, maxZoom: 19, mini: true, padding: 10, alignment: Alignment.bottomRight, - ), + )), ], - ), + children: [ + TileLayerWidget(options: TileLayerOptions( + 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/polygon.dart b/example/lib/pages/polygon.dart index 35e28f351..33a8caf62 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -52,14 +52,16 @@ class PolygonPage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - PolygonLayerOptions(polygons: [ + )), + PolygonLayerWidget( + options: PolygonLayerOptions(polygons: [ Polygon( points: notFilledPoints, isFilled: false, // By default it's false @@ -88,7 +90,7 @@ class PolygonPage extends StatelessWidget { borderColor: Colors.lightBlue, color: Colors.lightBlue, ), - ]), + ])), ], ), ), diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index 6de576a6f..db55393e4 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -80,24 +80,25 @@ class _PolylinePageState extends State { }); }, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - PolylineLayerOptions( - polylines: [ + )), + PolylineLayerWidget( + options: PolylineLayerOptions(polylines: [ Polyline( points: points, strokeWidth: 4, color: Colors.purple), - ], + ]), ), - PolylineLayerOptions( - polylines: [ + PolylineLayerWidget( + options: PolylineLayerOptions(polylines: [ Polyline( points: pointsGradient, strokeWidth: 4, @@ -107,12 +108,13 @@ class _PolylinePageState extends State { const Color(0xff007E2D), ], ), - ], + ]), ), - PolylineLayerOptions( + PolylineLayerWidget( + options: PolylineLayerOptions( polylines: snapshot.data!, polylineCulling: true, - ), + )), ], ), ), diff --git a/example/lib/pages/reset_tile_layer.dart b/example/lib/pages/reset_tile_layer.dart index 0156211e1..5ed072517 100644 --- a/example/lib/pages/reset_tile_layer.dart +++ b/example/lib/pages/reset_tile_layer.dart @@ -75,14 +75,14 @@ class ResetTileLayerPageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( reset: resetController.stream, urlTemplate: layerToggle ? layer1 : layer2, subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)) ], ), ), diff --git a/example/lib/pages/scale_layer_plugin_option.dart b/example/lib/pages/scale_layer_plugin_option.dart index cfd45efb8..7d38e519a 100644 --- a/example/lib/pages/scale_layer_plugin_option.dart +++ b/example/lib/pages/scale_layer_plugin_option.dart @@ -6,57 +6,22 @@ import 'package:flutter_map/plugin_api.dart'; import 'package:flutter_map_example/pages/scalebar_utils.dart' as util; -class ScaleLayerPluginOption extends LayerOptions { +class ScaleLayerPluginOption { TextStyle? textStyle; Color lineColor; double lineWidth; final EdgeInsets? padding; ScaleLayerPluginOption({ - Key? key, this.textStyle, this.lineColor = Colors.white, this.lineWidth = 2, this.padding, - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); -} - -class ScaleLayerPlugin implements MapPlugin { - @override - Widget createLayer( - LayerOptions options, MapState mapState, Stream stream) { - if (options is ScaleLayerPluginOption) { - return ScaleLayerWidget(options, mapState); - } - throw Exception('Unknown options type for ScaleLayerPlugin: $options'); - } - - @override - bool supportsLayer(LayerOptions options) { - return options is ScaleLayerPluginOption; - } + }) ; } class ScaleLayerWidget extends StatelessWidget { - final ScaleLayerPluginOption scaleLayerOpts; - final MapState map; - ScaleLayerWidget(this.scaleLayerOpts, this.map) - : super(key: scaleLayerOpts.key); - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context); - return StreamBuilder( - stream: mapState?.onMoved, - builder: (BuildContext context, _) { - return ScaleLayer(scaleLayerOpts, map); - }); - } -} - -class ScaleLayer extends StatelessWidget { - final ScaleLayerPluginOption scaleLayerOpts; - final MapState map; + final ScaleLayerPluginOption options; final scale = [ 25000000, 15000000, @@ -83,10 +48,11 @@ class ScaleLayer extends StatelessWidget { 5 ]; - ScaleLayer(this.scaleLayerOpts, this.map) : super(key: scaleLayerOpts.key); + ScaleLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { + final map = MapState.maybeOf(context)!; final zoom = map.zoom; final distance = scale[max(0, min(20, zoom.round() + 2))].toDouble(); final center = map.center; @@ -105,10 +71,10 @@ class ScaleLayer extends StatelessWidget { painter: ScalePainter( width, displayDistance, - lineColor: scaleLayerOpts.lineColor, - lineWidth: scaleLayerOpts.lineWidth, - padding: scaleLayerOpts.padding, - textStyle: scaleLayerOpts.textStyle, + lineColor: options.lineColor, + lineWidth: options.lineWidth, + padding: options.padding, + textStyle: options.textStyle, ), ); }, diff --git a/example/lib/pages/sliding_map.dart b/example/lib/pages/sliding_map.dart index bc74030ea..ec500725d 100644 --- a/example/lib/pages/sliding_map.dart +++ b/example/lib/pages/sliding_map.dart @@ -34,12 +34,12 @@ class SlidingMapPage extends StatelessWidget { slideOnBoundaries: true, screenSize: MediaQuery.of(context).size, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( tileProvider: AssetTileProvider(), maxZoom: 14, 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 16ffe056f..1034998c3 100644 --- a/example/lib/pages/stateful_markers.dart +++ b/example/lib/pages/stateful_markers.dart @@ -63,14 +63,14 @@ class _StatefulMarkersPageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: _markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: _markers)), ], ), ), diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index f79afa4a1..c47656701 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -46,13 +46,13 @@ class TapToAddPageState extends State { center: LatLng(45.5231, -122.6765), zoom: 13, onTap: _handleTap), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ), - MarkerLayerOptions(markers: markers) + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/example/lib/pages/tile_builder_example.dart b/example/lib/pages/tile_builder_example.dart index 7dc4584d3..812470934 100644 --- a/example/lib/pages/tile_builder_example.dart +++ b/example/lib/pages/tile_builder_example.dart @@ -111,16 +111,16 @@ class _TileBuilderPageState extends State { center: LatLng(51.5, -0.09), zoom: 5, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', tileBuilder: tileBuilder, tilesContainerBuilder: darkMode ? darkModeTilesContainerBuilder : null, - ), - MarkerLayerOptions( + )), + MarkerLayerWidget(options: MarkerLayerOptions( markers: [ Marker( width: 80, @@ -131,7 +131,7 @@ class _TileBuilderPageState extends State { ), ), ], - ) + )), ], ), ), diff --git a/example/lib/pages/tile_loading_error_handle.dart b/example/lib/pages/tile_loading_error_handle.dart index 09a55eda3..42d411742 100644 --- a/example/lib/pages/tile_loading_error_handle.dart +++ b/example/lib/pages/tile_loading_error_handle.dart @@ -38,8 +38,8 @@ class _TileLoadingErrorHandleState extends State { needLoadingError = true; }, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], @@ -62,7 +62,7 @@ class _TileLoadingErrorHandleState extends State { needLoadingError = false; } }, - ), + )), ], ); }), diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index 47689bf6a..e95524e7d 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -25,21 +25,17 @@ class WidgetsPage extends StatelessWidget { options: MapOptions( center: LatLng(51.5, -0.09), zoom: 5, - plugins: [ - ZoomButtonsPlugin(), - ], ), - nonRotatedLayers: [ - ZoomButtonsPluginOption( + nonRotatedChildren: [ + //TODO test order + ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( minZoom: 4, maxZoom: 19, mini: true, padding: 10, alignment: Alignment.bottomLeft, - ) - ], - nonRotatedChildren: const [ - Text( + )), + const Text( 'Plugin is just Text widget', style: TextStyle( fontSize: 22, @@ -48,7 +44,7 @@ class WidgetsPage extends StatelessWidget { backgroundColor: Colors.yellow), ) ], - children: [ + children: [ TileLayerWidget( options: TileLayerOptions( urlTemplate: diff --git a/example/lib/pages/wms_tile_layer.dart b/example/lib/pages/wms_tile_layer.dart index b800cb007..b1f6688a4 100644 --- a/example/lib/pages/wms_tile_layer.dart +++ b/example/lib/pages/wms_tile_layer.dart @@ -27,15 +27,15 @@ class WMSLayerPage extends StatelessWidget { center: LatLng(42.58, 12.43), zoom: 6, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( wmsOptions: WMSTileLayerOptions( baseUrl: 'https://{s}.s2maps-tiles.eu/wms/?', layers: ['s2cloudless-2018_3857'], ), subdomains: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - ) + )) ], ), ), diff --git a/example/lib/pages/zoombuttons_plugin_option.dart b/example/lib/pages/zoombuttons_plugin_option.dart index 132ee0c76..c09c0dd9e 100644 --- a/example/lib/pages/zoombuttons_plugin_option.dart +++ b/example/lib/pages/zoombuttons_plugin_option.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/plugin_api.dart'; -class ZoomButtonsPluginOption extends LayerOptions { +class ZoomButtonsPluginOption { final double minZoom; final double maxZoom; final bool mini; @@ -15,7 +15,6 @@ class ZoomButtonsPluginOption extends LayerOptions { final IconData zoomOutIcon; ZoomButtonsPluginOption({ - Key? key, this.minZoom = 1, this.maxZoom = 18, this.mini = true, @@ -27,38 +26,19 @@ class ZoomButtonsPluginOption extends LayerOptions { this.zoomOutColor, this.zoomOutColorIcon, this.zoomOutIcon = Icons.zoom_out, - Stream? rebuild, - }) : super(key: key, rebuild: rebuild); -} - -class ZoomButtonsPlugin implements MapPlugin { - @override - Widget createLayer( - LayerOptions options, MapState mapState, Stream stream) { - if (options is ZoomButtonsPluginOption) { - return ZoomButtons(options, mapState, stream); - } - throw Exception('Unknown options type for ZoomButtonsPlugin: $options'); - } - - @override - bool supportsLayer(LayerOptions options) { - return options is ZoomButtonsPluginOption; - } + }); } class ZoomButtons extends StatelessWidget { final ZoomButtonsPluginOption zoomButtonsOpts; - final MapState map; - final Stream stream; final FitBoundsOptions options = const FitBoundsOptions(padding: EdgeInsets.all(12)); - ZoomButtons(this.zoomButtonsOpts, this.map, this.stream) - : super(key: zoomButtonsOpts.key); + const ZoomButtons({super.key, required this.zoomButtonsOpts}); @override Widget build(BuildContext context) { + final map = MapState.maybeOf(context)!; return Align( alignment: zoomButtonsOpts.alignment, child: Column( diff --git a/example/lib/test_app.dart b/example/lib/test_app.dart index 8a0fa89dd..76726f079 100644 --- a/example/lib/test_app.dart +++ b/example/lib/test_app.dart @@ -32,13 +32,13 @@ class _TestAppState extends State { center: LatLng(45.5231, -122.6765), zoom: 13, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( 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/widgets/drawer.dart b/example/lib/widgets/drawer.dart index f896d3ebb..a7418499e 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -21,7 +21,6 @@ import 'package:flutter_map_example/pages/network_tile_provider.dart'; import 'package:flutter_map_example/pages/offline_map.dart'; import 'package:flutter_map_example/pages/on_tap.dart'; import 'package:flutter_map_example/pages/overlay_image.dart'; -import 'package:flutter_map_example/pages/plugin_api.dart'; import 'package:flutter_map_example/pages/plugin_scalebar.dart'; import 'package:flutter_map_example/pages/plugin_zoombuttons.dart'; import 'package:flutter_map_example/pages/point_to_latlng.dart'; @@ -134,12 +133,6 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { MarkerRotatePage.route, currentRoute, ), - _buildMenuItem( - context, - const Text('Plugins'), - PluginPage.route, - currentRoute, - ), _buildMenuItem( context, const Text('ScaleBar Plugins'), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 07296df8a..8043cddc4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,7 +4,7 @@ publish_to: "none" version: 1.0.0 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: flutter: diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 4411cc564..cbf72991f 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -141,9 +141,9 @@ class RotatedOverlayImage extends BaseOverlayImage { } class OverlayImageLayerWidget extends StatelessWidget { - final OverlayImageLayerOptions overlayImageOpts; + final OverlayImageLayerOptions options; - const OverlayImageLayerWidget({super.key, required this.overlayImageOpts}); + const OverlayImageLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { @@ -151,7 +151,7 @@ class OverlayImageLayerWidget extends StatelessWidget { return ClipRect( child: Stack( children: [ - for (var overlayImage in overlayImageOpts.overlayImages) + for (var overlayImage in options.overlayImages) overlayImage.buildPositionedForOverlay(map), ], ), diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index dc2c603fe..20e9b7ea2 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -66,9 +66,9 @@ class Polygon { } class PolygonLayerWidget extends StatelessWidget { - final PolygonLayerOptions polygonOpts; + final PolygonLayerOptions options; - const PolygonLayerWidget({super.key, required this.polygonOpts}); + const PolygonLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { @@ -78,7 +78,7 @@ class PolygonLayerWidget extends StatelessWidget { final size = Size(bc.maxWidth, bc.maxHeight); final polygons = []; - for (final polygon in polygonOpts.polygons) { + for (final polygon in options.polygons) { polygon.offsets.clear(); if (null != polygon.holeOffsetsList) { @@ -87,7 +87,7 @@ class PolygonLayerWidget extends StatelessWidget { } } - if (polygonOpts.polygonCulling && + if (options.polygonCulling && !polygon.boundingBox.isOverlapping(map.bounds)) { // skip this polygon as it's offscreen continue; diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 9a50e56f4..8ca274320 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -65,9 +65,9 @@ class Polyline { class PolylineLayerWidget extends StatelessWidget { - final PolylineLayerOptions polylineOpts; + final PolylineLayerOptions options; - const PolylineLayerWidget({super.key, required this.polylineOpts}); + const PolylineLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { @@ -77,10 +77,10 @@ class PolylineLayerWidget extends StatelessWidget { final size = Size(bc.maxWidth, bc.maxHeight); final polylines = []; - for (final polylineOpt in polylineOpts.polylines) { + for (final polylineOpt in options.polylines) { polylineOpt.offsets.clear(); - if (polylineOpts.polylineCulling && + if (options.polylineCulling && !polylineOpt.boundingBox.isOverlapping(map.bounds)) { // skip this polyline as it's offscreen continue; @@ -89,7 +89,7 @@ class PolylineLayerWidget extends StatelessWidget { _fillOffsets(polylineOpt.offsets, polylineOpt.points, map); polylines.add(CustomPaint( - painter: PolylinePainter(polylineOpt, polylineOpts.saveLayers), + painter: PolylinePainter(polylineOpt, options.saveLayers), size: size, )); } diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index ee6286f07..31801c06a 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -21,7 +21,7 @@ part 'tile_layer_options.dart'; class TileLayerWidget extends StatelessWidget { final TileLayerOptions options; - const TileLayerWidget({Key? key, required this.options}) : super(key: key); + const TileLayerWidget({super.key, required this.options}); @override Widget build(BuildContext context) { From 26db69eeed7e76d867e4e3a3ecbc4808386f1265 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:55:54 -0500 Subject: [PATCH 05/32] migrate tests --- test/flutter_map_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 4ffd8158b..0b7bff000 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -109,12 +109,12 @@ class _TestAppState extends State { center: LatLng(45.5231, -122.6765), zoom: 13, ), - layers: [ - TileLayerOptions( + children: [ + TileLayerWidget(options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c']), - MarkerLayerOptions(markers: _markers), + subdomains: ['a', 'b', 'c'])), + MarkerLayerWidget(options: MarkerLayerOptions(markers: _markers)), ], ), ), From fcd403bfec74e289c851ff0c52a94024c0535d43 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:58:40 -0500 Subject: [PATCH 06/32] unnecessary empty check --- example/lib/pages/tap_to_add.dart | 12 ++++++------ lib/src/map/flutter_map_state.dart | 9 ++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index c47656701..59b4b01d8 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -47,12 +47,12 @@ class TapToAddPageState extends State { zoom: 13, onTap: _handleTap), children: [ - TileLayerWidget(options: TileLayerOptions( - urlTemplate: - 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + // TileLayerWidget(options: TileLayerOptions( + // urlTemplate: + // 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + // userAgentPackageName: 'dev.fleaflet.flutter_map.example', + // )), + // MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), ], ), ), diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 52bf759e0..72cae4aa0 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -165,17 +165,12 @@ class FlutterMapState extends MapGestureMixin child: Transform.rotate( angle: mapState.rotationRad, child: Stack( - children: [ - if (widget.children.isNotEmpty) ...widget.children, - ], + children: widget.children, ), ), ), Stack( - children: [ - if (widget.nonRotatedChildren.isNotEmpty) - ...widget.nonRotatedChildren, - ], + children: widget.nonRotatedChildren, ), ], ), From 98a40aa1ef8a1bb5110d7a7edcb47723147f3e05 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 19:59:11 -0500 Subject: [PATCH 07/32] fix accidental commit --- example/lib/pages/tap_to_add.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index 59b4b01d8..c47656701 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -47,12 +47,12 @@ class TapToAddPageState extends State { zoom: 13, onTap: _handleTap), children: [ - // TileLayerWidget(options: TileLayerOptions( - // urlTemplate: - // 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', - // userAgentPackageName: 'dev.fleaflet.flutter_map.example', - // )), - // MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + TileLayerWidget(options: TileLayerOptions( + urlTemplate: + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'dev.fleaflet.flutter_map.example', + )), + MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), ], ), ), From 5c2386145e4051ee29ecf2533ac660ea56c094b8 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 20:01:04 -0500 Subject: [PATCH 08/32] new super super --- lib/src/map/map_state_widget.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/map/map_state_widget.dart b/lib/src/map/map_state_widget.dart index ac6884b97..c5d7b7fb8 100644 --- a/lib/src/map/map_state_widget.dart +++ b/lib/src/map/map_state_widget.dart @@ -6,10 +6,10 @@ class MapStateInheritedWidget extends InheritedWidget { final MapState mapState; const MapStateInheritedWidget({ - Key? key, + super.key, required this.mapState, - required Widget child, - }) : super(key: key, child: child); + required super.child, + }); @override bool updateShouldNotify(MapStateInheritedWidget oldWidget) { From 0513912a4a01150023f8628529bdb50fe91462bf Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 22:02:03 -0500 Subject: [PATCH 09/32] instantiate mapcontrollerimpl at construction; mod circle layer; add rebuild listener --- lib/src/layer/circle_layer.dart | 10 +++------- lib/src/map/flutter_map_state.dart | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index 7bccf9441..52df64c4f 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -37,13 +37,7 @@ class CircleLayerWidget extends StatelessWidget { return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); - return _build(context, size); - }, - ); - } - - Widget _build(BuildContext context, Size size) { - final map = MapState.maybeOf(context)!; + final map = MapState.maybeOf(context)!; final circleWidgets = []; for (final circle in options.circles) { circle.offset = map.getOffsetFromOrigin(circle.point); @@ -65,6 +59,8 @@ class CircleLayerWidget extends StatelessWidget { return Stack( children: circleWidgets, ); + }, + ); } } diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 72cae4aa0..7666d2bae 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:async/async.dart'; import 'package:flutter/gestures.dart'; @@ -11,8 +12,9 @@ import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; class FlutterMapState extends MapGestureMixin with AutomaticKeepAliveClientMixin { final List> groups = >[]; + late StreamSubscription _rebuildStream; final _positionedTapController = PositionedTapController(); - MapController? _localController; + final MapController _localController = MapControllerImpl(); @override MapOptions get options => widget.options; @@ -21,7 +23,7 @@ class FlutterMapState extends MapGestureMixin late final MapState mapState; @override - MapController get mapController => widget.mapController ?? _localController!; + MapController get mapController => widget.mapController ?? _localController; @override void didUpdateWidget(FlutterMap oldWidget) { @@ -33,12 +35,19 @@ class FlutterMapState extends MapGestureMixin @override void initState() { super.initState(); - if (widget.mapController == null) _localController = MapControllerImpl(); mapState = MapState(options, (degree) { if (mounted) setState(() {}); }, mapController.mapEventSink); mapController.state = mapState; + // Whenever there is a map event (move, rotate, etc); + // setstate to trigger rebuilding children. + // It should be fine to trigger setState multiple times as long + // as it is within the same frame. (ex move and rotate events). + _rebuildStream = mapController.mapEventStream.listen((event) { + if(mounted) setState(() {}); + }); + // Callback onMapCreated if not null if (options.onMapCreated != null) { options.onMapCreated!(mapController); @@ -57,7 +66,8 @@ class FlutterMapState extends MapGestureMixin void dispose() { _disposeStreamGroups(); mapState.dispose(); - _localController?.dispose(); + _localController.dispose(); + _rebuildStream.cancel(); super.dispose(); } From 10b4c1ab4fbe01857c29ed25adc351da871f2617 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 22:50:59 -0500 Subject: [PATCH 10/32] remove options from zoom buttons example --- example/lib/pages/map_inside_listview.dart | 15 +++--- example/lib/pages/plugin_zoombuttons.dart | 6 +-- example/lib/pages/widgets.dart | 9 ++-- .../lib/pages/zoombuttons_plugin_option.dart | 51 +++++++++---------- 4 files changed, 38 insertions(+), 43 deletions(-) diff --git a/example/lib/pages/map_inside_listview.dart b/example/lib/pages/map_inside_listview.dart index bce110299..8f80ac2c6 100644 --- a/example/lib/pages/map_inside_listview.dart +++ b/example/lib/pages/map_inside_listview.dart @@ -27,14 +27,6 @@ class MapInsideListViewPage extends StatelessWidget { zoom: 5, ), children: [ - //TODO test order - ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( - minZoom: 4, - maxZoom: 19, - mini: true, - padding: 10, - alignment: Alignment.bottomLeft, - )), TileLayerWidget( options: TileLayerOptions( urlTemplate: @@ -43,6 +35,13 @@ class MapInsideListViewPage extends StatelessWidget { userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), ), + const FlutterMapZoomButtons( + minZoom: 4, + maxZoom: 19, + mini: true, + padding: 10, + alignment: Alignment.bottomLeft, + ), ], ), ), diff --git a/example/lib/pages/plugin_zoombuttons.dart b/example/lib/pages/plugin_zoombuttons.dart index f1f093e2f..a0f364f40 100644 --- a/example/lib/pages/plugin_zoombuttons.dart +++ b/example/lib/pages/plugin_zoombuttons.dart @@ -24,14 +24,14 @@ class PluginZoomButtons extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 5, ), - nonRotatedChildren: [ - ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( + nonRotatedChildren: const [ + FlutterMapZoomButtons( minZoom: 4, maxZoom: 19, mini: true, padding: 10, alignment: Alignment.bottomRight, - )), + ), ], children: [ TileLayerWidget(options: TileLayerOptions( diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index e95524e7d..0828592c2 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -26,16 +26,15 @@ class WidgetsPage extends StatelessWidget { center: LatLng(51.5, -0.09), zoom: 5, ), - nonRotatedChildren: [ - //TODO test order - ZoomButtons(zoomButtonsOpts: ZoomButtonsPluginOption( + nonRotatedChildren: const [ + FlutterMapZoomButtons( minZoom: 4, maxZoom: 19, mini: true, padding: 10, alignment: Alignment.bottomLeft, - )), - const Text( + ), + Text( 'Plugin is just Text widget', style: TextStyle( fontSize: 22, diff --git a/example/lib/pages/zoombuttons_plugin_option.dart b/example/lib/pages/zoombuttons_plugin_option.dart index c09c0dd9e..2e882bff2 100644 --- a/example/lib/pages/zoombuttons_plugin_option.dart +++ b/example/lib/pages/zoombuttons_plugin_option.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_map/plugin_api.dart'; -class ZoomButtonsPluginOption { +class FlutterMapZoomButtons extends StatelessWidget { + final double minZoom; final double maxZoom; final bool mini; @@ -14,7 +15,11 @@ class ZoomButtonsPluginOption { final IconData zoomInIcon; final IconData zoomOutIcon; - ZoomButtonsPluginOption({ + final FitBoundsOptions options = + const FitBoundsOptions(padding: EdgeInsets.all(12)); + + const FlutterMapZoomButtons({ + super.key, this.minZoom = 1, this.maxZoom = 18, this.mini = true, @@ -27,67 +32,59 @@ class ZoomButtonsPluginOption { this.zoomOutColorIcon, this.zoomOutIcon = Icons.zoom_out, }); -} - -class ZoomButtons extends StatelessWidget { - final ZoomButtonsPluginOption zoomButtonsOpts; - final FitBoundsOptions options = - const FitBoundsOptions(padding: EdgeInsets.all(12)); - - const ZoomButtons({super.key, required this.zoomButtonsOpts}); @override Widget build(BuildContext context) { final map = MapState.maybeOf(context)!; return Align( - alignment: zoomButtonsOpts.alignment, + alignment: alignment, child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: EdgeInsets.only( - left: zoomButtonsOpts.padding, - top: zoomButtonsOpts.padding, - right: zoomButtonsOpts.padding), + left: padding, + top: padding, + right: padding), child: FloatingActionButton( heroTag: 'zoomInButton', - mini: zoomButtonsOpts.mini, + mini: mini, backgroundColor: - zoomButtonsOpts.zoomInColor ?? Theme.of(context).primaryColor, + zoomInColor ?? Theme.of(context).primaryColor, onPressed: () { final bounds = map.getBounds(); final centerZoom = map.getBoundsCenterZoom(bounds, options); var zoom = centerZoom.zoom + 1; - if (zoom > zoomButtonsOpts.maxZoom) { - zoom = zoomButtonsOpts.maxZoom; + if (zoom > maxZoom) { + zoom = maxZoom; } map.move(centerZoom.center, zoom, source: MapEventSource.custom); }, - child: Icon(zoomButtonsOpts.zoomInIcon, - color: zoomButtonsOpts.zoomInColorIcon ?? + child: Icon(zoomInIcon, + color: zoomInColorIcon ?? IconTheme.of(context).color), ), ), Padding( - padding: EdgeInsets.all(zoomButtonsOpts.padding), + padding: EdgeInsets.all(padding), child: FloatingActionButton( heroTag: 'zoomOutButton', - mini: zoomButtonsOpts.mini, - backgroundColor: zoomButtonsOpts.zoomOutColor ?? + mini: mini, + backgroundColor: zoomOutColor ?? Theme.of(context).primaryColor, onPressed: () { final bounds = map.getBounds(); final centerZoom = map.getBoundsCenterZoom(bounds, options); var zoom = centerZoom.zoom - 1; - if (zoom < zoomButtonsOpts.minZoom) { - zoom = zoomButtonsOpts.minZoom; + if (zoom < minZoom) { + zoom = minZoom; } map.move(centerZoom.center, zoom, source: MapEventSource.custom); }, - child: Icon(zoomButtonsOpts.zoomOutIcon, - color: zoomButtonsOpts.zoomOutColorIcon ?? + child: Icon(zoomOutIcon, + color: zoomOutColorIcon ?? IconTheme.of(context).color), ), ), From 5d02dee36f037de32528259710a0c4544f1e9131 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 2 Aug 2022 23:07:56 -0500 Subject: [PATCH 11/32] assume new major version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index cbf394a05..67af55046 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: 2.2.0 +version: 3.0.0 repository: https://github.com/fleaflet/flutter_map documentation: https://docs.fleaflet.dev From f7d9674f6b9623fe0f34cb96a891f67d837fdb05 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 12:32:49 -0500 Subject: [PATCH 12/32] mod api's. Working on MarkerLayer; caching behavior needs to be tested --- .../lib/pages/animated_map_controller.dart | 2 +- example/lib/pages/epsg3413_crs.dart | 5 +- example/lib/pages/home.dart | 2 +- example/lib/pages/live_location.dart | 3 +- example/lib/pages/many_markers.dart | 4 +- example/lib/pages/map_controller.dart | 2 +- example/lib/pages/marker_anchor.dart | 3 +- example/lib/pages/marker_rotate.dart | 4 +- example/lib/pages/moving_markers.dart | 2 +- example/lib/pages/network_tile_provider.dart | 2 +- example/lib/pages/on_tap.dart | 3 +- example/lib/pages/overlay_image.dart | 10 +- example/lib/pages/point_to_latlng.dart | 5 +- example/lib/pages/polygon.dart | 5 +- example/lib/pages/polyline.dart | 15 +- example/lib/pages/reset_tile_layer.dart | 2 +- example/lib/pages/stateful_markers.dart | 2 +- example/lib/pages/tap_to_add.dart | 2 +- example/lib/pages/tile_builder_example.dart | 4 +- example/lib/pages/widgets.dart | 3 +- lib/src/layer/marker_layer.dart | 131 +++++++----------- lib/src/layer/overlay_image_layer.dart | 16 +-- lib/src/layer/polygon_layer.dart | 46 +++--- lib/src/layer/polyline_layer.dart | 70 +++++----- test/flutter_map_test.dart | 2 +- 25 files changed, 140 insertions(+), 205 deletions(-) diff --git a/example/lib/pages/animated_map_controller.dart b/example/lib/pages/animated_map_controller.dart index 537da904d..ba9b2e2eb 100644 --- a/example/lib/pages/animated_map_controller.dart +++ b/example/lib/pages/animated_map_controller.dart @@ -186,7 +186,7 @@ class AnimatedMapControllerPageState extends State subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/epsg3413_crs.dart b/example/lib/pages/epsg3413_crs.dart index 8e70513b5..cd6587c65 100644 --- a/example/lib/pages/epsg3413_crs.dart +++ b/example/lib/pages/epsg3413_crs.dart @@ -147,8 +147,7 @@ class _EPSG3413PageState extends State { layers: ['gebco_north_polar_view'], ), )), - OverlayImageLayerWidget( - options: OverlayImageLayerOptions( + OverlayImageLayer( overlayImages: [ OverlayImage( bounds: LatLngBounds( @@ -159,7 +158,7 @@ class _EPSG3413PageState extends State { 'map/epsg3413/amsr2.png', ).image, ) - ]), + ], ), CircleLayerWidget(options: CircleLayerOptions( circles: circles, diff --git a/example/lib/pages/home.dart b/example/lib/pages/home.dart index 33d2999e9..858e26020 100644 --- a/example/lib/pages/home.dart +++ b/example/lib/pages/home.dart @@ -70,7 +70,7 @@ class HomePage extends StatelessWidget { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/live_location.dart b/example/lib/pages/live_location.dart index 5e884811a..b819c5c52 100644 --- a/example/lib/pages/live_location.dart +++ b/example/lib/pages/live_location.dart @@ -147,8 +147,7 @@ class _LiveLocationPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget( - options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index 72c98b388..b4900113e 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -84,9 +84,9 @@ class _ManyMarkersPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions( + MarkerLayer( markers: allMarkers.sublist( - 0, min(allMarkers.length, _sliderVal)))), + 0, min(allMarkers.length, _sliderVal))), ], ), ), diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 3d2990e6f..52cc37431 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -166,7 +166,7 @@ class MapControllerPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart index db7c74072..3ae6b54a8 100644 --- a/example/lib/pages/marker_anchor.dart +++ b/example/lib/pages/marker_anchor.dart @@ -120,8 +120,7 @@ class MarkerAnchorPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget( - options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart index f46c8a788..7889dee79 100644 --- a/example/lib/pages/marker_rotate.dart +++ b/example/lib/pages/marker_rotate.dart @@ -144,10 +144,10 @@ class MarkerRotatePageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions( + MarkerLayer( rotate: rotateMarkerLayerOptions, markers: markers, - )) + ) ], ), ), diff --git a/example/lib/pages/moving_markers.dart b/example/lib/pages/moving_markers.dart index 8e59cecae..bdc26bdd1 100644 --- a/example/lib/pages/moving_markers.dart +++ b/example/lib/pages/moving_markers.dart @@ -65,7 +65,7 @@ class _MovingMarkersPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: [_marker!])), + MarkerLayer(markers: [_marker!]), ], ), ), diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart index 42db66dc5..100e1f0bc 100644 --- a/example/lib/pages/network_tile_provider.dart +++ b/example/lib/pages/network_tile_provider.dart @@ -72,7 +72,7 @@ class NetworkTileProviderPage extends StatelessWidget { tileProvider: NetworkTileProvider(), userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)) + MarkerLayer(markers: markers) ], ), ), diff --git a/example/lib/pages/on_tap.dart b/example/lib/pages/on_tap.dart index 291a67eda..e75b215d8 100644 --- a/example/lib/pages/on_tap.dart +++ b/example/lib/pages/on_tap.dart @@ -92,8 +92,7 @@ class OnTapPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget( - options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/overlay_image.dart b/example/lib/pages/overlay_image.dart index a76c515c3..9967ee29f 100644 --- a/example/lib/pages/overlay_image.dart +++ b/example/lib/pages/overlay_image.dart @@ -54,11 +54,9 @@ class OverlayImagePage extends StatelessWidget { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - OverlayImageLayerWidget( - options: OverlayImageLayerOptions( - overlayImages: overlayImages)), - MarkerLayerWidget( - options: MarkerLayerOptions(markers: [ + OverlayImageLayer( + overlayImages: overlayImages), + MarkerLayer(markers: [ Marker( point: topLeftCorner, builder: (context) => const _Circle( @@ -71,7 +69,7 @@ class OverlayImagePage extends StatelessWidget { point: bottomRightCorner, builder: (context) => const _Circle( color: Colors.redAccent, label: "BR")), - ])) + ]) ], ), ), diff --git a/example/lib/pages/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index 608f6b1a7..2ddee5f0e 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -74,8 +74,7 @@ class PointToLatlngPage extends State { userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), if (latLng != null) - MarkerLayerWidget( - options: MarkerLayerOptions( + MarkerLayer( markers: [ Marker( width: pointSize, @@ -84,7 +83,7 @@ class PointToLatlngPage extends State { builder: (ctx) => const FlutterLogo(), ) ], - )) + ) ], ), Container( diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index 33a8caf62..e55ef19a6 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -60,8 +60,7 @@ class PolygonPage extends StatelessWidget { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - PolygonLayerWidget( - options: PolygonLayerOptions(polygons: [ + PolygonLayer(polygons: [ Polygon( points: notFilledPoints, isFilled: false, // By default it's false @@ -90,7 +89,7 @@ class PolygonPage extends StatelessWidget { borderColor: Colors.lightBlue, color: Colors.lightBlue, ), - ])), + ]), ], ), ), diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index db55393e4..3ab0ca279 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -89,16 +89,14 @@ class _PolylinePageState extends State { userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - PolylineLayerWidget( - options: PolylineLayerOptions(polylines: [ + PolylineLayer(polylines: [ Polyline( points: points, strokeWidth: 4, color: Colors.purple), - ]), + ], ), - PolylineLayerWidget( - options: PolylineLayerOptions(polylines: [ + PolylineLayer(polylines: [ Polyline( points: pointsGradient, strokeWidth: 4, @@ -108,13 +106,12 @@ class _PolylinePageState extends State { const Color(0xff007E2D), ], ), - ]), + ], ), - PolylineLayerWidget( - options: PolylineLayerOptions( + PolylineLayer( polylines: snapshot.data!, polylineCulling: true, - )), + ), ], ), ), diff --git a/example/lib/pages/reset_tile_layer.dart b/example/lib/pages/reset_tile_layer.dart index 5ed072517..2128436a6 100644 --- a/example/lib/pages/reset_tile_layer.dart +++ b/example/lib/pages/reset_tile_layer.dart @@ -82,7 +82,7 @@ class ResetTileLayerPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)) + MarkerLayer(markers: markers) ], ), ), diff --git a/example/lib/pages/stateful_markers.dart b/example/lib/pages/stateful_markers.dart index 1034998c3..47e0c642c 100644 --- a/example/lib/pages/stateful_markers.dart +++ b/example/lib/pages/stateful_markers.dart @@ -70,7 +70,7 @@ class _StatefulMarkersPageState extends State { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: _markers)), + MarkerLayer(markers: _markers), ], ), ), diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index c47656701..41b32a5ca 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -52,7 +52,7 @@ class TapToAddPageState extends State { 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - MarkerLayerWidget(options: MarkerLayerOptions(markers: markers)), + MarkerLayer(markers: markers), ], ), ), diff --git a/example/lib/pages/tile_builder_example.dart b/example/lib/pages/tile_builder_example.dart index 812470934..215c36c74 100644 --- a/example/lib/pages/tile_builder_example.dart +++ b/example/lib/pages/tile_builder_example.dart @@ -120,7 +120,7 @@ class _TileBuilderPageState extends State { tilesContainerBuilder: darkMode ? darkModeTilesContainerBuilder : null, )), - MarkerLayerWidget(options: MarkerLayerOptions( + MarkerLayer( markers: [ Marker( width: 80, @@ -131,7 +131,7 @@ class _TileBuilderPageState extends State { ), ), ], - )), + ), ], ), ), diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index 0828592c2..41f614171 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -97,8 +97,7 @@ class _MovingWithoutRefreshAllMapMarkersState @override Widget build(BuildContext context) { - return MarkerLayerWidget( - options: MarkerLayerOptions(markers: [_marker!]), + return MarkerLayer(markers: [_marker!], ); } } diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index c86e8d972..e251741d0 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -4,47 +4,6 @@ import 'package:flutter_map/src/core/bounds.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; -/// Configuration for marker layer -class MarkerLayerOptions { - final List markers; - - /// Toggle marker position caching. Enabling will improve performance, but may introducen - /// errors when adding/removing markers. Default is enabled (`true`). - final bool usePxCache; - - /// If true markers will be counter rotated to the map rotation - final bool? rotate; - - /// The origin of the coordinate system (relative to the upper left corner of - /// this render object) in which to apply the matrix. - /// - /// Setting an origin is equivalent to conjugating the transform matrix by a - /// translation. This property is provided just for convenience. - final Offset? rotateOrigin; - - /// The alignment of the origin, relative to the size of the box. - /// - /// This is equivalent to setting an origin based on the size of the box. - /// If it is specified at the same time as the [rotateOrigin], both are applied. - /// - /// An [AlignmentDirectional.centerStart] value is the same as an [Alignment] - /// whose [Alignment.x] value is `-1.0` if [Directionality.of] returns - /// [TextDirection.ltr], and `1.0` if [Directionality.of] returns - /// [TextDirection.rtl]. Similarly [AlignmentDirectional.centerEnd] is the - /// same as an [Alignment] whose [Alignment.x] value is `1.0` if - /// [Directionality.of] returns [TextDirection.ltr], and `-1.0` if - /// [Directionality.of] returns [TextDirection.rtl]. - final AlignmentGeometry? rotateAlignment; - - MarkerLayerOptions({ - this.markers = const [], - this.rotate = false, - this.rotateOrigin, - this.rotateAlignment = Alignment.center, - this.usePxCache = true, - }); -} - class Anchor { final double left; final double top; @@ -164,24 +123,44 @@ class Marker { }) : anchor = Anchor.forPos(anchorPos, width, height); } -class MarkerLayerWidget extends StatelessWidget { - final MarkerLayerOptions options; +class MarkerLayer extends StatefulWidget { + final List markers; - const MarkerLayerWidget({super.key, required this.options}); + /// Toggle marker position caching. Enabling will improve performance, but may introducen + /// errors when adding/removing markers. Default is enabled (`true`). + final bool usePxCache; - @override - Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; - return MarkerLayer(key: key, markerLayerOptions: options, map: mapState); - } -} + /// If true markers will be counter rotated to the map rotation + final bool? rotate; -class MarkerLayer extends StatefulWidget { - final MarkerLayerOptions markerLayerOptions; - final MapState map; + /// The origin of the coordinate system (relative to the upper left corner of + /// this render object) in which to apply the matrix. + /// + /// Setting an origin is equivalent to conjugating the transform matrix by a + /// translation. This property is provided just for convenience. + final Offset? rotateOrigin; + + /// The alignment of the origin, relative to the size of the box. + /// + /// This is equivalent to setting an origin based on the size of the box. + /// If it is specified at the same time as the [rotateOrigin], both are applied. + /// + /// An [AlignmentDirectional.centerStart] value is the same as an [Alignment] + /// whose [Alignment.x] value is `-1.0` if [Directionality.of] returns + /// [TextDirection.ltr], and `1.0` if [Directionality.of] returns + /// [TextDirection.rtl]. Similarly [AlignmentDirectional.centerEnd] is the + /// same as an [Alignment] whose [Alignment.x] value is `1.0` if + /// [Directionality.of] returns [TextDirection.ltr], and `-1.0` if + /// [Directionality.of] returns [TextDirection.rtl]. + final AlignmentGeometry? rotateAlignment; const MarkerLayer( - {super.key, required this.markerLayerOptions, required this.map}); + {super.key, + this.markers = const [], + this.rotate = false, + this.rotateOrigin, + this.rotateAlignment = Alignment.center, + this.usePxCache = true}); @override State createState() => _MarkerLayerState(); @@ -197,17 +176,17 @@ class _MarkerLayerState extends State { var _pxCache = []; /// Calling this every time markerOpts change should guarantee proper length - List generatePxCache() { - if (widget.markerLayerOptions.usePxCache) { + List generatePxCache(MapState map) { + if (widget.usePxCache) { return List.generate( - widget.markerLayerOptions.markers.length, - (i) => widget.map.project(widget.markerLayerOptions.markers[i].point), + widget.markers.length, + (i) => map.project(widget.markers[i].point), ); } return []; } - bool updatePxCacheIfNeeded() { + bool updatePxCacheIfNeeded(MapState map) { var didUpdate = false; /// markers may be modified, so update cache. Note, someone may @@ -215,38 +194,24 @@ class _MarkerLayerState extends State { /// this case. Parent widget setState should be called to call /// didUpdateWidget to force a cache reload - if (widget.markerLayerOptions.markers.length != _pxCache.length) { - _pxCache = generatePxCache(); + if (widget.markers.length != _pxCache.length) { + _pxCache = generatePxCache(map); didUpdate = true; } return didUpdate; } - @override - void initState() { - super.initState(); - _pxCache = generatePxCache(); - } - - @override - void didUpdateWidget(covariant MarkerLayer oldWidget) { - super.didUpdateWidget(oldWidget); - lastZoom = -1.0; - _pxCache = generatePxCache(); - } - @override Widget build(BuildContext context) { - final layerOptions = widget.markerLayerOptions; - final map = widget.map; - final usePxCache = layerOptions.usePxCache; + final map = MapState.maybeOf(context)!; + final usePxCache = widget.usePxCache; final markers = []; final sameZoom = map.zoom == lastZoom; - final cacheUpdated = updatePxCacheIfNeeded(); + final cacheUpdated = updatePxCacheIfNeeded(map); - for (var i = 0; i < layerOptions.markers.length; i++) { - final marker = layerOptions.markers[i]; + for (var i = 0; i < widget.markers.length; i++) { + final marker = widget.markers[i]; // Decide whether to use cached point or calculate it final pxPoint = usePxCache && (sameZoom || cacheUpdated) @@ -275,12 +240,12 @@ class _MarkerLayerState extends State { } final pos = pxPoint - map.getPixelOrigin(); - final markerWidget = (marker.rotate ?? layerOptions.rotate ?? false) + final markerWidget = (marker.rotate ?? widget.rotate ?? false) // Counter rotated marker to the map rotation ? Transform.rotate( angle: -map.rotationRad, - origin: marker.rotateOrigin ?? layerOptions.rotateOrigin, - alignment: marker.rotateAlignment ?? layerOptions.rotateAlignment, + origin: marker.rotateOrigin ?? widget.rotateOrigin, + alignment: marker.rotateAlignment ?? widget.rotateAlignment, child: marker.builder(context), ) : marker.builder(context); diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index cbf72991f..7bb55b9b2 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -4,14 +4,6 @@ import 'package:flutter_map/src/map/map.dart'; import 'package:flutter_map/src/core/bounds.dart'; import 'package:latlong2/latlong.dart'; -class OverlayImageLayerOptions { - final List overlayImages; - - OverlayImageLayerOptions({ - this.overlayImages = const [], - }); -} - /// Base class for all overlay images. abstract class BaseOverlayImage { ImageProvider get imageProvider; @@ -140,10 +132,10 @@ class RotatedOverlayImage extends BaseOverlayImage { } } -class OverlayImageLayerWidget extends StatelessWidget { - final OverlayImageLayerOptions options; +class OverlayImageLayer extends StatelessWidget { + final List overlayImages; - const OverlayImageLayerWidget({super.key, required this.options}); + const OverlayImageLayer({super.key, this.overlayImages = const []}); @override Widget build(BuildContext context) { @@ -151,7 +143,7 @@ class OverlayImageLayerWidget extends StatelessWidget { return ClipRect( child: Stack( children: [ - for (var overlayImage in options.overlayImages) + for (var overlayImage in overlayImages) overlayImage.buildPositionedForOverlay(map), ], ), diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index 20e9b7ea2..3f599d479 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -6,23 +6,6 @@ import 'package:flutter_map/src/layer/label.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI -class PolygonLayerOptions { - final List polygons; - final bool polygonCulling; - - /// screen space culling of polygons based on bounding box - PolygonLayerOptions({ - this.polygons = const [], - this.polygonCulling = false, - }) { - if (polygonCulling) { - for (final polygon in polygons) { - polygon.boundingBox = LatLngBounds.fromPoints(polygon.points); - } - } - } -} - enum PolygonLabelPlacement { centroid, polylabel, @@ -65,10 +48,23 @@ class Polygon { : List.generate(holePointsList.length, (_) => []); } -class PolygonLayerWidget extends StatelessWidget { - final PolygonLayerOptions options; +class PolygonLayer extends StatelessWidget { + final List polygons; - const PolygonLayerWidget({super.key, required this.options}); + /// screen space culling of polygons based on bounding box + final bool polygonCulling; + + PolygonLayer({ + super.key, + this.polygons = const [], + this.polygonCulling = false, + }) { + if (polygonCulling) { + for (final polygon in polygons) { + polygon.boundingBox = LatLngBounds.fromPoints(polygon.points); + } + } + } @override Widget build(BuildContext context) { @@ -76,9 +72,9 @@ class PolygonLayerWidget extends StatelessWidget { builder: (BuildContext context, BoxConstraints bc) { final map = MapState.maybeOf(context)!; final size = Size(bc.maxWidth, bc.maxHeight); - final polygons = []; + final polygonsWidget = []; - for (final polygon in options.polygons) { + for (final polygon in polygons) { polygon.offsets.clear(); if (null != polygon.holeOffsetsList) { @@ -87,7 +83,7 @@ class PolygonLayerWidget extends StatelessWidget { } } - if (options.polygonCulling && + if (polygonCulling && !polygon.boundingBox.isOverlapping(map.bounds)) { // skip this polygon as it's offscreen continue; @@ -103,7 +99,7 @@ class PolygonLayerWidget extends StatelessWidget { } } - polygons.add( + polygonsWidget.add( CustomPaint( painter: PolygonPainter(polygon), size: size, @@ -112,7 +108,7 @@ class PolygonLayerWidget extends StatelessWidget { } return Stack( - children: polygons, + children: polygonsWidget, ); }, ); diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 8ca274320..6d146fd78 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -6,35 +6,6 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; -class PolylineLayerOptions { - /// List of polylines to draw. - final List polylines; - - final bool polylineCulling; - - /// {@macro newPolylinePainter.saveLayers} - /// - /// By default, this value is set to `false` to improve performance on - /// layers containing a lot of polylines. - /// - /// You might want to set this to `true` if you get unwanted darker lines - /// where they overlap but, keep in mind that this might reduce the - /// performance of the layer. - final bool saveLayers; - - PolylineLayerOptions({ - this.polylines = const [], - this.polylineCulling = false, - this.saveLayers = false, - }) { - if (polylineCulling) { - for (final polyline in polylines) { - polyline.boundingBox = LatLngBounds.fromPoints(polyline.points); - } - } - } -} - class Polyline { final List points; final List offsets = []; @@ -63,11 +34,34 @@ class Polyline { }); } +class PolylineLayer extends StatelessWidget { + /// List of polylines to draw. + final List polylines; -class PolylineLayerWidget extends StatelessWidget { - final PolylineLayerOptions options; + final bool polylineCulling; - const PolylineLayerWidget({super.key, required this.options}); + /// {@macro newPolylinePainter.saveLayers} + /// + /// By default, this value is set to `false` to improve performance on + /// layers containing a lot of polylines. + /// + /// You might want to set this to `true` if you get unwanted darker lines + /// where they overlap but, keep in mind that this might reduce the + /// performance of the layer. + final bool saveLayers; + + PolylineLayer({ + super.key, + this.polylines = const [], + this.polylineCulling = false, + this.saveLayers = false, + }) { + if (polylineCulling) { + for (final polyline in polylines) { + polyline.boundingBox = LatLngBounds.fromPoints(polyline.points); + } + } + } @override Widget build(BuildContext context) { @@ -75,12 +69,12 @@ class PolylineLayerWidget extends StatelessWidget { return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); - final polylines = []; + final polylineWidgets = []; - for (final polylineOpt in options.polylines) { + for (final polylineOpt in polylines) { polylineOpt.offsets.clear(); - if (options.polylineCulling && + if (polylineCulling && !polylineOpt.boundingBox.isOverlapping(map.bounds)) { // skip this polyline as it's offscreen continue; @@ -88,14 +82,14 @@ class PolylineLayerWidget extends StatelessWidget { _fillOffsets(polylineOpt.offsets, polylineOpt.points, map); - polylines.add(CustomPaint( - painter: PolylinePainter(polylineOpt, options.saveLayers), + polylineWidgets.add(CustomPaint( + painter: PolylinePainter(polylineOpt, saveLayers), size: size, )); } return Stack( - children: polylines, + children: polylineWidgets, ); }, ); diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 0b7bff000..8a4468386 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -114,7 +114,7 @@ class _TestAppState extends State { urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'])), - MarkerLayerWidget(options: MarkerLayerOptions(markers: _markers)), + MarkerLayer(markers: _markers), ], ), ), From 244f0fac1a60505b980c4094f9153cc58f630682 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 12:58:53 -0500 Subject: [PATCH 13/32] remove marker layer cache --- lib/src/layer/marker_layer.dart | 56 +++------------------------------ 1 file changed, 5 insertions(+), 51 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index e251741d0..dc378fd31 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -126,10 +126,6 @@ class Marker { class MarkerLayer extends StatefulWidget { final List markers; - /// Toggle marker position caching. Enabling will improve performance, but may introducen - /// errors when adding/removing markers. Default is enabled (`true`). - final bool usePxCache; - /// If true markers will be counter rotated to the map rotation final bool? rotate; @@ -159,67 +155,26 @@ class MarkerLayer extends StatefulWidget { this.markers = const [], this.rotate = false, this.rotateOrigin, - this.rotateAlignment = Alignment.center, - this.usePxCache = true}); + this.rotateAlignment = Alignment.center}); @override State createState() => _MarkerLayerState(); } class _MarkerLayerState extends State { - double lastZoom = -1; - - /// List containing cached pixel positions of markers - /// Should be discarded when zoom changes - // Has a fixed length of markerOpts.markers.length - better performance: - // https://stackoverflow.com/questions/15943890/is-there-a-performance-benefit-in-using-fixed-length-lists-in-dart - var _pxCache = []; - - /// Calling this every time markerOpts change should guarantee proper length - List generatePxCache(MapState map) { - if (widget.usePxCache) { - return List.generate( - widget.markers.length, - (i) => map.project(widget.markers[i].point), - ); - } - return []; - } - - bool updatePxCacheIfNeeded(MapState map) { - var didUpdate = false; - - /// markers may be modified, so update cache. Note, someone may - /// have not added to a cache, but modified, so this won't catch - /// this case. Parent widget setState should be called to call - /// didUpdateWidget to force a cache reload - - if (widget.markers.length != _pxCache.length) { - _pxCache = generatePxCache(map); - didUpdate = true; - } - return didUpdate; - } @override Widget build(BuildContext context) { final map = MapState.maybeOf(context)!; - final usePxCache = widget.usePxCache; final markers = []; - final sameZoom = map.zoom == lastZoom; - - final cacheUpdated = updatePxCacheIfNeeded(map); for (var i = 0; i < widget.markers.length; i++) { final marker = widget.markers[i]; - // Decide whether to use cached point or calculate it - final pxPoint = usePxCache && (sameZoom || cacheUpdated) - ? _pxCache[i] - : map.project(marker.point); - if (!sameZoom && usePxCache) { - _pxCache[i] = pxPoint; - } + // print(usePxCache && (sameZoom || cacheUpdated)); + + // Find the position of the point on the screen + final pxPoint = map.project(marker.point); // See if any portion of the Marker rect resides in the map bounds // If not, don't spend any resources on build function. @@ -261,7 +216,6 @@ class _MarkerLayerState extends State { ), ); } - lastZoom = map.zoom; return Stack( children: markers, ); From f4b6163dd237adc508048b3cfd08a020deb92fc4 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 13:12:11 -0500 Subject: [PATCH 14/32] migrate circle layer --- example/lib/pages/circle.dart | 2 +- example/lib/pages/epsg3413_crs.dart | 6 ++---- lib/src/layer/circle_layer.dart | 20 ++++++++------------ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/example/lib/pages/circle.dart b/example/lib/pages/circle.dart index 9befece8d..5d03e93fd 100644 --- a/example/lib/pages/circle.dart +++ b/example/lib/pages/circle.dart @@ -44,7 +44,7 @@ class CirclePage extends StatelessWidget { subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', )), - CircleLayerWidget(options: CircleLayerOptions(circles: circleMarkers)), + CircleLayer(circles: circleMarkers), ], ), ), diff --git a/example/lib/pages/epsg3413_crs.dart b/example/lib/pages/epsg3413_crs.dart index cd6587c65..769c25cb2 100644 --- a/example/lib/pages/epsg3413_crs.dart +++ b/example/lib/pages/epsg3413_crs.dart @@ -135,7 +135,7 @@ class _EPSG3413PageState extends State { ), children: [ TileLayerWidget( - options: TileLayerOptions( + options: TileLayerOptions( opacity: 1, backgroundColor: Colors.transparent, wmsOptions: WMSTileLayerOptions( @@ -160,9 +160,7 @@ class _EPSG3413PageState extends State { ) ], ), - CircleLayerWidget(options: CircleLayerOptions( - circles: circles, - )), + CircleLayer(circles: circles), ], ), ), diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index 52df64c4f..c7b3f7c55 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -2,13 +2,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; -class CircleLayerOptions { - final List circles; - CircleLayerOptions({ - this.circles = const [], - }); -} - class CircleMarker { final LatLng point; final double radius; @@ -28,9 +21,12 @@ class CircleMarker { }); } -class CircleLayerWidget extends StatelessWidget { - final CircleLayerOptions options; - const CircleLayerWidget({super.key, required this.options}); +class CircleLayer extends StatelessWidget { + final List circles; + const CircleLayer({ + super.key, + this.circles = const [], + }); @override Widget build(BuildContext context) { @@ -38,8 +34,8 @@ class CircleLayerWidget extends StatelessWidget { builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); final map = MapState.maybeOf(context)!; - final circleWidgets = []; - for (final circle in options.circles) { + final circleWidgets = []; + for (final circle in circles) { circle.offset = map.getOffsetFromOrigin(circle.point); if (circle.useRadiusInMeter) { From e942cfa6b34298001708b4cc3d941f2582ad3236 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 16:03:20 -0500 Subject: [PATCH 15/32] remove on ready and dispose streamGroups --- lib/flutter_map.dart | 2 -- lib/src/map/flutter_map_state.dart | 14 ++------------ lib/src/map/map.dart | 3 --- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index c94766c7f..442395093 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -119,8 +119,6 @@ abstract class MapController { CenterZoom centerZoomFitBounds(LatLngBounds bounds, {FitBoundsOptions? options}); - Future get onReady; - LatLng get center; LatLngBounds? get bounds; diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 7666d2bae..15ee19282 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:async/async.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -11,8 +10,9 @@ import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; class FlutterMapState extends MapGestureMixin with AutomaticKeepAliveClientMixin { - final List> groups = >[]; + late StreamSubscription _rebuildStream; + final _positionedTapController = PositionedTapController(); final MapController _localController = MapControllerImpl(); @@ -54,17 +54,8 @@ class FlutterMapState extends MapGestureMixin } } - void _disposeStreamGroups() { - for (final group in groups) { - group.close(); - } - - groups.clear(); - } - @override void dispose() { - _disposeStreamGroups(); mapState.dispose(); _localController.dispose(); _rebuildStream.cancel(); @@ -74,7 +65,6 @@ class FlutterMapState extends MapGestureMixin @override Widget build(BuildContext context) { - _disposeStreamGroups(); super.build(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart index aa9e6e78d..2e6e44d4e 100644 --- a/lib/src/map/map.dart +++ b/lib/src/map/map.dart @@ -14,9 +14,6 @@ class MapControllerImpl implements MapController { @override StreamSink get mapEventSink => _mapEventSink.sink; - @override - Future get onReady => _readyCompleter.future; - @override void dispose() { _mapEventSink.close(); From 1826e5d2f2f2aed32c3c1dab5d2bf44bcecc379a Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 16:05:54 -0500 Subject: [PATCH 16/32] remove onmapcreated --- example/lib/pages/point_to_latlng.dart | 4 ---- lib/flutter_map.dart | 2 -- lib/src/map/flutter_map_state.dart | 4 ---- 3 files changed, 10 deletions(-) diff --git a/example/lib/pages/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index 2ddee5f0e..47ca59288 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -31,10 +31,6 @@ class PointToLatlngPage extends State { mapEventSubscription = mapController.mapEventStream .listen((mapEvent) => onMapEvent(mapEvent, context)); - - Future.delayed(Duration.zero, () { - mapController.onReady.then((_) => _updatePointLatLng(context)); - }); } @override diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 442395093..5afb13751 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -252,7 +252,6 @@ class MapOptions { final PointerCancelCallback? onPointerCancel; final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; - final MapCreatedCallback? onMapCreated; final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; @@ -310,7 +309,6 @@ class MapOptions { this.onPointerCancel, this.onPointerHover, this.onPositionChanged, - this.onMapCreated, this.slideOnBoundaries = false, this.adaptiveBoundaries = false, this.screenSize, diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 15ee19282..326f26d3a 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -48,10 +48,6 @@ class FlutterMapState extends MapGestureMixin if(mounted) setState(() {}); }); - // Callback onMapCreated if not null - if (options.onMapCreated != null) { - options.onMapCreated!(mapController); - } } @override From aed86a3ef8407b22b739fb050988a10352548f3e Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 19:14:31 -0500 Subject: [PATCH 17/32] rough new state management implementation --- .../lib/pages/scale_layer_plugin_option.dart | 2 +- .../lib/pages/zoombuttons_plugin_option.dart | 6 +- lib/flutter_map.dart | 12 +- lib/plugin_api.dart | 1 + lib/src/gestures/gestures.dart | 11 +- lib/src/layer/circle_layer.dart | 4 +- lib/src/layer/marker_layer.dart | 5 +- lib/src/layer/overlay_image_layer.dart | 10 +- lib/src/layer/polygon_layer.dart | 5 +- lib/src/layer/polyline_layer.dart | 5 +- lib/src/layer/tile_layer/tile_layer.dart | 61 +- .../tile_layer/transformation_calculator.dart | 6 +- lib/src/map/flutter_map_state.dart | 594 ++++++++++++++++- lib/src/map/map.dart | 612 +----------------- lib/src/map/map_state_widget.dart | 6 +- 15 files changed, 629 insertions(+), 711 deletions(-) diff --git a/example/lib/pages/scale_layer_plugin_option.dart b/example/lib/pages/scale_layer_plugin_option.dart index 7d38e519a..8ddc430a2 100644 --- a/example/lib/pages/scale_layer_plugin_option.dart +++ b/example/lib/pages/scale_layer_plugin_option.dart @@ -52,7 +52,7 @@ class ScaleLayerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; final zoom = map.zoom; final distance = scale[max(0, min(20, zoom.round() + 2))].toDouble(); final center = map.center; diff --git a/example/lib/pages/zoombuttons_plugin_option.dart b/example/lib/pages/zoombuttons_plugin_option.dart index 2e882bff2..0783bfd47 100644 --- a/example/lib/pages/zoombuttons_plugin_option.dart +++ b/example/lib/pages/zoombuttons_plugin_option.dart @@ -35,7 +35,7 @@ class FlutterMapZoomButtons extends StatelessWidget { @override Widget build(BuildContext context) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; return Align( alignment: alignment, child: Column( @@ -52,7 +52,7 @@ class FlutterMapZoomButtons extends StatelessWidget { backgroundColor: zoomInColor ?? Theme.of(context).primaryColor, onPressed: () { - final bounds = map.getBounds(); + final bounds = map.bounds; final centerZoom = map.getBoundsCenterZoom(bounds, options); var zoom = centerZoom.zoom + 1; if (zoom > maxZoom) { @@ -74,7 +74,7 @@ class FlutterMapZoomButtons extends StatelessWidget { backgroundColor: zoomOutColor ?? Theme.of(context).primaryColor, onPressed: () { - final bounds = map.getBounds(); + final bounds = map.bounds; final centerZoom = map.getBoundsCenterZoom(bounds, options); var zoom = centerZoom.zoom - 1; if (zoom < minZoom) { diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 5afb13751..22cc3bc7a 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -64,12 +64,12 @@ class FlutterMap extends StatefulWidget { final MapController? mapController; const FlutterMap({ - Key? key, + super.key, required this.options, this.children = const [], this.nonRotatedChildren = const [], this.mapController, - }) : super(key: key); + }); @override FlutterMapState createState() => FlutterMapState(); @@ -129,9 +129,7 @@ abstract class MapController { Stream get mapEventStream; - StreamSink get mapEventSink; - - set state(MapState state); + set state(FlutterMapState state); void dispose(); @@ -252,6 +250,9 @@ class MapOptions { final PointerCancelCallback? onPointerCancel; final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; + + final Function(MapEvent)? onMapEvent; + final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; @@ -309,6 +310,7 @@ class MapOptions { this.onPointerCancel, this.onPointerHover, this.onPositionChanged, + this.onMapEvent, this.slideOnBoundaries = false, this.adaptiveBoundaries = false, this.screenSize, diff --git a/lib/plugin_api.dart b/lib/plugin_api.dart index 5a34ac49d..c006a5d58 100644 --- a/lib/plugin_api.dart +++ b/lib/plugin_api.dart @@ -1,6 +1,7 @@ library flutter_map.plugin_api; export 'package:flutter_map/flutter_map.dart'; +export 'package:flutter_map/src/map/flutter_map_state.dart'; export 'package:flutter_map/src/core/bounds.dart'; export 'package:flutter_map/src/core/center_zoom.dart'; export 'package:flutter_map/src/map/map.dart'; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 48002d46e..74131da70 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/physics.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/latlng_tween.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart'; import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; @@ -106,7 +106,7 @@ abstract class MapGestureMixin extends State @override FlutterMap get widget; - MapState get mapState; + FlutterMapState get mapState; MapController get mapController; @@ -454,7 +454,6 @@ abstract class MapGestureMixin extends State newCenter, newZoom, hasGesture: true, - callOnMoveSink: false, source: eventSource, ); } @@ -477,14 +476,16 @@ abstract class MapGestureMixin extends State mapRotated = mapState.rotate( mapState.rotation + rotationDiff, hasGesture: true, - callOnMoveSink: false, source: eventSource, ); } } + //TODO maybe not needed? if (mapMoved || mapRotated) { - mapState.rebuildLayers(); + mapState.setState(() { + + }); } } } diff --git a/lib/src/layer/circle_layer.dart b/lib/src/layer/circle_layer.dart index c7b3f7c55..ccac574c3 100644 --- a/lib/src/layer/circle_layer.dart +++ b/lib/src/layer/circle_layer.dart @@ -1,5 +1,5 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart' hide Path; class CircleMarker { @@ -33,7 +33,7 @@ class CircleLayer extends StatelessWidget { return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; final circleWidgets = []; for (final circle in circles) { circle.offset = map.getOffsetFromOrigin(circle.point); diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index dc378fd31..f44b3c118 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -1,7 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart'; class Anchor { @@ -165,7 +165,7 @@ class _MarkerLayerState extends State { @override Widget build(BuildContext context) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; final markers = []; for (var i = 0; i < widget.markers.length; i++) { @@ -191,6 +191,7 @@ class _MarkerLayerState extends State { final ne = CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion); if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { + print("Skipping due to bounds"); continue; } diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 7bb55b9b2..91e8ae49f 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -1,6 +1,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:flutter_map/src/core/bounds.dart'; import 'package:latlong2/latlong.dart'; @@ -12,7 +12,7 @@ abstract class BaseOverlayImage { bool get gaplessPlayback; - Positioned buildPositionedForOverlay(MapState map); + Positioned buildPositionedForOverlay(FlutterMapState map); Image buildImageForOverlay() { return Image( @@ -45,7 +45,7 @@ class OverlayImage extends BaseOverlayImage { this.gaplessPlayback = false}); @override - Positioned buildPositionedForOverlay(MapState map) { + Positioned buildPositionedForOverlay(FlutterMapState map) { final pixelOrigin = map.getPixelOrigin(); // northWest is not necessarily upperLeft depending on projection final bounds = Bounds( @@ -93,7 +93,7 @@ class RotatedOverlayImage extends BaseOverlayImage { this.filterQuality = FilterQuality.medium}); @override - Positioned buildPositionedForOverlay(MapState map) { + Positioned buildPositionedForOverlay(FlutterMapState map) { final pixelOrigin = map.getPixelOrigin(); final pxTopLeft = map.project(topLeftCorner) - pixelOrigin; @@ -139,7 +139,7 @@ class OverlayImageLayer extends StatelessWidget { @override Widget build(BuildContext context) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; return ClipRect( child: Stack( children: [ diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index 3f599d479..44922e757 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/layer/label.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI @@ -70,7 +71,7 @@ class PolygonLayer extends StatelessWidget { Widget build(BuildContext context) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; final size = Size(bc.maxWidth, bc.maxHeight); final polygonsWidget = []; @@ -115,7 +116,7 @@ class PolygonLayer extends StatelessWidget { } void _fillOffsets( - final List offsets, final List points, MapState map) { + final List offsets, final List points, FlutterMapState map) { final len = points.length; for (var i = 0; i < len; ++i) { final point = points[i]; diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 6d146fd78..940c60342 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -3,6 +3,7 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; @@ -65,7 +66,7 @@ class PolylineLayer extends StatelessWidget { @override Widget build(BuildContext context) { - final map = MapState.maybeOf(context)!; + final map = FlutterMapState.maybeOf(context)!; return LayoutBuilder( builder: (BuildContext context, BoxConstraints bc) { final size = Size(bc.maxWidth, bc.maxHeight); @@ -96,7 +97,7 @@ class PolylineLayer extends StatelessWidget { } void _fillOffsets( - final List offsets, final List points, MapState map) { + final List offsets, final List points, FlutterMapState map) { final len = points.length; for (var i = 0; i < len; ++i) { final point = points[i]; diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 31801c06a..c26c0f34c 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -12,7 +12,7 @@ import 'package:flutter_map/src/layer/tile_layer/tile_manager.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_transformation.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_widget.dart'; import 'package:flutter_map/src/layer/tile_layer/transformation_calculator.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart'; import 'package:tuple/tuple.dart'; @@ -25,7 +25,7 @@ class TileLayerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final mapState = MapState.maybeOf(context)!; + final mapState = FlutterMapState.maybeOf(context)!; return TileLayer( mapState: mapState, options: options, @@ -35,7 +35,7 @@ class TileLayerWidget extends StatelessWidget { class TileLayer extends StatefulWidget { final TileLayerOptions options; - final MapState mapState; + final FlutterMapState mapState; const TileLayer({ super.key, @@ -48,7 +48,7 @@ class TileLayer extends StatefulWidget { } class _TileLayerState extends State with TickerProviderStateMixin { - MapState get map => widget.mapState; + FlutterMapState get map => widget.mapState; TileLayerOptions get options => widget.options; late Bounds _globalTileRange; @@ -56,7 +56,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { Tuple2? _wrapY; double? _tileZoom; - StreamSubscription? _moveSub; StreamSubscription? _resetSub; StreamController? _throttleUpdate; late CustomPoint _tileSize; @@ -74,7 +73,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { _tileSize = CustomPoint(options.tileSize, options.tileSize); _resetView(); _update(null); - _moveSub = widget.mapState.onMoved.listen((_) => _handleMove()); if (options.reset != null) { _resetSub = options.reset?.listen((_) => _resetTiles()); @@ -150,7 +148,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { void dispose() { _tileManager.removeAll(options.evictErrorTileStrategy); _resetSub?.cancel(); - _moveSub?.cancel(); _pruneLater?.cancel(); options.tileProvider.dispose(); _throttleUpdate?.close(); @@ -160,6 +157,29 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { + //Handle movement + final tileZoom = _clampZoom(map.zoom.roundToDouble()); + + if (_tileZoom == null) { + // if there is no _tileZoom available it means we are out within zoom level + // we will restore fully via _setView call if we are back on trail + if ((tileZoom <= options.maxZoom) && (tileZoom >= options.minZoom)) { + _tileZoom = tileZoom; + _setView(map.center, tileZoom); + } + } else { + if ((tileZoom - _tileZoom!).abs() >= 1) { + // It was a zoom lvl change + _setView(map.center, tileZoom); + } else { + if (_throttleUpdate == null) { + _update(null); + } else { + _throttleUpdate!.add(null); + } + } + } + final tilesToRender = _tileZoom == null ? _tileManager.all() : _tileManager.sortedByDistanceToZoomAscending( @@ -299,33 +319,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } - void _handleMove() { - final tileZoom = _clampZoom(map.zoom.roundToDouble()); - - if (_tileZoom == null) { - // if there is no _tileZoom available it means we are out within zoom level - // we will restore fully via _setView call if we are back on trail - if ((tileZoom <= options.maxZoom) && (tileZoom >= options.minZoom)) { - _tileZoom = tileZoom; - setState(() { - _setView(map.center, tileZoom); - }); - } - } else { - setState(() { - if ((tileZoom - _tileZoom!).abs() >= 1) { - // It was a zoom lvl change - _setView(map.center, tileZoom); - } else { - if (_throttleUpdate == null) { - _update(null); - } else { - _throttleUpdate!.add(null); - } - } - }); - } - } Bounds _getTiledPixelBounds(LatLng center) { final scale = map.getZoomScale(map.zoom, _tileZoom); diff --git a/lib/src/layer/tile_layer/transformation_calculator.dart b/lib/src/layer/tile_layer/transformation_calculator.dart index 6f1419198..61a5c47d5 100644 --- a/lib/src/layer/tile_layer/transformation_calculator.dart +++ b/lib/src/layer/tile_layer/transformation_calculator.dart @@ -1,13 +1,13 @@ import 'package:flutter_map/src/layer/tile_layer/level.dart'; import 'package:flutter_map/src/layer/tile_layer/tile_transformation.dart'; -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; class TransformationCalculator { final Map _levels = {}; Level? levelAt(double zoom) => _levels[zoom]; - Level getOrCreateLevel(double zoom, MapState map) { + Level getOrCreateLevel(double zoom, FlutterMapState map) { final level = _levels[zoom]; if (level != null) return level; @@ -30,7 +30,7 @@ class TransformationCalculator { _levels.remove(levelZoom); } - TileTransformation transformationFor(double levelZoom, MapState map) { + TileTransformation transformationFor(double levelZoom, FlutterMapState map) { final level = _levels[levelZoom]!; final scale = map.getZoomScale(map.zoom, level.zoom); final pixelOrigin = map.getNewPixelOrigin(map.center, map.zoom).round(); diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 326f26d3a..e701497ad 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -6,13 +6,14 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; import 'package:flutter_map/src/map/map.dart'; import 'package:flutter_map/src/map/map_state_widget.dart'; +import 'package:latlong2/latlong.dart'; import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart'; +import 'dart:math' as math; +import 'package:flutter_map/src/core/bounds.dart'; class FlutterMapState extends MapGestureMixin with AutomaticKeepAliveClientMixin { - late StreamSubscription _rebuildStream; - final _positionedTapController = PositionedTapController(); final MapController _localController = MapControllerImpl(); @@ -20,42 +21,37 @@ class FlutterMapState extends MapGestureMixin MapOptions get options => widget.options; @override - late final MapState mapState; + FlutterMapState get mapState => this; @override MapController get mapController => widget.mapController ?? _localController; - @override - void didUpdateWidget(FlutterMap oldWidget) { - super.didUpdateWidget(oldWidget); - - mapState.options = options; - } - @override void initState() { super.initState(); - mapState = MapState(options, (degree) { - if (mounted) setState(() {}); - }, mapController.mapEventSink); - mapController.state = mapState; - - // Whenever there is a map event (move, rotate, etc); - // setstate to trigger rebuilding children. - // It should be fine to trigger setState multiple times as long - // as it is within the same frame. (ex move and rotate events). - _rebuildStream = mapController.mapEventStream.listen((event) { - if(mounted) setState(() {}); - }); + //TODO there has to be a better way to pass state to my controller + mapController.state = this; + + // Initialize all variables here, if they need to be updated after the map changes + // like center, or bounds they also need to be updated in build. + _rotation = options.rotation; + _zoom = options.zoom; + _rotationRad = degToRadian(options.rotation); + _pixelBounds = getPixelBounds(zoom); + _bounds = _calculateBounds(); + + move(options.center, zoom, source: MapEventSource.initialization); + + // Funally, fit the map to restrictions + if (options.bounds != null) { + fitBounds(options.bounds!, options.boundsOptions); + } } @override void dispose() { - mapState.dispose(); _localController.dispose(); - _rebuildStream.cancel(); - super.dispose(); } @@ -64,17 +60,16 @@ class FlutterMapState extends MapGestureMixin super.build(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - final hasLateSize = mapState.hasLateSize(constraints); + setOriginalSize(constraints.maxWidth, constraints.maxHeight); - mapState.setOriginalSize(constraints.maxWidth, constraints.maxHeight); + _rotationRad = degToRadian(rotation); + _pixelBounds = getPixelBounds(zoom); + _bounds = _calculateBounds(); - // It's possible on first call to LayoutBuilder, it may not know a size - // which will cause methods like fitBounds to break. These methods - // could be called in initIfLateSize() - if (hasLateSize) { - mapState.initIfLateSize(); + if (options.bounds != null) { + fitBounds(options.bounds!, options.boundsOptions); } - final size = mapState.size; + final scaleGestureTeam = GestureArenaTeam(); @@ -114,7 +109,7 @@ class FlutterMapState extends MapGestureMixin ); return MapStateInheritedWidget( - mapState: mapState, + mapState: this, child: Listener( onPointerDown: onPointerDown, onPointerUp: onPointerUp, @@ -150,6 +145,7 @@ class FlutterMapState extends MapGestureMixin } Widget _buildMap(CustomPoint size) { + print("Map built"); return ClipRect( child: Stack( children: [ @@ -159,7 +155,7 @@ class FlutterMapState extends MapGestureMixin minHeight: size.y, maxHeight: size.y, child: Transform.rotate( - angle: mapState.rotationRad, + angle: rotationRad, child: Stack( children: widget.children, ), @@ -175,4 +171,532 @@ class FlutterMapState extends MapGestureMixin @override bool get wantKeepAlive => options.keepAlive; + + ///MAP STATE + ///MAP STATE + ///MAP STATE + ///MAP STATE + ///MAP STATE + ///MAP STATE + ///MAP STATE + ///MAP STATE + + late double _zoom; + late double _rotation; + late double _rotationRad; + + double get zoom => _zoom; + + double get rotation => _rotation; + + set rotation(double rotation) { + _rotation = rotation; + _rotationRad = degToRadian(rotation); + } + + double get rotationRad => _rotationRad; + + LatLng? _lastCenter; + late CustomPoint _pixelOrigin; + + LatLng get center => getCenter(); + + late LatLngBounds _bounds; + LatLngBounds get bounds => _bounds; + + late Bounds _pixelBounds; + Bounds get pixelBounds => _pixelBounds; + + // Original size of the map where rotation isn't calculated + CustomPoint? _originalSize; + + CustomPoint? get originalSize => _originalSize; + + void setOriginalSize(double width, double height) { + final isCurrSizeNull = _originalSize == null; + if (isCurrSizeNull || + _originalSize!.x != width || + _originalSize!.y != height) { + _originalSize = CustomPoint(width, height); + + _updateSizeByOriginalSizeAndRotation(); + } + } + + // Extended size of the map where rotation is calculated + CustomPoint? _size; + + CustomPoint get size => _size ?? const CustomPoint(0.0, 0.0); + + void _updateSizeByOriginalSizeAndRotation() { + final originalWidth = _originalSize!.x; + final originalHeight = _originalSize!.y; + + if (_rotation != 0.0) { + final cosAngle = math.cos(_rotationRad).abs(); + final sinAngle = math.sin(_rotationRad).abs(); + final num width = + (originalWidth * cosAngle) + (originalHeight * sinAngle); + final num height = + (originalHeight * cosAngle) + (originalWidth * sinAngle); + + _size = CustomPoint(width, height); + } else { + _size = CustomPoint(originalWidth, originalHeight); + } + + _pixelOrigin = getNewPixelOrigin(_lastCenter!); + } + + void _handleMoveEmit(LatLng targetCenter, double targetZoom, bool hasGesture, + MapEventSource source, String? id) { + if (source == MapEventSource.flingAnimationController) { + emitMapEvent( + MapEventFlingAnimation( + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } else if (source == MapEventSource.doubleTapZoomAnimationController) { + emitMapEvent( + MapEventDoubleTapZoom( + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } else if (source == MapEventSource.scrollWheel) { + emitMapEvent( + MapEventScrollWheelZoom( + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } else if (source == MapEventSource.onDrag || + source == MapEventSource.onMultiFinger) { + emitMapEvent( + MapEventMove( + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } else if (source == MapEventSource.mapController) { + emitMapEvent( + MapEventMove( + id: id, + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } else if (source == MapEventSource.custom) { + // for custom source, emit move event if zoom or center has changed + if (targetZoom != _zoom || + _lastCenter == null || + targetCenter.latitude != _lastCenter!.latitude || + targetCenter.longitude != _lastCenter!.longitude) { + emitMapEvent( + MapEventMove( + id: id, + center: _lastCenter!, + zoom: _zoom, + targetCenter: targetCenter, + targetZoom: targetZoom, + source: source, + ), + ); + } + } + } + + void emitMapEvent(MapEvent event) { + setState(() { + widget.options.onMapEvent?.call(event); + }); + } + + bool rotate( + double degree, { + bool hasGesture = false, + required MapEventSource source, + String? id, + }) { + if (degree != _rotation) { + final oldRotation = _rotation; + rotation = degree; + _updateSizeByOriginalSizeAndRotation(); + + // onRotationChanged(_rotation); + + emitMapEvent( + MapEventRotate( + id: id, + currentRotation: oldRotation, + targetRotation: _rotation, + center: _lastCenter!, + zoom: _zoom, + source: source, + ), + ); + + return true; + } + + return false; + } + + MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, + {required MapEventSource source, String? id}) { + final moveSucc = + move(center, zoom, id: id, source: source); + final rotateSucc = + rotate(degree, id: id, source: source); + + if (moveSucc || rotateSucc) { + setState(() {}); + } + + return MoveAndRotateResult(moveSucc, rotateSucc); + } + + bool move(LatLng center, double zoom, + {bool hasGesture = false, + required MapEventSource source, + String? id}) { + zoom = fitZoomToBounds(zoom); + final mapMoved = center != _lastCenter || zoom != _zoom; + + if (_lastCenter != null && (!mapMoved || !_bounds.isValid)) { + return false; + } + + if (options.isOutOfBounds(center)) { + if (!options.slideOnBoundaries) { + return false; + } + center = options.containPoint(center, _lastCenter ?? center); + } + + // Try and fit the corners of the map inside the visible area. + // If it's still outside (so response is null), don't perform a move. + if (options.maxBounds != null) { + final adjustedCenter = + adjustCenterIfOutsideMaxBounds(center, zoom, options.maxBounds!); + if (adjustedCenter == null) { + return false; + } else { + center = adjustedCenter; + } + } + + _handleMoveEmit(center, zoom, hasGesture, source, id); + + _zoom = zoom; + _lastCenter = center; + _pixelBounds = getPixelBounds(_zoom); + _pixelOrigin = getNewPixelOrigin(center); + setState(() { + + }); + + if (options.onPositionChanged != null) { + final mapPosition = MapPosition( + center: center, bounds: _bounds, zoom: zoom, hasGesture: hasGesture); + + options.onPositionChanged!(mapPosition, hasGesture); + } + + return true; + } + + double fitZoomToBounds(double? zoom) { + zoom ??= _zoom; + // Abide to min/max zoom + if (options.maxZoom != null) { + zoom = (zoom > options.maxZoom!) ? options.maxZoom! : zoom; + } + if (options.minZoom != null) { + zoom = (zoom < options.minZoom!) ? options.minZoom! : zoom; + } + return zoom; + } + + void fitBounds(LatLngBounds bounds, FitBoundsOptions options) { + if (!bounds.isValid) { + throw Exception('Bounds are not valid.'); + } + final target = getBoundsCenterZoom(bounds, options); + move(target.center, target.zoom, source: MapEventSource.fitBounds); + } + + CenterZoom centerZoomFitBounds( + LatLngBounds bounds, FitBoundsOptions options) { + if (!bounds.isValid) { + throw Exception('Bounds are not valid.'); + } + return getBoundsCenterZoom(bounds, options); + } + + LatLng getCenter() { + if (_lastCenter != null) { + return _lastCenter!; + } + return layerPointToLatLng(_centerLayerPoint); + } + + LatLngBounds _calculateBounds() { + print("got bounds"); + return LatLngBounds( + unproject(_pixelBounds.bottomLeft), + unproject(_pixelBounds.topRight), + ); + } + + CenterZoom getBoundsCenterZoom( + LatLngBounds bounds, FitBoundsOptions options) { + final paddingTL = + CustomPoint(options.padding.left, options.padding.top); + final paddingBR = + CustomPoint(options.padding.right, options.padding.bottom); + + final paddingTotalXY = paddingTL + paddingBR; + + var zoom = getBoundsZoom(bounds, paddingTotalXY, inside: options.inside); + zoom = math.min(options.maxZoom, zoom); + + final paddingOffset = (paddingBR - paddingTL) / 2; + final swPoint = project(bounds.southWest!, zoom); + final nePoint = project(bounds.northEast!, zoom); + final center = unproject((swPoint + nePoint) / 2 + paddingOffset, zoom); + return CenterZoom( + center: center, + zoom: zoom, + ); + } + + double getBoundsZoom(LatLngBounds bounds, CustomPoint padding, + {bool inside = false}) { + var zoom = this.zoom; + final min = options.minZoom ?? 0.0; + final max = options.maxZoom ?? double.infinity; + final nw = bounds.northWest; + final se = bounds.southEast; + var size = this.size - padding; + // Prevent negative size which results in NaN zoom value later on in the calculation + size = CustomPoint(math.max(0.0, size.x), math.max(0.0, size.y)); + final boundsSize = Bounds(project(se, zoom), project(nw, zoom)).size; + final scaleX = size.x / boundsSize.x; + final scaleY = size.y / boundsSize.y; + final scale = inside ? math.max(scaleX, scaleY) : math.min(scaleX, scaleY); + + zoom = getScaleZoom(scale, zoom); + + return math.max(min, math.min(max, zoom)); + } + + CustomPoint project(LatLng latlng, [double? zoom]) { + zoom ??= _zoom; + return options.crs.latLngToPoint(latlng, zoom); + } + + LatLng unproject(CustomPoint point, [double? zoom]) { + zoom ??= _zoom; + return options.crs.pointToLatLng(point, zoom)!; + } + + LatLng layerPointToLatLng(CustomPoint point) { + return unproject(point); + } + + CustomPoint get _centerLayerPoint { + return size / 2; + } + + double getZoomScale(double toZoom, double? fromZoom) { + final crs = options.crs; + fromZoom = fromZoom ?? _zoom; + return crs.scale(toZoom) / crs.scale(fromZoom); + } + + double getScaleZoom(double scale, double? fromZoom) { + final crs = options.crs; + fromZoom = fromZoom ?? _zoom; + return crs.zoom(scale * crs.scale(fromZoom)) as double; + } + + Bounds? getPixelWorldBounds(double? zoom) { + return options.crs.getProjectedBounds(zoom ?? _zoom); + } + + CustomPoint getPixelOrigin() { + return _pixelOrigin; + } + + Offset getOffsetFromOrigin(LatLng pos) { + final delta = project(pos) - getPixelOrigin(); + return Offset(delta.x.toDouble(), delta.y.toDouble()); + } + + CustomPoint getNewPixelOrigin(LatLng center, [double? zoom]) { + final viewHalf = size / 2.0; + return (project(center, zoom) - viewHalf).round(); + } + + Bounds getPixelBounds(double zoom) { + print("pixel bounds"); + final mapZoom = zoom; + final scale = getZoomScale(mapZoom, zoom); + final pixelCenter = project(center, zoom).floor(); + final halfSize = size / (scale * 2); + return Bounds(pixelCenter - halfSize, pixelCenter + halfSize); + } + + LatLng? adjustCenterIfOutsideMaxBounds( + LatLng testCenter, double testZoom, LatLngBounds maxBounds) { + LatLng? newCenter; + + final swPixel = project(maxBounds.southWest!, testZoom); + final nePixel = project(maxBounds.northEast!, testZoom); + + final centerPix = project(testCenter, testZoom); + + final halfSizeX = size.x / 2; + final halfSizeY = size.y / 2; + + // Try and find the edge value that the center could use to stay within + // the maxBounds. This should be ok for panning. If we zoom, it is possible + // there is no solution to keep all corners within the bounds. If the edges + // are still outside the bounds, don't return anything. + final leftOkCenter = math.min(swPixel.x, nePixel.x) + halfSizeX; + final rightOkCenter = math.max(swPixel.x, nePixel.x) - halfSizeX; + final topOkCenter = math.min(swPixel.y, nePixel.y) + halfSizeY; + final botOkCenter = math.max(swPixel.y, nePixel.y) - halfSizeY; + + double? newCenterX; + double? newCenterY; + + var wasAdjusted = false; + + if (centerPix.x < leftOkCenter) { + wasAdjusted = true; + newCenterX = leftOkCenter; + } else if (centerPix.x > rightOkCenter) { + wasAdjusted = true; + newCenterX = rightOkCenter; + } + + if (centerPix.y < topOkCenter) { + wasAdjusted = true; + newCenterY = topOkCenter; + } else if (centerPix.y > botOkCenter) { + wasAdjusted = true; + newCenterY = botOkCenter; + } + + if (!wasAdjusted) { + return testCenter; + } + + final newCx = newCenterX ?? centerPix.x; + final newCy = newCenterY ?? centerPix.y; + + // Have a final check, see if the adjusted center is within maxBounds. + // If not, give up. + if (newCx < leftOkCenter || + newCx > rightOkCenter || + newCy < topOkCenter || + newCy > botOkCenter) { + return null; + } else { + newCenter = unproject(CustomPoint(newCx, newCy), testZoom); + } + + return newCenter; + } + + // This will convert a latLng to a position that we could use with a widget + // outside of FlutterMap layer space. Eg using a Positioned Widget. + CustomPoint latLngToScreenPoint(LatLng latLng) { + final nonRotatedPixelOrigin = + (project(getCenter(), zoom) - originalSize! / 2.0).round(); + + var point = options.crs.latLngToPoint(latLng, zoom); + + final mapCenter = options.crs.latLngToPoint(center, zoom); + + if (rotation != 0.0) { + point = rotatePoint(mapCenter, point, counterRotation: false); + } + + return point - nonRotatedPixelOrigin; + } + + LatLng? pointToLatLng(CustomPoint localPoint) { + if (originalSize == null) { + return null; + } + + final width = originalSize!.x; + final height = originalSize!.y; + + final localPointCenterDistance = + CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y); + final mapCenter = options.crs.latLngToPoint(center, zoom); + + var point = mapCenter - localPointCenterDistance; + + if (rotation != 0.0) { + point = rotatePoint(mapCenter, point); + } + + return options.crs.pointToLatLng(point, zoom); + } + + // Sometimes we need to make allowances that a rotation already exists, so + // it needs to be reversed (pointToLatLng), and sometimes we want to use + // the same rotation to create a new position (latLngToScreenpoint). + // counterRotation just makes allowances this for this. + CustomPoint rotatePoint( + CustomPoint mapCenter, CustomPoint point, + {bool counterRotation = true}) { + final counterRotationFactor = counterRotation ? -1 : 1; + + final m = Matrix4.identity() + ..translate(mapCenter.x.toDouble(), mapCenter.y.toDouble()) + ..rotateZ(rotationRad * counterRotationFactor) + ..translate(-mapCenter.x.toDouble(), -mapCenter.y.toDouble()); + + final tp = MatrixUtils.transformPoint( + m, Offset(point.x.toDouble(), point.y.toDouble())); + + return CustomPoint(tp.dx, tp.dy); + } + + static FlutterMapState? maybeOf(BuildContext context, {bool nullOk = false}) { + final widget = + context.dependOnInheritedWidgetOfExactType(); + if (nullOk || widget != null) { + return widget?.mapState; + } + throw FlutterError( + 'MapState.of() called with a context that does not contain a FlutterMap.'); + } + + + + + } diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart index 2e6e44d4e..9498bae1b 100644 --- a/lib/src/map/map.dart +++ b/lib/src/map/map.dart @@ -1,32 +1,23 @@ import 'dart:async'; -import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:flutter_map/src/core/bounds.dart'; -import 'package:flutter_map/src/map/map_state_widget.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart'; class MapControllerImpl implements MapController { - final Completer _readyCompleter = Completer(); final StreamController _mapEventSink = StreamController.broadcast(); - @override - StreamSink get mapEventSink => _mapEventSink.sink; - @override void dispose() { _mapEventSink.close(); } - late MapState _state; + late FlutterMapState _state; @override - set state(MapState state) { + set state(FlutterMapState state) { _state = state; - if (!_readyCompleter.isCompleted) { - _readyCompleter.complete(); - } } @override @@ -97,600 +88,3 @@ class MapControllerImpl implements MapController { @override Stream get mapEventStream => _mapEventSink.stream; } - -class MapState { - MapOptions options; - final ValueChanged onRotationChanged; - final StreamController _onMoveSink; - final StreamSink _mapEventSink; - - double _zoom; - double _rotation; - double _rotationRad; - - double get zoom => _zoom; - - double get rotation => _rotation; - - set rotation(double rotation) { - _rotation = rotation; - _rotationRad = degToRadian(rotation); - } - - double get rotationRad => _rotationRad; - - LatLng? _lastCenter; - LatLngBounds? _lastBounds; - Bounds? _lastPixelBounds; - late CustomPoint _pixelOrigin; - bool _initialized = false; - - MapState(this.options, this.onRotationChanged, this._mapEventSink) - : _rotation = options.rotation, - _rotationRad = degToRadian(options.rotation), - _zoom = options.zoom, - _onMoveSink = StreamController.broadcast(); - - Stream get onMoved => _onMoveSink.stream; - - // Original size of the map where rotation isn't calculated - CustomPoint? _originalSize; - - CustomPoint? get originalSize => _originalSize; - - void setOriginalSize(double width, double height) { - final isCurrSizeNull = _originalSize == null; - if (isCurrSizeNull || - _originalSize!.x != width || - _originalSize!.y != height) { - _originalSize = CustomPoint(width, height); - - _updateSizeByOriginalSizeAndRotation(); - - // rebuild layers if screen size has been changed - if (!isCurrSizeNull) { - _onMoveSink.add(null); - } - } - } - - // Extended size of the map where rotation is calculated - CustomPoint? _size; - - CustomPoint get size => _size ?? const CustomPoint(0.0, 0.0); - - void _updateSizeByOriginalSizeAndRotation() { - final originalWidth = _originalSize!.x; - final originalHeight = _originalSize!.y; - - if (_rotation != 0.0) { - final cosAngle = math.cos(_rotationRad).abs(); - final sinAngle = math.sin(_rotationRad).abs(); - final num width = - (originalWidth * cosAngle) + (originalHeight * sinAngle); - final num height = - (originalHeight * cosAngle) + (originalWidth * sinAngle); - - _size = CustomPoint(width, height); - } else { - _size = CustomPoint(originalWidth, originalHeight); - } - - if (!_initialized) { - _init(); - _initialized = true; - } - - _pixelOrigin = getNewPixelOrigin(_lastCenter!); - } - - LatLng get center => getCenter(); - - LatLngBounds get bounds => getBounds(); - - Bounds get pixelBounds => getLastPixelBounds(); - - void _init() { - if (options.bounds != null) { - fitBounds(options.bounds!, options.boundsOptions); - } else { - move(options.center, zoom, source: MapEventSource.initialization); - } - } - - // Check if we've just got a new size constraints. Initially a layoutBuilder - // May not be able to calculate a size, and end up with 0,0 - bool hasLateSize(BoxConstraints constraints) { - if (options.bounds != null && - originalSize != null && - originalSize!.x == 0.0 && - constraints.maxWidth != 0.0) { - return true; - } - return false; - } - - // If we've just calculated a size, we may want to call some methods that - // rely on it, like fitBounds. Add any others here. - void initIfLateSize() { - if (options.bounds != null) { - fitBounds(options.bounds!, options.boundsOptions); - } - } - - void _handleMoveEmit(LatLng targetCenter, double targetZoom, bool hasGesture, - MapEventSource source, String? id) { - if (source == MapEventSource.flingAnimationController) { - emitMapEvent( - MapEventFlingAnimation( - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } else if (source == MapEventSource.doubleTapZoomAnimationController) { - emitMapEvent( - MapEventDoubleTapZoom( - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } else if (source == MapEventSource.scrollWheel) { - emitMapEvent( - MapEventScrollWheelZoom( - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } else if (source == MapEventSource.onDrag || - source == MapEventSource.onMultiFinger) { - emitMapEvent( - MapEventMove( - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } else if (source == MapEventSource.mapController) { - emitMapEvent( - MapEventMove( - id: id, - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } else if (source == MapEventSource.custom) { - // for custom source, emit move event if zoom or center has changed - if (targetZoom != _zoom || - _lastCenter == null || - targetCenter.latitude != _lastCenter!.latitude || - targetCenter.longitude != _lastCenter!.longitude) { - emitMapEvent( - MapEventMove( - id: id, - center: _lastCenter!, - zoom: _zoom, - targetCenter: targetCenter, - targetZoom: targetZoom, - source: source, - ), - ); - } - } - } - - void emitMapEvent(MapEvent event) { - _mapEventSink.add(event); - } - - void dispose() { - _onMoveSink.close(); - _mapEventSink.close(); - } - - void rebuildLayers() { - _onMoveSink.add(null); - } - - bool rotate( - double degree, { - bool hasGesture = false, - bool callOnMoveSink = true, - required MapEventSource source, - String? id, - }) { - if (degree != _rotation) { - final oldRotation = _rotation; - rotation = degree; - _updateSizeByOriginalSizeAndRotation(); - - onRotationChanged(_rotation); - - emitMapEvent( - MapEventRotate( - id: id, - currentRotation: oldRotation, - targetRotation: _rotation, - center: _lastCenter!, - zoom: _zoom, - source: source, - ), - ); - - if (callOnMoveSink) { - _onMoveSink.add(null); - } - - return true; - } - - return false; - } - - MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, - {required MapEventSource source, String? id}) { - final moveSucc = - move(center, zoom, id: id, source: source, callOnMoveSink: false); - final rotateSucc = - rotate(degree, id: id, source: source, callOnMoveSink: false); - - if (moveSucc || rotateSucc) { - _onMoveSink.add(null); - } - - return MoveAndRotateResult(moveSucc, rotateSucc); - } - - bool move(LatLng center, double zoom, - {bool hasGesture = false, - bool callOnMoveSink = true, - required MapEventSource source, - String? id}) { - zoom = fitZoomToBounds(zoom); - final mapMoved = center != _lastCenter || zoom != _zoom; - - if (_lastCenter != null && (!mapMoved || !bounds.isValid)) { - return false; - } - - if (options.isOutOfBounds(center)) { - if (!options.slideOnBoundaries) { - return false; - } - center = options.containPoint(center, _lastCenter ?? center); - } - - // Try and fit the corners of the map inside the visible area. - // If it's still outside (so response is null), don't perform a move. - if (options.maxBounds != null) { - final adjustedCenter = - adjustCenterIfOutsideMaxBounds(center, zoom, options.maxBounds!); - if (adjustedCenter == null) { - return false; - } else { - center = adjustedCenter; - } - } - - _handleMoveEmit(center, zoom, hasGesture, source, id); - - _zoom = zoom; - _lastCenter = center; - _lastPixelBounds = getPixelBounds(_zoom); - _lastBounds = _calculateBounds(); - _pixelOrigin = getNewPixelOrigin(center); - if (callOnMoveSink) { - _onMoveSink.add(null); - } - - if (options.onPositionChanged != null) { - final mapPosition = MapPosition( - center: center, bounds: bounds, zoom: zoom, hasGesture: hasGesture); - - options.onPositionChanged!(mapPosition, hasGesture); - } - - return true; - } - - double fitZoomToBounds(double? zoom) { - zoom ??= _zoom; - // Abide to min/max zoom - if (options.maxZoom != null) { - zoom = (zoom > options.maxZoom!) ? options.maxZoom! : zoom; - } - if (options.minZoom != null) { - zoom = (zoom < options.minZoom!) ? options.minZoom! : zoom; - } - return zoom; - } - - void fitBounds(LatLngBounds bounds, FitBoundsOptions options) { - if (!bounds.isValid) { - throw Exception('Bounds are not valid.'); - } - final target = getBoundsCenterZoom(bounds, options); - move(target.center, target.zoom, source: MapEventSource.fitBounds); - } - - CenterZoom centerZoomFitBounds( - LatLngBounds bounds, FitBoundsOptions options) { - if (!bounds.isValid) { - throw Exception('Bounds are not valid.'); - } - return getBoundsCenterZoom(bounds, options); - } - - LatLng getCenter() { - if (_lastCenter != null) { - return _lastCenter!; - } - return layerPointToLatLng(_centerLayerPoint); - } - - LatLngBounds getBounds() { - if (_lastBounds != null) { - return _lastBounds!; - } - - return _calculateBounds(); - } - - Bounds getLastPixelBounds() { - if (_lastPixelBounds != null) { - return _lastPixelBounds!; - } - - return getPixelBounds(zoom); - } - - LatLngBounds _calculateBounds() { - final bounds = getLastPixelBounds(); - return LatLngBounds( - unproject(bounds.bottomLeft), - unproject(bounds.topRight), - ); - } - - CenterZoom getBoundsCenterZoom( - LatLngBounds bounds, FitBoundsOptions options) { - final paddingTL = - CustomPoint(options.padding.left, options.padding.top); - final paddingBR = - CustomPoint(options.padding.right, options.padding.bottom); - - final paddingTotalXY = paddingTL + paddingBR; - - var zoom = getBoundsZoom(bounds, paddingTotalXY, inside: options.inside); - zoom = math.min(options.maxZoom, zoom); - - final paddingOffset = (paddingBR - paddingTL) / 2; - final swPoint = project(bounds.southWest!, zoom); - final nePoint = project(bounds.northEast!, zoom); - final center = unproject((swPoint + nePoint) / 2 + paddingOffset, zoom); - return CenterZoom( - center: center, - zoom: zoom, - ); - } - - double getBoundsZoom(LatLngBounds bounds, CustomPoint padding, - {bool inside = false}) { - var zoom = this.zoom; - final min = options.minZoom ?? 0.0; - final max = options.maxZoom ?? double.infinity; - final nw = bounds.northWest; - final se = bounds.southEast; - var size = this.size - padding; - // Prevent negative size which results in NaN zoom value later on in the calculation - size = CustomPoint(math.max(0.0, size.x), math.max(0.0, size.y)); - final boundsSize = Bounds(project(se, zoom), project(nw, zoom)).size; - final scaleX = size.x / boundsSize.x; - final scaleY = size.y / boundsSize.y; - final scale = inside ? math.max(scaleX, scaleY) : math.min(scaleX, scaleY); - - zoom = getScaleZoom(scale, zoom); - - return math.max(min, math.min(max, zoom)); - } - - CustomPoint project(LatLng latlng, [double? zoom]) { - zoom ??= _zoom; - return options.crs.latLngToPoint(latlng, zoom); - } - - LatLng unproject(CustomPoint point, [double? zoom]) { - zoom ??= _zoom; - return options.crs.pointToLatLng(point, zoom)!; - } - - LatLng layerPointToLatLng(CustomPoint point) { - return unproject(point); - } - - CustomPoint get _centerLayerPoint { - return size / 2; - } - - double getZoomScale(double toZoom, double? fromZoom) { - final crs = options.crs; - fromZoom = fromZoom ?? _zoom; - return crs.scale(toZoom) / crs.scale(fromZoom); - } - - double getScaleZoom(double scale, double? fromZoom) { - final crs = options.crs; - fromZoom = fromZoom ?? _zoom; - return crs.zoom(scale * crs.scale(fromZoom)) as double; - } - - Bounds? getPixelWorldBounds(double? zoom) { - return options.crs.getProjectedBounds(zoom ?? _zoom); - } - - CustomPoint getPixelOrigin() { - return _pixelOrigin; - } - - Offset getOffsetFromOrigin(LatLng pos) { - final delta = project(pos) - getPixelOrigin(); - return Offset(delta.x.toDouble(), delta.y.toDouble()); - } - - CustomPoint getNewPixelOrigin(LatLng center, [double? zoom]) { - final viewHalf = size / 2.0; - return (project(center, zoom) - viewHalf).round(); - } - - Bounds getPixelBounds(double zoom) { - final mapZoom = zoom; - final scale = getZoomScale(mapZoom, zoom); - final pixelCenter = project(center, zoom).floor(); - final halfSize = size / (scale * 2); - return Bounds(pixelCenter - halfSize, pixelCenter + halfSize); - } - - LatLng? adjustCenterIfOutsideMaxBounds( - LatLng testCenter, double testZoom, LatLngBounds maxBounds) { - LatLng? newCenter; - - final swPixel = project(maxBounds.southWest!, testZoom); - final nePixel = project(maxBounds.northEast!, testZoom); - - final centerPix = project(testCenter, testZoom); - - final halfSizeX = size.x / 2; - final halfSizeY = size.y / 2; - - // Try and find the edge value that the center could use to stay within - // the maxBounds. This should be ok for panning. If we zoom, it is possible - // there is no solution to keep all corners within the bounds. If the edges - // are still outside the bounds, don't return anything. - final leftOkCenter = math.min(swPixel.x, nePixel.x) + halfSizeX; - final rightOkCenter = math.max(swPixel.x, nePixel.x) - halfSizeX; - final topOkCenter = math.min(swPixel.y, nePixel.y) + halfSizeY; - final botOkCenter = math.max(swPixel.y, nePixel.y) - halfSizeY; - - double? newCenterX; - double? newCenterY; - - var wasAdjusted = false; - - if (centerPix.x < leftOkCenter) { - wasAdjusted = true; - newCenterX = leftOkCenter; - } else if (centerPix.x > rightOkCenter) { - wasAdjusted = true; - newCenterX = rightOkCenter; - } - - if (centerPix.y < topOkCenter) { - wasAdjusted = true; - newCenterY = topOkCenter; - } else if (centerPix.y > botOkCenter) { - wasAdjusted = true; - newCenterY = botOkCenter; - } - - if (!wasAdjusted) { - return testCenter; - } - - final newCx = newCenterX ?? centerPix.x; - final newCy = newCenterY ?? centerPix.y; - - // Have a final check, see if the adjusted center is within maxBounds. - // If not, give up. - if (newCx < leftOkCenter || - newCx > rightOkCenter || - newCy < topOkCenter || - newCy > botOkCenter) { - return null; - } else { - newCenter = unproject(CustomPoint(newCx, newCy), testZoom); - } - - return newCenter; - } - - // This will convert a latLng to a position that we could use with a widget - // outside of FlutterMap layer space. Eg using a Positioned Widget. - CustomPoint latLngToScreenPoint(LatLng latLng) { - final nonRotatedPixelOrigin = - (project(getCenter(), zoom) - originalSize! / 2.0).round(); - - var point = options.crs.latLngToPoint(latLng, zoom); - - final mapCenter = options.crs.latLngToPoint(center, zoom); - - if (rotation != 0.0) { - point = rotatePoint(mapCenter, point, counterRotation: false); - } - - return point - nonRotatedPixelOrigin; - } - - LatLng? pointToLatLng(CustomPoint localPoint) { - if (originalSize == null) { - return null; - } - - final width = originalSize!.x; - final height = originalSize!.y; - - final localPointCenterDistance = - CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y); - final mapCenter = options.crs.latLngToPoint(center, zoom); - - var point = mapCenter - localPointCenterDistance; - - if (rotation != 0.0) { - point = rotatePoint(mapCenter, point); - } - - return options.crs.pointToLatLng(point, zoom); - } - - // Sometimes we need to make allowances that a rotation already exists, so - // it needs to be reversed (pointToLatLng), and sometimes we want to use - // the same rotation to create a new position (latLngToScreenpoint). - // counterRotation just makes allowances this for this. - CustomPoint rotatePoint( - CustomPoint mapCenter, CustomPoint point, - {bool counterRotation = true}) { - final counterRotationFactor = counterRotation ? -1 : 1; - - final m = Matrix4.identity() - ..translate(mapCenter.x.toDouble(), mapCenter.y.toDouble()) - ..rotateZ(rotationRad * counterRotationFactor) - ..translate(-mapCenter.x.toDouble(), -mapCenter.y.toDouble()); - - final tp = MatrixUtils.transformPoint( - m, Offset(point.x.toDouble(), point.y.toDouble())); - - return CustomPoint(tp.dx, tp.dy); - } - - static MapState? maybeOf(BuildContext context, {bool nullOk = false}) { - final widget = - context.dependOnInheritedWidgetOfExactType(); - if (nullOk || widget != null) { - return widget?.mapState; - } - throw FlutterError( - 'MapState.of() called with a context that does not contain a FlutterMap.'); - } -} diff --git a/lib/src/map/map_state_widget.dart b/lib/src/map/map_state_widget.dart index c5d7b7fb8..11bed19c1 100644 --- a/lib/src/map/map_state_widget.dart +++ b/lib/src/map/map_state_widget.dart @@ -1,9 +1,9 @@ import 'package:flutter/widgets.dart'; - -import 'package:flutter_map/src/map/map.dart'; +import 'package:flutter_map/src/map/flutter_map_state.dart'; class MapStateInheritedWidget extends InheritedWidget { - final MapState mapState; + + final FlutterMapState mapState; const MapStateInheritedWidget({ super.key, From 4bf3d19511be7334304fd2f8490e7bb9f7973d95 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 20:45:39 -0500 Subject: [PATCH 18/32] improve degrees and pixel origin --- lib/src/layer/marker_layer.dart | 2 +- lib/src/layer/overlay_image_layer.dart | 13 +++++-------- .../tile_layer/transformation_calculator.dart | 2 +- lib/src/map/flutter_map_state.dart | 17 +++++------------ 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index f44b3c118..fe241db39 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -195,7 +195,7 @@ class _MarkerLayerState extends State { continue; } - final pos = pxPoint - map.getPixelOrigin(); + final pos = pxPoint - map.pixelOrigin; final markerWidget = (marker.rotate ?? widget.rotate ?? false) // Counter rotated marker to the map rotation ? Transform.rotate( diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 91e8ae49f..61cb657da 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -46,11 +46,10 @@ class OverlayImage extends BaseOverlayImage { @override Positioned buildPositionedForOverlay(FlutterMapState map) { - final pixelOrigin = map.getPixelOrigin(); // northWest is not necessarily upperLeft depending on projection final bounds = Bounds( - map.project(this.bounds.northWest) - pixelOrigin, - map.project(this.bounds.southEast) - pixelOrigin, + map.project(this.bounds.northWest) - map.pixelOrigin, + map.project(this.bounds.southEast) - map.pixelOrigin, ); return Positioned( left: bounds.topLeft.x.toDouble(), @@ -94,11 +93,9 @@ class RotatedOverlayImage extends BaseOverlayImage { @override Positioned buildPositionedForOverlay(FlutterMapState map) { - final pixelOrigin = map.getPixelOrigin(); - - final pxTopLeft = map.project(topLeftCorner) - pixelOrigin; - final pxBottomRight = map.project(bottomRightCorner) - pixelOrigin; - final pxBottomLeft = (map.project(bottomLeftCorner) - pixelOrigin); + final pxTopLeft = map.project(topLeftCorner) - map.pixelOrigin; + final pxBottomRight = map.project(bottomRightCorner) - map.pixelOrigin; + final pxBottomLeft = (map.project(bottomLeftCorner) - map.pixelOrigin); // calculate pixel coordinate of top-right corner by calculating the // vector from bottom-left to top-left and adding it to bottom-right final pxTopRight = (pxTopLeft - pxBottomLeft + pxBottomRight); diff --git a/lib/src/layer/tile_layer/transformation_calculator.dart b/lib/src/layer/tile_layer/transformation_calculator.dart index 61a5c47d5..afab98b5b 100644 --- a/lib/src/layer/tile_layer/transformation_calculator.dart +++ b/lib/src/layer/tile_layer/transformation_calculator.dart @@ -12,7 +12,7 @@ class TransformationCalculator { if (level != null) return level; return _levels[zoom] = Level( - origin: map.project(map.unproject(map.getPixelOrigin()), zoom), + origin: map.project(map.unproject(map.pixelOrigin), zoom), zoom: zoom, ); } diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index e701497ad..c86d19929 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -189,15 +189,12 @@ class FlutterMapState extends MapGestureMixin double get rotation => _rotation; - set rotation(double rotation) { - _rotation = rotation; - _rotationRad = degToRadian(rotation); - } - - double get rotationRad => _rotationRad; + double get rotationRad => degToRadian(_rotation); LatLng? _lastCenter; + late CustomPoint _pixelOrigin; + CustomPoint get pixelOrigin => _pixelOrigin; LatLng get center => getCenter(); @@ -336,7 +333,7 @@ class FlutterMapState extends MapGestureMixin }) { if (degree != _rotation) { final oldRotation = _rotation; - rotation = degree; + _rotation = degree; _updateSizeByOriginalSizeAndRotation(); // onRotationChanged(_rotation); @@ -541,12 +538,8 @@ class FlutterMapState extends MapGestureMixin return options.crs.getProjectedBounds(zoom ?? _zoom); } - CustomPoint getPixelOrigin() { - return _pixelOrigin; - } - Offset getOffsetFromOrigin(LatLng pos) { - final delta = project(pos) - getPixelOrigin(); + final delta = project(pos) - _pixelOrigin; return Offset(delta.x.toDouble(), delta.y.toDouble()); } From 9ca935fc9b74e7249c633958d09c669e771cb963 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 21:16:13 -0500 Subject: [PATCH 19/32] rename variables and functions; _lastCenter is now non-nullable --- lib/flutter_map.dart | 5 +-- lib/src/gestures/gestures.dart | 8 ++-- lib/src/layer/polygon_layer.dart | 1 - lib/src/layer/polyline_layer.dart | 1 - lib/src/map/flutter_map_state.dart | 70 +++++++++++++----------------- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 22cc3bc7a..c85b3943d 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -151,7 +151,6 @@ typedef PointerCancelCallback = void Function( typedef PointerHoverCallback = void Function( PointerHoverEvent event, LatLng point); typedef PositionCallback = void Function(MapPosition position, bool hasGesture); -typedef MapCreatedCallback = void Function(MapController mapController); /// Allows you to provide your map's starting properties for [zoom], [rotation] /// and [center]. Alternatively you can provide [bounds] instead of [center]. @@ -250,9 +249,7 @@ class MapOptions { final PointerCancelCallback? onPointerCancel; final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; - - final Function(MapEvent)? onMapEvent; - + final void Function(MapEvent)? onMapEvent; final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 74131da70..ab20de88b 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -548,8 +548,8 @@ abstract class MapGestureMixin extends State final direction = details.velocity.pixelsPerSecond / magnitude; final distance = (Offset.zero & - Size(mapState.originalSize!.x as double, - mapState.originalSize!.y as double)) + Size(mapState.nonrotatedSize!.x as double, + mapState.nonrotatedSize!.y as double)) .shortestSide; final flingOffset = _focalStartLocal - _lastFocalLocal; @@ -614,7 +614,7 @@ abstract class MapGestureMixin extends State LatLng _offsetToCrs(Offset offset, [double? zoom]) { final focalStartPt = mapState.project(mapState.center, zoom ?? mapState.zoom); - final point = (_offsetToPoint(offset) - (mapState.originalSize! / 2.0)) + final point = (_offsetToPoint(offset) - (mapState.nonrotatedSize! / 2.0)) .rotate(mapState.rotationRad); final newCenterPt = focalStartPt + point; @@ -647,7 +647,7 @@ abstract class MapGestureMixin extends State List _getNewEventCenterZoomPosition( CustomPoint cursorPos, double newZoom) { // Calculate offset of mouse cursor from viewport center - final viewCenter = mapState.originalSize! / 2; + final viewCenter = mapState.nonrotatedSize! / 2; final offset = (cursorPos - viewCenter).rotate(mapState.rotationRad); // Match new center coordinate to mouse cursor position final scale = mapState.getZoomScale(newZoom, mapState.zoom); diff --git a/lib/src/layer/polygon_layer.dart b/lib/src/layer/polygon_layer.dart index 44922e757..bebffe7dc 100644 --- a/lib/src/layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer.dart @@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/layer/label.dart'; import 'package:flutter_map/src/map/flutter_map_state.dart'; -import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI enum PolygonLabelPlacement { diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index 940c60342..190b003ed 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -4,7 +4,6 @@ import 'dart:ui' as ui; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/map/flutter_map_state.dart'; -import 'package:flutter_map/src/map/map.dart'; import 'package:latlong2/latlong.dart'; class Polyline { diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index c86d19929..22eef3146 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -36,6 +34,7 @@ class FlutterMapState extends MapGestureMixin // Initialize all variables here, if they need to be updated after the map changes // like center, or bounds they also need to be updated in build. _rotation = options.rotation; + _lastCenter = options.center; _zoom = options.zoom; _rotationRad = degToRadian(options.rotation); _pixelBounds = getPixelBounds(zoom); @@ -60,7 +59,7 @@ class FlutterMapState extends MapGestureMixin super.build(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - setOriginalSize(constraints.maxWidth, constraints.maxHeight); + setSize(constraints.maxWidth, constraints.maxHeight); _rotationRad = degToRadian(rotation); _pixelBounds = getPixelBounds(zoom); @@ -191,12 +190,12 @@ class FlutterMapState extends MapGestureMixin double get rotationRad => degToRadian(_rotation); - LatLng? _lastCenter; - + late CustomPoint _pixelOrigin; CustomPoint get pixelOrigin => _pixelOrigin; - LatLng get center => getCenter(); + late LatLng _lastCenter; + LatLng get center => _lastCenter; late LatLngBounds _bounds; LatLngBounds get bounds => _bounds; @@ -205,16 +204,15 @@ class FlutterMapState extends MapGestureMixin Bounds get pixelBounds => _pixelBounds; // Original size of the map where rotation isn't calculated - CustomPoint? _originalSize; - - CustomPoint? get originalSize => _originalSize; + CustomPoint? _nonrotatedSize; + CustomPoint? get nonrotatedSize => _nonrotatedSize; - void setOriginalSize(double width, double height) { - final isCurrSizeNull = _originalSize == null; + void setSize(double width, double height) { + final isCurrSizeNull = _nonrotatedSize == null; if (isCurrSizeNull || - _originalSize!.x != width || - _originalSize!.y != height) { - _originalSize = CustomPoint(width, height); + _nonrotatedSize!.x != width || + _nonrotatedSize!.y != height) { + _nonrotatedSize = CustomPoint(width, height); _updateSizeByOriginalSizeAndRotation(); } @@ -226,8 +224,8 @@ class FlutterMapState extends MapGestureMixin CustomPoint get size => _size ?? const CustomPoint(0.0, 0.0); void _updateSizeByOriginalSizeAndRotation() { - final originalWidth = _originalSize!.x; - final originalHeight = _originalSize!.y; + final originalWidth = _nonrotatedSize!.x; + final originalHeight = _nonrotatedSize!.y; if (_rotation != 0.0) { final cosAngle = math.cos(_rotationRad).abs(); @@ -242,7 +240,7 @@ class FlutterMapState extends MapGestureMixin _size = CustomPoint(originalWidth, originalHeight); } - _pixelOrigin = getNewPixelOrigin(_lastCenter!); + _pixelOrigin = getNewPixelOrigin(_lastCenter); } void _handleMoveEmit(LatLng targetCenter, double targetZoom, bool hasGesture, @@ -250,7 +248,7 @@ class FlutterMapState extends MapGestureMixin if (source == MapEventSource.flingAnimationController) { emitMapEvent( MapEventFlingAnimation( - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -260,7 +258,7 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.doubleTapZoomAnimationController) { emitMapEvent( MapEventDoubleTapZoom( - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -270,7 +268,7 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.scrollWheel) { emitMapEvent( MapEventScrollWheelZoom( - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -281,7 +279,7 @@ class FlutterMapState extends MapGestureMixin source == MapEventSource.onMultiFinger) { emitMapEvent( MapEventMove( - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -292,7 +290,7 @@ class FlutterMapState extends MapGestureMixin emitMapEvent( MapEventMove( id: id, - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -302,13 +300,12 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.custom) { // for custom source, emit move event if zoom or center has changed if (targetZoom != _zoom || - _lastCenter == null || - targetCenter.latitude != _lastCenter!.latitude || - targetCenter.longitude != _lastCenter!.longitude) { + targetCenter.latitude != _lastCenter.latitude || + targetCenter.longitude != _lastCenter.longitude) { emitMapEvent( MapEventMove( id: id, - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -343,7 +340,7 @@ class FlutterMapState extends MapGestureMixin id: id, currentRotation: oldRotation, targetRotation: _rotation, - center: _lastCenter!, + center: _lastCenter, zoom: _zoom, source: source, ), @@ -376,7 +373,7 @@ class FlutterMapState extends MapGestureMixin zoom = fitZoomToBounds(zoom); final mapMoved = center != _lastCenter || zoom != _zoom; - if (_lastCenter != null && (!mapMoved || !_bounds.isValid)) { + if (!mapMoved || !_bounds.isValid) { return false; } @@ -384,7 +381,7 @@ class FlutterMapState extends MapGestureMixin if (!options.slideOnBoundaries) { return false; } - center = options.containPoint(center, _lastCenter ?? center); + center = options.containPoint(center, _lastCenter); } // Try and fit the corners of the map inside the visible area. @@ -447,13 +444,6 @@ class FlutterMapState extends MapGestureMixin return getBoundsCenterZoom(bounds, options); } - LatLng getCenter() { - if (_lastCenter != null) { - return _lastCenter!; - } - return layerPointToLatLng(_centerLayerPoint); - } - LatLngBounds _calculateBounds() { print("got bounds"); return LatLngBounds( @@ -624,7 +614,7 @@ class FlutterMapState extends MapGestureMixin // outside of FlutterMap layer space. Eg using a Positioned Widget. CustomPoint latLngToScreenPoint(LatLng latLng) { final nonRotatedPixelOrigin = - (project(getCenter(), zoom) - originalSize! / 2.0).round(); + (project(_lastCenter, zoom) - nonrotatedSize! / 2.0).round(); var point = options.crs.latLngToPoint(latLng, zoom); @@ -638,12 +628,12 @@ class FlutterMapState extends MapGestureMixin } LatLng? pointToLatLng(CustomPoint localPoint) { - if (originalSize == null) { + if (nonrotatedSize == null) { return null; } - final width = originalSize!.x; - final height = originalSize!.y; + final width = nonrotatedSize!.x; + final height = nonrotatedSize!.y; final localPointCenterDistance = CustomPoint((width / 2) - localPoint.x, (height / 2) - localPoint.y); From 22a602b72142d46fad75ea7c22fdf9fdaa0edfe4 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 3 Aug 2022 23:28:52 -0500 Subject: [PATCH 20/32] improve state management and clean up variable names --- lib/src/map/flutter_map_state.dart | 117 ++++++++++++----------------- 1 file changed, 49 insertions(+), 68 deletions(-) diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 22eef3146..99fa60a4c 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -11,7 +11,6 @@ import 'package:flutter_map/src/core/bounds.dart'; class FlutterMapState extends MapGestureMixin with AutomaticKeepAliveClientMixin { - final _positionedTapController = PositionedTapController(); final MapController _localController = MapControllerImpl(); @@ -34,7 +33,7 @@ class FlutterMapState extends MapGestureMixin // Initialize all variables here, if they need to be updated after the map changes // like center, or bounds they also need to be updated in build. _rotation = options.rotation; - _lastCenter = options.center; + _center = options.center; _zoom = options.zoom; _rotationRad = degToRadian(options.rotation); _pixelBounds = getPixelBounds(zoom); @@ -69,7 +68,6 @@ class FlutterMapState extends MapGestureMixin fitBounds(options.bounds!, options.boundsOptions); } - final scaleGestureTeam = GestureArenaTeam(); RawGestureDetector scaleGestureDetector({required Widget child}) => @@ -144,7 +142,6 @@ class FlutterMapState extends MapGestureMixin } Widget _buildMap(CustomPoint size) { - print("Map built"); return ClipRect( child: Stack( children: [ @@ -190,12 +187,11 @@ class FlutterMapState extends MapGestureMixin double get rotationRad => degToRadian(_rotation); - late CustomPoint _pixelOrigin; CustomPoint get pixelOrigin => _pixelOrigin; - late LatLng _lastCenter; - LatLng get center => _lastCenter; + late LatLng _center; + LatLng get center => _center; late LatLngBounds _bounds; LatLngBounds get bounds => _bounds; @@ -240,7 +236,7 @@ class FlutterMapState extends MapGestureMixin _size = CustomPoint(originalWidth, originalHeight); } - _pixelOrigin = getNewPixelOrigin(_lastCenter); + _pixelOrigin = getNewPixelOrigin(_center); } void _handleMoveEmit(LatLng targetCenter, double targetZoom, bool hasGesture, @@ -248,7 +244,7 @@ class FlutterMapState extends MapGestureMixin if (source == MapEventSource.flingAnimationController) { emitMapEvent( MapEventFlingAnimation( - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -258,7 +254,7 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.doubleTapZoomAnimationController) { emitMapEvent( MapEventDoubleTapZoom( - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -268,7 +264,7 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.scrollWheel) { emitMapEvent( MapEventScrollWheelZoom( - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -279,7 +275,7 @@ class FlutterMapState extends MapGestureMixin source == MapEventSource.onMultiFinger) { emitMapEvent( MapEventMove( - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -290,7 +286,7 @@ class FlutterMapState extends MapGestureMixin emitMapEvent( MapEventMove( id: id, - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -300,12 +296,12 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.custom) { // for custom source, emit move event if zoom or center has changed if (targetZoom != _zoom || - targetCenter.latitude != _lastCenter.latitude || - targetCenter.longitude != _lastCenter.longitude) { + targetCenter.latitude != _center.latitude || + targetCenter.longitude != _center.longitude) { emitMapEvent( MapEventMove( id: id, - center: _lastCenter, + center: _center, zoom: _zoom, targetCenter: targetCenter, targetZoom: targetZoom, @@ -323,28 +319,27 @@ class FlutterMapState extends MapGestureMixin } bool rotate( - double degree, { + double newRotation, { bool hasGesture = false, required MapEventSource source, String? id, }) { - if (degree != _rotation) { - final oldRotation = _rotation; - _rotation = degree; - _updateSizeByOriginalSizeAndRotation(); - - // onRotationChanged(_rotation); - + if (newRotation != _rotation) { emitMapEvent( MapEventRotate( id: id, - currentRotation: oldRotation, - targetRotation: _rotation, - center: _lastCenter, + currentRotation: _rotation, + targetRotation: newRotation, + center: _center, zoom: _zoom, source: source, ), ); + setState(() { + _rotation = newRotation; + }); + + _updateSizeByOriginalSizeAndRotation(); return true; } @@ -352,72 +347,65 @@ class FlutterMapState extends MapGestureMixin return false; } - MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, + MoveAndRotateResult moveAndRotate( + LatLng newCenter, double newZoom, double newRotation, {required MapEventSource source, String? id}) { - final moveSucc = - move(center, zoom, id: id, source: source); - final rotateSucc = - rotate(degree, id: id, source: source); - - if (moveSucc || rotateSucc) { - setState(() {}); - } + final moveSucc = move(newCenter, newZoom, id: id, source: source); + final rotateSucc = rotate(newRotation, id: id, source: source); return MoveAndRotateResult(moveSucc, rotateSucc); } - bool move(LatLng center, double zoom, - {bool hasGesture = false, - required MapEventSource source, - String? id}) { - zoom = fitZoomToBounds(zoom); - final mapMoved = center != _lastCenter || zoom != _zoom; + bool move(LatLng newCenter, double newZoom, + {bool hasGesture = false, required MapEventSource source, String? id}) { + newZoom = fitZoomToBounds(newZoom); + final mapMoved = newCenter != _center || newZoom != _zoom; if (!mapMoved || !_bounds.isValid) { return false; } - if (options.isOutOfBounds(center)) { + if (options.isOutOfBounds(newCenter)) { if (!options.slideOnBoundaries) { return false; } - center = options.containPoint(center, _lastCenter); + newCenter = options.containPoint(newCenter, _center); } // Try and fit the corners of the map inside the visible area. // If it's still outside (so response is null), don't perform a move. if (options.maxBounds != null) { - final adjustedCenter = - adjustCenterIfOutsideMaxBounds(center, zoom, options.maxBounds!); + final adjustedCenter = adjustCenterIfOutsideMaxBounds( + newCenter, newZoom, options.maxBounds!); if (adjustedCenter == null) { return false; } else { - center = adjustedCenter; + newCenter = adjustedCenter; } } - _handleMoveEmit(center, zoom, hasGesture, source, id); + _handleMoveEmit(newCenter, newZoom, hasGesture, source, id); - _zoom = zoom; - _lastCenter = center; - _pixelBounds = getPixelBounds(_zoom); - _pixelOrigin = getNewPixelOrigin(center); setState(() { - + _zoom = newZoom; + _center = newCenter; }); - if (options.onPositionChanged != null) { - final mapPosition = MapPosition( - center: center, bounds: _bounds, zoom: zoom, hasGesture: hasGesture); + _pixelBounds = getPixelBounds(_zoom); + _pixelOrigin = getNewPixelOrigin(newCenter); - options.onPositionChanged!(mapPosition, hasGesture); - } + options.onPositionChanged?.call( + MapPosition( + center: newCenter, + bounds: _bounds, + zoom: newZoom, + hasGesture: hasGesture), + hasGesture); return true; } - double fitZoomToBounds(double? zoom) { - zoom ??= _zoom; + double fitZoomToBounds(double zoom) { // Abide to min/max zoom if (options.maxZoom != null) { zoom = (zoom > options.maxZoom!) ? options.maxZoom! : zoom; @@ -445,7 +433,6 @@ class FlutterMapState extends MapGestureMixin } LatLngBounds _calculateBounds() { - print("got bounds"); return LatLngBounds( unproject(_pixelBounds.bottomLeft), unproject(_pixelBounds.topRight), @@ -539,7 +526,6 @@ class FlutterMapState extends MapGestureMixin } Bounds getPixelBounds(double zoom) { - print("pixel bounds"); final mapZoom = zoom; final scale = getZoomScale(mapZoom, zoom); final pixelCenter = project(center, zoom).floor(); @@ -614,7 +600,7 @@ class FlutterMapState extends MapGestureMixin // outside of FlutterMap layer space. Eg using a Positioned Widget. CustomPoint latLngToScreenPoint(LatLng latLng) { final nonRotatedPixelOrigin = - (project(_lastCenter, zoom) - nonrotatedSize! / 2.0).round(); + (project(_center, zoom) - nonrotatedSize! / 2.0).round(); var point = options.crs.latLngToPoint(latLng, zoom); @@ -677,9 +663,4 @@ class FlutterMapState extends MapGestureMixin throw FlutterError( 'MapState.of() called with a context that does not contain a FlutterMap.'); } - - - - - } From 10fe1c6329ecf329b7896409b65ef8e30ce042a6 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 4 Aug 2022 01:04:23 -0500 Subject: [PATCH 21/32] fix lint --- lib/src/layer/marker_layer.dart | 1 - lib/src/map/flutter_map_state.dart | 4 ---- 2 files changed, 5 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index fe241db39..9112e6208 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -191,7 +191,6 @@ class _MarkerLayerState extends State { final ne = CustomPoint(pxPoint.x - rightPortion, pxPoint.y + topPortion); if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { - print("Skipping due to bounds"); continue; } diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 99fa60a4c..eac47cde3 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -495,10 +495,6 @@ class FlutterMapState extends MapGestureMixin return unproject(point); } - CustomPoint get _centerLayerPoint { - return size / 2; - } - double getZoomScale(double toZoom, double? fromZoom) { final crs = options.crs; fromZoom = fromZoom ?? _zoom; From c23e4ba860bb669d56489f6f60aa074325bccd8b Mon Sep 17 00:00:00 2001 From: Moo Date: Thu, 4 Aug 2022 17:14:32 -0500 Subject: [PATCH 22/32] Update lib/src/layer/overlay_image_layer.dart Co-authored-by: Luka S --- lib/src/layer/overlay_image_layer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 61cb657da..7d6390d8c 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -95,7 +95,7 @@ class RotatedOverlayImage extends BaseOverlayImage { Positioned buildPositionedForOverlay(FlutterMapState map) { final pxTopLeft = map.project(topLeftCorner) - map.pixelOrigin; final pxBottomRight = map.project(bottomRightCorner) - map.pixelOrigin; - final pxBottomLeft = (map.project(bottomLeftCorner) - map.pixelOrigin); + final pxBottomLeft = map.project(bottomLeftCorner) - map.pixelOrigin; // calculate pixel coordinate of top-right corner by calculating the // vector from bottom-left to top-left and adding it to bottom-right final pxTopRight = (pxTopLeft - pxBottomLeft + pxBottomRight); From d270a8632c5f9ddd5d3167949bb6941fb1a0fb5c Mon Sep 17 00:00:00 2001 From: Spencer Date: Sun, 7 Aug 2022 00:08:06 -0500 Subject: [PATCH 23/32] fix state handling issues, implement event stream in controller --- example/lib/pages/interactive_test_page.dart | 38 +++------- example/lib/pages/latlng_to_screen_point.dart | 26 +++---- example/lib/pages/map_controller.dart | 42 +++++------ example/lib/pages/point_to_latlng.dart | 29 +++----- lib/flutter_map.dart | 13 ++-- lib/src/gestures/gestures.dart | 20 +++--- lib/src/map/flutter_map_state.dart | 71 +++++++++++-------- lib/src/map/map.dart | 17 +++-- 8 files changed, 120 insertions(+), 136 deletions(-) diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index 12dd6bde3..7dd5f5a49 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -17,26 +17,15 @@ class InteractiveTestPage extends StatefulWidget { } class _InteractiveTestPageState extends State { - late final MapController mapController; // Enable pinchZoom and doubleTapZoomBy by default int flags = InteractiveFlag.pinchZoom | InteractiveFlag.doubleTapZoom; - late final StreamSubscription subscription; + MapEvent? _latestEvent; @override void initState() { super.initState(); - mapController = MapController(); - - subscription = mapController.mapEventStream.listen(onMapEvent); - } - - @override - void dispose() { - subscription.cancel(); - - super.dispose(); } void onMapEvent(MapEvent mapEvent) { @@ -44,6 +33,10 @@ class _InteractiveTestPageState extends State { // do not flood console with move and rotate events debugPrint(mapEvent.toString()); } + + setState(() { + _latestEvent = mapEvent; + }); } void updateFlags(int flag) { @@ -148,29 +141,16 @@ class _InteractiveTestPageState extends State { Padding( padding: const EdgeInsets.only(top: 8, bottom: 8), child: Center( - child: StreamBuilder( - stream: mapController.mapEventStream, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (!snapshot.hasData) { - return const Text( - 'Current event: none\nSource: none', - textAlign: TextAlign.center, - ); - } - - return Text( - 'Current event: ${snapshot.data.runtimeType}\nSource: ${snapshot.data!.source}', + child: Text( + 'Current event: ${_latestEvent?.runtimeType ?? "none"}\nSource: ${_latestEvent?.source ?? "none"}', textAlign: TextAlign.center, - ); - }, - ), + ), ), ), Flexible( child: FlutterMap( - mapController: mapController, options: MapOptions( + onMapEvent: onMapEvent, center: LatLng(51.5, -0.09), zoom: 11, interactiveFlags: flags, diff --git a/example/lib/pages/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index a904b8730..09ea20420 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -18,23 +18,14 @@ class LatLngScreenPointTestPage extends StatefulWidget { } class _LatLngScreenPointTestPageState extends State { - late final MapController mapController; - late final StreamSubscription subscription; + late final MapController _mapController; - CustomPoint textPos = const CustomPoint(10.0, 10.0); + CustomPoint _textPos = const CustomPoint(10.0, 10.0); @override void initState() { super.initState(); - mapController = MapController(); - subscription = mapController.mapEventStream.listen(onMapEvent); - } - - @override - void dispose() { - subscription.cancel(); - - super.dispose(); + _mapController = MapController(); } void onMapEvent(MapEvent mapEvent) { @@ -53,11 +44,12 @@ class _LatLngScreenPointTestPageState extends State { Padding( padding: const EdgeInsets.all(8), child: FlutterMap( - mapController: mapController, + mapController: _mapController, options: MapOptions( + onMapEvent: onMapEvent, onTap: (tapPos, latLng) { - final pt1 = mapController.latLngToScreenPoint(latLng); - textPos = CustomPoint(pt1!.x, pt1.y); + final pt1 = _mapController.latLngToScreenPoint(latLng); + _textPos = CustomPoint(pt1!.x, pt1.y); setState(() {}); }, center: LatLng(51.5, -0.09), @@ -75,8 +67,8 @@ class _LatLngScreenPointTestPageState extends State { ), ), Positioned( - left: textPos.x.toDouble(), - top: textPos.y.toDouble(), + left: _textPos.x.toDouble(), + top: _textPos.y.toDouble(), width: 20, height: 20, child: const FlutterLogo()) diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 52cc37431..548a40045 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -17,18 +17,18 @@ class MapControllerPage extends StatefulWidget { } } -class MapControllerPageState extends State { - static LatLng london = LatLng(51.5, -0.09); - static LatLng paris = LatLng(48.8566, 2.3522); - static LatLng dublin = LatLng(53.3498, -6.2603); +final LatLng london = LatLng(51.5, -0.09); +final LatLng paris = LatLng(48.8566, 2.3522); +final LatLng dublin = LatLng(53.3498, -6.2603); - late final MapController mapController; - double rotation = 0; +class MapControllerPageState extends State { + late final MapController _mapController; + double _rotation = 0; @override void initState() { super.initState(); - mapController = MapController(); + _mapController = MapController(); } @override @@ -76,23 +76,23 @@ class MapControllerPageState extends State { children: [ MaterialButton( onPressed: () { - mapController.move(london, 18); + _mapController.move(london, 18); }, child: const Text('London'), ), MaterialButton( onPressed: () { - mapController.move(paris, 5); + _mapController.move(paris, 5); }, child: const Text('Paris'), ), MaterialButton( onPressed: () { - mapController.move(dublin, 5); + _mapController.move(dublin, 5); }, child: const Text('Dublin'), ), - CurrentLocation(mapController: mapController), + CurrentLocation(mapController: _mapController), ], ), ), @@ -106,7 +106,7 @@ class MapControllerPageState extends State { bounds.extend(dublin); bounds.extend(paris); bounds.extend(london); - mapController.fitBounds( + _mapController.fitBounds( bounds, options: const FitBoundsOptions( padding: EdgeInsets.only(left: 15, right: 15), @@ -118,7 +118,7 @@ class MapControllerPageState extends State { Builder(builder: (BuildContext context) { return MaterialButton( onPressed: () { - final bounds = mapController.bounds!; + final bounds = _mapController.bounds!; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( @@ -136,14 +136,14 @@ class MapControllerPageState extends State { const Text('Rotation:'), Expanded( child: Slider( - value: rotation, + value: _rotation, min: 0, max: 360, onChanged: (degree) { setState(() { - rotation = degree; + _rotation = degree; }); - mapController.rotate(degree); + _mapController.rotate(degree); }, ), ) @@ -152,7 +152,7 @@ class MapControllerPageState extends State { ), Flexible( child: FlutterMap( - mapController: mapController, + mapController: _mapController, options: MapOptions( center: LatLng(51.5, -0.09), zoom: 5, @@ -160,7 +160,8 @@ class MapControllerPageState extends State { minZoom: 3, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayerWidget( + options: TileLayerOptions( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], @@ -198,7 +199,6 @@ class _CurrentLocationState extends State { @override void initState() { super.initState(); - mapEventSubscription = widget.mapController.mapEventStream.listen(onMapEvent); } @@ -218,7 +218,8 @@ class _CurrentLocationState extends State { } void onMapEvent(MapEvent mapEvent) { - if (mapEvent is MapEventMove && mapEvent.id == _eventKey.toString()) { + if (mapEvent is MapEventMove && mapEvent.id != _eventKey.toString()) { + print("map event ${mapEvent.id}"); setIcon(Icons.gps_not_fixed); } } @@ -236,6 +237,7 @@ class _CurrentLocationState extends State { ); if (moved) { + print("moveed"); setIcon(Icons.gps_fixed); } else { setIcon(Icons.gps_not_fixed); diff --git a/example/lib/pages/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index 47ca59288..3e1a7bb54 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -17,8 +17,7 @@ class PointToLatLngPage extends StatefulWidget { } class PointToLatlngPage extends State { - late final MapController mapController; - late final StreamSubscription mapEventSubscription; + late final MapController mapController = MapController(); final pointSize = 40.0; final pointY = 200.0; @@ -27,10 +26,10 @@ class PointToLatlngPage extends State { @override void initState() { super.initState(); - mapController = MapController(); - mapEventSubscription = mapController.mapEventStream - .listen((mapEvent) => onMapEvent(mapEvent, context)); + WidgetsBinding.instance.addPostFrameCallback((_) { + updatePoint(null, context); + }); } @override @@ -57,6 +56,9 @@ class PointToLatlngPage extends State { FlutterMap( mapController: mapController, options: MapOptions( + onMapEvent: (event) { + updatePoint(null, context); + }, center: LatLng(51.5, -0.09), zoom: 5, minZoom: 3, @@ -104,27 +106,14 @@ class PointToLatlngPage extends State { ); } - void onMapEvent(MapEvent mapEvent, BuildContext context) { - _updatePointLatLng(context); - } - - void _updatePointLatLng(BuildContext context) { + void updatePoint(MapEvent? event, BuildContext context) { final pointX = _getPointX(context); - - final latLng = mapController.pointToLatLng(CustomPoint(pointX, pointY)); - setState(() { - this.latLng = latLng; + latLng = mapController.pointToLatLng(CustomPoint(pointX, pointY)); }); } double _getPointX(BuildContext context) { return MediaQuery.of(context).size.width / 2; } - - @override - void dispose() { - super.dispose(); - mapEventSubscription.cancel(); - } } diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index c85b3943d..a0acce73c 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -84,7 +84,7 @@ class FlutterMap extends StatefulWidget { abstract class MapController { /// Moves the map to a specific location and zoom level /// - /// Optionally provide [id] attribute and if you listen to [mapEventStream] + /// Optionally provide [id] attribute and if you listen to [mapEventCallback] /// later a [MapEventMove] event will be emitted (if move was success) with /// same [id] attribute. Event's source attribute will be /// [MapEventSource.mapController]. @@ -96,7 +96,7 @@ abstract class MapController { /// Sets the map rotation to a certain degrees angle (in decimal). /// - /// Optionally provide [id] attribute and if you listen to [mapEventStream] + /// Optionally provide [id] attribute and if you listen to [mapEventCallback] /// later a [MapEventRotate] event will be emitted (if rotate was success) /// with same [id] attribute. Event's source attribute will be /// [MapEventSource.mapController]. @@ -127,12 +127,14 @@ abstract class MapController { double get rotation; - Stream get mapEventStream; - set state(FlutterMapState state); + Stream get mapEventStream; + void dispose(); + StreamSink get mapEventSink; + LatLng? pointToLatLng(CustomPoint point); CustomPoint? latLngToScreenPoint(LatLng latLng); @@ -151,6 +153,7 @@ typedef PointerCancelCallback = void Function( typedef PointerHoverCallback = void Function( PointerHoverEvent event, LatLng point); typedef PositionCallback = void Function(MapPosition position, bool hasGesture); +typedef MapEventCallback = void Function(MapEvent); /// Allows you to provide your map's starting properties for [zoom], [rotation] /// and [center]. Alternatively you can provide [bounds] instead of [center]. @@ -249,7 +252,7 @@ class MapOptions { final PointerCancelCallback? onPointerCancel; final PointerHoverCallback? onPointerHover; final PositionCallback? onPositionChanged; - final void Function(MapEvent)? onMapEvent; + final MapEventCallback? onMapEvent; final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index ab20de88b..8c4056e39 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -19,6 +19,8 @@ abstract class MapGestureMixin extends State var _pointerCounter = 0; + bool _isListeningForInterruptions = false; + void onPointerDown(PointerDownEvent event) { ++_pointerCounter; if (mapState.options.onPointerDown != null) { @@ -770,22 +772,20 @@ abstract class MapGestureMixin extends State ); } + //TODO refactor void _startListeningForAnimationInterruptions() { - if (_mapControllerAnimationInterruption != null) return; - // cancel map animation controllers on map controller move events - _mapControllerAnimationInterruption = mapController.mapEventStream - .where((event) => - event.source == MapEventSource.mapController && - event is MapEventMove) - .listen(_handleAnimationInterruptions); + _isListeningForInterruptions = true; } void _stopListeningForAnimationInterruptions() { - _mapControllerAnimationInterruption?.cancel(); - _mapControllerAnimationInterruption = null; + _isListeningForInterruptions = false; } - void _handleAnimationInterruptions(MapEvent event) { + void handleAnimationInterruptions(MapEvent event) { + if(_isListeningForInterruptions == false) { + //Do not handle animation interruptions if not listening + return; + } closeDoubleTapController(event.source); closeFlingAnimationController(event.source); } diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index eac47cde3..aa5cc1da4 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; @@ -27,7 +29,6 @@ class FlutterMapState extends MapGestureMixin void initState() { super.initState(); - //TODO there has to be a better way to pass state to my controller mapController.state = this; // Initialize all variables here, if they need to be updated after the map changes @@ -47,10 +48,11 @@ class FlutterMapState extends MapGestureMixin } } + //This may not be required. @override - void dispose() { - _localController.dispose(); - super.dispose(); + void didUpdateWidget(FlutterMap oldWidget) { + super.didUpdateWidget(oldWidget); + mapController.state = this; } @override @@ -239,13 +241,13 @@ class FlutterMapState extends MapGestureMixin _pixelOrigin = getNewPixelOrigin(_center); } - void _handleMoveEmit(LatLng targetCenter, double targetZoom, bool hasGesture, + void _handleMoveEmit(LatLng targetCenter, double targetZoom, LatLng oldCenter, double oldZoom, bool hasGesture, MapEventSource source, String? id) { if (source == MapEventSource.flingAnimationController) { emitMapEvent( MapEventFlingAnimation( - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -254,8 +256,8 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.doubleTapZoomAnimationController) { emitMapEvent( MapEventDoubleTapZoom( - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -264,8 +266,8 @@ class FlutterMapState extends MapGestureMixin } else if (source == MapEventSource.scrollWheel) { emitMapEvent( MapEventScrollWheelZoom( - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -275,8 +277,8 @@ class FlutterMapState extends MapGestureMixin source == MapEventSource.onMultiFinger) { emitMapEvent( MapEventMove( - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -286,8 +288,8 @@ class FlutterMapState extends MapGestureMixin emitMapEvent( MapEventMove( id: id, - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -295,14 +297,14 @@ class FlutterMapState extends MapGestureMixin ); } else if (source == MapEventSource.custom) { // for custom source, emit move event if zoom or center has changed - if (targetZoom != _zoom || - targetCenter.latitude != _center.latitude || - targetCenter.longitude != _center.longitude) { + if (targetZoom != oldZoom || + targetCenter.latitude != oldCenter.latitude || + targetCenter.longitude != oldCenter.longitude) { emitMapEvent( MapEventMove( id: id, - center: _center, - zoom: _zoom, + center: oldCenter, + zoom: oldZoom, targetCenter: targetCenter, targetZoom: targetZoom, source: source, @@ -313,9 +315,15 @@ class FlutterMapState extends MapGestureMixin } void emitMapEvent(MapEvent event) { + if (event.source == MapEventSource.mapController && + event is MapEventMove) { + handleAnimationInterruptions(event); + } + setState(() { widget.options.onMapEvent?.call(event); }); + mapController.mapEventSink.add(event); } bool rotate( @@ -325,22 +333,23 @@ class FlutterMapState extends MapGestureMixin String? id, }) { if (newRotation != _rotation) { + final double oldRotation = _rotation; + //Apply state then emit events and callbacks + setState(() { + _rotation = newRotation; + }); + _updateSizeByOriginalSizeAndRotation(); + emitMapEvent( MapEventRotate( id: id, - currentRotation: _rotation, - targetRotation: newRotation, + currentRotation: oldRotation, + targetRotation: _rotation, center: _center, zoom: _zoom, source: source, ), ); - setState(() { - _rotation = newRotation; - }); - - _updateSizeByOriginalSizeAndRotation(); - return true; } @@ -384,8 +393,10 @@ class FlutterMapState extends MapGestureMixin } } - _handleMoveEmit(newCenter, newZoom, hasGesture, source, id); + final LatLng oldCenter = _center; + final double oldZoom = _zoom; + //Apply state then emit events and callbacks setState(() { _zoom = newZoom; _center = newCenter; @@ -394,6 +405,8 @@ class FlutterMapState extends MapGestureMixin _pixelBounds = getPixelBounds(_zoom); _pixelOrigin = getNewPixelOrigin(newCenter); + _handleMoveEmit(newCenter, newZoom, oldCenter, oldZoom, hasGesture, source, id); + options.onPositionChanged?.call( MapPosition( center: newCenter, diff --git a/lib/src/map/map.dart b/lib/src/map/map.dart index 9498bae1b..8d1e4f0da 100644 --- a/lib/src/map/map.dart +++ b/lib/src/map/map.dart @@ -6,12 +6,14 @@ import 'package:flutter_map/src/map/flutter_map_state.dart'; import 'package:latlong2/latlong.dart'; class MapControllerImpl implements MapController { + final StreamController _mapEventSink = StreamController.broadcast(); @override - void dispose() { - _mapEventSink.close(); - } + StreamSink get mapEventSink => _mapEventSink.sink; + + @override + Stream get mapEventStream => _mapEventSink.stream; late FlutterMapState _state; @@ -20,6 +22,11 @@ class MapControllerImpl implements MapController { _state = state; } + @override + void dispose() { + _mapEventSink.close(); + } + @override MoveAndRotateResult moveAndRotate(LatLng center, double zoom, double degree, {String? id}) { @@ -84,7 +91,5 @@ class MapControllerImpl implements MapController { return _state.rotatePoint(mapCenter, point, counterRotation: counterRotation); } - - @override - Stream get mapEventStream => _mapEventSink.stream; + } From b166628ca79c3904640f82b002fd66d1d6c459e4 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 10 Aug 2022 01:04:30 -0500 Subject: [PATCH 24/32] migrate tilelayer to match api --- .../lib/pages/animated_map_controller.dart | 4 +- example/lib/pages/circle.dart | 4 +- example/lib/pages/custom_crs/custom_crs.dart | 4 +- example/lib/pages/epsg3413_crs.dart | 5 +- example/lib/pages/epsg4326_crs.dart | 4 +- example/lib/pages/esri.dart | 4 +- example/lib/pages/home.dart | 4 +- example/lib/pages/interactive_test_page.dart | 4 +- example/lib/pages/latlng_to_screen_point.dart | 4 +- example/lib/pages/live_location.dart | 5 +- example/lib/pages/many_markers.dart | 4 +- example/lib/pages/map_controller.dart | 5 +- example/lib/pages/map_inside_listview.dart | 4 +- example/lib/pages/marker_anchor.dart | 5 +- example/lib/pages/marker_rotate.dart | 4 +- example/lib/pages/max_bounds.dart | 4 +- example/lib/pages/moving_markers.dart | 4 +- example/lib/pages/network_tile_provider.dart | 4 +- example/lib/pages/offline_map.dart | 4 +- example/lib/pages/on_tap.dart | 5 +- example/lib/pages/overlay_image.dart | 5 +- example/lib/pages/plugin_scalebar.dart | 5 +- example/lib/pages/plugin_zoombuttons.dart | 4 +- example/lib/pages/point_to_latlng.dart | 5 +- example/lib/pages/polygon.dart | 5 +- example/lib/pages/polyline.dart | 5 +- example/lib/pages/reset_tile_layer.dart | 4 +- example/lib/pages/sliding_map.dart | 4 +- example/lib/pages/stateful_markers.dart | 4 +- example/lib/pages/tap_to_add.dart | 4 +- example/lib/pages/tile_builder_example.dart | 4 +- .../lib/pages/tile_loading_error_handle.dart | 4 +- example/lib/pages/widgets.dart | 4 +- example/lib/pages/wms_tile_layer.dart | 4 +- example/lib/test_app.dart | 4 +- lib/src/layer/tile_layer/tile_layer.dart | 507 +++++++++++++----- .../layer/tile_layer/tile_layer_options.dart | 293 ---------- lib/src/layer/tile_layer/tile_manager.dart | 6 +- .../tile_provider/base_tile_provider.dart | 8 +- .../tile_provider/file_tile_provider_io.dart | 2 +- .../tile_provider/file_tile_provider_web.dart | 2 +- .../tile_provider/tile_provider_io.dart | 14 +- .../tile_provider/tile_provider_web.dart | 14 +- test/flutter_map_test.dart | 4 +- 44 files changed, 478 insertions(+), 522 deletions(-) diff --git a/example/lib/pages/animated_map_controller.dart b/example/lib/pages/animated_map_controller.dart index ba9b2e2eb..74679c821 100644 --- a/example/lib/pages/animated_map_controller.dart +++ b/example/lib/pages/animated_map_controller.dart @@ -180,12 +180,12 @@ class AnimatedMapControllerPageState extends State maxZoom: 10, minZoom: 3), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/circle.dart b/example/lib/pages/circle.dart index 5d03e93fd..b6c09c080 100644 --- a/example/lib/pages/circle.dart +++ b/example/lib/pages/circle.dart @@ -38,12 +38,12 @@ class CirclePage extends StatelessWidget { zoom: 11, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), CircleLayer(circles: circleMarkers), ], ), diff --git a/example/lib/pages/custom_crs/custom_crs.dart b/example/lib/pages/custom_crs/custom_crs.dart index e730a97db..f190f9e00 100644 --- a/example/lib/pages/custom_crs/custom_crs.dart +++ b/example/lib/pages/custom_crs/custom_crs.dart @@ -138,7 +138,7 @@ class _CustomCrsPageState extends State { }), ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( opacity: 1, backgroundColor: Colors.transparent, wmsOptions: WMSTileLayerOptions( @@ -150,7 +150,7 @@ class _CustomCrsPageState extends State { 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', layers: ['gebco_north_polar_view'], ), - )), + ), ], ), ), diff --git a/example/lib/pages/epsg3413_crs.dart b/example/lib/pages/epsg3413_crs.dart index 769c25cb2..43ea26d6a 100644 --- a/example/lib/pages/epsg3413_crs.dart +++ b/example/lib/pages/epsg3413_crs.dart @@ -134,8 +134,7 @@ class _EPSG3413PageState extends State { maxZoom: maxZoom, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( opacity: 1, backgroundColor: Colors.transparent, wmsOptions: WMSTileLayerOptions( @@ -146,7 +145,7 @@ class _EPSG3413PageState extends State { 'https://www.gebco.net/data_and_products/gebco_web_services/north_polar_view_wms/mapserv?', layers: ['gebco_north_polar_view'], ), - )), + ), OverlayImageLayer( overlayImages: [ OverlayImage( diff --git a/example/lib/pages/epsg4326_crs.dart b/example/lib/pages/epsg4326_crs.dart index 24054c7b3..7bd3f8664 100644 --- a/example/lib/pages/epsg4326_crs.dart +++ b/example/lib/pages/epsg4326_crs.dart @@ -30,14 +30,14 @@ class EPSG4326Page extends StatelessWidget { zoom: 0, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( wmsOptions: WMSTileLayerOptions( crs: const Epsg4326(), 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 691c3a98e..bc5245bc1 100644 --- a/example/lib/pages/esri.dart +++ b/example/lib/pages/esri.dart @@ -28,11 +28,11 @@ class EsriPage extends StatelessWidget { zoom: 13, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( 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 858e26020..54b526b97 100644 --- a/example/lib/pages/home.dart +++ b/example/lib/pages/home.dart @@ -64,12 +64,12 @@ class HomePage extends StatelessWidget { ), ], children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/interactive_test_page.dart b/example/lib/pages/interactive_test_page.dart index 7dd5f5a49..db566a894 100644 --- a/example/lib/pages/interactive_test_page.dart +++ b/example/lib/pages/interactive_test_page.dart @@ -156,12 +156,12 @@ class _InteractiveTestPageState extends State { interactiveFlags: flags, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( 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/latlng_to_screen_point.dart b/example/lib/pages/latlng_to_screen_point.dart index 09ea20420..6988c2b75 100644 --- a/example/lib/pages/latlng_to_screen_point.dart +++ b/example/lib/pages/latlng_to_screen_point.dart @@ -57,12 +57,12 @@ class _LatLngScreenPointTestPageState extends State { rotation: 0, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( 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 b819c5c52..36fddd722 100644 --- a/example/lib/pages/live_location.dart +++ b/example/lib/pages/live_location.dart @@ -140,13 +140,12 @@ class _LiveLocationPageState extends State { interactiveFlags: interActiveFlags, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index b4900113e..0a18d4c89 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -78,12 +78,12 @@ class _ManyMarkersPageState extends State { interactiveFlags: InteractiveFlag.all - InteractiveFlag.rotate, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer( markers: allMarkers.sublist( 0, min(allMarkers.length, _sliderVal))), diff --git a/example/lib/pages/map_controller.dart b/example/lib/pages/map_controller.dart index 548a40045..b32c434be 100644 --- a/example/lib/pages/map_controller.dart +++ b/example/lib/pages/map_controller.dart @@ -160,13 +160,12 @@ class MapControllerPageState extends State { minZoom: 3, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/map_inside_listview.dart b/example/lib/pages/map_inside_listview.dart index 8f80ac2c6..e47bfc3b6 100644 --- a/example/lib/pages/map_inside_listview.dart +++ b/example/lib/pages/map_inside_listview.dart @@ -27,14 +27,12 @@ class MapInsideListViewPage extends StatelessWidget { zoom: 5, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', ), - ), const FlutterMapZoomButtons( minZoom: 4, maxZoom: 19, diff --git a/example/lib/pages/marker_anchor.dart b/example/lib/pages/marker_anchor.dart index 3ae6b54a8..c4f60cec4 100644 --- a/example/lib/pages/marker_anchor.dart +++ b/example/lib/pages/marker_anchor.dart @@ -113,13 +113,12 @@ class MarkerAnchorPageState extends State { zoom: 5, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/marker_rotate.dart b/example/lib/pages/marker_rotate.dart index 7889dee79..b895ab012 100644 --- a/example/lib/pages/marker_rotate.dart +++ b/example/lib/pages/marker_rotate.dart @@ -138,12 +138,12 @@ class MarkerRotatePageState extends State { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer( rotate: rotateMarkerLayerOptions, markers: markers, diff --git a/example/lib/pages/max_bounds.dart b/example/lib/pages/max_bounds.dart index dbb660a2a..b0a423774 100644 --- a/example/lib/pages/max_bounds.dart +++ b/example/lib/pages/max_bounds.dart @@ -31,13 +31,13 @@ class MaxBoundsPage extends StatelessWidget { screenSize: MediaQuery.of(context).size, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( maxZoom: 15, 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 bdc26bdd1..6406d1709 100644 --- a/example/lib/pages/moving_markers.dart +++ b/example/lib/pages/moving_markers.dart @@ -59,12 +59,12 @@ class _MovingMarkersPageState extends State { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: [_marker!]), ], ), diff --git a/example/lib/pages/network_tile_provider.dart b/example/lib/pages/network_tile_provider.dart index 100e1f0bc..f512637bd 100644 --- a/example/lib/pages/network_tile_provider.dart +++ b/example/lib/pages/network_tile_provider.dart @@ -65,13 +65,13 @@ class NetworkTileProviderPage extends StatelessWidget { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], tileProvider: NetworkTileProvider(), userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers) ], ), diff --git a/example/lib/pages/offline_map.dart b/example/lib/pages/offline_map.dart index 30010ef6f..95a969b2a 100644 --- a/example/lib/pages/offline_map.dart +++ b/example/lib/pages/offline_map.dart @@ -33,11 +33,11 @@ class OfflineMapPage extends StatelessWidget { nePanBoundary: LatLng(56.7378, 11.6644), ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( tileProvider: AssetTileProvider(), maxZoom: 14, 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 e75b215d8..8c0bb8849 100644 --- a/example/lib/pages/on_tap.dart +++ b/example/lib/pages/on_tap.dart @@ -85,13 +85,12 @@ class OnTapPageState extends State { minZoom: 3, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/overlay_image.dart b/example/lib/pages/overlay_image.dart index 9967ee29f..a90135dec 100644 --- a/example/lib/pages/overlay_image.dart +++ b/example/lib/pages/overlay_image.dart @@ -47,13 +47,12 @@ class OverlayImagePage extends StatelessWidget { zoom: 6, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), OverlayImageLayer( overlayImages: overlayImages), MarkerLayer(markers: [ diff --git a/example/lib/pages/plugin_scalebar.dart b/example/lib/pages/plugin_scalebar.dart index 9104aaf6c..51de08e60 100644 --- a/example/lib/pages/plugin_scalebar.dart +++ b/example/lib/pages/plugin_scalebar.dart @@ -35,13 +35,12 @@ class PluginScaleBar extends StatelessWidget { )), ], children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( 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/plugin_zoombuttons.dart b/example/lib/pages/plugin_zoombuttons.dart index a0f364f40..9939e4c2d 100644 --- a/example/lib/pages/plugin_zoombuttons.dart +++ b/example/lib/pages/plugin_zoombuttons.dart @@ -34,12 +34,12 @@ class PluginZoomButtons extends StatelessWidget { ), ], children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( 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/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index 3e1a7bb54..4d76adc6e 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -64,13 +64,12 @@ class PointToLatlngPage extends State { minZoom: 3, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), if (latLng != null) MarkerLayer( markers: [ diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index e55ef19a6..b50806c20 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -53,13 +53,12 @@ class PolygonPage extends StatelessWidget { zoom: 5, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), PolygonLayer(polygons: [ Polygon( points: notFilledPoints, diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index 3ab0ca279..6eda841ec 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -81,14 +81,13 @@ class _PolylinePageState extends State { }, ), children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), PolylineLayer(polylines: [ Polyline( points: points, diff --git a/example/lib/pages/reset_tile_layer.dart b/example/lib/pages/reset_tile_layer.dart index 2128436a6..a8d76b048 100644 --- a/example/lib/pages/reset_tile_layer.dart +++ b/example/lib/pages/reset_tile_layer.dart @@ -76,12 +76,12 @@ class ResetTileLayerPageState extends State { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( reset: resetController.stream, urlTemplate: layerToggle ? layer1 : layer2, subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers) ], ), diff --git a/example/lib/pages/sliding_map.dart b/example/lib/pages/sliding_map.dart index ec500725d..ed0262d22 100644 --- a/example/lib/pages/sliding_map.dart +++ b/example/lib/pages/sliding_map.dart @@ -35,11 +35,11 @@ class SlidingMapPage extends StatelessWidget { screenSize: MediaQuery.of(context).size, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( tileProvider: AssetTileProvider(), maxZoom: 14, 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 47e0c642c..5544f834c 100644 --- a/example/lib/pages/stateful_markers.dart +++ b/example/lib/pages/stateful_markers.dart @@ -64,12 +64,12 @@ class _StatefulMarkersPageState extends State { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: _markers), ], ), diff --git a/example/lib/pages/tap_to_add.dart b/example/lib/pages/tap_to_add.dart index 41b32a5ca..28ff287ca 100644 --- a/example/lib/pages/tap_to_add.dart +++ b/example/lib/pages/tap_to_add.dart @@ -47,11 +47,11 @@ class TapToAddPageState extends State { zoom: 13, onTap: _handleTap), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', userAgentPackageName: 'dev.fleaflet.flutter_map.example', - )), + ), MarkerLayer(markers: markers), ], ), diff --git a/example/lib/pages/tile_builder_example.dart b/example/lib/pages/tile_builder_example.dart index 215c36c74..07c47bce9 100644 --- a/example/lib/pages/tile_builder_example.dart +++ b/example/lib/pages/tile_builder_example.dart @@ -112,14 +112,14 @@ class _TileBuilderPageState extends State { zoom: 5, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], userAgentPackageName: 'dev.fleaflet.flutter_map.example', tileBuilder: tileBuilder, tilesContainerBuilder: darkMode ? darkModeTilesContainerBuilder : null, - )), + ), MarkerLayer( markers: [ Marker( diff --git a/example/lib/pages/tile_loading_error_handle.dart b/example/lib/pages/tile_loading_error_handle.dart index 42d411742..340eb77a7 100644 --- a/example/lib/pages/tile_loading_error_handle.dart +++ b/example/lib/pages/tile_loading_error_handle.dart @@ -39,7 +39,7 @@ class _TileLoadingErrorHandleState extends State { }, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], @@ -62,7 +62,7 @@ class _TileLoadingErrorHandleState extends State { needLoadingError = false; } }, - )), + ), ], ); }), diff --git a/example/lib/pages/widgets.dart b/example/lib/pages/widgets.dart index 41f614171..ad5bea8ad 100644 --- a/example/lib/pages/widgets.dart +++ b/example/lib/pages/widgets.dart @@ -44,13 +44,11 @@ class WidgetsPage extends StatelessWidget { ) ], children: [ - TileLayerWidget( - options: TileLayerOptions( + TileLayer( 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 b1f6688a4..c3b1414ee 100644 --- a/example/lib/pages/wms_tile_layer.dart +++ b/example/lib/pages/wms_tile_layer.dart @@ -28,14 +28,14 @@ class WMSLayerPage extends StatelessWidget { zoom: 6, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( wmsOptions: WMSTileLayerOptions( baseUrl: 'https://{s}.s2maps-tiles.eu/wms/?', 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 76726f079..908645a94 100644 --- a/example/lib/test_app.dart +++ b/example/lib/test_app.dart @@ -33,12 +33,12 @@ class _TestAppState extends State { zoom: 13, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['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 c26c0f34c..835e1351a 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -18,39 +18,306 @@ import 'package:tuple/tuple.dart'; part 'tile_layer_options.dart'; -class TileLayerWidget extends StatelessWidget { - final TileLayerOptions options; - - const TileLayerWidget({super.key, required this.options}); - - @override - Widget build(BuildContext context) { - final mapState = FlutterMapState.maybeOf(context)!; - return TileLayer( - mapState: mapState, - options: options, - ); - } -} - +/// 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 TileLayer extends StatefulWidget { - final TileLayerOptions options; - final FlutterMapState mapState; - - const TileLayer({ + + /// 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 + /// load retina tiles (can be omitted) + /// + /// Example: + /// + /// https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png + /// + /// Is translated to this: + /// + /// https://a.tile.openstreetmap.org/12/2177/1259.png + final String? urlTemplate; + + /// 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; + + /// If not `null`, then tiles will pull's WMS protocol requests + final WMSTileLayerOptions? wmsOptions; + + /// Size for the tile. + /// Default is 256 + final double tileSize; + + // The minimum zoom level down to which this layer will be + // displayed (inclusive). + final double minZoom; + + /// The maximum zoom level up to which this layer will be displayed + /// (inclusive). In most tile providers goes from 0 to 19. + final double maxZoom; + + /// Minimum zoom number the tile source has available. If it is specified, the + /// tiles on all zoom levels lower than minNativeZoom will be loaded from + /// minNativeZoom level and auto-scaled. + final double? minNativeZoom; + + /// Maximum zoom number the tile source has available. If it is specified, the + /// tiles on all zoom levels higher than maxNativeZoom will be loaded from + /// maxNativeZoom level and auto-scaled. + final double? maxNativeZoom; + + /// If set to true, the zoom number used in tile URLs will be reversed + /// (`maxZoom - zoom` instead of `zoom`) + final bool zoomReverse; + + /// The zoom number used in tile URLs will be offset with this value. + final double zoomOffset; + + /// List of subdomains for the URL. + /// + /// Example: + /// + /// Subdomains = {a,b,c} + /// + /// and the URL is as follows: + /// + /// https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png + /// + /// then: + /// + /// https://a.tile.openstreetmap.org/{z}/{x}/{y}.png + /// https://b.tile.openstreetmap.org/{z}/{x}/{y}.png + /// https://c.tile.openstreetmap.org/{z}/{x}/{y}.png + final List subdomains; + + /// Color shown behind the tiles + final Color backgroundColor; + + /// Opacity of the rendered tile + final double opacity; + + /// Provider with which to load map tiles + /// + /// The default is [NetworkNoRetryTileProvider]. Alternatively, use + /// [NetworkTileProvider] for a network provider which will retry requests. + /// + /// 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. + /// + /// `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 ()'. + /// + /// 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'. + /// + /// Custom [TileProvider]s can also be used, but these will not follow the header + /// rules above. + final TileProvider tileProvider; + + /// When panning the map, keep this many rows and columns of tiles before + /// unloading them. + final int keepBuffer; + + /// Tile image to show in place of the tile that failed to load. + final ImageProvider? errorImage; + + /// Static information that should replace placeholders in the [urlTemplate]. + /// Applying API keys is a good example on how to use this parameter. + /// + /// Example: + /// + /// ```dart + /// + /// TileLayerOptions( + /// urlTemplate: "https://api.tiles.mapbox.com/v4/" + /// "{id}/{z}/{x}/{y}{r}.png?access_token={accessToken}", + /// additionalOptions: { + /// 'accessToken': '', + /// 'id': 'mapbox.streets', + /// }, + /// ), + /// ``` + final Map additionalOptions; + + /// Tiles will not update more than once every `updateInterval` (default 200 + /// milliseconds) when panning. It can be null (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) + final Duration? updateInterval; + + /// Tiles fade in duration in milliseconds (default 100). This can be null to + /// avoid fade in. + final Duration? tileFadeInDuration; + + /// Opacity start value when Tile starts fade in (0.0 - 1.0) Takes effect if + /// `tileFadeInDuration` is not null + final double tileFadeInStart; + + /// Opacity start value when an exists Tile starts fade in with different Url + /// (0.0 - 1.0) Takes effect when `tileFadeInDuration` is not null and if + /// `overrideTilesWhenUrlChanges` if true + final double tileFadeInStartWhenOverride; + + /// `false`: current Tiles will be first dropped and then reload via new url + /// (default) `true`: current Tiles will be visible until new ones aren't + /// loaded (new Tiles are loaded independently) @see + /// https://github.com/johnpryan/flutter_map/issues/583 + final bool overrideTilesWhenUrlChanges; + + /// If `true`, it will request four tiles of half the specified size and a + /// bigger zoom level in place of one to utilize the high resolution. + /// + /// If `true` then MapOptions's `maxZoom` should be `maxZoom - 1` since + /// retinaMode just simulates retina display by playing with `zoomOffset`. If + /// geoserver supports retina `@2` tiles then it it advised to use them + /// instead of simulating it (use {r} in the [urlTemplate]) + /// + /// It is advised to use retinaMode if display supports it, write code like + /// this: + /// + /// ```dart + /// TileLayerOptions( + /// retinaMode: true && MediaQuery.of(context).devicePixelRatio > 1.0, + /// ), + /// ``` + final bool retinaMode; + + /// This callback will be execute if some errors occur when fetching tiles. + final ErrorTileCallBack? errorTileCallback; + + final TemplateFunction templateFunction; + + /// Function which may Wrap Tile with custom Widget + /// There are predefined examples in 'tile_builder.dart' + final TileBuilder? tileBuilder; + + /// Function which may wrap Tiles Container with custom Widget + /// There are predefined examples in 'tile_builder.dart' + final TilesContainerBuilder? tilesContainerBuilder; + + // If a Tile was loaded with error and if strategy isn't `none` then TileProvider + // will be asked to evict Image based on current strategy + // (see #576 - even Error Images are cached in flutter) + final EvictErrorTileStrategy evictErrorTileStrategy; + + /// This option is useful when you have a transparent layer: rather than + /// keeping the old layer visible when zooming (resulting in both layers + /// being temporarily visible), the old layer is removed as quickly as + /// possible when this is set to `true` (default `false`). + /// + /// This option is likely to cause some flickering of the transparent layer, + /// most noticeable when using pinch-to-zoom. It's best used with maps that + /// have `interactive` set to `false`, and zoom using buttons that call + /// `MapController.move()`. + /// + /// When set to `true`, the `tileFadeIn*` options will be ignored. + final bool fastReplace; + + /// Stream to notify the [TileLayer] that it needs resetting + final Stream? reset; + + /// Only load tiles that are within these bounds + final LatLngBounds? tileBounds; + + TileLayer({ super.key, - required this.options, - required this.mapState, - }); + this.urlTemplate, + double tileSize = 256.0, + double minZoom = 0.0, + double maxZoom = 18.0, + this.minNativeZoom, + this.maxNativeZoom, + this.zoomReverse = false, + double zoomOffset = 0.0, + Map? additionalOptions, + this.subdomains = const [], + this.keepBuffer = 2, + this.backgroundColor = const Color(0xFFE0E0E0), + this.errorImage, + TileProvider? tileProvider, + this.tms = false, + 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) + Duration updateInterval = const Duration(milliseconds: 200), + Duration tileFadeInDuration = const Duration(milliseconds: 100), + this.tileFadeInStart = 0.0, + this.tileFadeInStartWhenOverride = 0.0, + this.overrideTilesWhenUrlChanges = false, + this.retinaMode = false, + this.errorTileCallback, + this.templateFunction = util.template, + this.tileBuilder, + this.tilesContainerBuilder, + this.evictErrorTileStrategy = EvictErrorTileStrategy.none, + this.fastReplace = false, + this.reset, + this.tileBounds, + String userAgentPackageName = 'unknown', + }) : updateInterval = + 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), + maxZoom = + wmsOptions == null && retinaMode && maxZoom > 0.0 && !zoomReverse + ? maxZoom - 1.0 + : maxZoom, + minZoom = + wmsOptions == null && retinaMode && maxZoom > 0.0 && zoomReverse + ? math.max(minZoom + 1.0, 0) + : minZoom, + zoomOffset = wmsOptions == null && retinaMode && maxZoom > 0.0 + ? (zoomReverse ? zoomOffset - 1.0 : zoomOffset + 1.0) + : zoomOffset, + tileSize = wmsOptions == null && retinaMode && maxZoom > 0.0 + ? (tileSize / 2.0).floorToDouble() + : tileSize, + 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)', + }); @override State createState() => _TileLayerState(); } class _TileLayerState extends State with TickerProviderStateMixin { - FlutterMapState get map => widget.mapState; - TileLayerOptions get options => widget.options; late Bounds _globalTileRange; Tuple2? _wrapX; Tuple2? _wrapY; @@ -70,15 +337,14 @@ class _TileLayerState extends State with TickerProviderStateMixin { super.initState(); _tileManager = TileManager(); _transformationCalculator = TransformationCalculator(); - _tileSize = CustomPoint(options.tileSize, options.tileSize); - _resetView(); - _update(null); + _tileSize = CustomPoint(widget.tileSize, widget.tileSize); - if (options.reset != null) { - _resetSub = options.reset?.listen((_) => _resetTiles()); + if (widget.reset != null) { + _resetSub = widget.reset?.listen((_) => _resetTiles()); } - _initThrottleUpdate(); + //TODO fix + // _initThrottleUpdate(); } @override @@ -86,36 +352,37 @@ class _TileLayerState extends State with TickerProviderStateMixin { super.didUpdateWidget(oldWidget); var reloadTiles = false; - if (oldWidget.options.tileSize != options.tileSize) { - _tileSize = CustomPoint(options.tileSize, options.tileSize); + if (oldWidget.tileSize != widget.tileSize) { + _tileSize = CustomPoint(widget.tileSize, widget.tileSize); reloadTiles = true; } - if (oldWidget.options.retinaMode != options.retinaMode) { + if (oldWidget.retinaMode != widget.retinaMode) { reloadTiles = true; } reloadTiles |= - !_tileManager.allWithinZoom(options.minZoom, options.maxZoom); + !_tileManager.allWithinZoom(widget.minZoom, widget.maxZoom); - if (oldWidget.options.updateInterval != options.updateInterval) { + if (oldWidget.updateInterval != widget.updateInterval) { _throttleUpdate?.close(); - _initThrottleUpdate(); + //TODO fix + // _initThrottleUpdate(); } if (!reloadTiles) { - final oldUrl = oldWidget.options.wmsOptions?._encodedBaseUrl ?? - oldWidget.options.urlTemplate; - final newUrl = options.wmsOptions?._encodedBaseUrl ?? options.urlTemplate; + final oldUrl = oldWidget.wmsOptions?._encodedBaseUrl ?? + oldWidget.urlTemplate; + final newUrl = widget.wmsOptions?._encodedBaseUrl ?? widget.urlTemplate; - final oldOptions = oldWidget.options.additionalOptions; - final newOptions = options.additionalOptions; + final oldOptions = oldWidget.additionalOptions; + final newOptions = widget.additionalOptions; if (oldUrl != newUrl || !(const MapEquality()) .equals(oldOptions, newOptions)) { - if (options.overrideTilesWhenUrlChanges) { - _tileManager.reloadImages(options, _wrapX, _wrapY); + if (widget.overrideTilesWhenUrlChanges) { + _tileManager.reloadImages(widget, _wrapX, _wrapY); } else { reloadTiles = true; } @@ -123,33 +390,32 @@ class _TileLayerState extends State with TickerProviderStateMixin { } if (reloadTiles) { - _tileManager.removeAll(options.evictErrorTileStrategy); - _resetView(); - _update(null); + _tileManager.removeAll(widget.evictErrorTileStrategy); } } - void _initThrottleUpdate() { - if (options.updateInterval == null) { - _throttleUpdate = null; - } else { - _throttleUpdate = StreamController(sync: true); - _throttleUpdate!.stream - .transform( - util.throttleStreamTransformerWithTrailingCall( - options.updateInterval!, - ), - ) - .listen(_update); - } - } +//TODO fix + // void _initThrottleUpdate() { + // if (widget.updateInterval == null) { + // _throttleUpdate = null; + // } else { + // _throttleUpdate = StreamController(sync: true); + // _throttleUpdate!.stream + // .transform( + // util.throttleStreamTransformerWithTrailingCall( + // widget.updateInterval!, + // ), + // ) + // .listen(_update); + // } + // } @override void dispose() { - _tileManager.removeAll(options.evictErrorTileStrategy); + _tileManager.removeAll(widget.evictErrorTileStrategy); _resetSub?.cancel(); _pruneLater?.cancel(); - options.tileProvider.dispose(); + widget.tileProvider.dispose(); _throttleUpdate?.close(); super.dispose(); @@ -157,33 +423,38 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { + final map = FlutterMapState.maybeOf(context)!; + //Handle movement final tileZoom = _clampZoom(map.zoom.roundToDouble()); if (_tileZoom == null) { // if there is no _tileZoom available it means we are out within zoom level // we will restore fully via _setView call if we are back on trail - if ((tileZoom <= options.maxZoom) && (tileZoom >= options.minZoom)) { + if ((tileZoom <= widget.maxZoom) && (tileZoom >= widget.minZoom)) { _tileZoom = tileZoom; - _setView(map.center, tileZoom); + _setView(map, map.center, tileZoom); } } else { if ((tileZoom - _tileZoom!).abs() >= 1) { // It was a zoom lvl change - _setView(map.center, tileZoom); + _setView(map, map.center, tileZoom); } else { if (_throttleUpdate == null) { - _update(null); + //TODO what is this for? + // _update(null); } else { _throttleUpdate!.add(null); } } } + _setView(map, map.center, map.zoom); + final tilesToRender = _tileZoom == null ? _tileManager.all() : _tileManager.sortedByDistanceToZoomAscending( - options.maxZoom, _tileZoom!); + widget.maxZoom, _tileZoom!); final Map zoomToTransformation = {}; final tileWidgets = [ @@ -197,8 +468,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { tile.coords.z, map, )), - errorImage: options.errorImage, - tileBuilder: options.tileBuilder, + errorImage: widget.errorImage, + tileBuilder: widget.tileBuilder, key: ValueKey(tile.coordsKey), ) ]; @@ -207,18 +478,18 @@ class _TileLayerState extends State with TickerProviderStateMixin { children: tileWidgets, ); - final tilesLayer = options.tilesContainerBuilder == null + final tilesLayer = widget.tilesContainerBuilder == null ? tilesContainer - : options.tilesContainerBuilder!( + : widget.tilesContainerBuilder!( context, tilesContainer, tilesToRender, ); return Opacity( - opacity: options.opacity, + opacity: widget.opacity, child: Container( - color: options.backgroundColor, + color: widget.backgroundColor, child: tilesLayer, ), ); @@ -226,7 +497,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { CustomPoint getTileSize() => _tileSize; - Level? _updateLevels() { + Level? _updateLevels(FlutterMapState map) { final zoom = _tileZoom; if (zoom == null) return null; @@ -235,7 +506,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { levelZoom != zoom && !_tileManager.anyWithZoomLevel(levelZoom)); for (final z in toRemove) { - _tileManager.removeAtZoom(z, options.evictErrorTileStrategy); + _tileManager.removeAtZoom(z, widget.evictErrorTileStrategy); _transformationCalculator.removeLevel(z); } @@ -244,48 +515,42 @@ class _TileLayerState extends State with TickerProviderStateMixin { ///removes all loaded tiles and resets the view void _resetTiles() { - _tileManager.removeAll(options.evictErrorTileStrategy); - _resetView(); - } - - void _resetView() { - _setView(map.center, map.zoom); + _tileManager.removeAll(widget.evictErrorTileStrategy); } double _clampZoom(double zoom) { - if (null != options.minNativeZoom && zoom < options.minNativeZoom!) { - return options.minNativeZoom!; + if (null != widget.minNativeZoom && zoom < widget.minNativeZoom!) { + return widget.minNativeZoom!; } - if (null != options.maxNativeZoom && options.maxNativeZoom! < zoom) { - return options.maxNativeZoom!; + if (null != widget.maxNativeZoom && widget.maxNativeZoom! < zoom) { + return widget.maxNativeZoom!; } return zoom; } - void _setView(LatLng center, double zoom) { + void _setView(FlutterMapState map, LatLng center, double zoom) { double? tileZoom = _clampZoom(zoom.roundToDouble()); - if ((tileZoom > options.maxZoom) || (tileZoom < options.minZoom)) { + if ((tileZoom > widget.maxZoom) || (tileZoom < widget.minZoom)) { tileZoom = null; } _tileZoom = tileZoom; - _tileManager.abortLoading(_tileZoom, options.evictErrorTileStrategy); + _tileManager.abortLoading(_tileZoom, widget.evictErrorTileStrategy); - _updateLevels(); - _resetGrid(); + _updateLevels(map); + _resetGrid(map); if (_tileZoom != null) { - _update(center); + _update(map, center); } - _tileManager.prune(_tileZoom, options.evictErrorTileStrategy); + _tileManager.prune(_tileZoom, widget.evictErrorTileStrategy); } - void _resetGrid() { - final map = this.map; + void _resetGrid(FlutterMapState map) { final crs = map.options.crs; final tileSize = getTileSize(); final tileZoom = _tileZoom; @@ -320,7 +585,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } - Bounds _getTiledPixelBounds(LatLng center) { + Bounds _getTiledPixelBounds(FlutterMapState map, LatLng center) { final scale = map.getZoomScale(map.zoom, _tileZoom); final pixelCenter = map.project(center, _tileZoom).floor(); final halfSize = map.size / (scale * 2); @@ -330,7 +595,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { // Private method to load tiles in the grid's active zoom level according to // map bounds - void _update(LatLng? center) { + void _update(FlutterMapState map, LatLng? center) { if (_tileZoom == null) { return; } @@ -338,11 +603,11 @@ class _TileLayerState extends State with TickerProviderStateMixin { final zoom = _clampZoom(map.zoom); center ??= map.center; - final pixelBounds = _getTiledPixelBounds(center); + final pixelBounds = _getTiledPixelBounds(map, center); final tileRange = _pxBoundsToTileRange(pixelBounds); final tileCenter = tileRange.center; final queue = >[]; - final margin = options.keepBuffer; + final margin = widget.keepBuffer; final noPruneRange = Bounds( tileRange.bottomLeft - CustomPoint(margin, -margin), tileRange.topRight + CustomPoint(margin, -margin), @@ -353,7 +618,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { // _update just loads more tiles. If the tile zoom level differs too much // from the map's, let _setView reset levels and prune old tiles. if ((zoom - _tileZoom!).abs() > 1) { - _setView(center, zoom); + _setView(map, center, zoom); return; } @@ -363,15 +628,15 @@ class _TileLayerState extends State with TickerProviderStateMixin { final coords = Coords(i.toDouble(), j.toDouble()); coords.z = _tileZoom!; - if (options.tileBounds != null) { + if (widget.tileBounds != null) { final tilePxBounds = _pxBoundsToTileRange( - _latLngBoundsToPixelBounds(options.tileBounds!, _tileZoom!)); + _latLngBoundsToPixelBounds(map, widget.tileBounds!, _tileZoom!)); if (!_areCoordsInsideTileBounds(coords, tilePxBounds)) { continue; } } - if (!_isValidTile(coords)) { + if (!_isValidTile(map.options.crs, coords)) { continue; } @@ -382,7 +647,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } _tileManager.evictErrorTilesBasedOnStrategy( - tileRange, options.evictErrorTileStrategy); + tileRange, widget.evictErrorTileStrategy); // sort tile queue to load tiles in order of their distance to center queue.sort((a, b) => @@ -391,10 +656,10 @@ class _TileLayerState extends State with TickerProviderStateMixin { for (final coords in queue) { final newTile = Tile( coords: coords, - tilePos: _getTilePos(coords), + tilePos: _getTilePos(map, coords), current: true, imageProvider: - options.tileProvider.getImage(coords.wrap(_wrapX, _wrapY), options), + widget.tileProvider.getImage(coords.wrap(_wrapX, _wrapY), widget), tileReady: _tileReady, ); @@ -406,9 +671,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } - bool _isValidTile(Coords coords) { - final crs = map.options.crs; - + bool _isValidTile(Crs crs, Coords coords) { if (!crs.infinite) { // don't load tile if it's out of bounds and not wrapped final bounds = _globalTileRange; @@ -432,7 +695,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { return true; } - Bounds _latLngBoundsToPixelBounds(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); @@ -445,8 +708,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { tile!.loadError = true; - if (options.errorTileCallback != null) { - options.errorTileCallback!(tile, error); + if (widget.errorTileCallback != null) { + widget.errorTileCallback!(tile, error); } } else { tile!.loadError = false; @@ -455,29 +718,29 @@ class _TileLayerState extends State with TickerProviderStateMixin { tile = _tileManager.tileAt(tile.coords); if (tile == null) return; - if (options.fastReplace && mounted) { + if (widget.fastReplace && mounted) { setState(() { tile!.active = true; if (_tileManager.allLoaded) { // We're not waiting for anything, prune the tiles immediately. - _tileManager.prune(_tileZoom, options.evictErrorTileStrategy); + _tileManager.prune(_tileZoom, widget.evictErrorTileStrategy); } }); return; } final fadeInStart = tile.loaded == null - ? options.tileFadeInStart - : options.tileFadeInStartWhenOverride; + ? widget.tileFadeInStart + : widget.tileFadeInStartWhenOverride; tile.loaded = DateTime.now(); - if (options.tileFadeInDuration == null || + if (widget.tileFadeInDuration == null || fadeInStart == 1.0 || - (tile.loadError && null == options.errorImage)) { + (tile.loadError && null == widget.errorImage)) { tile.active = true; } else { tile.startFadeInAnimation( - options.tileFadeInDuration!, + widget.tileFadeInDuration!, this, from: fadeInStart, ); @@ -492,13 +755,13 @@ class _TileLayerState extends State with TickerProviderStateMixin { // fade-in) to trigger a pruning. _pruneLater?.cancel(); _pruneLater = Timer( - options.tileFadeInDuration != null - ? options.tileFadeInDuration! + const Duration(milliseconds: 50) + widget.tileFadeInDuration != null + ? widget.tileFadeInDuration! + const Duration(milliseconds: 50) : const Duration(milliseconds: 50), () { if (mounted) { setState(() { - _tileManager.prune(_tileZoom, options.evictErrorTileStrategy); + _tileManager.prune(_tileZoom, widget.evictErrorTileStrategy); }); } }, @@ -506,7 +769,7 @@ class _TileLayerState extends State with TickerProviderStateMixin { } } - CustomPoint _getTilePos(Coords coords) { + CustomPoint _getTilePos(FlutterMapState map, Coords coords) { final level = _transformationCalculator.getOrCreateLevel(coords.z as double, map); return coords.scaleBy(getTileSize()) - level.origin; diff --git a/lib/src/layer/tile_layer/tile_layer_options.dart b/lib/src/layer/tile_layer/tile_layer_options.dart index 7fdcb32e2..443e584e5 100644 --- a/lib/src/layer/tile_layer/tile_layer_options.dart +++ b/lib/src/layer/tile_layer/tile_layer_options.dart @@ -18,299 +18,6 @@ enum EvictErrorTileStrategy { 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 { - /// 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 - /// load retina tiles (can be omitted) - /// - /// Example: - /// - /// https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png - /// - /// Is translated to this: - /// - /// https://a.tile.openstreetmap.org/12/2177/1259.png - final String? urlTemplate; - - /// 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; - - /// If not `null`, then tiles will pull's WMS protocol requests - final WMSTileLayerOptions? wmsOptions; - - /// Size for the tile. - /// Default is 256 - final double tileSize; - - // The minimum zoom level down to which this layer will be - // displayed (inclusive). - final double minZoom; - - /// The maximum zoom level up to which this layer will be displayed - /// (inclusive). In most tile providers goes from 0 to 19. - final double maxZoom; - - /// Minimum zoom number the tile source has available. If it is specified, the - /// tiles on all zoom levels lower than minNativeZoom will be loaded from - /// minNativeZoom level and auto-scaled. - final double? minNativeZoom; - - /// Maximum zoom number the tile source has available. If it is specified, the - /// tiles on all zoom levels higher than maxNativeZoom will be loaded from - /// maxNativeZoom level and auto-scaled. - final double? maxNativeZoom; - - /// If set to true, the zoom number used in tile URLs will be reversed - /// (`maxZoom - zoom` instead of `zoom`) - final bool zoomReverse; - - /// The zoom number used in tile URLs will be offset with this value. - final double zoomOffset; - - /// List of subdomains for the URL. - /// - /// Example: - /// - /// Subdomains = {a,b,c} - /// - /// and the URL is as follows: - /// - /// https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png - /// - /// then: - /// - /// https://a.tile.openstreetmap.org/{z}/{x}/{y}.png - /// https://b.tile.openstreetmap.org/{z}/{x}/{y}.png - /// https://c.tile.openstreetmap.org/{z}/{x}/{y}.png - final List subdomains; - - /// Color shown behind the tiles - final Color backgroundColor; - - /// Opacity of the rendered tile - final double opacity; - - /// Provider with which to load map tiles - /// - /// The default is [NetworkNoRetryTileProvider]. Alternatively, use - /// [NetworkTileProvider] for a network provider which will retry requests. - /// - /// 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. - /// - /// `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 ()'. - /// - /// 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'. - /// - /// 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; - - /// Tile image to show in place of the tile that failed to load. - final ImageProvider? errorImage; - - /// Static information that should replace placeholders in the [urlTemplate]. - /// Applying API keys is a good example on how to use this parameter. - /// - /// Example: - /// - /// ```dart - /// - /// TileLayerOptions( - /// urlTemplate: "https://api.tiles.mapbox.com/v4/" - /// "{id}/{z}/{x}/{y}{r}.png?access_token={accessToken}", - /// additionalOptions: { - /// 'accessToken': '', - /// 'id': 'mapbox.streets', - /// }, - /// ), - /// ``` - final Map additionalOptions; - - /// Tiles will not update more than once every `updateInterval` (default 200 - /// milliseconds) when panning. It can be null (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) - final Duration? updateInterval; - - /// Tiles fade in duration in milliseconds (default 100). This can be null to - /// avoid fade in. - final Duration? tileFadeInDuration; - - /// Opacity start value when Tile starts fade in (0.0 - 1.0) Takes effect if - /// `tileFadeInDuration` is not null - final double tileFadeInStart; - - /// Opacity start value when an exists Tile starts fade in with different Url - /// (0.0 - 1.0) Takes effect when `tileFadeInDuration` is not null and if - /// `overrideTilesWhenUrlChanges` if true - final double tileFadeInStartWhenOverride; - - /// `false`: current Tiles will be first dropped and then reload via new url - /// (default) `true`: current Tiles will be visible until new ones aren't - /// loaded (new Tiles are loaded independently) @see - /// https://github.com/johnpryan/flutter_map/issues/583 - final bool overrideTilesWhenUrlChanges; - - /// If `true`, it will request four tiles of half the specified size and a - /// bigger zoom level in place of one to utilize the high resolution. - /// - /// If `true` then MapOptions's `maxZoom` should be `maxZoom - 1` since - /// retinaMode just simulates retina display by playing with `zoomOffset`. If - /// geoserver supports retina `@2` tiles then it it advised to use them - /// instead of simulating it (use {r} in the [urlTemplate]) - /// - /// It is advised to use retinaMode if display supports it, write code like - /// this: - /// - /// ```dart - /// TileLayerOptions( - /// retinaMode: true && MediaQuery.of(context).devicePixelRatio > 1.0, - /// ), - /// ``` - final bool retinaMode; - - /// This callback will be execute if some errors occur when fetching tiles. - final ErrorTileCallBack? errorTileCallback; - - final TemplateFunction templateFunction; - - /// Function which may Wrap Tile with custom Widget - /// There are predefined examples in 'tile_builder.dart' - final TileBuilder? tileBuilder; - - /// Function which may wrap Tiles Container with custom Widget - /// There are predefined examples in 'tile_builder.dart' - final TilesContainerBuilder? tilesContainerBuilder; - - // If a Tile was loaded with error and if strategy isn't `none` then TileProvider - // will be asked to evict Image based on current strategy - // (see #576 - even Error Images are cached in flutter) - final EvictErrorTileStrategy evictErrorTileStrategy; - - /// This option is useful when you have a transparent layer: rather than - /// keeping the old layer visible when zooming (resulting in both layers - /// being temporarily visible), the old layer is removed as quickly as - /// possible when this is set to `true` (default `false`). - /// - /// This option is likely to cause some flickering of the transparent layer, - /// most noticeable when using pinch-to-zoom. It's best used with maps that - /// have `interactive` set to `false`, and zoom using buttons that call - /// `MapController.move()`. - /// - /// When set to `true`, the `tileFadeIn*` options will be ignored. - final bool fastReplace; - - /// Stream to notify the [TileLayer] that it needs resetting - Stream? reset; - - /// Only load tiles that are within these bounds - LatLngBounds? tileBounds; - - TileLayerOptions({ - this.urlTemplate, - double tileSize = 256.0, - double minZoom = 0.0, - double maxZoom = 18.0, - this.minNativeZoom, - this.maxNativeZoom, - this.zoomReverse = false, - double zoomOffset = 0.0, - Map? additionalOptions, - this.subdomains = const [], - this.keepBuffer = 2, - this.backgroundColor = const Color(0xFFE0E0E0), - this.errorImage, - TileProvider? tileProvider, - this.tms = false, - 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) - Duration updateInterval = const Duration(milliseconds: 200), - Duration tileFadeInDuration = const Duration(milliseconds: 100), - this.tileFadeInStart = 0.0, - this.tileFadeInStartWhenOverride = 0.0, - this.overrideTilesWhenUrlChanges = false, - this.retinaMode = false, - this.errorTileCallback, - this.templateFunction = util.template, - this.tileBuilder, - this.tilesContainerBuilder, - this.evictErrorTileStrategy = EvictErrorTileStrategy.none, - this.fastReplace = false, - this.reset, - this.tileBounds, - String userAgentPackageName = 'unknown', - }) : updateInterval = - 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), - maxZoom = - wmsOptions == null && retinaMode && maxZoom > 0.0 && !zoomReverse - ? maxZoom - 1.0 - : maxZoom, - minZoom = - wmsOptions == null && retinaMode && maxZoom > 0.0 && zoomReverse - ? math.max(minZoom + 1.0, 0) - : minZoom, - zoomOffset = wmsOptions == null && retinaMode && maxZoom > 0.0 - ? (zoomReverse ? zoomOffset - 1.0 : zoomOffset + 1.0) - : zoomOffset, - tileSize = wmsOptions == null && retinaMode && maxZoom > 0.0 - ? (tileSize / 2.0).floorToDouble() - : tileSize, - 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)', - }); -} - class WMSTileLayerOptions { final service = 'WMS'; final request = 'GetMap'; diff --git a/lib/src/layer/tile_layer/tile_manager.dart b/lib/src/layer/tile_layer/tile_manager.dart index 408435a3d..9f71ebb1a 100644 --- a/lib/src/layer/tile_layer/tile_manager.dart +++ b/lib/src/layer/tile_layer/tile_manager.dart @@ -93,13 +93,13 @@ class TileManager { } void reloadImages( - TileLayerOptions options, + TileLayer layer, Tuple2? wrapX, Tuple2? wrapY, ) { for (final tile in _tiles.values) { - tile.imageProvider = options.tileProvider - .getImage(tile.coords.wrap(wrapX, wrapY), options); + tile.imageProvider = layer.tileProvider + .getImage(tile.coords.wrap(wrapX, wrapY), layer); tile.loadTileImage(); } } 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 9ede51ece..345e74fa9 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 @@ -17,13 +17,13 @@ abstract class TileProvider { }); /// Retrieve a tile as an image, based on it's coordinates and the current [TileLayerOptions] - ImageProvider getImage(Coords coords, TileLayerOptions options); + ImageProvider getImage(Coords coords, TileLayer 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) { + String getTileUrl(Coords coords, TileLayer options) { final urlTemplate = (options.wmsOptions != null) ? options.wmsOptions! .getUrl(coords, options.tileSize.toInt(), options.retinaMode) @@ -46,7 +46,7 @@ abstract class TileProvider { return options.templateFunction(urlTemplate!, allOpts); } - double _getZoomForUrl(Coords coords, TileLayerOptions options) { + double _getZoomForUrl(Coords coords, TileLayer options) { var zoom = coords.z; if (options.zoomReverse) { @@ -61,7 +61,7 @@ abstract class TileProvider { } /// Get a subdomain value for a tile, based on it's coordinates and the current [TileLayerOptions] - String getSubdomain(Coords coords, TileLayerOptions options) { + String getSubdomain(Coords coords, TileLayer options) { if (options.subdomains.isEmpty) { return ''; } 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 0085d4236..46dc14ea9 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 @@ -8,7 +8,7 @@ class FileTileProvider extends TileProvider { FileTileProvider(); @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return FileImage(File(getTileUrl(coords, 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 0c842f4c1..2a6264e1f 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 @@ -9,7 +9,7 @@ class FileTileProvider extends TileProvider { FileTileProvider(); @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return NetworkImage(getTileUrl(coords, options)); } } 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 f1e126af6..0014a2187 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 @@ -27,7 +27,7 @@ class NetworkTileProvider extends TileProvider { late final RetryClient retryClient; @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => HttpOverrides.runZoned( () => FMNetworkImageProvider( getTileUrl(coords, options), @@ -56,7 +56,7 @@ class NetworkNoRetryTileProvider extends TileProvider { late final HttpClient httpClient; @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => FMNetworkNoRetryImageProvider( getTileUrl(coords, options), headers: headers, @@ -80,7 +80,7 @@ class NonCachingNetworkTileProvider extends TileProvider { late final HttpClient httpClient; @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => NetworkNoRetryTileProvider( headers: headers, httpClient: httpClient, @@ -91,7 +91,7 @@ class AssetTileProvider extends TileProvider { AssetTileProvider(); @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return AssetImage(getTileUrl(coords, options)); } } @@ -100,17 +100,17 @@ class AssetTileProvider extends TileProvider { /// /// 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; + final String Function(Coords coors, TileLayer options) customTileUrl; CustomTileProvider({required this.customTileUrl}); @override - String getTileUrl(Coords coords, TileLayerOptions options) { + String getTileUrl(Coords coords, TileLayer options) { return customTileUrl(coords, options); } @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return AssetImage(getTileUrl(coords, options)); } } 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 b4ed8cd47..8540b5638 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 @@ -21,7 +21,7 @@ class NetworkTileProvider extends TileProvider { late final RetryClient retryClient; @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => FMNetworkImageProvider( getTileUrl(coords, options), headers: headers..remove('User-Agent'), @@ -41,7 +41,7 @@ class NetworkNoRetryTileProvider extends TileProvider { } @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => NetworkImage( getTileUrl(coords, options), headers: headers..remove('User-Agent'), @@ -59,7 +59,7 @@ class NonCachingNetworkTileProvider extends TileProvider { } @override - ImageProvider getImage(Coords coords, TileLayerOptions options) => + ImageProvider getImage(Coords coords, TileLayer options) => NetworkNoRetryTileProvider( headers: headers, ).getImage(coords, options); @@ -69,7 +69,7 @@ class AssetTileProvider extends TileProvider { AssetTileProvider(); @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return AssetImage(getTileUrl(coords, options)); } } @@ -78,17 +78,17 @@ class AssetTileProvider extends TileProvider { /// /// 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; + final String Function(Coords coors, TileLayer options) customTileUrl; CustomTileProvider({required this.customTileUrl}); @override - String getTileUrl(Coords coords, TileLayerOptions options) { + String getTileUrl(Coords coords, TileLayer options) { return customTileUrl(coords, options); } @override - ImageProvider getImage(Coords coords, TileLayerOptions options) { + ImageProvider getImage(Coords coords, TileLayer options) { return AssetImage(getTileUrl(coords, options)); } } diff --git a/test/flutter_map_test.dart b/test/flutter_map_test.dart index 8a4468386..6148ff3b6 100644 --- a/test/flutter_map_test.dart +++ b/test/flutter_map_test.dart @@ -110,10 +110,10 @@ class _TestAppState extends State { zoom: 13, ), children: [ - TileLayerWidget(options: TileLayerOptions( + TileLayer( urlTemplate: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - subdomains: ['a', 'b', 'c'])), + subdomains: ['a', 'b', 'c']), MarkerLayer(markers: _markers), ], ), From c6f31eaee57fa77785e7840931b0a9767e99a171 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 10 Aug 2022 16:59:23 -0500 Subject: [PATCH 25/32] fix 1308 and remove allowPanning (broken anways) --- example/lib/pages/map_inside_listview.dart | 1 + lib/flutter_map.dart | 6 +- lib/src/gestures/gestures.dart | 7 - lib/src/map/flutter_map_state.dart | 168 +++++++++++++-------- 4 files changed, 112 insertions(+), 70 deletions(-) diff --git a/example/lib/pages/map_inside_listview.dart b/example/lib/pages/map_inside_listview.dart index e47bfc3b6..d4eba274a 100644 --- a/example/lib/pages/map_inside_listview.dart +++ b/example/lib/pages/map_inside_listview.dart @@ -23,6 +23,7 @@ class MapInsideListViewPage extends StatelessWidget { height: 300, child: FlutterMap( options: MapOptions( + absorbPanEventsOnScrollables: true, center: LatLng(51.5, -0.09), zoom: 5, ), diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index a0acce73c..056f86a3f 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -242,8 +242,7 @@ class MapOptions { /// see [InteractiveFlag] for custom settings final int interactiveFlags; - final bool allowPanning; - final bool allowPanningOnScrollingParent; + final bool absorbPanEventsOnScrollables; final TapCallback? onTap; final LongPressCallback? onLongPress; @@ -280,7 +279,7 @@ class MapOptions { double? _safeAreaZoom; MapOptions({ - this.allowPanningOnScrollingParent = true, + this.absorbPanEventsOnScrollables = true, this.crs = const Epsg3857(), LatLng? center, this.bounds, @@ -302,7 +301,6 @@ class MapOptions { this.minZoom, this.maxZoom, this.interactiveFlags = InteractiveFlag.all, - this.allowPanning = true, this.onTap, this.onLongPress, this.onPointerDown, diff --git a/lib/src/gestures/gestures.dart b/lib/src/gestures/gestures.dart index 8c4056e39..0a5df7348 100644 --- a/lib/src/gestures/gestures.dart +++ b/lib/src/gestures/gestures.dart @@ -501,10 +501,6 @@ abstract class MapGestureMixin extends State void handleScaleEnd(ScaleEndDetails details) { _resetDoubleTapHold(); - if (!options.allowPanning) { - return; - } - final eventSource = _dragMode ? MapEventSource.dragEnd : MapEventSource.multiFingerEnd; @@ -625,9 +621,6 @@ abstract class MapGestureMixin extends State void handleDoubleTap(TapPosition tapPosition) { _resetDoubleTapHold(); - if (!options.allowPanning) { - return; - } closeFlingAnimationController(MapEventSource.doubleTap); closeDoubleTapController(MapEventSource.doubleTap); diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index aa5cc1da4..6462faeb5 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/src/gestures/gestures.dart'; @@ -14,6 +15,8 @@ import 'package:flutter_map/src/core/bounds.dart'; class FlutterMapState extends MapGestureMixin with AutomaticKeepAliveClientMixin { final _positionedTapController = PositionedTapController(); + final GestureArenaTeam _team = GestureArenaTeam(); + final MapController _localController = MapControllerImpl(); @override @@ -70,42 +73,104 @@ class FlutterMapState extends MapGestureMixin fitBounds(options.bounds!, options.boundsOptions); } - final scaleGestureTeam = GestureArenaTeam(); - - RawGestureDetector scaleGestureDetector({required Widget child}) => - RawGestureDetector( - gestures: { - ScaleGestureRecognizer: - GestureRecognizerFactoryWithHandlers( - () => ScaleGestureRecognizer(), - (ScaleGestureRecognizer instance) { - scaleGestureTeam.captain = instance; - instance.team ??= scaleGestureTeam; - instance - ..onStart = handleScaleStart - ..onUpdate = handleScaleUpdate - ..onEnd = handleScaleEnd; - }), - VerticalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers< - VerticalDragGestureRecognizer>( - () => VerticalDragGestureRecognizer(), - (VerticalDragGestureRecognizer instance) { - instance.team ??= scaleGestureTeam; - // these empty lambdas are necessary to activate this gesture recognizer - instance.onUpdate = (_) {}; - }), - HorizontalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(), - (HorizontalDragGestureRecognizer instance) { - instance.team ??= scaleGestureTeam; - instance.onUpdate = (_) {}; - }) - }, - child: child, - ); + final DeviceGestureSettings? gestureSettings = + MediaQuery.maybeOf(context)?.gestureSettings; + final Map gestures = + {}; + + gestures[TapGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(debugOwner: this), + (TapGestureRecognizer instance) { + instance + ..onTapDown = _positionedTapController.onTapDown + ..onTapUp = handleOnTapUp + ..onTap = _positionedTapController.onTap; + // ..onTapCancel = onTapCancel + // ..onSecondaryTap = onSecondaryTap + // ..onSecondaryTapDown = onSecondaryTapDown + // ..onSecondaryTapUp = onSecondaryTapUp + // ..onSecondaryTapCancel = onSecondaryTapCancel + // ..onTertiaryTapDown = onTertiaryTapDown + // ..onTertiaryTapUp = onTertiaryTapUp + // ..onTertiaryTapCancel = onTertiaryTapCancel + // ..gestureSettings = gestureSettings; + // instance.team = _team; + }, + ); + + gestures[LongPressGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => LongPressGestureRecognizer(debugOwner: this), + (LongPressGestureRecognizer instance) { + instance + // ..onLongPressDown = onLongPressDown + // ..onLongPressCancel = onLongPressCancel + ..onLongPress = _positionedTapController.onLongPress; + // ..onLongPressStart = onLongPressStart + // ..onLongPressMoveUpdate = onLongPressMoveUpdate + // ..onLongPressUp = onLongPressUp + // ..onLongPressEnd = onLongPressEnd + // ..onSecondaryLongPressDown = onSecondaryLongPressDown + // ..onSecondaryLongPressCancel = onSecondaryLongPressCancel + // ..onSecondaryLongPress = onSecondaryLongPress + // ..onSecondaryLongPressStart = onSecondaryLongPressStart + // ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate + // ..onSecondaryLongPressUp = onSecondaryLongPressUp + // ..onSecondaryLongPressEnd = onSecondaryLongPressEnd + // ..onTertiaryLongPressDown = onTertiaryLongPressDown + // ..onTertiaryLongPressCancel = onTertiaryLongPressCancel + // ..onTertiaryLongPress = onTertiaryLongPress + // ..onTertiaryLongPressStart = onTertiaryLongPressStart + // ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate + // ..onTertiaryLongPressUp = onTertiaryLongPressUp + // ..onTertiaryLongPressEnd = onTertiaryLongPressEnd + // ..gestureSettings = gestureSettings; + // instance.team = _team; + }, + ); + + if (options.absorbPanEventsOnScrollables && + InteractiveFlag.hasFlag(options.interactiveFlags, InteractiveFlag.drag)) { + gestures[VerticalDragGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => VerticalDragGestureRecognizer(debugOwner: this), + (VerticalDragGestureRecognizer instance) { + instance.onUpdate = (details) { + //Absorbing vertical drags + }; + // ..dragStartBehavior = dragStartBehavior + instance.gestureSettings = gestureSettings; + instance.team ??= _team; + }, + ); + gestures[HorizontalDragGestureRecognizer] = + GestureRecognizerFactoryWithHandlers< + HorizontalDragGestureRecognizer>( + () => HorizontalDragGestureRecognizer(debugOwner: this), + (HorizontalDragGestureRecognizer instance) { + instance.onUpdate = (details) { + //Absorbing horizontal drags + }; + // ..dragStartBehavior = dragStartBehavior + instance.gestureSettings = gestureSettings; + instance.team ??= _team; + }, + ); + } + + gestures[ScaleGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => ScaleGestureRecognizer(debugOwner: this), + (ScaleGestureRecognizer instance) { + instance + ..onStart = handleScaleStart + ..onUpdate = handleScaleUpdate + ..onEnd = handleScaleEnd; + instance.team ??= _team; + _team.captain = instance; + }, + ); return MapStateInheritedWidget( mapState: this, @@ -120,23 +185,8 @@ class FlutterMapState extends MapGestureMixin onTap: handleTap, onLongPress: handleLongPress, onDoubleTap: handleDoubleTap, - child: options.allowPanningOnScrollingParent - ? GestureDetector( - onTap: _positionedTapController.onTap, - onLongPress: _positionedTapController.onLongPress, - onTapDown: _positionedTapController.onTapDown, - onTapUp: handleOnTapUp, - child: scaleGestureDetector(child: _buildMap(size)), - ) - : GestureDetector( - onScaleStart: handleScaleStart, - onScaleUpdate: handleScaleUpdate, - onScaleEnd: handleScaleEnd, - onTap: _positionedTapController.onTap, - onLongPress: _positionedTapController.onLongPress, - onTapDown: _positionedTapController.onTapDown, - onTapUp: handleOnTapUp, - child: _buildMap(size)), + child: + RawGestureDetector(gestures: gestures, child: _buildMap(size)), ), ), ); @@ -241,8 +291,8 @@ class FlutterMapState extends MapGestureMixin _pixelOrigin = getNewPixelOrigin(_center); } - void _handleMoveEmit(LatLng targetCenter, double targetZoom, LatLng oldCenter, double oldZoom, bool hasGesture, - MapEventSource source, String? id) { + void _handleMoveEmit(LatLng targetCenter, double targetZoom, LatLng oldCenter, + double oldZoom, bool hasGesture, MapEventSource source, String? id) { if (source == MapEventSource.flingAnimationController) { emitMapEvent( MapEventFlingAnimation( @@ -315,11 +365,10 @@ class FlutterMapState extends MapGestureMixin } void emitMapEvent(MapEvent event) { - if (event.source == MapEventSource.mapController && - event is MapEventMove) { + if (event.source == MapEventSource.mapController && event is MapEventMove) { handleAnimationInterruptions(event); } - + setState(() { widget.options.onMapEvent?.call(event); }); @@ -405,7 +454,8 @@ class FlutterMapState extends MapGestureMixin _pixelBounds = getPixelBounds(_zoom); _pixelOrigin = getNewPixelOrigin(newCenter); - _handleMoveEmit(newCenter, newZoom, oldCenter, oldZoom, hasGesture, source, id); + _handleMoveEmit( + newCenter, newZoom, oldCenter, oldZoom, hasGesture, source, id); options.onPositionChanged?.call( MapPosition( From 321d0da70eed40f21f4313a0d9ba8ffca5968a60 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 10 Aug 2022 19:19:43 -0500 Subject: [PATCH 26/32] move calculations outside of layoutbuilder --- lib/src/map/flutter_map_state.dart | 201 ++++++++++++++--------------- 1 file changed, 100 insertions(+), 101 deletions(-) diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 6462faeb5..66eb2e5bb 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -61,116 +61,115 @@ class FlutterMapState extends MapGestureMixin @override Widget build(BuildContext context) { super.build(context); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - setSize(constraints.maxWidth, constraints.maxHeight); - _rotationRad = degToRadian(rotation); - _pixelBounds = getPixelBounds(zoom); - _bounds = _calculateBounds(); + final DeviceGestureSettings? gestureSettings = + MediaQuery.maybeOf(context)?.gestureSettings; + final Map gestures = + {}; + + gestures[TapGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(debugOwner: this), + (TapGestureRecognizer instance) { + instance + ..onTapDown = _positionedTapController.onTapDown + ..onTapUp = handleOnTapUp + ..onTap = _positionedTapController.onTap; + // ..onTapCancel = onTapCancel + // ..onSecondaryTap = onSecondaryTap + // ..onSecondaryTapDown = onSecondaryTapDown + // ..onSecondaryTapUp = onSecondaryTapUp + // ..onSecondaryTapCancel = onSecondaryTapCancel + // ..onTertiaryTapDown = onTertiaryTapDown + // ..onTertiaryTapUp = onTertiaryTapUp + // ..onTertiaryTapCancel = onTertiaryTapCancel + // ..gestureSettings = gestureSettings; + // instance.team = _team; + }, + ); - if (options.bounds != null) { - fitBounds(options.bounds!, options.boundsOptions); - } + gestures[LongPressGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => LongPressGestureRecognizer(debugOwner: this), + (LongPressGestureRecognizer instance) { + instance.onLongPress = _positionedTapController.onLongPress; + // ..onLongPressDown = onLongPressDown + // ..onLongPressCancel = onLongPressCancel + // ..onLongPressStart = onLongPressStart + // ..onLongPressMoveUpdate = onLongPressMoveUpdate + // ..onLongPressUp = onLongPressUp + // ..onLongPressEnd = onLongPressEnd + // ..onSecondaryLongPressDown = onSecondaryLongPressDown + // ..onSecondaryLongPressCancel = onSecondaryLongPressCancel + // ..onSecondaryLongPress = onSecondaryLongPress + // ..onSecondaryLongPressStart = onSecondaryLongPressStart + // ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate + // ..onSecondaryLongPressUp = onSecondaryLongPressUp + // ..onSecondaryLongPressEnd = onSecondaryLongPressEnd + // ..onTertiaryLongPressDown = onTertiaryLongPressDown + // ..onTertiaryLongPressCancel = onTertiaryLongPressCancel + // ..onTertiaryLongPress = onTertiaryLongPress + // ..onTertiaryLongPressStart = onTertiaryLongPressStart + // ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate + // ..onTertiaryLongPressUp = onTertiaryLongPressUp + // ..onTertiaryLongPressEnd = onTertiaryLongPressEnd + // ..gestureSettings = gestureSettings; + // instance.team = _team; + }, + ); - final DeviceGestureSettings? gestureSettings = - MediaQuery.maybeOf(context)?.gestureSettings; - final Map gestures = - {}; - - gestures[TapGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(debugOwner: this), - (TapGestureRecognizer instance) { - instance - ..onTapDown = _positionedTapController.onTapDown - ..onTapUp = handleOnTapUp - ..onTap = _positionedTapController.onTap; - // ..onTapCancel = onTapCancel - // ..onSecondaryTap = onSecondaryTap - // ..onSecondaryTapDown = onSecondaryTapDown - // ..onSecondaryTapUp = onSecondaryTapUp - // ..onSecondaryTapCancel = onSecondaryTapCancel - // ..onTertiaryTapDown = onTertiaryTapDown - // ..onTertiaryTapUp = onTertiaryTapUp - // ..onTertiaryTapCancel = onTertiaryTapCancel - // ..gestureSettings = gestureSettings; - // instance.team = _team; + if (options.absorbPanEventsOnScrollables && + InteractiveFlag.hasFlag( + options.interactiveFlags, InteractiveFlag.drag)) { + gestures[VerticalDragGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => VerticalDragGestureRecognizer(debugOwner: this), + (VerticalDragGestureRecognizer instance) { + instance.onUpdate = (details) { + //Absorbing vertical drags + }; + // ..dragStartBehavior = dragStartBehavior + instance.gestureSettings = gestureSettings; + instance.team ??= _team; }, ); - - gestures[LongPressGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer(debugOwner: this), - (LongPressGestureRecognizer instance) { - instance - // ..onLongPressDown = onLongPressDown - // ..onLongPressCancel = onLongPressCancel - ..onLongPress = _positionedTapController.onLongPress; - // ..onLongPressStart = onLongPressStart - // ..onLongPressMoveUpdate = onLongPressMoveUpdate - // ..onLongPressUp = onLongPressUp - // ..onLongPressEnd = onLongPressEnd - // ..onSecondaryLongPressDown = onSecondaryLongPressDown - // ..onSecondaryLongPressCancel = onSecondaryLongPressCancel - // ..onSecondaryLongPress = onSecondaryLongPress - // ..onSecondaryLongPressStart = onSecondaryLongPressStart - // ..onSecondaryLongPressMoveUpdate = onSecondaryLongPressMoveUpdate - // ..onSecondaryLongPressUp = onSecondaryLongPressUp - // ..onSecondaryLongPressEnd = onSecondaryLongPressEnd - // ..onTertiaryLongPressDown = onTertiaryLongPressDown - // ..onTertiaryLongPressCancel = onTertiaryLongPressCancel - // ..onTertiaryLongPress = onTertiaryLongPress - // ..onTertiaryLongPressStart = onTertiaryLongPressStart - // ..onTertiaryLongPressMoveUpdate = onTertiaryLongPressMoveUpdate - // ..onTertiaryLongPressUp = onTertiaryLongPressUp - // ..onTertiaryLongPressEnd = onTertiaryLongPressEnd - // ..gestureSettings = gestureSettings; - // instance.team = _team; + gestures[HorizontalDragGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => HorizontalDragGestureRecognizer(debugOwner: this), + (HorizontalDragGestureRecognizer instance) { + instance.onUpdate = (details) { + //Absorbing horizontal drags + }; + // ..dragStartBehavior = dragStartBehavior + instance.gestureSettings = gestureSettings; + instance.team ??= _team; }, ); + } - if (options.absorbPanEventsOnScrollables && - InteractiveFlag.hasFlag(options.interactiveFlags, InteractiveFlag.drag)) { - gestures[VerticalDragGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => VerticalDragGestureRecognizer(debugOwner: this), - (VerticalDragGestureRecognizer instance) { - instance.onUpdate = (details) { - //Absorbing vertical drags - }; - // ..dragStartBehavior = dragStartBehavior - instance.gestureSettings = gestureSettings; - instance.team ??= _team; - }, - ); - gestures[HorizontalDragGestureRecognizer] = - GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(debugOwner: this), - (HorizontalDragGestureRecognizer instance) { - instance.onUpdate = (details) { - //Absorbing horizontal drags - }; - // ..dragStartBehavior = dragStartBehavior - instance.gestureSettings = gestureSettings; - instance.team ??= _team; - }, - ); - } + gestures[ScaleGestureRecognizer] = + GestureRecognizerFactoryWithHandlers( + () => ScaleGestureRecognizer(debugOwner: this), + (ScaleGestureRecognizer instance) { + instance + ..onStart = handleScaleStart + ..onUpdate = handleScaleUpdate + ..onEnd = handleScaleEnd; + instance.team ??= _team; + _team.captain = instance; + }, + ); - gestures[ScaleGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => ScaleGestureRecognizer(debugOwner: this), - (ScaleGestureRecognizer instance) { - instance - ..onStart = handleScaleStart - ..onUpdate = handleScaleUpdate - ..onEnd = handleScaleEnd; - instance.team ??= _team; - _team.captain = instance; - }, - ); + _rotationRad = degToRadian(rotation); + _pixelBounds = getPixelBounds(zoom); + _bounds = _calculateBounds(); + if (options.bounds != null) { + fitBounds(options.bounds!, options.boundsOptions); + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + setSize(constraints.maxWidth, constraints.maxHeight); return MapStateInheritedWidget( mapState: this, From b680cce2bb9577c908b0263bfab916321c6609d0 Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 10 Aug 2022 19:25:27 -0500 Subject: [PATCH 27/32] fix regression with layout --- lib/src/map/flutter_map_state.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index 66eb2e5bb..d2a95d99c 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -160,9 +160,6 @@ class FlutterMapState extends MapGestureMixin }, ); - _rotationRad = degToRadian(rotation); - _pixelBounds = getPixelBounds(zoom); - _bounds = _calculateBounds(); if (options.bounds != null) { fitBounds(options.bounds!, options.boundsOptions); } @@ -171,6 +168,10 @@ class FlutterMapState extends MapGestureMixin builder: (BuildContext context, BoxConstraints constraints) { setSize(constraints.maxWidth, constraints.maxHeight); + _rotationRad = degToRadian(rotation); + _pixelBounds = getPixelBounds(zoom); + _bounds = _calculateBounds(); + return MapStateInheritedWidget( mapState: this, child: Listener( From 8133cefa076e676e80ed43c4e3156058e63c25a0 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 11 Aug 2022 11:30:51 -0500 Subject: [PATCH 28/32] fix hero error in example app --- example/lib/pages/point_to_latlng.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/lib/pages/point_to_latlng.dart b/example/lib/pages/point_to_latlng.dart index 4d76adc6e..10142578a 100644 --- a/example/lib/pages/point_to_latlng.dart +++ b/example/lib/pages/point_to_latlng.dart @@ -40,11 +40,13 @@ class PointToLatlngPage extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( + heroTag: 'rotate', child: const Icon(Icons.rotate_right), onPressed: () => mapController.rotate(60), ), const SizedBox(height: 15), FloatingActionButton( + heroTag: 'cancel', child: const Icon(Icons.cancel), onPressed: () => mapController.rotate(0), ), From 7604b4138ec0676c276502acfd1fb2c0a4b0fb85 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 11 Aug 2022 11:57:52 -0500 Subject: [PATCH 29/32] add onMapReady to mapOptions --- lib/flutter_map.dart | 9 +++++++++ lib/src/map/flutter_map_state.dart | 2 ++ 2 files changed, 11 insertions(+) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 056f86a3f..74aeae1e0 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -262,6 +262,14 @@ class MapOptions { final LatLng? swPanBoundary; final LatLng? nePanBoundary; + /// OnMapReady is called after the map runs it's initState. + /// At that point the map has assigned its state to the controller + /// Only use this if your map isn't built immediately (like inside FutureBuilder) + /// and you need to access the controller as soon as the map is built. + /// Otherwise you can use WidgetsBinding.instance.addPostFrameCallback + /// In initState to controll the map before the next frame + final void Function()? onMapReady; + /// Restrict outer edges of map to LatLng Bounds, to prevent gray areas when /// panning or zooming. LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)) /// would represent the full extent of the map, so no gray area outside of it. @@ -309,6 +317,7 @@ class MapOptions { this.onPointerHover, this.onPositionChanged, this.onMapEvent, + this.onMapReady, this.slideOnBoundaries = false, this.adaptiveBoundaries = false, this.screenSize, diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index d2a95d99c..e9dbf2e11 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -49,6 +49,8 @@ class FlutterMapState extends MapGestureMixin if (options.bounds != null) { fitBounds(options.bounds!, options.boundsOptions); } + + options.onMapReady?.call(); } //This may not be required. From c3e5354b3df4a1ea1d674fe1abb5265fd4dec4d2 Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 11 Aug 2022 12:30:33 -0500 Subject: [PATCH 30/32] convert onMapReady to postFrameCallback remove map controller from options --- lib/flutter_map.dart | 104 +---------------------------- lib/src/map/flutter_map_state.dart | 102 ++++++++++++++++++++++++++-- 2 files changed, 100 insertions(+), 106 deletions(-) diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart index 74aeae1e0..0a798a4e3 100644 --- a/lib/flutter_map.dart +++ b/lib/flutter_map.dart @@ -1,7 +1,6 @@ library flutter_map; import 'dart:async'; -import 'dart:math'; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; @@ -255,7 +254,6 @@ class MapOptions { final bool slideOnBoundaries; final Size? screenSize; final bool adaptiveBoundaries; - final MapController? controller; final LatLng center; final LatLngBounds? bounds; final FitBoundsOptions boundsOptions; @@ -283,9 +281,6 @@ class MapOptions { /// widget from rebuilding. final bool keepAlive; - _SafeArea? _safeAreaCache; - double? _safeAreaZoom; - MapOptions({ this.absorbPanEventsOnScrollables = true, this.crs = const Epsg3857(), @@ -321,7 +316,6 @@ class MapOptions { this.slideOnBoundaries = false, this.adaptiveBoundaries = false, this.screenSize, - this.controller, this.swPanBoundary, this.nePanBoundary, this.maxBounds, @@ -330,80 +324,9 @@ class MapOptions { assert(rotationThreshold >= 0.0), assert(pinchZoomThreshold >= 0.0), assert(pinchMoveThreshold >= 0.0) { - _safeAreaZoom = zoom; - assert(slideOnBoundaries || - !isOutOfBounds(center)); //You cannot start outside pan boundary - assert(!adaptiveBoundaries || screenSize != null, - 'screenSize must be set in order to enable adaptive boundaries.'); - assert(!adaptiveBoundaries || controller != null, - 'controller must be set in order to enable adaptive boundaries.'); - } - - //if there is a pan boundary, do not cross - bool isOutOfBounds(LatLng? center) { - if (adaptiveBoundaries) { - return !_safeArea!.contains(center); - } - if (swPanBoundary != null && nePanBoundary != null) { - if (center == null) { - return true; - } else if (center.latitude < swPanBoundary!.latitude || - center.latitude > nePanBoundary!.latitude) { - return true; - } else if (center.longitude < swPanBoundary!.longitude || - center.longitude > nePanBoundary!.longitude) { - return true; - } - } - return false; - } - - LatLng containPoint(LatLng point, LatLng fallback) { - if (adaptiveBoundaries) { - return _safeArea!.containPoint(point, fallback); - } else { - return LatLng( - point.latitude.clamp(swPanBoundary!.latitude, nePanBoundary!.latitude), - point.longitude - .clamp(swPanBoundary!.longitude, nePanBoundary!.longitude), - ); - } - } - - _SafeArea? get _safeArea { - final controllerZoom = _getControllerZoom(); - if (controllerZoom != _safeAreaZoom || _safeAreaCache == null) { - _safeAreaZoom = controllerZoom; - final halfScreenHeight = _calculateScreenHeightInDegrees() / 2; - final halfScreenWidth = _calculateScreenWidthInDegrees() / 2; - final southWestLatitude = swPanBoundary!.latitude + halfScreenHeight; - final southWestLongitude = swPanBoundary!.longitude + halfScreenWidth; - final northEastLatitude = nePanBoundary!.latitude - halfScreenHeight; - final northEastLongitude = nePanBoundary!.longitude - halfScreenWidth; - _safeAreaCache = _SafeArea( - LatLng( - southWestLatitude, - southWestLongitude, - ), - LatLng( - northEastLatitude, - northEastLongitude, - ), - ); - } - return _safeAreaCache; + assert(!adaptiveBoundaries || screenSize != null, + 'screenSize must be set in order to enable adaptive boundaries.'); } - - double _calculateScreenWidthInDegrees() { - final zoom = _getControllerZoom(); - final degreesPerPixel = 360 / pow(2, zoom + 8); - return screenSize!.width * degreesPerPixel; - } - - double _calculateScreenHeightInDegrees() => - screenSize!.height * 170.102258 / pow(2, _getControllerZoom() + 8); - - double _getControllerZoom() => controller!.zoom; } class FitBoundsOptions { @@ -440,29 +363,6 @@ class MapPosition { other.zoom == zoom; } -class _SafeArea { - final LatLngBounds bounds; - final bool isLatitudeBlocked; - final bool isLongitudeBlocked; - - _SafeArea(LatLng southWest, LatLng northEast) - : bounds = LatLngBounds(southWest, northEast), - isLatitudeBlocked = southWest.latitude > northEast.latitude, - isLongitudeBlocked = southWest.longitude > northEast.longitude; - - bool contains(LatLng? point) => - isLatitudeBlocked || isLongitudeBlocked ? false : bounds.contains(point); - - LatLng containPoint(LatLng point, LatLng fallback) => LatLng( - isLatitudeBlocked - ? fallback.latitude - : point.latitude.clamp(bounds.south, bounds.north), - isLongitudeBlocked - ? fallback.longitude - : point.longitude.clamp(bounds.west, bounds.east), - ); -} - class MoveAndRotateResult { final bool moveSuccess; final bool rotateSuccess; diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index e9dbf2e11..e4f5a5781 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -49,8 +49,9 @@ class FlutterMapState extends MapGestureMixin if (options.bounds != null) { fitBounds(options.bounds!, options.boundsOptions); } - - options.onMapReady?.call(); + WidgetsBinding.instance.addPostFrameCallback((_) { + options.onMapReady?.call(); + }); } //This may not be required. @@ -425,11 +426,11 @@ class FlutterMapState extends MapGestureMixin return false; } - if (options.isOutOfBounds(newCenter)) { + if (isOutOfBounds(newCenter)) { if (!options.slideOnBoundaries) { return false; } - newCenter = options.containPoint(newCenter, _center); + newCenter = containPoint(newCenter, _center); } // Try and fit the corners of the map inside the visible area. @@ -715,6 +716,74 @@ class FlutterMapState extends MapGestureMixin return CustomPoint(tp.dx, tp.dy); } + + _SafeArea? _safeAreaCache; + double? _safeAreaZoom; + + //if there is a pan boundary, do not cross + bool isOutOfBounds(LatLng? center) { + if (options.adaptiveBoundaries) { + return !_safeArea!.contains(center); + } + if (options.swPanBoundary != null && options.nePanBoundary != null) { + if (center == null) { + return true; + } else if (center.latitude < options.swPanBoundary!.latitude || + center.latitude > options.nePanBoundary!.latitude) { + return true; + } else if (center.longitude < options.swPanBoundary!.longitude || + center.longitude > options.nePanBoundary!.longitude) { + return true; + } + } + return false; + } + + LatLng containPoint(LatLng point, LatLng fallback) { + if (options.adaptiveBoundaries) { + return _safeArea!.containPoint(point, fallback); + } else { + return LatLng( + point.latitude.clamp(options.swPanBoundary!.latitude, options.nePanBoundary!.latitude), + point.longitude + .clamp(options.swPanBoundary!.longitude, options.nePanBoundary!.longitude), + ); + } + } + + _SafeArea? get _safeArea { + final controllerZoom = _zoom; + if (controllerZoom != _safeAreaZoom || _safeAreaCache == null) { + _safeAreaZoom = controllerZoom; + final halfScreenHeight = _calculateScreenHeightInDegrees() / 2; + final halfScreenWidth = _calculateScreenWidthInDegrees() / 2; + final southWestLatitude = options.swPanBoundary!.latitude + halfScreenHeight; + final southWestLongitude = options.swPanBoundary!.longitude + halfScreenWidth; + final northEastLatitude = options.nePanBoundary!.latitude - halfScreenHeight; + final northEastLongitude = options.nePanBoundary!.longitude - halfScreenWidth; + _safeAreaCache = _SafeArea( + LatLng( + southWestLatitude, + southWestLongitude, + ), + LatLng( + northEastLatitude, + northEastLongitude, + ), + ); + } + return _safeAreaCache; + } + + double _calculateScreenWidthInDegrees() { + final degreesPerPixel = 360 / math.pow(2, zoom + 8); + return options.screenSize!.width * degreesPerPixel; + } + + double _calculateScreenHeightInDegrees() => + options.screenSize!.height * 170.102258 / math.pow(2, zoom + 8); + + static FlutterMapState? maybeOf(BuildContext context, {bool nullOk = false}) { final widget = context.dependOnInheritedWidgetOfExactType(); @@ -725,3 +794,28 @@ class FlutterMapState extends MapGestureMixin 'MapState.of() called with a context that does not contain a FlutterMap.'); } } + + + +class _SafeArea { + final LatLngBounds bounds; + final bool isLatitudeBlocked; + final bool isLongitudeBlocked; + + _SafeArea(LatLng southWest, LatLng northEast) + : bounds = LatLngBounds(southWest, northEast), + isLatitudeBlocked = southWest.latitude > northEast.latitude, + isLongitudeBlocked = southWest.longitude > northEast.longitude; + + bool contains(LatLng? point) => + isLatitudeBlocked || isLongitudeBlocked ? false : bounds.contains(point); + + LatLng containPoint(LatLng point, LatLng fallback) => LatLng( + isLatitudeBlocked + ? fallback.latitude + : point.latitude.clamp(bounds.south, bounds.north), + isLongitudeBlocked + ? fallback.longitude + : point.longitude.clamp(bounds.west, bounds.east), + ); +} \ No newline at end of file From f5295634e94f76f53b0ed1d9f76b36d8a6f265db Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 16 Aug 2022 11:39:55 -0500 Subject: [PATCH 31/32] fix state issue. remote unused _rotationRad --- lib/src/map/flutter_map_state.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/src/map/flutter_map_state.dart b/lib/src/map/flutter_map_state.dart index e4f5a5781..361c377a8 100644 --- a/lib/src/map/flutter_map_state.dart +++ b/lib/src/map/flutter_map_state.dart @@ -39,7 +39,6 @@ class FlutterMapState extends MapGestureMixin _rotation = options.rotation; _center = options.center; _zoom = options.zoom; - _rotationRad = degToRadian(options.rotation); _pixelBounds = getPixelBounds(zoom); _bounds = _calculateBounds(); @@ -163,15 +162,19 @@ class FlutterMapState extends MapGestureMixin }, ); + //Update on state change + _pixelBounds = getPixelBounds(zoom); + _bounds = _calculateBounds(); + _pixelOrigin = getNewPixelOrigin(_center); + if (options.bounds != null) { fitBounds(options.bounds!, options.boundsOptions); } return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { + //Update on layout change setSize(constraints.maxWidth, constraints.maxHeight); - - _rotationRad = degToRadian(rotation); _pixelBounds = getPixelBounds(zoom); _bounds = _calculateBounds(); @@ -234,7 +237,6 @@ class FlutterMapState extends MapGestureMixin late double _zoom; late double _rotation; - late double _rotationRad; double get zoom => _zoom; @@ -279,8 +281,8 @@ class FlutterMapState extends MapGestureMixin final originalHeight = _nonrotatedSize!.y; if (_rotation != 0.0) { - final cosAngle = math.cos(_rotationRad).abs(); - final sinAngle = math.sin(_rotationRad).abs(); + final cosAngle = math.cos(rotationRad).abs(); + final sinAngle = math.sin(rotationRad).abs(); final num width = (originalWidth * cosAngle) + (originalHeight * sinAngle); final num height = From 0f9690d1d05b2167bab5891dd3030b5e91cce8a3 Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 16 Aug 2022 11:59:27 -0500 Subject: [PATCH 32/32] remove deprecated provider --- .../tile_provider/tile_provider_io.dart | 32 ------------------- .../tile_provider/tile_provider_web.dart | 26 --------------- 2 files changed, 58 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 0014a2187..324099ca4 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 @@ -64,38 +64,6 @@ class NetworkNoRetryTileProvider extends TileProvider { ); } -/// 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, TileLayer options) => - NetworkNoRetryTileProvider( - headers: headers, - httpClient: httpClient, - ).getImage(coords, options); -} - -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. 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 8540b5638..cba3bb93d 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 @@ -48,32 +48,6 @@ class NetworkNoRetryTileProvider extends TileProvider { ); } -/// 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, TileLayer options) => - NetworkNoRetryTileProvider( - headers: headers, - ).getImage(coords, options); -} - -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.