-
Notifications
You must be signed in to change notification settings - Fork 31
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
Polygon clipping package conversion #170
base: main
Are you sure you want to change the base?
Conversation
This initial draft commit aimed to set a baseline direct line-by-line conversion of the original JS package to the best of my ability. There’s a lot of work to be done and no functionality has been tested yet, here’s the state of this draft and what’s left to be decided and completed. Basic work left to be done:
High Level Work Left to be Done:
|
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.
The main points from my code review:
–> I didn't take a look at every line of code, but I think I already pointed out a few important things
–> we always need strongly typed parameters and return types of functions (if you have dynamic, consider replacing them with GeoJSONObject [for any GeoJSON object] or GeometryObject [for a concrete Geometry like Point, LineString, Polygon, and the Multi** types])
-> Make sure to always use the types from our library, meaning, coordinates should always use the class Position
(which contains a lot of vector operations already (addition, subtraction, multiplication, etc.)) and Point
(which is the standard type from the GeoJSON RFC standard to encode geographical points)
-> We already have a sweepline intersections package, please take a look if you can use that instead of implementing all the logic from scratch https://github.com/dartclub/sweepline_intersections/
lib/src/polygon_clipping/bbox.dart
Outdated
class BoundingBox { | ||
Point ll; // Lower left point | ||
Point ur; // Upper right point | ||
|
||
BoundingBox(this.ll, this.ur); | ||
} |
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 BBox
class here – you can find it in the geojson.dart file
lib/src/polygon_clipping/bbox.dart
Outdated
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)); | ||
} |
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 take a look if we have similar helper functions from other already implemented functions – but let's leave it like it is for now.
Here I could find a helper for overlapping of bboxes.
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
List<List<List<List<double>>>> getGeom() { | ||
List<List<List<List<double>>>> geom = []; | ||
|
||
for (int i = 0, iMax = polys.length; i < iMax; i++) { | ||
List<List<List<double>>>? polyGeom = polys[i].getGeom(); | ||
if (polyGeom == null) continue; | ||
geom.add(polyGeom); | ||
} | ||
|
||
return geom; | ||
} |
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.
we should definitely use the turf GeoJSON classes here, like Polygon or MultiPolygon for static types
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.
Agreed, any advice on deliniating the return between multipolygon or polygons? I'm making good progress on testing all the components and working my way up to these here now.
lib/src/polygon_clipping/index.dart
Outdated
dynamic union(dynamic geom, List<dynamic> moreGeoms) => | ||
operation.run("union", geom, moreGeoms); | ||
|
||
dynamic intersection(dynamic geom, List<dynamic> moreGeoms) => | ||
operation.run("intersection", geom, moreGeoms); | ||
|
||
dynamic xor(dynamic geom, List<dynamic> moreGeoms) => | ||
operation.run("xor", geom, moreGeoms); | ||
|
||
dynamic difference(dynamic subjectGeom, List<dynamic> clippingGeoms) => | ||
operation.run("difference", subjectGeom, clippingGeoms); |
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.
we should have at least a "bit" of static typization for parameters and return types: we have the type GeoJSONObject for any geojson type (Feature, GeometryCollection, Polygon, Point, etc.) or GeometryObject
for a concrete geometry (Point, Linestring, Polygon, Multi**)
late String type; | ||
int numMultiPolys = 0; | ||
|
||
List<dynamic> run(String type, dynamic geom, List<dynamic> moreGeoms) { |
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.
we should always have proper typization instead of dynamic, (what I described with GeoJSONObject
and GeometryObject
)
@@ -0,0 +1,83 @@ | |||
import 'dart:collection'; | |||
import 'dart:math'; | |||
|
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.
we already have rounding functions in our helpers file.
Please take a look if that works for you:
turf_dart/lib/src/helpers.dart
Line 81 in f377e46
num round(num value, [num precision = 0]) { |
Another thing: Please always Position
class for coordinates (and maybe an extension or extend to add new things)
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.
Is it worth running a benchmark on the two methods or should I just use the existing one in helpers?
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.
If the existing one works for this use case, let's use that one. And if you see a need for perf. optimization, you can go for it and propose a rewrite of the round we have
// Give segments unique ID's to get consistent sorting of | ||
// segments and sweep events when all else is identical | ||
import 'dart:math'; | ||
|
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.
generally: strong types for parameters and return types of functions. Will take a look into this file in a future PR
lib/src/polygon_clipping/vector.dart
Outdated
num crossProduct(Point a, Point b) => a.x * b.y - a.y * b.x; | ||
|
||
/* Dot Product of two vectors with first point at origin */ | ||
num dotProduct(Point a, Point b) => a.x * b.x + a.y * b.y; | ||
|
||
/* Comparator for two vectors with same starting point */ | ||
num compareVectorAngles(Point basePt, Point endPt1, Point endPt2) { | ||
double res = orient2d( | ||
endPt1.x.toDouble(), | ||
endPt1.y.toDouble(), | ||
basePt.x.toDouble(), | ||
basePt.y.toDouble(), | ||
endPt2.x.toDouble(), | ||
endPt2.y.toDouble(), | ||
); | ||
return res > 0 | ||
? -1 | ||
: res < 0 | ||
? 1 | ||
: 0; |
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.
we already have implemented those functions on Position
s
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 can't seem to find the equivalent of the compareVectorAngles. Do you happen to know which method you are referring to? I see the dot and cross product.
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.
ok, compareVectorAngles
is the one function we don't have, but we have most of the others (esp. basic vector arithmetic like + - )
You can just use:
Position(...) + Position(...)
– it uses operator overloading in dart
I'll dive into this today thanks! I'll look at reusing the sweepline though the sweepline from polygon_clipping wasn't very well commented so I still need to understand it's functionality a bit more to understand if they are interchangeable. |
I looked at the sweepline_intersections package this weekend, and there might be a way to use this, but its logic is different than the one used by polygon_clipping, and as far as I can tell, it wouldn't be an easy swap with the logic already written. I'm happy to switch, but I'd probably need support from someone who understands that package better. The algorithm used for the intersections and the optimization of construction between the intersections is deeply intertwined between the relationships of the segmenting and reconstruction of polygon_clipping. Because the "intersection" is more than just a point in polygon clipping, returning both the broken segments and their relationships to each other and the optimization of the algorithm. If someone more familiar with that package wants to look at the relationship between SweetEvents, SweepLines, and Segments in this package, I'm happy to change it, but I don't see a good way to implement it. |
We can start with what you already have. Maybe @jsiedentop would be a good conversation partner to talk about different sweepline algo implementations. |
@lukas-h I'm not yet as deep into the subject as you may think. I would be fine with using different implementations initially. We could create an issue on the topic so that we can take a closer look later. It seems, that we have potential to improve the sweetline_intersections package to fit our needs in this case. With good test coverage of the polygon_clipping functionality, we shouldn’t be afraid to change the implementation under the hood someday in the future. |
@aardrop I'm planning a new release for this week. Any chance to finish this feature or will we save it for the next release? |
I'd love to finish it this week but I'm just over half way done writing the testing scripts. Unfortunately because it's all interconnected I'm not even certain its functional until I finish these tests. I'd plan on this going in the next one to be safe. If I can pull this together in the next two days I'll let you know! |
Thanks for the update. Take your time with the testing – there's no rush. I've planned a few smaller topics for the next days, so just wanted to check if we should sync up for the release or if you need more time. |
A dart conversion of polygon_clipping with adopted dart native classes such as Point, flutter's Vector2, SplayTreeMap, and SplayTreeSet.