Skip to content

Commit

Permalink
fix: 1338 - longitude +-180 with correct polylines and polygons
Browse files Browse the repository at this point in the history
Impacted files
* `crs.dart`: new methods `getHalfWorldWidth` and `projectList`
* `painter.dart`: refactored using pre-computed `List<Double>`
* `polygon.dart`: added an example around longitude 180
* `polyline.dart`: added an example around longitude 180
* `polyline_layer.dart`: we don't cull polylines that go beyond longitude 180
* `projected_polygon.dart`: using new method `Projection.projectList`
* `projected_polyline.dart`: using new method `Projection.projectList`
  • Loading branch information
monsieurtanuki committed Sep 22, 2024
1 parent 5f2d646 commit a1e186c
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 65 deletions.
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';
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 halfWorldWith = 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 > halfWorldWith) {
x -= 2 * halfWorldWith;
} else if (x - previousX < -halfWorldWith) {
x += 2 * halfWorldWith;
}
}
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) {
for (final point in points) {
if (point.x > halfWorldWidth || point.x < -halfWorldWidth) {
return true;
}
}
return false;
}
}

0 comments on commit a1e186c

Please sign in to comment.