Skip to content

Commit ba17b39

Browse files
authored
feat: implement efficient(-ish) change detection for PolygonLayer & PolylineLayer (#1904)
1 parent a2bf534 commit ba17b39

File tree

10 files changed

+367
-249
lines changed

10 files changed

+367
-249
lines changed

example/lib/pages/polygon_perf_stress.dart

+8-10
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PolygonPerfStressPage extends StatefulWidget {
1919
}
2020

2121
class _PolygonPerfStressPageState extends State<PolygonPerfStressPage> {
22-
double simplificationTolerance = 0.5;
22+
double simplificationTolerance = 0.3;
2323
bool useAltRendering = true;
2424
double borderThickness = 1;
2525

@@ -63,15 +63,13 @@ class _PolygonPerfStressPageState extends State<PolygonPerfStressPage> {
6363
openStreetMapTileLayer,
6464
FutureBuilder(
6565
future: geoJsonParser,
66-
builder: (context, geoJsonParser) =>
67-
geoJsonParser.connectionState != ConnectionState.done ||
68-
geoJsonParser.data == null
69-
? const SizedBox.shrink()
70-
: PolygonLayer(
71-
polygons: geoJsonParser.data!.polygons,
72-
useAltRendering: useAltRendering,
73-
simplificationTolerance: simplificationTolerance,
74-
),
66+
builder: (context, geoJsonParser) => geoJsonParser.data == null
67+
? const SizedBox.shrink()
68+
: PolygonLayer(
69+
polygons: geoJsonParser.data!.polygons,
70+
useAltRendering: useAltRendering,
71+
simplificationTolerance: simplificationTolerance,
72+
),
7573
),
7674
],
7775
),

example/lib/pages/polyline_perf_stress.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PolylinePerfStressPage extends StatefulWidget {
1919
}
2020

2121
class _PolylinePerfStressPageState extends State<PolylinePerfStressPage> {
22-
double simplificationTolerance = 0.5;
22+
double simplificationTolerance = 0.3;
2323

2424
final _randomWalk = [const LatLng(44.861294, 13.845086)];
2525

lib/src/layer/circle_layer/circle_marker.dart

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ part of 'circle_layer.dart';
33
/// Immutable marker options for [CircleMarker]. Circle markers are a more
44
/// simple and performant way to draw markers as the regular [Marker]
55
@immutable
6-
base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
6+
class CircleMarker<R extends Object> with HitDetectableElement<R> {
77
/// An optional [Key] for the [CircleMarker].
88
/// This key is not used internally.
99
final Key? key;
@@ -27,6 +27,9 @@ base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
2727
/// Set to true if the radius should use the unit meters.
2828
final bool useRadiusInMeter;
2929

30+
@override
31+
final R? hitValue;
32+
3033
/// Constructor to create a new [CircleMarker] object
3134
const CircleMarker({
3235
required this.point,
@@ -36,6 +39,6 @@ base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
3639
this.color = const Color(0xFF00FF00),
3740
this.borderStrokeWidth = 0.0,
3841
this.borderColor = const Color(0xFFFFFF00),
39-
super.hitValue,
42+
this.hitValue,
4043
});
4144
}

lib/src/layer/polygon_layer/polygon_layer.dart

+44-108
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'package:flutter/foundation.dart';
77
import 'package:flutter/widgets.dart';
88
import 'package:flutter_map/flutter_map.dart';
99
import 'package:flutter_map/src/layer/shared/layer_interactivity/internal_hit_detectable.dart';
10+
import 'package:flutter_map/src/layer/shared/layer_projection_simplification/state.dart';
11+
import 'package:flutter_map/src/layer/shared/layer_projection_simplification/widget.dart';
1012
import 'package:flutter_map/src/layer/shared/line_patterns/pixel_hiker.dart';
1113
import 'package:flutter_map/src/misc/offsets.dart';
1214
import 'package:flutter_map/src/misc/point_in_polygon.dart';
@@ -21,7 +23,8 @@ part 'projected_polygon.dart';
2123

2224
/// A polygon layer for [FlutterMap].
2325
@immutable
24-
class PolygonLayer<R extends Object> extends StatefulWidget {
26+
base class PolygonLayer<R extends Object>
27+
extends ProjectionSimplificationManagementSupportedWidget {
2528
/// [Polygon]s to draw
2629
final List<Polygon<R>> polygons;
2730

@@ -52,16 +55,6 @@ class PolygonLayer<R extends Object> extends StatefulWidget {
5255
/// Defaults to `true`. Disabling is not recommended.
5356
final bool polygonCulling;
5457

55-
/// Distance between two neighboring polygon points, in logical pixels scaled
56-
/// to floored zoom
57-
///
58-
/// Increasing this value results in points further apart being collapsed and
59-
/// thus more simplified polygons. Higher values improve performance at the
60-
/// cost of visual fidelity and vice versa.
61-
///
62-
/// Defaults to 0.5. Set to 0 to disable simplification.
63-
final double simplificationTolerance;
64-
6558
/// Whether to draw per-polygon labels
6659
///
6760
/// Defaults to `true`.
@@ -82,79 +75,63 @@ class PolygonLayer<R extends Object> extends StatefulWidget {
8275
this.useAltRendering = false,
8376
this.debugAltRenderer = false,
8477
this.polygonCulling = true,
85-
this.simplificationTolerance = 0.5,
8678
this.polygonLabels = true,
8779
this.drawLabelsLast = false,
8880
this.hitNotifier,
89-
}) : assert(
90-
simplificationTolerance >= 0,
91-
'simplificationTolerance cannot be negative: $simplificationTolerance',
92-
);
81+
super.simplificationTolerance,
82+
super.useDynamicUpdate,
83+
}) : super();
9384

9485
@override
9586
State<PolygonLayer<R>> createState() => _PolygonLayerState<R>();
9687
}
9788

98-
class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>> {
99-
List<_ProjectedPolygon<R>>? _cachedProjectedPolygons;
100-
final _cachedSimplifiedPolygons = <int, List<_ProjectedPolygon<R>>>{};
101-
102-
double? _devicePixelRatio;
89+
class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>>
90+
with
91+
ProjectionSimplificationManagement<_ProjectedPolygon<R>, Polygon<R>,
92+
PolygonLayer<R>> {
93+
@override
94+
_ProjectedPolygon<R> projectElement({
95+
required Projection projection,
96+
required Polygon<R> element,
97+
}) =>
98+
_ProjectedPolygon._fromPolygon(projection, element);
10399

104100
@override
105-
void didUpdateWidget(PolygonLayer<R> oldWidget) {
106-
super.didUpdateWidget(oldWidget);
101+
_ProjectedPolygon<R> simplifyProjectedElement({
102+
required _ProjectedPolygon<R> projectedElement,
103+
required double tolerance,
104+
}) =>
105+
_ProjectedPolygon._(
106+
polygon: projectedElement.polygon,
107+
points: simplifyPoints(
108+
points: projectedElement.points,
109+
tolerance: tolerance,
110+
highQuality: true,
111+
),
112+
holePoints: List.generate(
113+
projectedElement.holePoints.length,
114+
(j) => simplifyPoints(
115+
points: projectedElement.holePoints[j],
116+
tolerance: tolerance,
117+
highQuality: true,
118+
),
119+
growable: false,
120+
),
121+
);
107122

108-
if (!listEquals(oldWidget.polygons, widget.polygons)) {
109-
// If the polylines have changed, then both the projections and the
110-
// projection-dependendent simplifications must be invalidated
111-
_cachedProjectedPolygons = null;
112-
_cachedSimplifiedPolygons.clear();
113-
} else if (oldWidget.simplificationTolerance !=
114-
widget.simplificationTolerance) {
115-
// If only the simplification tolerance has changed, this does not affect
116-
// the projections (as that is done before simplification), so only
117-
// invalidate the simplifications
118-
_cachedSimplifiedPolygons.clear();
119-
}
120-
}
123+
@override
124+
Iterable<Polygon<R>> getElements(PolygonLayer<R> widget) => widget.polygons;
121125

122126
@override
123127
Widget build(BuildContext context) {
124-
final camera = MapCamera.of(context);
128+
super.build(context);
125129

126-
final projected = _cachedProjectedPolygons ??= List.generate(
127-
widget.polygons.length,
128-
(i) => _ProjectedPolygon._fromPolygon(
129-
camera.crs.projection,
130-
widget.polygons[i],
131-
),
132-
growable: false,
133-
);
134-
135-
late final List<_ProjectedPolygon<R>> simplified;
136-
if (widget.simplificationTolerance == 0) {
137-
simplified = projected;
138-
} else {
139-
// If the DPR has changed, invalidate the simplification cache
140-
final newDPR = MediaQuery.devicePixelRatioOf(context);
141-
if (newDPR != _devicePixelRatio) {
142-
_devicePixelRatio = newDPR;
143-
_cachedSimplifiedPolygons.clear();
144-
}
145-
146-
simplified = _cachedSimplifiedPolygons[camera.zoom.floor()] ??=
147-
_computeZoomLevelSimplification(
148-
camera: camera,
149-
polygons: projected,
150-
pixelTolerance: widget.simplificationTolerance,
151-
devicePixelRatio: newDPR,
152-
);
153-
}
130+
final camera = MapCamera.of(context);
154131

155132
final culled = !widget.polygonCulling
156-
? simplified
157-
: simplified
133+
? simplifiedElements.toList()
134+
: simplifiedElements
158135
.where(
159136
(p) => p.polygon.boundingBox.isOverlapping(camera.visibleBounds),
160137
)
@@ -213,45 +190,4 @@ class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>> {
213190
yield prevValue += polygon.holePoints[i].length;
214191
}
215192
}
216-
217-
List<_ProjectedPolygon<R>> _computeZoomLevelSimplification({
218-
required MapCamera camera,
219-
required List<_ProjectedPolygon<R>> polygons,
220-
required double pixelTolerance,
221-
required double devicePixelRatio,
222-
}) {
223-
final tolerance = getEffectiveSimplificationTolerance(
224-
crs: camera.crs,
225-
zoom: camera.zoom.floor(),
226-
pixelTolerance: pixelTolerance,
227-
devicePixelRatio: devicePixelRatio,
228-
);
229-
230-
return List<_ProjectedPolygon<R>>.generate(
231-
polygons.length,
232-
(i) {
233-
final polygon = polygons[i];
234-
final holes = polygon.holePoints;
235-
236-
return _ProjectedPolygon._(
237-
polygon: polygon.polygon,
238-
points: simplifyPoints(
239-
points: polygon.points,
240-
tolerance: tolerance,
241-
highQuality: true,
242-
),
243-
holePoints: List.generate(
244-
holes.length,
245-
(j) => simplifyPoints(
246-
points: holes[j],
247-
tolerance: tolerance,
248-
highQuality: true,
249-
),
250-
growable: false,
251-
),
252-
);
253-
},
254-
growable: false,
255-
);
256-
}
257193
}

lib/src/layer/polygon_layer/projected_polygon.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
part of 'polygon_layer.dart';
22

33
@immutable
4-
base class _ProjectedPolygon<R extends Object> extends HitDetectableElement<R> {
4+
class _ProjectedPolygon<R extends Object> with HitDetectableElement<R> {
55
final Polygon<R> polygon;
66
final List<DoublePoint> points;
77
final List<List<DoublePoint>> holePoints;

0 commit comments

Comments
 (0)