-
Notifications
You must be signed in to change notification settings - Fork 36
Polygon clipping package conversion #170
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
bcb21ac
1a9edbe
c5fe123
15f8fc3
5d33eff
36b6cf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||
| import 'dart:math'; | ||||
|
|
||||
| class BoundingBox { | ||||
| Point ll; // Lower left point | ||||
| Point ur; // Upper right point | ||||
|
|
||||
| BoundingBox(this.ll, this.ur); | ||||
| } | ||||
|
|
||||
| bool isInBbox(BoundingBox bbox, Point point) { | ||||
| return (bbox.ll.x <= point.x && | ||||
| point.x <= bbox.ur.x && | ||||
| bbox.ll.y <= point.y && | ||||
| point.y <= bbox.ur.y); | ||||
| } | ||||
|
|
||||
| BoundingBox? getBboxOverlap(BoundingBox b1, BoundingBox b2) { | ||||
| // Check if the bboxes overlap at all | ||||
| if (b2.ur.x < b1.ll.x || | ||||
| b1.ur.x < b2.ll.x || | ||||
| b2.ur.y < b1.ll.y || | ||||
| b1.ur.y < b2.ll.y) { | ||||
| return null; | ||||
| } | ||||
|
|
||||
| // Find the middle two X values | ||||
| final lowerX = b1.ll.x < b2.ll.x ? b2.ll.x : b1.ll.x; | ||||
| final upperX = b1.ur.x < b2.ur.x ? b1.ur.x : b2.ur.x; | ||||
|
|
||||
| // Find the middle two Y values | ||||
| final lowerY = b1.ll.y < b2.ll.y ? b2.ll.y : b1.ll.y; | ||||
| final upperY = b1.ur.y < b2.ur.y ? b1.ur.y : b2.ur.y; | ||||
|
|
||||
| // Create a new bounding box with the overlap | ||||
| return BoundingBox(Point(lowerX, lowerY), Point(upperX, upperY)); | ||||
| } | ||||
|
||||
| bool _doBBoxesOverlap(BBox bbox1, BBox bbox2) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that method, could I add methods and helpers to the existing BBox method to make it more interoperable with the rest of the package's dependency on Positions?
i.e. add factory constructor from Positons, add getter methods for Position 1 and 2 of the List lat lng points described?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more question on the bounding box, the specification the BBox class references https://datatracker.ietf.org/doc/html/rfc7946#section-5
It shows the convention of the first point being the lower left and the second point being the upper right but the BBox does not require these conditions but this polygon clipping uses this relationship to adjust the bounds to the points of the polygons.
Do you want me to add this to the BBox class, add methods/getters to make BBox work for polygon clipping, or make an extended class with this relationship?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that method, could I add methods and helpers to the existing BBox method to make it more interoperable with the rest of the package's dependency on Positions?
i.e. add factory constructor from Positons, add getter methods for Position 1 and 2 of the List lat lng points described?
sounds good – go for it!
Do you want me to add this to the BBox class, add methods/getters to make BBox work for polygon clipping, or make an extended class with this relationship?
let's stay conformant with the RFC and maybe work with an extension or class that extends BBox
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good to clarify the RFC seems to follow the lower left upper right convention, so we want to enforce that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| // Dart doesn't have integer math; everything is floating point. | ||
| // Precision is maintained using double-precision floating-point numbers. | ||
|
|
||
| // IE Polyfill (not applicable in Dart) | ||
| // If epsilon is undefined, set it to 2^-52 (similar to JavaScript). | ||
| // In Dart, this step is unnecessary. | ||
|
|
||
| // Calculate the square of epsilon for later use. | ||
|
|
||
| import 'package:turf/src/polygon_clipping/utils.dart'; | ||
|
|
||
| const double epsilonsqrd = epsilon * epsilon; | ||
| // FLP (Floating-Point) comparator function | ||
| int cmp(double a, double b) { | ||
| // Check if both numbers are close to zero. | ||
| if (-epsilon < a && a < epsilon) { | ||
| if (-epsilon < b && b < epsilon) { | ||
| return 0; // Both numbers are effectively zero. | ||
| } | ||
| } | ||
|
|
||
| // Check if the numbers are approximately equal (within epsilon). | ||
| final double ab = a - b; | ||
| if (ab * ab < epsilonsqrd * a * b) { | ||
| return 0; // Numbers are approximately equal. | ||
| } | ||
|
|
||
| // Normal comparison: return -1 if a < b, 1 if a > b. | ||
| return a < b ? -1 : 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import 'dart:math'; | ||
|
|
||
| import 'package:turf/src/polygon_clipping/bbox.dart'; | ||
| import 'package:turf/src/polygon_clipping/point_extension.dart'; | ||
|
|
||
| import 'rounder.dart'; | ||
| import 'segment.dart'; | ||
|
|
||
| //TODO: mark factory methods to remove late values; | ||
| class RingIn { | ||
| List<Segment> segments = []; | ||
| final bool isExterior; | ||
| final PolyIn poly; | ||
| late BoundingBox bbox; | ||
aardrop marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| RingIn(List<Point> geomRing, this.poly, this.isExterior) { | ||
| if (!(geomRing is List && geomRing.isNotEmpty)) { | ||
| throw ArgumentError( | ||
| "Input geometry is not a valid Polygon or MultiPolygon"); | ||
| } | ||
|
|
||
| final firstPoint = rounder.round(geomRing[0].x, geomRing[0].y); | ||
| bbox = BoundingBox( | ||
| Point(firstPoint.x, firstPoint.y), | ||
| Point(firstPoint.x, firstPoint.y), | ||
| ); | ||
|
|
||
| var prevPoint = firstPoint; | ||
| for (var i = 1; i < geomRing.length; i++) { | ||
| var point = rounder.round(geomRing[i].x, geomRing[i].y); | ||
| // skip repeated points | ||
| if (point.x == prevPoint.x && point.y == prevPoint.y) continue; | ||
| segments.add(Segment.fromRing(PointEvents.fromPoint(prevPoint), | ||
| PointEvents.fromPoint(point), this)); | ||
|
|
||
| bbox.ll = Point(min(point.x, bbox.ll.x), min(point.y, bbox.ll.y)); | ||
| bbox.ur = Point(max(point.x, bbox.ur.x), max(point.y, bbox.ur.y)); | ||
|
|
||
| prevPoint = point; | ||
| } | ||
| // add segment from last to first if last is not the same as first | ||
| if (firstPoint.x != prevPoint.x || firstPoint.y != prevPoint.y) { | ||
| segments.add(Segment.fromRing(PointEvents.fromPoint(prevPoint), | ||
| PointEvents.fromPoint(firstPoint), this)); | ||
| } | ||
| } | ||
|
|
||
| List getSweepEvents() { | ||
| final sweepEvents = []; | ||
| for (var i = 0; i < segments.length; i++) { | ||
| final segment = segments[i]; | ||
| sweepEvents.add(segment.leftSE); | ||
| sweepEvents.add(segment.rightSE); | ||
| } | ||
| return sweepEvents; | ||
| } | ||
| } | ||
|
|
||
| //TODO: mark factory methods to remove late values; | ||
| class PolyIn { | ||
| late RingIn exteriorRing; | ||
| late List<RingIn> interiorRings; | ||
| final MultiPolyIn multiPoly; | ||
| late BoundingBox bbox; | ||
|
|
||
| PolyIn(List<dynamic> geomPoly, this.multiPoly) { | ||
|
||
| if (!(geomPoly is List)) { | ||
| throw ArgumentError( | ||
| "Input geometry is not a valid Polygon or MultiPolygon"); | ||
| } | ||
| exteriorRing = RingIn(geomPoly[0], this, true); | ||
| // copy by value | ||
| bbox = exteriorRing.bbox; | ||
|
|
||
| interiorRings = []; | ||
| for (var i = 1; i < geomPoly.length; i++) { | ||
| final ring = RingIn(geomPoly[i], this, false); | ||
| bbox.ll = | ||
| Point(min(ring.bbox.ll.x, bbox.ll.x), min(ring.bbox.ll.y, bbox.ll.y)); | ||
| bbox.ur = | ||
| Point(max(ring.bbox.ur.x, bbox.ur.x), max(ring.bbox.ur.y, bbox.ur.y)); | ||
| interiorRings.add(ring); | ||
| } | ||
| } | ||
|
|
||
| List getSweepEvents() { | ||
| final sweepEvents = exteriorRing.getSweepEvents(); | ||
| for (var i = 0; i < interiorRings.length; i++) { | ||
| final ringSweepEvents = interiorRings[i].getSweepEvents(); | ||
| for (var j = 0; j < ringSweepEvents.length; j++) { | ||
| sweepEvents.add(ringSweepEvents[j]); | ||
| } | ||
| } | ||
| return sweepEvents; | ||
| } | ||
|
||
| } | ||
|
|
||
| //TODO: mark factory methods to remove late values; | ||
| class MultiPolyIn { | ||
| late List<PolyIn> polys; | ||
| final bool isSubject; | ||
| late BoundingBox bbox; | ||
|
|
||
| MultiPolyIn(List<dynamic> geom, this.isSubject) { | ||
|
||
| if (!(geom is List)) { | ||
| throw ArgumentError( | ||
| "Input geometry is not a valid Polygon or MultiPolygon"); | ||
| } | ||
|
|
||
| try { | ||
| // if the input looks like a polygon, convert it to a multipolygon | ||
| if (geom[0][0][0] is num) geom = [geom]; | ||
| } catch (ex) { | ||
| // The input is either malformed or has empty arrays. | ||
| // In either case, it will be handled later on. | ||
| } | ||
|
|
||
| polys = []; | ||
| bbox = BoundingBox( | ||
| Point(double.infinity, double.infinity), | ||
| Point(double.negativeInfinity, double.negativeInfinity), | ||
| ); | ||
| for (var i = 0; i < geom.length; i++) { | ||
| final poly = PolyIn(geom[i], this); | ||
| bbox.ll = | ||
| Point(min(poly.bbox.ll.x, bbox.ll.x), min(poly.bbox.ll.y, bbox.ll.y)); | ||
| bbox.ur = | ||
| Point(max(poly.bbox.ur.x, bbox.ur.x), max(poly.bbox.ur.y, bbox.ur.y)); | ||
| } | ||
| } | ||
|
|
||
| List getSweepEvents() { | ||
|
||
| final sweepEvents = []; | ||
| for (var i = 0; i < polys.length; i++) { | ||
| final polySweepEvents = polys[i].getSweepEvents(); | ||
| for (var j = 0; j < polySweepEvents.length; j++) { | ||
| sweepEvents.add(polySweepEvents[j]); | ||
| } | ||
| } | ||
| return sweepEvents; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's use the turf_dart
BBoxclass here – you can find it in the geojson.dart file