diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart index 1489e49a1..98d50b3e4 100644 --- a/example/lib/pages/polygon.dart +++ b/example/lib/pages/polygon.dart @@ -328,6 +328,35 @@ class _PolygonPageState extends State { 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), diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart index 60ef904a8..499481017 100644 --- a/example/lib/pages/polyline.dart +++ b/example/lib/pages/polyline.dart @@ -22,6 +22,23 @@ class _PolylinePageState extends State { List>? _hoverLines; final _polylinesRaw = >[ + 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), diff --git a/lib/src/geo/crs.dart b/lib/src/geo/crs.dart index 63b5e1936..c8ea74bf1 100644 --- a/lib/src/geo/crs.dart +++ b/lib/src/geo/crs.dart @@ -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'; import 'package:latlong2/latlong.dart'; import 'package:meta/meta.dart'; import 'package:proj4dart/proj4dart.dart' as proj4; @@ -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 projectList(List points, {LatLng? referencePoint}) { + late double previousX; + final halfWorldWith = getHalfWorldWidth(); + return List.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 > halfWorldWith) { + x -= 2 * halfWorldWith; + } else if (x - previousX < -halfWorldWith) { + x += 2 * halfWorldWith; + } + } + previousX = x; + return DoublePoint(x, y); + }, + growable: false, + ); + } } class _LonLat extends Projection { diff --git a/lib/src/layer/polygon_layer/painter.dart b/lib/src/layer/polygon_layer/painter.dart index 44aff4b8b..91ed21a74 100644 --- a/lib/src/layer/polygon_layer/painter.dart +++ b/lib/src/layer/polygon_layer/painter.dart @@ -286,13 +286,12 @@ base class _PolygonPainter // and the normal points are the same filledPath.fillType = PathFillType.evenOdd; - final holeOffsetsList = List>.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 @@ -307,15 +306,23 @@ base class _PolygonPainter } 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, + ); + } } } @@ -434,28 +441,6 @@ base class _PolygonPainter } } - void _addHoleBordersToPath( - Path path, - Polygon polygon, - List> 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 ( diff --git a/lib/src/layer/polygon_layer/projected_polygon.dart b/lib/src/layer/polygon_layer/projected_polygon.dart index 1c25ef0f5..af18c74be 100644 --- a/lib/src/layer/polygon_layer/projected_polygon.dart +++ b/lib/src/layer/polygon_layer/projected_polygon.dart @@ -18,35 +18,22 @@ class _ProjectedPolygon with HitDetectableElement { _ProjectedPolygon._fromPolygon(Projection projection, Polygon polygon) : this._( polygon: polygon, - points: List.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 >[]; } return List>.generate( holes.length, - (j) { - final points = holes[j]; - return List.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, ); }(), diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 47da085ff..1741f6e2e 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -140,6 +140,8 @@ class _PolylineLayerState extends State> projection.project(boundsAdjusted.northEast), ); + final halfWorldWidth = projection.getHalfWorldWidth(); + for (final projectedPolyline in polylines) { final polyline = projectedPolyline.polyline; @@ -149,6 +151,12 @@ class _PolylineLayerState extends State> 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; diff --git a/lib/src/layer/polyline_layer/projected_polyline.dart b/lib/src/layer/polyline_layer/projected_polyline.dart index eca98465a..feb05bbe1 100644 --- a/lib/src/layer/polyline_layer/projected_polyline.dart +++ b/lib/src/layer/polyline_layer/projected_polyline.dart @@ -16,13 +16,16 @@ class _ProjectedPolyline with HitDetectableElement { _ProjectedPolyline._fromPolyline(Projection projection, Polyline polyline) : this._( polyline: polyline, - points: List.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) { + for (final point in points) { + if (point.x > halfWorldWidth || point.x < -halfWorldWidth) { + return true; + } + } + return false; + } }