Skip to content

Commit

Permalink
feat: implement efficient(-ish) change detection for PolygonLayer &…
Browse files Browse the repository at this point in the history
… `PolylineLayer` (fleaflet#1904)
  • Loading branch information
JaffaKetchup authored Jun 8, 2024
1 parent a2bf534 commit ba17b39
Show file tree
Hide file tree
Showing 10 changed files with 367 additions and 249 deletions.
18 changes: 8 additions & 10 deletions example/lib/pages/polygon_perf_stress.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PolygonPerfStressPage extends StatefulWidget {
}

class _PolygonPerfStressPageState extends State<PolygonPerfStressPage> {
double simplificationTolerance = 0.5;
double simplificationTolerance = 0.3;
bool useAltRendering = true;
double borderThickness = 1;

Expand Down Expand Up @@ -63,15 +63,13 @@ class _PolygonPerfStressPageState extends State<PolygonPerfStressPage> {
openStreetMapTileLayer,
FutureBuilder(
future: geoJsonParser,
builder: (context, geoJsonParser) =>
geoJsonParser.connectionState != ConnectionState.done ||
geoJsonParser.data == null
? const SizedBox.shrink()
: PolygonLayer(
polygons: geoJsonParser.data!.polygons,
useAltRendering: useAltRendering,
simplificationTolerance: simplificationTolerance,
),
builder: (context, geoJsonParser) => geoJsonParser.data == null
? const SizedBox.shrink()
: PolygonLayer(
polygons: geoJsonParser.data!.polygons,
useAltRendering: useAltRendering,
simplificationTolerance: simplificationTolerance,
),
),
],
),
Expand Down
2 changes: 1 addition & 1 deletion example/lib/pages/polyline_perf_stress.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PolylinePerfStressPage extends StatefulWidget {
}

class _PolylinePerfStressPageState extends State<PolylinePerfStressPage> {
double simplificationTolerance = 0.5;
double simplificationTolerance = 0.3;

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

Expand Down
7 changes: 5 additions & 2 deletions lib/src/layer/circle_layer/circle_marker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ part of 'circle_layer.dart';
/// Immutable marker options for [CircleMarker]. Circle markers are a more
/// simple and performant way to draw markers as the regular [Marker]
@immutable
base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
class CircleMarker<R extends Object> with HitDetectableElement<R> {
/// An optional [Key] for the [CircleMarker].
/// This key is not used internally.
final Key? key;
Expand All @@ -27,6 +27,9 @@ base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
/// Set to true if the radius should use the unit meters.
final bool useRadiusInMeter;

@override
final R? hitValue;

/// Constructor to create a new [CircleMarker] object
const CircleMarker({
required this.point,
Expand All @@ -36,6 +39,6 @@ base class CircleMarker<R extends Object> extends HitDetectableElement<R> {
this.color = const Color(0xFF00FF00),
this.borderStrokeWidth = 0.0,
this.borderColor = const Color(0xFFFFFF00),
super.hitValue,
this.hitValue,
});
}
152 changes: 44 additions & 108 deletions lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/src/layer/shared/layer_interactivity/internal_hit_detectable.dart';
import 'package:flutter_map/src/layer/shared/layer_projection_simplification/state.dart';
import 'package:flutter_map/src/layer/shared/layer_projection_simplification/widget.dart';
import 'package:flutter_map/src/layer/shared/line_patterns/pixel_hiker.dart';
import 'package:flutter_map/src/misc/offsets.dart';
import 'package:flutter_map/src/misc/point_in_polygon.dart';
Expand All @@ -21,7 +23,8 @@ part 'projected_polygon.dart';

/// A polygon layer for [FlutterMap].
@immutable
class PolygonLayer<R extends Object> extends StatefulWidget {
base class PolygonLayer<R extends Object>
extends ProjectionSimplificationManagementSupportedWidget {
/// [Polygon]s to draw
final List<Polygon<R>> polygons;

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

/// Distance between two neighboring polygon points, in logical pixels scaled
/// to floored zoom
///
/// Increasing this value results in points further apart being collapsed and
/// thus more simplified polygons. Higher values improve performance at the
/// cost of visual fidelity and vice versa.
///
/// Defaults to 0.5. Set to 0 to disable simplification.
final double simplificationTolerance;

/// Whether to draw per-polygon labels
///
/// Defaults to `true`.
Expand All @@ -82,79 +75,63 @@ class PolygonLayer<R extends Object> extends StatefulWidget {
this.useAltRendering = false,
this.debugAltRenderer = false,
this.polygonCulling = true,
this.simplificationTolerance = 0.5,
this.polygonLabels = true,
this.drawLabelsLast = false,
this.hitNotifier,
}) : assert(
simplificationTolerance >= 0,
'simplificationTolerance cannot be negative: $simplificationTolerance',
);
super.simplificationTolerance,
super.useDynamicUpdate,
}) : super();

@override
State<PolygonLayer<R>> createState() => _PolygonLayerState<R>();
}

class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>> {
List<_ProjectedPolygon<R>>? _cachedProjectedPolygons;
final _cachedSimplifiedPolygons = <int, List<_ProjectedPolygon<R>>>{};

double? _devicePixelRatio;
class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>>
with
ProjectionSimplificationManagement<_ProjectedPolygon<R>, Polygon<R>,
PolygonLayer<R>> {
@override
_ProjectedPolygon<R> projectElement({
required Projection projection,
required Polygon<R> element,
}) =>
_ProjectedPolygon._fromPolygon(projection, element);

@override
void didUpdateWidget(PolygonLayer<R> oldWidget) {
super.didUpdateWidget(oldWidget);
_ProjectedPolygon<R> simplifyProjectedElement({
required _ProjectedPolygon<R> projectedElement,
required double tolerance,
}) =>
_ProjectedPolygon._(
polygon: projectedElement.polygon,
points: simplifyPoints(
points: projectedElement.points,
tolerance: tolerance,
highQuality: true,
),
holePoints: List.generate(
projectedElement.holePoints.length,
(j) => simplifyPoints(
points: projectedElement.holePoints[j],
tolerance: tolerance,
highQuality: true,
),
growable: false,
),
);

if (!listEquals(oldWidget.polygons, widget.polygons)) {
// If the polylines have changed, then both the projections and the
// projection-dependendent simplifications must be invalidated
_cachedProjectedPolygons = null;
_cachedSimplifiedPolygons.clear();
} else if (oldWidget.simplificationTolerance !=
widget.simplificationTolerance) {
// If only the simplification tolerance has changed, this does not affect
// the projections (as that is done before simplification), so only
// invalidate the simplifications
_cachedSimplifiedPolygons.clear();
}
}
@override
Iterable<Polygon<R>> getElements(PolygonLayer<R> widget) => widget.polygons;

@override
Widget build(BuildContext context) {
final camera = MapCamera.of(context);
super.build(context);

final projected = _cachedProjectedPolygons ??= List.generate(
widget.polygons.length,
(i) => _ProjectedPolygon._fromPolygon(
camera.crs.projection,
widget.polygons[i],
),
growable: false,
);

late final List<_ProjectedPolygon<R>> simplified;
if (widget.simplificationTolerance == 0) {
simplified = projected;
} else {
// If the DPR has changed, invalidate the simplification cache
final newDPR = MediaQuery.devicePixelRatioOf(context);
if (newDPR != _devicePixelRatio) {
_devicePixelRatio = newDPR;
_cachedSimplifiedPolygons.clear();
}

simplified = _cachedSimplifiedPolygons[camera.zoom.floor()] ??=
_computeZoomLevelSimplification(
camera: camera,
polygons: projected,
pixelTolerance: widget.simplificationTolerance,
devicePixelRatio: newDPR,
);
}
final camera = MapCamera.of(context);

final culled = !widget.polygonCulling
? simplified
: simplified
? simplifiedElements.toList()
: simplifiedElements
.where(
(p) => p.polygon.boundingBox.isOverlapping(camera.visibleBounds),
)
Expand Down Expand Up @@ -213,45 +190,4 @@ class _PolygonLayerState<R extends Object> extends State<PolygonLayer<R>> {
yield prevValue += polygon.holePoints[i].length;
}
}

List<_ProjectedPolygon<R>> _computeZoomLevelSimplification({
required MapCamera camera,
required List<_ProjectedPolygon<R>> polygons,
required double pixelTolerance,
required double devicePixelRatio,
}) {
final tolerance = getEffectiveSimplificationTolerance(
crs: camera.crs,
zoom: camera.zoom.floor(),
pixelTolerance: pixelTolerance,
devicePixelRatio: devicePixelRatio,
);

return List<_ProjectedPolygon<R>>.generate(
polygons.length,
(i) {
final polygon = polygons[i];
final holes = polygon.holePoints;

return _ProjectedPolygon._(
polygon: polygon.polygon,
points: simplifyPoints(
points: polygon.points,
tolerance: tolerance,
highQuality: true,
),
holePoints: List.generate(
holes.length,
(j) => simplifyPoints(
points: holes[j],
tolerance: tolerance,
highQuality: true,
),
growable: false,
),
);
},
growable: false,
);
}
}
2 changes: 1 addition & 1 deletion lib/src/layer/polygon_layer/projected_polygon.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
part of 'polygon_layer.dart';

@immutable
base class _ProjectedPolygon<R extends Object> extends HitDetectableElement<R> {
class _ProjectedPolygon<R extends Object> with HitDetectableElement<R> {
final Polygon<R> polygon;
final List<DoublePoint> points;
final List<List<DoublePoint>> holePoints;
Expand Down
Loading

0 comments on commit ba17b39

Please sign in to comment.