From 7722a7c4022d0c9181cfeeaa10c5dea5b5ea4782 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 21 Jan 2024 22:13:14 +0100 Subject: [PATCH 1/8] Project polylines at construction time in line with recent changes to polygons. This reduces the number of operations per frame. Also move simplifications into projected/planar space which helps to avoid distortions due to projections. --- lib/src/layer/polyline_layer/painter.dart | 30 ++-- lib/src/layer/polyline_layer/polyline.dart | 15 -- .../layer/polyline_layer/polyline_layer.dart | 163 ++++++++++-------- .../polyline_layer/projected_polyline.dart | 25 +++ lib/src/misc/simplify.dart | 23 --- 5 files changed, 127 insertions(+), 129 deletions(-) create mode 100644 lib/src/layer/polyline_layer/projected_polyline.dart diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index 28ef16ec2..36a0bceab 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -3,7 +3,7 @@ part of 'polyline_layer.dart'; /// [CustomPainter] for [Polygon]s. class _PolylinePainter extends CustomPainter { /// Reference to the list of [Polyline]s. - final List> polylines; + final List<_ProjectedPolyline> polylines; /// Reference to the [MapCamera]. final MapCamera camera; @@ -20,18 +20,6 @@ class _PolylinePainter extends CustomPainter { required this.minimumHitbox, }); - List getOffsets(Offset origin, List points) => List.generate( - points.length, - (index) => getOffset(origin, points[index]), - growable: false, - ); - - Offset getOffset(Offset origin, LatLng point) { - // Critically create as little garbage as possible. This is called on every frame. - final projected = camera.project(point); - return Offset(projected.x - origin.dx, projected.y - origin.dy); - } - @override bool? hitTest(Offset position) { if (hitNotifier == null) return null; @@ -41,8 +29,11 @@ class _PolylinePainter extends CustomPainter { final origin = camera.project(camera.center).toOffset() - camera.size.toOffset() / 2; - for (final polyline in polylines.reversed) { - if (polyline.hitValue == null) continue; + for (final projectedPolyline in polylines.reversed) { + final polyline = projectedPolyline.polyline as Polyline; + if (polyline.hitValue == null) { + continue; + } // TODO: For efficiency we'd ideally filter by bounding box here. However // we'd need to compute an extended bounding box that accounts account for @@ -51,7 +42,7 @@ class _PolylinePainter extends CustomPainter { // continue; // } - final offsets = getOffsets(origin, polyline.points); + final offsets = getOffsetsXY(camera, origin, projectedPolyline.points); final strokeWidth = polyline.useStrokeWidthInMeter ? _metersToStrokeWidth( origin, @@ -141,8 +132,9 @@ class _PolylinePainter extends CustomPainter { final origin = camera.project(camera.center).toOffset() - camera.size.toOffset() / 2; - for (final polyline in polylines) { - final offsets = getOffsets(origin, polyline.points); + for (final projectedPolyline in polylines) { + final polyline = projectedPolyline.polyline; + final offsets = getOffsetsXY(camera, origin, projectedPolyline.points); if (offsets.isEmpty) { continue; } @@ -281,7 +273,7 @@ class _PolylinePainter extends CustomPainter { double strokeWidthInMeters, ) { final r = _distance.offset(p0, strokeWidthInMeters, 180); - final delta = o0 - getOffset(origin, r); + final delta = o0 - getOffset(camera, origin, r); return delta.distance; } diff --git a/lib/src/layer/polyline_layer/polyline.dart b/lib/src/layer/polyline_layer/polyline.dart index 30bca02da..d1e5f9788 100644 --- a/lib/src/layer/polyline_layer/polyline.dart +++ b/lib/src/layer/polyline_layer/polyline.dart @@ -52,21 +52,6 @@ class Polyline { this.hitValue, }); - Polyline copyWithNewPoints(List points) => Polyline( - points: points, - strokeWidth: strokeWidth, - color: color, - borderStrokeWidth: borderStrokeWidth, - borderColor: borderColor, - gradientColors: gradientColors, - colorsStop: colorsStop, - isDotted: isDotted, - strokeCap: strokeCap, - strokeJoin: strokeJoin, - useStrokeWidthInMeter: useStrokeWidthInMeter, - hitValue: hitValue, - ); - @override bool operator ==(Object other) => identical(this, other) || diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index e908effe8..2e6aefb25 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -5,11 +5,13 @@ import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_map/flutter_map.dart'; +import 'package:flutter_map/src/misc/offsets.dart'; import 'package:flutter_map/src/misc/simplify.dart'; import 'package:latlong2/latlong.dart'; part 'painter.dart'; part 'polyline.dart'; +part 'projected_polyline.dart'; /// A [Polyline] (aka. LineString) layer for [FlutterMap]. @immutable @@ -26,17 +28,14 @@ class PolylineLayer extends StatefulWidget { /// Defaults to 10. Set to `null` to disable culling. final double? cullingMargin; - /// Distance between two mergeable polyline points, in decimal degrees scaled + /// Distance between two neighboring polyline points, in logical pixels scaled /// to floored zoom /// - /// Increasing results in a more jagged, less accurate simplification, with - /// improved performance; and vice versa. + /// Increasing this value results in points further apart being collapsed and + /// thus more simplified polylines. Higher values improve performance at the + /// cost of visual fidelity and vice versa. /// - /// Note that this value is internally scaled using the current map zoom, to - /// optimize visual performance in conjunction with improved performance with - /// culling. - /// - /// Defaults to 0.5. Set to 0 to disable simplification. + /// Defaults to 0.4. Set to 0 to disable simplification. final double simplificationTolerance; /// A notifier to be notified when a hit test occurs on the layer @@ -64,7 +63,7 @@ class PolylineLayer extends StatefulWidget { super.key, required this.polylines, this.cullingMargin = 10, - this.simplificationTolerance = 0.5, + this.simplificationTolerance = 0.4, this.hitNotifier, this.minimumHitbox = 10, }); @@ -74,47 +73,51 @@ class PolylineLayer extends StatefulWidget { } class _PolylineLayerState extends State> { - final _cachedSimplifiedPolylines = >>{}; + List<_ProjectedPolyline>? _cachedProjectedPolylines; + final _cachedSimplifiedPolylines = >{}; final _culledPolylines = - >[]; // Avoids repetitive memory reallocation + <_ProjectedPolyline>[]; // Avoids repetitive memory reallocation @override void didUpdateWidget(PolylineLayer oldWidget) { super.didUpdateWidget(oldWidget); - // IF old yes & new no, clear - // IF old no & new yes, compute - // IF old no & new no, nothing - // IF old yes & new yes & (different tolerance | different lines), both - // otherwise, nothing - if (oldWidget.simplificationTolerance != 0 && - widget.simplificationTolerance != 0 && - (!listEquals(oldWidget.polylines, widget.polylines) || - oldWidget.simplificationTolerance != - widget.simplificationTolerance)) { - _cachedSimplifiedPolylines.clear(); - _computeZoomLevelSimplification(MapCamera.of(context).zoom.floor()); - } else if (oldWidget.simplificationTolerance != 0 && - widget.simplificationTolerance == 0) { - _cachedSimplifiedPolylines.clear(); - } else if (oldWidget.simplificationTolerance == 0 && - widget.simplificationTolerance != 0) { - _computeZoomLevelSimplification(MapCamera.of(context).zoom.floor()); - } + // Reuse cache + if (widget.simplificationTolerance != 0 && + oldWidget.simplificationTolerance == widget.simplificationTolerance && + listEquals(oldWidget.polylines, widget.polylines)) return; + + _cachedSimplifiedPolylines.clear(); + _cachedProjectedPolylines = null; } @override Widget build(BuildContext context) { final camera = MapCamera.of(context); + final projected = _cachedProjectedPolylines ??= List.generate( + widget.polylines.length, + (i) => _ProjectedPolyline.fromPolyline( + camera.crs.projection, + widget.polylines[i], + ), + growable: false, + ); + final simplified = widget.simplificationTolerance == 0 - ? widget.polylines - : _computeZoomLevelSimplification(camera.zoom.floor()); + ? projected + : _cachedSimplifiedPolylines[camera.zoom.floor()] ??= + _computeZoomLevelSimplification( + polylines: projected, + pixelTolerance: widget.simplificationTolerance, + camera: camera, + ); final culled = widget.cullingMargin == null ? simplified : _aggressivelyCullPolylines( + projection: camera.crs.projection, polylines: simplified, camera: camera, cullingMargin: widget.cullingMargin!, @@ -133,22 +136,9 @@ class _PolylineLayerState extends State> { ); } - // TODO BEFORE v7: Use same algorithm as polygons - List> _computeZoomLevelSimplification(int zoom) => - _cachedSimplifiedPolylines[zoom] ??= widget.polylines - .map( - (polyline) => polyline.copyWithNewPoints( - simplify( - points: polyline.points, - tolerance: widget.simplificationTolerance / math.pow(2, zoom), - highQuality: true, - ), - ), - ) - .toList(); - - List> _aggressivelyCullPolylines({ - required List> polylines, + List<_ProjectedPolyline> _aggressivelyCullPolylines({ + required Projection projection, + required List<_ProjectedPolyline> polylines, required MapCamera camera, required double cullingMargin, }) { @@ -169,47 +159,47 @@ class _PolylineLayerState extends State> { ), ); - for (final polyline in polylines) { + // segment is visible + final projectedBounds = Bounds( + projection.project(boundsAdjusted.southWest), + projection.project(boundsAdjusted.northEast), + ); + + for (final projectedPolyline in polylines) { + final polyline = projectedPolyline.polyline; + // Gradient poylines cannot be easily segmented if (polyline.gradientColors != null) { - _culledPolylines.add(polyline); + _culledPolylines.add(projectedPolyline); continue; } + // pointer that indicates the start of the visible polyline segment int start = -1; bool fullyVisible = true; - for (int i = 0; i < polyline.points.length - 1; i++) { + for (int i = 0; i < projectedPolyline.points.length - 1; i++) { //current pair - final p1 = polyline.points[i]; - final p2 = polyline.points[i + 1]; - - // segment is visible - if (Bounds( - math.Point( - boundsAdjusted.southWest.longitude, - boundsAdjusted.southWest.latitude, - ), - math.Point( - boundsAdjusted.northEast.longitude, - boundsAdjusted.northEast.latitude, - ), - ).aabbContainsLine( - p1.longitude, p1.latitude, p2.longitude, p2.latitude)) { + final p1 = projectedPolyline.points[i]; + final p2 = projectedPolyline.points[i + 1]; + + if (projectedBounds.aabbContainsLine(p1.x, p1.y, p2.x, p2.y)) { // segment is visible if (start == -1) { start = i; } - if (!fullyVisible && i == polyline.points.length - 2) { - final segment = polyline.points.sublist(start, i + 2); - _culledPolylines.add(polyline.copyWithNewPoints(segment)); + if (!fullyVisible && i == projectedPolyline.points.length - 2) { + final segment = projectedPolyline.points.sublist(start, i + 2); + _culledPolylines + .add(_ProjectedPolyline._(polyline: polyline, points: segment)); } } else { fullyVisible = false; // if we cannot see the segment, then reset start if (start != -1) { // partial start - final segment = polyline.points.sublist(start, i + 1); - _culledPolylines.add(polyline.copyWithNewPoints(segment)); + final segment = projectedPolyline.points.sublist(start, i + 1); + _culledPolylines + .add(_ProjectedPolyline._(polyline: polyline, points: segment)); start = -1; } if (start != -1) { @@ -218,9 +208,38 @@ class _PolylineLayerState extends State> { } } - if (fullyVisible) _culledPolylines.add(polyline); + if (fullyVisible) _culledPolylines.add(projectedPolyline); } return _culledPolylines; } + + static List<_ProjectedPolyline> _computeZoomLevelSimplification({ + required List<_ProjectedPolyline> polylines, + required double pixelTolerance, + required MapCamera camera, + }) { + final tolerance = getEffectiveSimplificationTolerance( + crs: camera.crs, + zoom: camera.zoom.floor(), + pixelTolerance: pixelTolerance, + ); + + return List<_ProjectedPolyline>.generate( + polylines.length, + (i) { + final polyline = polylines[i]; + + return _ProjectedPolyline._( + polyline: polyline.polyline, + points: simplifyPoints( + points: polyline.points, + tolerance: tolerance, + highQuality: true, + ), + ); + }, + growable: false, + ); + } } diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart new file mode 100644 index 000000000..d43d0486d --- /dev/null +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -0,0 +1,25 @@ +part of 'polyline_layer.dart'; + +@immutable +class _ProjectedPolyline { + final Polyline polyline; + final List points; + + const _ProjectedPolyline._({ + required this.polyline, + required this.points, + }); + + _ProjectedPolyline.fromPolyline(Projection projection, Polyline polyline) + : this._( + polyline: polyline, + points: List.generate( + polyline.points.length, + (j) { + final (x, y) = projection.projectXY(polyline.points[j]); + return DoublePoint(x, y); + }, + growable: false, + ), + ); +} diff --git a/lib/src/misc/simplify.dart b/lib/src/misc/simplify.dart index 84dae5177..4f09a85f2 100644 --- a/lib/src/misc/simplify.dart +++ b/lib/src/misc/simplify.dart @@ -125,29 +125,6 @@ List simplifyDouglasPeucker( return simplified; } -List simplify({ - required List points, - required double tolerance, - required bool highQuality, -}) { - // Don't simplify anything less than a square - if (points.length <= 4) return points; - - List nextPoints = List.generate( - points.length, - (i) => DoublePoint(points[i].longitude, points[i].latitude), - ); - final double sqTolerance = tolerance * tolerance; - nextPoints = highQuality - ? simplifyDouglasPeucker(nextPoints, sqTolerance) - : simplifyRadialDist(nextPoints, sqTolerance); - - return List.generate( - nextPoints.length, - (i) => LatLng(nextPoints[i].y, nextPoints[i].x), - ); -} - List simplifyPoints({ required final List points, required double tolerance, From a7d3be523a24b135125d130cb34abebebeb34607 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 21 Jan 2024 22:52:25 +0100 Subject: [PATCH 2/8] Drive-by simplification of the polyline culling code. --- .../layer/polyline_layer/polyline_layer.dart | 45 ++++++++++--------- lib/src/misc/simplify.dart | 1 - 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 2e6aefb25..a3bb3043c 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -160,7 +160,7 @@ class _PolylineLayerState extends State> { ); // segment is visible - final projectedBounds = Bounds( + final projBounds = Bounds( projection.project(boundsAdjusted.southWest), projection.project(boundsAdjusted.northEast), ); @@ -176,39 +176,44 @@ class _PolylineLayerState extends State> { // pointer that indicates the start of the visible polyline segment int start = -1; - bool fullyVisible = true; + bool containsSegment = false; for (int i = 0; i < projectedPolyline.points.length - 1; i++) { - //current pair + // Current segment (p1, p2). final p1 = projectedPolyline.points[i]; final p2 = projectedPolyline.points[i + 1]; - if (projectedBounds.aabbContainsLine(p1.x, p1.y, p2.x, p2.y)) { - // segment is visible + containsSegment = projBounds.aabbContainsLine(p1.x, p1.y, p2.x, p2.y); + if (containsSegment) { if (start == -1) { start = i; } - if (!fullyVisible && i == projectedPolyline.points.length - 2) { - final segment = projectedPolyline.points.sublist(start, i + 2); - _culledPolylines - .add(_ProjectedPolyline._(polyline: polyline, points: segment)); - } } else { - fullyVisible = false; - // if we cannot see the segment, then reset start + // If we cannot see this segment but have seen previous ones, flush the last polyline fragment. if (start != -1) { - // partial start - final segment = projectedPolyline.points.sublist(start, i + 1); - _culledPolylines - .add(_ProjectedPolyline._(polyline: polyline, points: segment)); + _culledPolylines.add(_ProjectedPolyline._( + polyline: polyline, + points: projectedPolyline.points.sublist(start, i + 1), + )); + + // Reset start. start = -1; } - if (start != -1) { - start = i; - } } } - if (fullyVisible) _culledPolylines.add(projectedPolyline); + // If the last segment was visible push that last visible polyline + // fragment, which may also be the entire polyline if `start == 0`. + if (containsSegment) { + _culledPolylines.add( + start == 0 + ? projectedPolyline + : _ProjectedPolyline._( + polyline: polyline, + // Special case: the entire polyline is visible + points: projectedPolyline.points.sublist(start), + ), + ); + } } return _culledPolylines; diff --git a/lib/src/misc/simplify.dart b/lib/src/misc/simplify.dart index 4f09a85f2..ea0828ec6 100644 --- a/lib/src/misc/simplify.dart +++ b/lib/src/misc/simplify.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; import 'package:flutter_map/src/geo/crs.dart'; -import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; /// Internal double-precision point/vector implementation not to be used in publicly. From 5a19858bf42948cddec6dc3781044c025c6bf92c Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Mon, 22 Jan 2024 10:14:41 +0000 Subject: [PATCH 3/8] Added generic typing to `_ProjectedPolyline` --- lib/src/layer/polyline_layer/projected_polyline.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart index d43d0486d..0cc872c25 100644 --- a/lib/src/layer/polyline_layer/projected_polyline.dart +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -1,8 +1,8 @@ part of 'polyline_layer.dart'; @immutable -class _ProjectedPolyline { - final Polyline polyline; +class _ProjectedPolyline { + final Polyline polyline; final List points; const _ProjectedPolyline._({ @@ -10,7 +10,7 @@ class _ProjectedPolyline { required this.points, }); - _ProjectedPolyline.fromPolyline(Projection projection, Polyline polyline) + _ProjectedPolyline.fromPolyline(Projection projection, Polyline polyline) : this._( polyline: polyline, points: List.generate( From 2c52ec2f34516c36a50dd68f278083a54f294665 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 Jan 2024 23:09:55 +0100 Subject: [PATCH 4/8] Fix strokeWidthInMeters. --- lib/src/layer/polyline_layer/painter.dart | 7 +++++-- lib/src/layer/polyline_layer/polyline_layer.dart | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index 36a0bceab..dddf9c2cf 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -46,7 +46,7 @@ class _PolylinePainter extends CustomPainter { final strokeWidth = polyline.useStrokeWidthInMeter ? _metersToStrokeWidth( origin, - polyline.points.first, + _unproject(projectedPolyline.points.first), offsets.first, polyline.strokeWidth, ) @@ -151,7 +151,7 @@ class _PolylinePainter extends CustomPainter { if (polyline.useStrokeWidthInMeter) { strokeWidth = _metersToStrokeWidth( origin, - polyline.points.first, + _unproject(projectedPolyline.points.first), offsets.first, polyline.strokeWidth, ); @@ -277,6 +277,9 @@ class _PolylinePainter extends CustomPainter { return delta.distance; } + LatLng _unproject(DoublePoint p0) => + camera.crs.projection.unprojectXY(p0.x, p0.y); + @override bool shouldRepaint(_PolylinePainter oldDelegate) => false; } diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index a3bb3043c..418db6701 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -145,7 +145,8 @@ class _PolylineLayerState extends State> { _culledPolylines.clear(); final bounds = camera.visibleBounds; - final margin = cullingMargin / math.pow(2, camera.zoom.floorToDouble()); + final margin = cullingMargin / math.pow(2, camera.zoom); + // The min(-90), max(180), ... are used to get around the limits of LatLng // the value cannot be greater or smaller than that final boundsAdjusted = LatLngBounds( From ecbddd31988e652a06874250f22b84284e73f808 Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Mon, 22 Jan 2024 20:33:01 +0000 Subject: [PATCH 5/8] Improve consistency of visibility of `_ProjectedPoly*.(_)fromPoly*` --- lib/src/layer/polygon_layer/polygon_layer.dart | 2 +- lib/src/layer/polygon_layer/projected_polygon.dart | 2 +- lib/src/layer/polyline_layer/polyline_layer.dart | 2 +- lib/src/layer/polyline_layer/projected_polyline.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index dee8474af..0bc3bff8b 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -84,7 +84,7 @@ class _PolygonLayerState extends State { final projected = _cachedProjectedPolygons ??= List.generate( widget.polygons.length, - (i) => _ProjectedPolygon.fromPolygon( + (i) => _ProjectedPolygon._fromPolygon( camera.crs.projection, widget.polygons[i], ), diff --git a/lib/src/layer/polygon_layer/projected_polygon.dart b/lib/src/layer/polygon_layer/projected_polygon.dart index f597244c7..ca1e74a94 100644 --- a/lib/src/layer/polygon_layer/projected_polygon.dart +++ b/lib/src/layer/polygon_layer/projected_polygon.dart @@ -12,7 +12,7 @@ class _ProjectedPolygon { this.holePoints, }); - _ProjectedPolygon.fromPolygon(Projection projection, Polygon polygon) + _ProjectedPolygon._fromPolygon(Projection projection, Polygon polygon) : this._( polygon: polygon, points: List.generate( diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 418db6701..cc89a2baf 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -98,7 +98,7 @@ class _PolylineLayerState extends State> { final projected = _cachedProjectedPolylines ??= List.generate( widget.polylines.length, - (i) => _ProjectedPolyline.fromPolyline( + (i) => _ProjectedPolyline._fromPolyline( camera.crs.projection, widget.polylines[i], ), diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart index 0cc872c25..a9f2facca 100644 --- a/lib/src/layer/polyline_layer/projected_polyline.dart +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -10,7 +10,7 @@ class _ProjectedPolyline { required this.points, }); - _ProjectedPolyline.fromPolyline(Projection projection, Polyline polyline) + _ProjectedPolyline._fromPolyline(Projection projection, Polyline polyline) : this._( polyline: polyline, points: List.generate( From 33dc98f5dcd6bc73563ac2a41675957fa269fb8f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 Jan 2024 23:17:14 +0100 Subject: [PATCH 6/8] Reduce virtual function overhead. --- lib/src/misc/bounds.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/misc/bounds.dart b/lib/src/misc/bounds.dart index 4c4de27a2..e7270aefb 100644 --- a/lib/src/misc/bounds.dart +++ b/lib/src/misc/bounds.dart @@ -111,7 +111,7 @@ class Bounds { (b.max.y >= min.y); } - bool aabbContainsLine(num x1, num y1, num x2, num y2) { + bool aabbContainsLine(double x1, double y1, double x2, double y2) { // Completely outside. if ((x1 <= min.x && x2 <= min.x) || (y1 <= min.y && y2 <= min.y) || @@ -122,13 +122,13 @@ class Bounds { final m = (y2 - y1) / (x2 - x1); - num y = m * (min.x - x1) + y1; + double y = m * (min.x - x1) + y1; if (y > min.y && y < max.y) return true; y = m * (max.x - x1) + y1; if (y > min.y && y < max.y) return true; - num x = (min.y - y1) / m + x1; + double x = (min.y - y1) / m + x1; if (x > min.x && x < max.x) return true; x = (max.y - y1) / m + x1; From 9344c991918e3b1a20a762cb4ff7570eccf08c18 Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Tue, 23 Jan 2024 18:40:16 +0000 Subject: [PATCH 7/8] Made `_ProjectedPoly*` mutable to reduce GC stress Minor syntactic/formatting improvements --- .../layer/polygon_layer/polygon_layer.dart | 18 +++-- .../polygon_layer/projected_polygon.dart | 66 ++++++++----------- lib/src/layer/polyline_layer/painter.dart | 10 ++- .../layer/polyline_layer/polyline_layer.dart | 34 ++++------ .../polyline_layer/projected_polyline.dart | 27 +++----- 5 files changed, 64 insertions(+), 91 deletions(-) diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index 0bc3bff8b..75dbfe637 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -135,17 +135,16 @@ class _PolygonLayerState extends State { return List<_ProjectedPolygon>.generate( polygons.length, (i) { - final polygon = polygons[i]; - final holes = polygon.holePoints; + final projectedPolygon = polygons[i]; + final holes = projectedPolygon.holePoints; - return _ProjectedPolygon._( - polygon: polygon.polygon, - points: simplifyPoints( - points: polygon.points, + return projectedPolygon + ..points = simplifyPoints( + points: projectedPolygon.points, tolerance: tolerance, highQuality: true, - ), - holePoints: holes == null + ) + ..holePoints = holes == null ? null : List>.generate( holes.length, @@ -155,8 +154,7 @@ class _PolygonLayerState extends State { highQuality: true, ), growable: false, - ), - ); + ); }, growable: false, ); diff --git a/lib/src/layer/polygon_layer/projected_polygon.dart b/lib/src/layer/polygon_layer/projected_polygon.dart index ca1e74a94..4b361fbfb 100644 --- a/lib/src/layer/polygon_layer/projected_polygon.dart +++ b/lib/src/layer/polygon_layer/projected_polygon.dart @@ -1,47 +1,39 @@ part of 'polygon_layer.dart'; -@immutable class _ProjectedPolygon { final Polygon polygon; - final List points; - final List>? holePoints; - const _ProjectedPolygon._({ - required this.polygon, - required this.points, - this.holePoints, - }); + // Mutable to reduce GC stress from repetitive allocation + List points; + List>? holePoints; - _ProjectedPolygon._fromPolygon(Projection projection, Polygon polygon) - : this._( - polygon: polygon, - points: List.generate( - polygon.points.length, + _ProjectedPolygon._fromPolygon(Projection projection, this.polygon) + : points = List.generate( + polygon.points.length, + (j) { + final (x, y) = projection.projectXY(polygon.points[j]); + return DoublePoint(x, y); + }, + growable: false, + ), + holePoints = (() { + final holes = polygon.holePointsList; + if (holes == null) return null; + + return List>.generate( + holes.length, (j) { - final (x, y) = projection.projectXY(polygon.points[j]); - return DoublePoint(x, y); + final points = holes[j]; + return List.generate( + points.length, + (k) { + final (x, y) = projection.projectXY(points[k]); + return DoublePoint(x, y); + }, + growable: false, + ); }, growable: false, - ), - holePoints: () { - final holes = polygon.holePointsList; - if (holes == null) return null; - - return List>.generate( - holes.length, - (j) { - final points = holes[j]; - return List.generate( - points.length, - (k) { - final (x, y) = projection.projectXY(points[k]); - return DoublePoint(x, y); - }, - growable: false, - ); - }, - growable: false, - ); - }(), - ); + ); + }()); } diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index dddf9c2cf..ca15db628 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -31,9 +31,8 @@ class _PolylinePainter extends CustomPainter { for (final projectedPolyline in polylines.reversed) { final polyline = projectedPolyline.polyline as Polyline; - if (polyline.hitValue == null) { - continue; - } + + if (polyline.hitValue == null) continue; // TODO: For efficiency we'd ideally filter by bounding box here. However // we'd need to compute an extended bounding box that accounts account for @@ -134,10 +133,9 @@ class _PolylinePainter extends CustomPainter { for (final projectedPolyline in polylines) { final polyline = projectedPolyline.polyline; + final offsets = getOffsetsXY(camera, origin, projectedPolyline.points); - if (offsets.isEmpty) { - continue; - } + if (offsets.isEmpty) continue; final hash = polyline.renderHashCode; if (needsLayerSaving || (lastHash != null && lastHash != hash)) { diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index cc89a2baf..7ce1b1476 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -191,10 +191,10 @@ class _PolylineLayerState extends State> { } else { // If we cannot see this segment but have seen previous ones, flush the last polyline fragment. if (start != -1) { - _culledPolylines.add(_ProjectedPolyline._( - polyline: polyline, - points: projectedPolyline.points.sublist(start, i + 1), - )); + _culledPolylines.add( + projectedPolyline + ..points = projectedPolyline.points.sublist(start, i + 1), + ); // Reset start. start = -1; @@ -208,11 +208,9 @@ class _PolylineLayerState extends State> { _culledPolylines.add( start == 0 ? projectedPolyline - : _ProjectedPolyline._( - polyline: polyline, - // Special case: the entire polyline is visible - points: projectedPolyline.points.sublist(start), - ), + // Special case: the entire polyline is visible + : (projectedPolyline + ..points = projectedPolyline.points.sublist(start)), ); } } @@ -233,18 +231,12 @@ class _PolylineLayerState extends State> { return List<_ProjectedPolyline>.generate( polylines.length, - (i) { - final polyline = polylines[i]; - - return _ProjectedPolyline._( - polyline: polyline.polyline, - points: simplifyPoints( - points: polyline.points, - tolerance: tolerance, - highQuality: true, - ), - ); - }, + (i) => polylines[i] + ..points = simplifyPoints( + points: polylines[i].points, + tolerance: tolerance, + highQuality: true, + ), growable: false, ); } diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart index a9f2facca..26b3e0b0f 100644 --- a/lib/src/layer/polyline_layer/projected_polyline.dart +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -1,25 +1,18 @@ part of 'polyline_layer.dart'; -@immutable class _ProjectedPolyline { final Polyline polyline; - final List points; - const _ProjectedPolyline._({ - required this.polyline, - required this.points, - }); + // Mutable to reduce GC stress from repetitive allocation + List points; - _ProjectedPolyline._fromPolyline(Projection projection, Polyline polyline) - : this._( - polyline: polyline, - points: List.generate( - polyline.points.length, - (j) { - final (x, y) = projection.projectXY(polyline.points[j]); - return DoublePoint(x, y); - }, - growable: false, - ), + _ProjectedPolyline._fromPolyline(Projection projection, this.polyline) + : points = List.generate( + polyline.points.length, + (j) { + final (x, y) = projection.projectXY(polyline.points[j]); + return DoublePoint(x, y); + }, + growable: false, ); } From 7ef68dbd69bd26e94c56a0b47e10b3f3688f74d2 Mon Sep 17 00:00:00 2001 From: JaffaKetchup Date: Tue, 23 Jan 2024 20:54:07 +0000 Subject: [PATCH 8/8] Revert "Made `_ProjectedPoly*` mutable to reduce GC stress" This reverts commit 9344c991918e3b1a20a762cb4ff7570eccf08c18. --- .../layer/polygon_layer/polygon_layer.dart | 18 ++--- .../polygon_layer/projected_polygon.dart | 66 +++++++++++-------- lib/src/layer/polyline_layer/painter.dart | 10 +-- .../layer/polyline_layer/polyline_layer.dart | 34 ++++++---- .../polyline_layer/projected_polyline.dart | 27 +++++--- 5 files changed, 91 insertions(+), 64 deletions(-) diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index 75dbfe637..0bc3bff8b 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -135,16 +135,17 @@ class _PolygonLayerState extends State { return List<_ProjectedPolygon>.generate( polygons.length, (i) { - final projectedPolygon = polygons[i]; - final holes = projectedPolygon.holePoints; + final polygon = polygons[i]; + final holes = polygon.holePoints; - return projectedPolygon - ..points = simplifyPoints( - points: projectedPolygon.points, + return _ProjectedPolygon._( + polygon: polygon.polygon, + points: simplifyPoints( + points: polygon.points, tolerance: tolerance, highQuality: true, - ) - ..holePoints = holes == null + ), + holePoints: holes == null ? null : List>.generate( holes.length, @@ -154,7 +155,8 @@ class _PolygonLayerState extends State { highQuality: true, ), growable: false, - ); + ), + ); }, growable: false, ); diff --git a/lib/src/layer/polygon_layer/projected_polygon.dart b/lib/src/layer/polygon_layer/projected_polygon.dart index 4b361fbfb..ca1e74a94 100644 --- a/lib/src/layer/polygon_layer/projected_polygon.dart +++ b/lib/src/layer/polygon_layer/projected_polygon.dart @@ -1,39 +1,47 @@ part of 'polygon_layer.dart'; +@immutable class _ProjectedPolygon { final Polygon polygon; + final List points; + final List>? holePoints; - // Mutable to reduce GC stress from repetitive allocation - List points; - List>? holePoints; + const _ProjectedPolygon._({ + required this.polygon, + required this.points, + this.holePoints, + }); - _ProjectedPolygon._fromPolygon(Projection projection, this.polygon) - : points = List.generate( - polygon.points.length, - (j) { - final (x, y) = projection.projectXY(polygon.points[j]); - return DoublePoint(x, y); - }, - growable: false, - ), - holePoints = (() { - final holes = polygon.holePointsList; - if (holes == null) return null; - - return List>.generate( - holes.length, + _ProjectedPolygon._fromPolygon(Projection projection, Polygon polygon) + : this._( + polygon: polygon, + points: List.generate( + polygon.points.length, (j) { - final points = holes[j]; - return List.generate( - points.length, - (k) { - final (x, y) = projection.projectXY(points[k]); - return DoublePoint(x, y); - }, - growable: false, - ); + final (x, y) = projection.projectXY(polygon.points[j]); + return DoublePoint(x, y); }, growable: false, - ); - }()); + ), + holePoints: () { + final holes = polygon.holePointsList; + if (holes == null) return null; + + return List>.generate( + holes.length, + (j) { + final points = holes[j]; + return List.generate( + points.length, + (k) { + final (x, y) = projection.projectXY(points[k]); + return DoublePoint(x, y); + }, + growable: false, + ); + }, + growable: false, + ); + }(), + ); } diff --git a/lib/src/layer/polyline_layer/painter.dart b/lib/src/layer/polyline_layer/painter.dart index ca15db628..dddf9c2cf 100644 --- a/lib/src/layer/polyline_layer/painter.dart +++ b/lib/src/layer/polyline_layer/painter.dart @@ -31,8 +31,9 @@ class _PolylinePainter extends CustomPainter { for (final projectedPolyline in polylines.reversed) { final polyline = projectedPolyline.polyline as Polyline; - - if (polyline.hitValue == null) continue; + if (polyline.hitValue == null) { + continue; + } // TODO: For efficiency we'd ideally filter by bounding box here. However // we'd need to compute an extended bounding box that accounts account for @@ -133,9 +134,10 @@ class _PolylinePainter extends CustomPainter { for (final projectedPolyline in polylines) { final polyline = projectedPolyline.polyline; - final offsets = getOffsetsXY(camera, origin, projectedPolyline.points); - if (offsets.isEmpty) continue; + if (offsets.isEmpty) { + continue; + } final hash = polyline.renderHashCode; if (needsLayerSaving || (lastHash != null && lastHash != hash)) { diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 7ce1b1476..cc89a2baf 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -191,10 +191,10 @@ class _PolylineLayerState extends State> { } else { // If we cannot see this segment but have seen previous ones, flush the last polyline fragment. if (start != -1) { - _culledPolylines.add( - projectedPolyline - ..points = projectedPolyline.points.sublist(start, i + 1), - ); + _culledPolylines.add(_ProjectedPolyline._( + polyline: polyline, + points: projectedPolyline.points.sublist(start, i + 1), + )); // Reset start. start = -1; @@ -208,9 +208,11 @@ class _PolylineLayerState extends State> { _culledPolylines.add( start == 0 ? projectedPolyline - // Special case: the entire polyline is visible - : (projectedPolyline - ..points = projectedPolyline.points.sublist(start)), + : _ProjectedPolyline._( + polyline: polyline, + // Special case: the entire polyline is visible + points: projectedPolyline.points.sublist(start), + ), ); } } @@ -231,12 +233,18 @@ class _PolylineLayerState extends State> { return List<_ProjectedPolyline>.generate( polylines.length, - (i) => polylines[i] - ..points = simplifyPoints( - points: polylines[i].points, - tolerance: tolerance, - highQuality: true, - ), + (i) { + final polyline = polylines[i]; + + return _ProjectedPolyline._( + polyline: polyline.polyline, + points: simplifyPoints( + points: polyline.points, + tolerance: tolerance, + highQuality: true, + ), + ); + }, growable: false, ); } diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart index 26b3e0b0f..a9f2facca 100644 --- a/lib/src/layer/polyline_layer/projected_polyline.dart +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -1,18 +1,25 @@ part of 'polyline_layer.dart'; +@immutable class _ProjectedPolyline { final Polyline polyline; + final List points; - // Mutable to reduce GC stress from repetitive allocation - List points; + const _ProjectedPolyline._({ + required this.polyline, + required this.points, + }); - _ProjectedPolyline._fromPolyline(Projection projection, this.polyline) - : points = List.generate( - polyline.points.length, - (j) { - final (x, y) = projection.projectXY(polyline.points[j]); - return DoublePoint(x, y); - }, - growable: false, + _ProjectedPolyline._fromPolyline(Projection projection, Polyline polyline) + : this._( + polyline: polyline, + points: List.generate( + polyline.points.length, + (j) { + final (x, y) = projection.projectXY(polyline.points[j]); + return DoublePoint(x, y); + }, + growable: false, + ), ); }