From b2c1b34030ce280716bdccb3c32192a00f830b2a Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 01:09:56 +0100 Subject: [PATCH 01/10] Add ManyMarkers example --- example/lib/main.dart | 2 + example/lib/pages/many_markers.dart | 93 +++++++++++++++++++++++++++++ example/lib/widgets/drawer.dart | 8 +++ 3 files changed, 103 insertions(+) create mode 100644 example/lib/pages/many_markers.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 7008138bf..78a045e04 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -6,6 +6,7 @@ import './pages/custom_crs/custom_crs.dart'; import './pages/esri.dart'; import './pages/home.dart'; import './pages/live_location.dart'; +import './pages/many_markers.dart'; import './pages/map_controller.dart'; import './pages/marker_anchor.dart'; import './pages/moving_markers.dart'; @@ -58,6 +59,7 @@ class MyApp extends StatelessWidget { LiveLocationPage.route: (context) => LiveLocationPage(), TileLoadingErrorHandle.route: (context) => TileLoadingErrorHandle(), InteractiveTestPage.route: (context) => InteractiveTestPage(), + ManyMarkersPage.route: (context) => ManyMarkersPage(), }, ); } diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart new file mode 100644 index 000000000..d008e7bc7 --- /dev/null +++ b/example/lib/pages/many_markers.dart @@ -0,0 +1,93 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong/latlong.dart'; + +import '../widgets/drawer.dart'; + +/// On this page, [MAX_MARKERS_COUNT] markers are randomly generated +/// across europe, and then you can limit them with a slider +/// +/// This way, you can test how map performs under a lot of markers +class ManyMarkersPage extends StatefulWidget { + static const String route = '/many_markers'; + + @override + _ManyMarkersPageState createState() => _ManyMarkersPageState(); +} + +class _ManyMarkersPageState extends State { + static const MAX_MARKERS_COUNT = 5000; + + double doubleInRange(Random source, num start, num end) => + source.nextDouble() * (end - start) + start; + List allMarkers = []; + + int _sliderVal = MAX_MARKERS_COUNT ~/ 10; + + @override + void initState() { + super.initState(); + Future.microtask(() { + var r = Random(); + for (var x = 0; x < MAX_MARKERS_COUNT; x++) { + allMarkers.add( + Marker( + point: LatLng( + doubleInRange(r, 37, 55), + doubleInRange(r, -9, 30), + ), + builder: (context) => const Icon( + Icons.circle, + color: Colors.red, + size: 12.0, + ), + ), + ); + } + setState(() {}); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('A lot of markers')), + drawer: buildDrawer(context, ManyMarkersPage.route), + body: Column( + children: [ + Slider( + min: 0, + max: MAX_MARKERS_COUNT.toDouble(), + divisions: MAX_MARKERS_COUNT ~/ 500, + label: 'Markers', + value: _sliderVal.toDouble(), + onChanged: (newVal) { + _sliderVal = newVal.toInt(); + setState(() {}); + }, + ), + Text('$_sliderVal markers'), + Flexible( + child: FlutterMap( + options: MapOptions( + center: LatLng(50, 20), + zoom: 5.0, + interactiveFlags: InteractiveFlag.all - InteractiveFlag.rotate, + ), + layers: [ + TileLayerOptions( + urlTemplate: + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + subdomains: ['a', 'b', 'c'], + ), + MarkerLayerOptions(markers: allMarkers.sublist(0, _sliderVal)), + ], + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/widgets/drawer.dart b/example/lib/widgets/drawer.dart index 2953f641e..fe6259899 100644 --- a/example/lib/widgets/drawer.dart +++ b/example/lib/widgets/drawer.dart @@ -7,6 +7,7 @@ import '../pages/esri.dart'; import '../pages/home.dart'; import '../pages/interactive_test_page.dart'; import '../pages/live_location.dart'; +import '../pages/many_markers.dart'; import '../pages/map_controller.dart'; import '../pages/marker_anchor.dart'; import '../pages/moving_markers.dart'; @@ -185,6 +186,13 @@ Drawer buildDrawer(BuildContext context, String currentRoute) { InteractiveTestPage.route, currentRoute, ), + ListTile( + title: const Text('A lot of markers'), + selected: currentRoute == ManyMarkersPage.route, + onTap: () { + Navigator.pushReplacementNamed(context, ManyMarkersPage.route); + }, + ) ], ), ); From 229f1ab3eaa9ea95a2c5008a5788977916e1eb53 Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 01:18:36 +0100 Subject: [PATCH 02/10] Just move the goddamn _boundsContainsMarker to the beginning... --- lib/src/layer/marker_layer.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index c23978fab..43f8bf0d9 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -128,6 +128,10 @@ class MarkerLayer extends StatelessWidget { builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; for (var markerOpt in markerOpts.markers) { + if (!_boundsContainsMarker(markerOpt)) { + continue; + } + var pos = map.project(markerOpt.point); pos = pos.multiplyBy(map.getZoomScale(map.zoom, map.zoom)) - map.getPixelOrigin(); @@ -137,10 +141,6 @@ class MarkerLayer extends StatelessWidget { var pixelPosY = (pos.y - (markerOpt.height - markerOpt.anchor.top)).toDouble(); - if (!_boundsContainsMarker(markerOpt)) { - continue; - } - markers.add( Positioned( width: markerOpt.width, From 51524675456511ea58572cb584a93337bb865e4e Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 01:44:59 +0100 Subject: [PATCH 03/10] Share the .project() --- lib/src/layer/marker_layer.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 43f8bf0d9..dc32be850 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -110,9 +110,7 @@ class MarkerLayer extends StatelessWidget { MarkerLayer(this.markerOpts, this.map, this.stream) : super(key: markerOpts.key); - bool _boundsContainsMarker(Marker marker) { - var pixelPoint = map.project(marker.point); - + bool _boundsContainsMarker(Marker marker, CustomPoint pixelPoint) { final width = marker.width - marker.anchor.left; final height = marker.height - marker.anchor.top; @@ -128,11 +126,11 @@ class MarkerLayer extends StatelessWidget { builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; for (var markerOpt in markerOpts.markers) { - if (!_boundsContainsMarker(markerOpt)) { + var pos = map.project(markerOpt.point); + if (!_boundsContainsMarker(markerOpt, pos)) { continue; } - var pos = map.project(markerOpt.point); pos = pos.multiplyBy(map.getZoomScale(map.zoom, map.zoom)) - map.getPixelOrigin(); From 08299dc099a8ab936a13b8c012a4137f7d0a6f9b Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 01:58:21 +0100 Subject: [PATCH 04/10] Share even more stuff and simplify code a little --- lib/src/layer/marker_layer.dart | 38 +++++++++++++-------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index dc32be850..20eecca99 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -110,42 +110,34 @@ class MarkerLayer extends StatelessWidget { MarkerLayer(this.markerOpts, this.map, this.stream) : super(key: markerOpts.key); - bool _boundsContainsMarker(Marker marker, CustomPoint pixelPoint) { - final width = marker.width - marker.anchor.left; - final height = marker.height - marker.anchor.top; - - var sw = CustomPoint(pixelPoint.x + width, pixelPoint.y - height); - var ne = CustomPoint(pixelPoint.x - width, pixelPoint.y + height); - return map.pixelBounds.containsPartialBounds(Bounds(sw, ne)); - } - @override Widget build(BuildContext context) { return StreamBuilder( stream: stream, // a Stream or null builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; - for (var markerOpt in markerOpts.markers) { - var pos = map.project(markerOpt.point); - if (!_boundsContainsMarker(markerOpt, pos)) { + for (var marker in markerOpts.markers) { + var pxPoint = map.project(marker.point); + + final width = marker.width - marker.anchor.left; + final height = marker.height - marker.anchor.top; + var sw = CustomPoint(pxPoint.x + width, pxPoint.y - height); + var ne = CustomPoint(pxPoint.x - width, pxPoint.y + height); + + if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { continue; } - pos = pos.multiplyBy(map.getZoomScale(map.zoom, map.zoom)) - + final pos = pxPoint.multiplyBy(map.getZoomScale(map.zoom, map.zoom)) - map.getPixelOrigin(); - var pixelPosX = - (pos.x - (markerOpt.width - markerOpt.anchor.left)).toDouble(); - var pixelPosY = - (pos.y - (markerOpt.height - markerOpt.anchor.top)).toDouble(); - markers.add( Positioned( - width: markerOpt.width, - height: markerOpt.height, - left: pixelPosX, - top: pixelPosY, - child: markerOpt.builder(context), + width: marker.width, + height: marker.height, + left: pos.x - width, + top: pos.y - height, + child: marker.builder(context), ), ); } From d6edce79883197503dab1894fc60c15a96a0dc59 Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 02:09:20 +0100 Subject: [PATCH 05/10] What is this?? --- lib/src/layer/marker_layer.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 20eecca99..1d36771cb 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -128,8 +128,7 @@ class MarkerLayer extends StatelessWidget { continue; } - final pos = pxPoint.multiplyBy(map.getZoomScale(map.zoom, map.zoom)) - - map.getPixelOrigin(); + final pos = pxPoint - map.getPixelOrigin(); markers.add( Positioned( From 3c200b8f8bb2180c5b803bb3ea7db231787b0628 Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Sun, 21 Feb 2021 02:46:26 +0100 Subject: [PATCH 06/10] Experimental: cache list of pixel locations --- lib/src/layer/marker_layer.dart | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 1d36771cb..83efb5fc5 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -107,8 +107,17 @@ class MarkerLayer extends StatelessWidget { final MapState map; final Stream stream; + // Note: I don't know if it's okay to store mutable stuff like this in stless + // But it works, and stuff is discarded when something serious changes + /// List containing cached pixel positions of markers + /// Should be discarded when zoom changes + List _pxCache; + double lastZoom = 0; + MarkerLayer(this.markerOpts, this.map, this.stream) - : super(key: markerOpts.key); + : super(key: markerOpts.key) { + _pxCache = List(markerOpts.markers.length); + } @override Widget build(BuildContext context) { @@ -116,8 +125,16 @@ class MarkerLayer extends StatelessWidget { stream: stream, // a Stream or null builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; + final sameZoom = map.zoom == lastZoom; + var i = -1; for (var marker in markerOpts.markers) { - var pxPoint = map.project(marker.point); + i++; + + // Decide whether to use cached point or calculate it + var pxPoint = sameZoom ? _pxCache[i] : map.project(marker.point); + if (!sameZoom) { + _pxCache[i] = pxPoint; + } final width = marker.width - marker.anchor.left; final height = marker.height - marker.anchor.top; @@ -140,6 +157,7 @@ class MarkerLayer extends StatelessWidget { ), ); } + lastZoom = map.zoom; return Container( child: Stack( children: markers, From 100af71e7528fab7cdc1280e05b4be6da30a617f Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Thu, 1 Apr 2021 01:48:50 +0200 Subject: [PATCH 07/10] Apply stuff from code review - change to stateful - make loop nicer --- lib/src/layer/marker_layer.dart | 56 +++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 83efb5fc5..14fb6b6f3 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -102,36 +102,58 @@ class MarkerLayerWidget extends StatelessWidget { } } -class MarkerLayer extends StatelessWidget { +class MarkerLayer extends StatefulWidget { final MarkerLayerOptions markerOpts; final MapState map; final Stream stream; - // Note: I don't know if it's okay to store mutable stuff like this in stless - // But it works, and stuff is discarded when something serious changes + MarkerLayer(this.markerOpts, this.map, this.stream) + : super(key: markerOpts.key); + + @override + _MarkerLayerState createState() => _MarkerLayerState(); +} + +class _MarkerLayerState extends State { + var lastZoom = -1.0; + /// List containing cached pixel positions of markers /// Should be discarded when zoom changes - List _pxCache; - double lastZoom = 0; + // 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 = []; - MarkerLayer(this.markerOpts, this.map, this.stream) - : super(key: markerOpts.key) { - _pxCache = List(markerOpts.markers.length); + // Calling this every time markerOpts change should guarantee proper length + List generatePxCache() => List.generate( + widget.markerOpts.markers.length, + (i) => widget.map.project(widget.markerOpts.markers[i].point)); + + @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) { return StreamBuilder( - stream: stream, // a Stream or null + stream: widget.stream, // a Stream or null builder: (BuildContext context, AsyncSnapshot snapshot) { var markers = []; - final sameZoom = map.zoom == lastZoom; - var i = -1; - for (var marker in markerOpts.markers) { - i++; + final sameZoom = widget.map.zoom == lastZoom; + for (var i = 0; i < widget.markerOpts.markers.length; i++) { + var marker = widget.markerOpts.markers[i]; // Decide whether to use cached point or calculate it - var pxPoint = sameZoom ? _pxCache[i] : map.project(marker.point); + var pxPoint = + sameZoom ? _pxCache[i] : widget.map.project(marker.point); if (!sameZoom) { _pxCache[i] = pxPoint; } @@ -141,11 +163,11 @@ class MarkerLayer extends StatelessWidget { var sw = CustomPoint(pxPoint.x + width, pxPoint.y - height); var ne = CustomPoint(pxPoint.x - width, pxPoint.y + height); - if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { + if (!widget.map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) { continue; } - final pos = pxPoint - map.getPixelOrigin(); + final pos = pxPoint - widget.map.getPixelOrigin(); markers.add( Positioned( @@ -157,7 +179,7 @@ class MarkerLayer extends StatelessWidget { ), ); } - lastZoom = map.zoom; + lastZoom = widget.map.zoom; return Container( child: Stack( children: markers, From f0d7d8df4f81c2a271b440e549e4ff13bfb8046a Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Thu, 1 Apr 2021 02:00:50 +0200 Subject: [PATCH 08/10] Okay linter, whatever you want... --- example/lib/pages/many_markers.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index d008e7bc7..685a27250 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -6,7 +6,9 @@ import 'package:latlong/latlong.dart'; import '../widgets/drawer.dart'; -/// On this page, [MAX_MARKERS_COUNT] markers are randomly generated +const maxMarkersCount = 5000; + +/// On this page, [maxMarkersCount] markers are randomly generated /// across europe, and then you can limit them with a slider /// /// This way, you can test how map performs under a lot of markers @@ -18,20 +20,18 @@ class ManyMarkersPage extends StatefulWidget { } class _ManyMarkersPageState extends State { - static const MAX_MARKERS_COUNT = 5000; - double doubleInRange(Random source, num start, num end) => source.nextDouble() * (end - start) + start; List allMarkers = []; - int _sliderVal = MAX_MARKERS_COUNT ~/ 10; + int _sliderVal = maxMarkersCount ~/ 10; @override void initState() { super.initState(); Future.microtask(() { var r = Random(); - for (var x = 0; x < MAX_MARKERS_COUNT; x++) { + for (var x = 0; x < maxMarkersCount; x++) { allMarkers.add( Marker( point: LatLng( @@ -59,8 +59,8 @@ class _ManyMarkersPageState extends State { children: [ Slider( min: 0, - max: MAX_MARKERS_COUNT.toDouble(), - divisions: MAX_MARKERS_COUNT ~/ 500, + max: maxMarkersCount.toDouble(), + divisions: maxMarkersCount ~/ 500, label: 'Markers', value: _sliderVal.toDouble(), onChanged: (newVal) { From e96e4b19c487e663328f2ab3fc11d84418bae302 Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Mon, 26 Apr 2021 02:14:13 +0200 Subject: [PATCH 09/10] OMG FIXING MERGE CONFLICTS IS *HARD* Or my IDE is just useless --- example/lib/pages/many_markers.dart | 2 +- lib/src/layer/marker_layer.dart | 30 +++++++++++++++-------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index 685a27250..cc4ce8476 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; -import 'package:latlong/latlong.dart'; +import 'package:latlong2/latlong.dart'; import '../widgets/drawer.dart'; diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 63eaa2a36..639422178 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -180,8 +180,9 @@ class _MarkerLayerState extends State { // Calling this every time markerOpts change should guarantee proper length List generatePxCache() => List.generate( - widget.markerOpts.markers.length, - (i) => widget.map.project(widget.markerOpts.markers[i].point)); + widget.markerLayerOptions.markers.length, + (i) => widget.map.project(widget.markerLayerOptions.markers[i].point), + ); @override void initState() { @@ -223,17 +224,18 @@ class _MarkerLayerState extends State { } final pos = pxPoint - widget.map.getPixelOrigin(); - final widget = (marker.rotate ?? widget.markerLayerOptions.rotate) - // Counter rotated marker to the map rotation - ? Transform.rotate( - angle: -widget.map.rotationRad, - origin: marker.rotateOrigin ?? - widget.markerLayerOptions.rotateOrigin, - alignment: marker.rotateAlignment ?? - widget.markerLayerOptions.rotateAlignment, - child: marker.builder(context), - ) - : marker.builder(context); + final markerWidget = + (marker.rotate ?? widget.markerLayerOptions.rotate) + // Counter rotated marker to the map rotation + ? Transform.rotate( + angle: -widget.map.rotationRad, + origin: marker.rotateOrigin ?? + widget.markerLayerOptions.rotateOrigin, + alignment: marker.rotateAlignment ?? + widget.markerLayerOptions.rotateAlignment, + child: marker.builder(context), + ) + : marker.builder(context); markers.add( Positioned( @@ -241,7 +243,7 @@ class _MarkerLayerState extends State { height: marker.height, left: pos.x - width, top: pos.y - height, - child: widget, + child: markerWidget, ), ); } From 06499e78797bb8e0c648f7d08eb2e630466854c9 Mon Sep 17 00:00:00 2001 From: TheLastGimbus Date: Mon, 26 Apr 2021 13:46:20 +0200 Subject: [PATCH 10/10] Fix range error --- example/lib/pages/many_markers.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/lib/pages/many_markers.dart b/example/lib/pages/many_markers.dart index cc4ce8476..0b1fc4fd5 100644 --- a/example/lib/pages/many_markers.dart +++ b/example/lib/pages/many_markers.dart @@ -82,7 +82,9 @@ class _ManyMarkersPageState extends State { 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', subdomains: ['a', 'b', 'c'], ), - MarkerLayerOptions(markers: allMarkers.sublist(0, _sliderVal)), + MarkerLayerOptions( + markers: allMarkers.sublist( + 0, min(allMarkers.length, _sliderVal))), ], ), ),