Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added option to use pxCache and also length check on Markers to avoid crash #1147

Merged
merged 2 commits into from
Feb 26, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 53 additions & 23 deletions lib/src/layer/marker_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import 'package:latlong2/latlong.dart';
class MarkerLayerOptions extends LayerOptions {
final List<Marker> 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;

Expand Down Expand Up @@ -38,6 +42,7 @@ class MarkerLayerOptions extends LayerOptions {
this.rotate = false,
this.rotateOrigin,
this.rotateAlignment = Alignment.center,
this.usePxCache = true,
Stream<Null>? rebuild,
}) : super(key: key, rebuild: rebuild);
}
Expand Down Expand Up @@ -190,11 +195,31 @@ class _MarkerLayerState extends State<MarkerLayer> {
// https://stackoverflow.com/questions/15943890/is-there-a-performance-benefit-in-using-fixed-length-lists-in-dart
var _pxCache = <CustomPoint>[];

// Calling this every time markerOpts change should guarantee proper length
List<CustomPoint> generatePxCache() => List.generate(
/// Calling this every time markerOpts change should guarantee proper length
List<CustomPoint> generatePxCache() {
if (widget.markerLayerOptions.usePxCache) {
return List.generate(
widget.markerLayerOptions.markers.length,
(i) => widget.map.project(widget.markerLayerOptions.markers[i].point),
);
}
return [];
}

bool updatePxCacheIfNeeded() {
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.markerLayerOptions.markers.length != _pxCache.length) {
_pxCache = generatePxCache();
didUpdate = true;
}
return didUpdate;
}

@override
void initState() {
Expand All @@ -214,15 +239,22 @@ class _MarkerLayerState extends State<MarkerLayer> {
return StreamBuilder<int?>(
stream: widget.stream, // a Stream<int> or null
builder: (BuildContext context, AsyncSnapshot<int?> snapshot) {
var layerOptions = widget.markerLayerOptions;
var map = widget.map;
var usePxCache = layerOptions.usePxCache;
var markers = <Widget>[];
final sameZoom = widget.map.zoom == lastZoom;
for (var i = 0; i < widget.markerLayerOptions.markers.length; i++) {
var marker = widget.markerLayerOptions.markers[i];
final sameZoom = map.zoom == lastZoom;

var cacheUpdated = updatePxCacheIfNeeded();

for (var i = 0; i < layerOptions.markers.length; i++) {
var marker = layerOptions.markers[i];

// Decide whether to use cached point or calculate it
var pxPoint =
sameZoom ? _pxCache[i] : widget.map.project(marker.point);
if (!sameZoom) {
var pxPoint = usePxCache && (sameZoom || cacheUpdated)
? _pxCache[i]
: map.project(marker.point);
if (!sameZoom && usePxCache) {
_pxCache[i] = pxPoint;
}

Expand All @@ -231,23 +263,21 @@ class _MarkerLayerState extends State<MarkerLayer> {
var sw = CustomPoint(pxPoint.x + width, pxPoint.y - height);
var ne = CustomPoint(pxPoint.x - width, pxPoint.y + height);

if (!widget.map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) {
if (!map.pixelBounds.containsPartialBounds(Bounds(sw, ne))) {
continue;
}

final pos = pxPoint - widget.map.getPixelOrigin();
final markerWidget =
(marker.rotate ?? widget.markerLayerOptions.rotate ?? false)
// 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 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(
Expand All @@ -260,7 +290,7 @@ class _MarkerLayerState extends State<MarkerLayer> {
),
);
}
lastZoom = widget.map.zoom;
lastZoom = map.zoom;
return Container(
child: Stack(
children: markers,
Expand Down