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

feat: allow polylines & polygons to cross world boundary #1969

Merged
merged 16 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
29 changes: 29 additions & 0 deletions example/lib/pages/polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,35 @@ class _PolygonPageState extends State<PolygonPage> {
simplificationTolerance: 0,
useAltRendering: true,
polygons: [
Polygon(
points: const [
LatLng(40, 150),
LatLng(45, 160),
LatLng(50, 170),
LatLng(55, 180),
LatLng(50, -170),
LatLng(45, -160),
LatLng(40, -150),
LatLng(35, -160),
LatLng(30, -170),
LatLng(25, -180),
LatLng(30, 170),
LatLng(35, 160),
],
holePointsList: const [
[
LatLng(45, 175),
LatLng(45, -175),
LatLng(35, -175),
LatLng(35, 175),
],
],
color: const Color(0xFFFF0000),
hitValue: (
title: 'Red Line',
subtitle: 'Across the universe...',
),
),
Polygon(
points: const [
LatLng(50, -18),
Expand Down
17 changes: 17 additions & 0 deletions example/lib/pages/polyline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,23 @@ class _PolylinePageState extends State<PolylinePage> {
List<Polyline<HitValue>>? _hoverLines;

final _polylinesRaw = <Polyline<HitValue>>[
Polyline(
points: const [
LatLng(40, 150),
LatLng(45, 160),
LatLng(50, 170),
LatLng(55, 180),
LatLng(50, -170),
LatLng(45, -160),
LatLng(40, -150),
],
strokeWidth: 8,
color: const Color(0xFFFF0000),
hitValue: (
title: 'Red Line',
subtitle: 'Across the universe...',
),
),
Polyline(
points: [
const LatLng(51.5, -0.09),
Expand Down
40 changes: 40 additions & 0 deletions lib/src/geo/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:math' as math hide Point;
import 'dart:math' show Point;

import 'package:flutter_map/src/misc/bounds.dart';
import 'package:flutter_map/src/misc/simplify.dart';
mootw marked this conversation as resolved.
Show resolved Hide resolved
import 'package:latlong2/latlong.dart';
import 'package:meta/meta.dart';
import 'package:proj4dart/proj4dart.dart' as proj4;
Expand Down Expand Up @@ -388,6 +389,45 @@ abstract class Projection {

/// unproject cartesian x,y coordinates to [LatLng].
LatLng unprojectXY(double x, double y);

/// Returns half the width of the world in geometry coordinates.
double getHalfWorldWidth() {
final (x0, _) = projectXY(const LatLng(0, 0));
final (x180, _) = projectXY(const LatLng(0, 180));
return x0 > x180 ? x0 - x180 : x180 - x0;
}

/// Projects a list of [LatLng]s into geometry coordinates.
///
/// All resulting points gather somehow around the first point, or the
/// optional [referencePoint] if provided.
/// The typical use-case is when you display the whole world: you don't want
/// longitudes -179 and 179 to be projected each on one side.
/// [referencePoint] is used for polygon holes: we want the holes to be
/// displayed close to the polygon, not on the other side of the world.
List<DoublePoint> projectList(List<LatLng> points, {LatLng? referencePoint}) {
late double previousX;
final halfWorldWidth = getHalfWorldWidth();
return List<DoublePoint>.generate(
points.length,
(j) {
if (j == 0 && referencePoint != null) {
(previousX, _) = projectXY(referencePoint);
}
var (x, y) = projectXY(points[j]);
if (j > 0 || referencePoint != null) {
if (x - previousX > halfWorldWidth) {
x -= 2 * halfWorldWidth;
} else if (x - previousX < -halfWorldWidth) {
x += 2 * halfWorldWidth;
}
}
previousX = x;
return DoublePoint(x, y);
},
growable: false,
);
}
}

class _LonLat extends Projection {
Expand Down
61 changes: 23 additions & 38 deletions lib/src/layer/polygon_layer/painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,12 @@ base class _PolygonPainter<R extends Object>
// and the normal points are the same
filledPath.fillType = PathFillType.evenOdd;

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

for (final holeOffsets in holeOffsetsList) {
for (final singleHolePoints in projectedPolygon.holePoints) {
final holeOffsets = getOffsetsXY(
camera: camera,
origin: origin,
points: singleHolePoints,
);
filledPath.addPolygon(holeOffsets, true);

// TODO: Potentially more efficient and may change the need to do
Expand All @@ -307,15 +306,23 @@ base class _PolygonPainter<R extends Object>
}

if (!polygon.disableHolesBorder && polygon.borderStrokeWidth > 0.0) {
_addHoleBordersToPath(
borderPath,
polygon,
holeOffsetsList,
size,
canvas,
_getBorderPaint(polygon),
polygon.borderStrokeWidth,
);
final borderPaint = _getBorderPaint(polygon);
for (final singleHolePoints in projectedPolygon.holePoints) {
final holeOffsets = getOffsetsXY(
camera: camera,
origin: origin,
points: singleHolePoints,
);
_addBorderToPath(
borderPath,
polygon,
holeOffsets,
size,
canvas,
borderPaint,
polygon.borderStrokeWidth,
);
}
}
}

Expand Down Expand Up @@ -434,28 +441,6 @@ base class _PolygonPainter<R extends Object>
}
}

void _addHoleBordersToPath(
Path path,
Polygon polygon,
List<List<Offset>> holeOffsetsList,
Size canvasSize,
Canvas canvas,
Paint paint,
double strokeWidth,
) {
for (final offsets in holeOffsetsList) {
_addBorderToPath(
path,
polygon,
offsets,
canvasSize,
canvas,
paint,
strokeWidth,
);
}
}

({Offset min, Offset max}) _getBounds(Offset origin, Polygon polygon) {
final bBox = polygon.boundingBox;
return (
Expand Down
25 changes: 6 additions & 19 deletions lib/src/layer/polygon_layer/projected_polygon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,22 @@ class _ProjectedPolygon<R extends Object> with HitDetectableElement<R> {
_ProjectedPolygon._fromPolygon(Projection projection, Polygon<R> polygon)
: this._(
polygon: polygon,
points: List<DoublePoint>.generate(
polygon.points.length,
(j) {
final (x, y) = projection.projectXY(polygon.points[j]);
return DoublePoint(x, y);
},
growable: false,
),
points: projection.projectList(polygon.points),
holePoints: () {
final holes = polygon.holePointsList;
if (holes == null ||
holes.isEmpty ||
polygon.points.isEmpty ||
holes.every((e) => e.isEmpty)) {
return <List<DoublePoint>>[];
}

return List<List<DoublePoint>>.generate(
holes.length,
(j) {
final points = holes[j];
return List<DoublePoint>.generate(
points.length,
(k) {
final (x, y) = projection.projectXY(points[k]);
return DoublePoint(x, y);
},
growable: false,
);
},
(j) => projection.projectList(
holes[j],
referencePoint: polygon.points[0],
),
growable: false,
);
}(),
Expand Down
8 changes: 8 additions & 0 deletions lib/src/layer/polyline_layer/polyline_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ class _PolylineLayerState<R extends Object> extends State<PolylineLayer<R>>
projection.project(boundsAdjusted.northEast),
);

final halfWorldWidth = projection.getHalfWorldWidth();

for (final projectedPolyline in polylines) {
final polyline = projectedPolyline.polyline;

Expand All @@ -149,6 +151,12 @@ class _PolylineLayerState<R extends Object> extends State<PolylineLayer<R>>
continue;
}

// TODO: think about how to cull polylines that go beyond the universe.
if (projectedPolyline.goesBeyondTheUniverse(halfWorldWidth)) {
yield projectedPolyline;
continue;
}

// pointer that indicates the start of the visible polyline segment
int start = -1;
bool containsSegment = false;
Expand Down
19 changes: 11 additions & 8 deletions lib/src/layer/polyline_layer/projected_polyline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ class _ProjectedPolyline<R extends Object> with HitDetectableElement<R> {
_ProjectedPolyline._fromPolyline(Projection projection, Polyline<R> polyline)
: this._(
polyline: polyline,
points: List<DoublePoint>.generate(
polyline.points.length,
(j) {
final (x, y) = projection.projectXY(polyline.points[j]);
return DoublePoint(x, y);
},
growable: false,
),
points: projection.projectList(polyline.points),
);

/// Returns true if the points stretch on different versions of the world.
bool goesBeyondTheUniverse(double halfWorldWidth) {
monsieurtanuki marked this conversation as resolved.
Show resolved Hide resolved
for (final point in points) {
if (point.x > halfWorldWidth || point.x < -halfWorldWidth) {
return true;
}
}
return false;
}
}
Loading