Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8bb6b0d
implementation of polyline and polygon simplification
mootw Oct 25, 2023
99d1ebf
implement polyline culling
mootw Nov 7, 2023
59134ce
Update lib/src/layer/polyline_layer.dart
mootw Nov 12, 2023
c234cf7
Merge remote-tracking branch 'upstream/master'
mootw Nov 12, 2023
7b52f70
move aabbContainsLine to bounds
mootw Nov 12, 2023
ecfe774
dart format, move simplify to its own library
mootw Nov 12, 2023
080846f
organize imports
mootw Nov 12, 2023
aa2e37e
add descriptions and move simplify to be internal
mootw Nov 21, 2023
8edfb79
dart format
mootw Nov 21, 2023
f2b95ee
Merge remote-tracking branch 'upstream/master'
mootw Nov 21, 2023
9db6a78
fix analysis issues
mootw Nov 21, 2023
bd97f85
fix linting error (after upgrading my sdk)
mootw Nov 21, 2023
d1f2265
Improved polyline example page
JaffaKetchup Nov 21, 2023
706ce46
Fix Inno Setup for Windows example app
JaffaKetchup Nov 21, 2023
0d28450
Minor improvements to example application
JaffaKetchup Nov 21, 2023
fbf6207
Merge branch 'master' into master
JaffaKetchup Nov 22, 2023
6e395ee
Merge remote-tracking branch 'upstream/master'
mootw Dec 2, 2023
aff2dfc
fix lint
mootw Dec 2, 2023
84849ac
Merge branch 'master' into master
josxha Dec 8, 2023
5a8d0e5
Merge branch 'master' into master
josxha Dec 10, 2023
145cbcc
Merge remote-tracking branch 'upstream/master'
mootw Jan 5, 2024
5adede5
simplify before culling
mootw Jan 5, 2024
056daf8
dart format
mootw Jan 5, 2024
0cd8088
Added caching of simplification to polylines
JaffaKetchup Jan 7, 2024
cd4cc0a
Fixed issue where `Polyline.hashCode`s were improperly calculated, le…
JaffaKetchup Jan 9, 2024
7aac486
Added `hitValue` and generic typing to `Polyline`s, and reflected cha…
JaffaKetchup Jan 10, 2024
7af6387
De-associated `PolylineHit(Notifier)` with polylines by renaming to `…
JaffaKetchup Jan 11, 2024
b45cff6
Minor documentation improvements to `LayerHit`
JaffaKetchup Jan 11, 2024
8e276d1
Renamed `LayerHit` to `LayerHitResult` for improved clarity
JaffaKetchup Jan 12, 2024
f56828f
Minor documentation improvements
JaffaKetchup Jan 12, 2024
1915959
Adjusted default `PolylineLayer.cullingMargin` value
JaffaKetchup Jan 12, 2024
7eba143
Minor documentation improvement
JaffaKetchup Jan 12, 2024
1119435
Removed nullability from `simplificationTolerance`
JaffaKetchup Jan 12, 2024
d177a13
Merge branch 'master' of https://github.com/mootw/flutter_map into pr…
JaffaKetchup Jan 12, 2024
12612d5
Minor improvements
JaffaKetchup Jan 13, 2024
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
325 changes: 226 additions & 99 deletions example/lib/pages/polyline.dart

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ export 'package:flutter_map/src/layer/attribution_layer/rich/source.dart';
export 'package:flutter_map/src/layer/attribution_layer/rich/widget.dart';
export 'package:flutter_map/src/layer/attribution_layer/simple.dart';
export 'package:flutter_map/src/layer/circle_layer.dart';
export 'package:flutter_map/src/layer/general/hit_detection.dart';
export 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart';
export 'package:flutter_map/src/layer/general/translucent_pointer.dart';
export 'package:flutter_map/src/layer/marker_layer.dart';
export 'package:flutter_map/src/layer/overlay_image_layer.dart';
export 'package:flutter_map/src/layer/polygon_layer/polygon_layer.dart';
export 'package:flutter_map/src/layer/polyline_layer.dart';
export 'package:flutter_map/src/layer/polyline_layer/polyline_layer.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_builder.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_coordinates.dart';
export 'package:flutter_map/src/layer/tile_layer/tile_display.dart';
Expand Down
32 changes: 32 additions & 0 deletions lib/src/layer/general/hit_detection.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/widgets.dart';
import 'package:latlong2/latlong.dart';
import 'package:meta/meta.dart';

/// Result emmitted by hit notifiers (see [LayerHitNotifier]) when a hit is
/// detected on a feature within the respective layer
///
/// Not emitted if the hit was not over a feature.
@immutable
class LayerHitResult<R extends Object> {
/// `hitValue`s from all features hit (which have `hitValue`s defined)
///
/// If a feature is hit but has no `hitValue` defined, it will not be included.
///
/// Ordered by their corresponding feature, first-to-last, visually
/// top-to-bottom.
final List<R> hitValues;

/// Coordinates of the detected hit
///
/// Note that this may not lie on a feature.
final LatLng point;

@internal
const LayerHitResult({required this.hitValues, required this.point});
}

/// A [ValueNotifier] that notifies:
///
/// * a [LayerHitResult] when a hit is detected on a feature in a layer
/// * `null` when a hit is detected on the layer but not on a feature
typedef LayerHitNotifier<R extends Object> = ValueNotifier<LayerHitResult<R>?>;
111 changes: 83 additions & 28 deletions lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import 'dart:math';
import 'dart:ui' as ui;

import 'package:flutter/widgets.dart';
import 'package:flutter_map/src/geo/latlng_bounds.dart';
import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart';
import 'package:flutter_map/src/layer/polygon_layer/label.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/misc/offsets.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:flutter_map/src/misc/simplify.dart';
import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI

enum PolygonLabelPlacement {
Expand Down Expand Up @@ -104,40 +105,67 @@ class Polygon {

@immutable
class PolygonLayer extends StatelessWidget {
/// [Polygon]s to draw
final List<Polygon> polygons;

/// screen space culling of polygons based on bounding box
/// Whether to cull polygons and polygon sections that are outside of the
/// viewport
///
/// Defaults to `true`.
final bool polygonCulling;

// Turn on/off per-polygon label drawing on the layer-level.
/// Distance between two mergeable polygon points, in decimal degrees scaled
/// to floored zoom
///
/// Increasing results in a more jagged, less accurate simplification, with
/// improved performance; 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.
final double simplificationTolerance;

/// Whether to draw per-polygon labels
///
/// Defaults to `true`.
final bool polygonLabels;

// Whether to draw labels last and thus over all the polygons.
/// Whether to draw labels last and thus over all the polygons
///
/// Defaults to `false`.
final bool drawLabelsLast;

const PolygonLayer({
super.key,
required this.polygons,
this.polygonCulling = false,
this.polygonCulling = true,
this.simplificationTolerance = 0.5,
this.polygonLabels = true,
this.drawLabelsLast = false,
});

@override
Widget build(BuildContext context) {
final map = MapCamera.of(context);
final size = Size(map.size.x, map.size.y);
final camera = MapCamera.of(context);

final pgons = polygonCulling
? polygons.where((p) {
return p.boundingBox.isOverlapping(map.visibleBounds);
}).toList()
final culledPolygons = polygonCulling
? polygons
.where((p) => p.boundingBox.isOverlapping(camera.visibleBounds))
.toList()
: polygons;

return MobileLayerTransformer(
child: CustomPaint(
painter: PolygonPainter(pgons, map, polygonLabels, drawLabelsLast),
size: size,
painter: PolygonPainter(
polygons: culledPolygons,
camera: camera,
polygonLabels: polygonLabels,
drawLabelsLast: drawLabelsLast,
simplificationTolerance: simplificationTolerance,
),
size: Size(camera.size.x, camera.size.y),
isComplex: true,
),
);
Expand All @@ -146,14 +174,19 @@ class PolygonLayer extends StatelessWidget {

class PolygonPainter extends CustomPainter {
final List<Polygon> polygons;
final MapCamera map;
final MapCamera camera;
final LatLngBounds bounds;
final bool polygonLabels;
final bool drawLabelsLast;
final double simplificationTolerance;

PolygonPainter(
this.polygons, this.map, this.polygonLabels, this.drawLabelsLast)
: bounds = map.visibleBounds;
PolygonPainter({
required this.polygons,
required this.camera,
required this.polygonLabels,
required this.simplificationTolerance,
required this.drawLabelsLast,
}) : bounds = camera.visibleBounds;

int get hash {
_hash ??= Object.hashAll(polygons);
Expand All @@ -165,8 +198,30 @@ class PolygonPainter extends CustomPainter {
({Offset min, Offset max}) getBounds(Offset origin, Polygon polygon) {
final bbox = polygon.boundingBox;
return (
min: getOffset(map, origin, bbox.southWest),
max: getOffset(map, origin, bbox.northEast),
min: getOffset(origin, bbox.southWest),
max: getOffset(origin, bbox.northEast),
);
}

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);
}

List<Offset> getOffsets(Offset origin, List<LatLng> points) {
final renderedPoints = simplificationTolerance != 0
? simplify(
points,
simplificationTolerance / pow(2, camera.zoom.floor()),
highestQuality: true,
)
: points;

return List.generate(
renderedPoints.length,
(index) => getOffset(origin, renderedPoints[index]),
growable: false,
);
}

Expand Down Expand Up @@ -205,14 +260,14 @@ class PolygonPainter extends CustomPainter {
lastHash = null;
}

final origin = (map.project(map.center) - map.size / 2).toOffset();
final origin = (camera.project(camera.center) - camera.size / 2).toOffset();

// Main loop constructing batched fill and border paths from given polygons.
for (final polygon in polygons) {
if (polygon.points.isEmpty) {
continue;
}
final offsets = getOffsets(map, origin, polygon.points);
final offsets = getOffsets(origin, polygon.points);

// The hash is based on the polygons visual properties. If the hash from
// the current and the previous polygon no longer match, we need to flush
Expand Down Expand Up @@ -242,7 +297,7 @@ class PolygonPainter extends CustomPainter {

final holeOffsetsList = List<List<Offset>>.generate(
holePointsList.length,
(i) => getOffsets(map, origin, holePointsList[i]),
(i) => getOffsets(origin, holePointsList[i]),
growable: false,
);

Expand All @@ -266,11 +321,11 @@ class PolygonPainter extends CustomPainter {
// The painter will be null if the layouting algorithm determined that
// there isn't enough space.
final painter = buildLabelTextPainter(
mapSize: map.size,
placementPoint: map.getOffsetFromOrigin(polygon.labelPosition),
mapSize: camera.size,
placementPoint: camera.getOffsetFromOrigin(polygon.labelPosition),
bounds: getBounds(origin, polygon),
textPainter: polygon.textPainter!,
rotationRad: map.rotationRad,
rotationRad: camera.rotationRad,
rotate: polygon.rotateLabel,
padding: 20,
);
Expand All @@ -294,12 +349,12 @@ class PolygonPainter extends CustomPainter {
final textPainter = polygon.textPainter;
if (textPainter != null) {
final painter = buildLabelTextPainter(
mapSize: map.size,
mapSize: camera.size,
placementPoint:
map.project(polygon.labelPosition).toOffset() - origin,
camera.project(polygon.labelPosition).toOffset() - origin,
bounds: getBounds(origin, polygon),
textPainter: textPainter,
rotationRad: map.rotationRad,
rotationRad: camera.rotationRad,
rotate: polygon.rotateLabel,
padding: 20,
);
Expand Down
Loading