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

Polygon clipping package conversion #170

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

aardrop
Copy link

@aardrop aardrop commented Feb 17, 2024

A dart conversion of polygon_clipping with adopted dart native classes such as Point, flutter's Vector2, SplayTreeMap, and SplayTreeSet.

@aardrop
Copy link
Author

aardrop commented Feb 17, 2024

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:

  • Debug and test basic functionality
  • Is the class approach what we want to work with?
  • Are SplayTreeMap and SplayTreeSet working properly?
  • How do we want to stage polygon_clipping to expose object files relative to the original project?
  • Check nullsafety implementation and classes relative to original work?
  • How do we want to implement points relative to original work? Segment implements a point that also tracks attached events, this is currently being handled with an extension of the original math class.
  • Do we want to use this BoundingBox class, the one from Turf, or the Rectangle class from dart that has all the prebuilt method that the original project writes and is maintained under the dart repository?
  • What class structuring can we do to improve performance and readability?
  • Currently all variable maintain original names for the JS package but does naming convention need to be updated?
  • With the heavy typing of dart can we leverage the class structure more with things like factory constructors to improve dart specific performance?
  • Are there more generic JS inputs that could be improved by heavier typing like List<List to Turf’s polygon or List
  • How do we want to handle the index file?

High Level Work Left to be Done:

  • test and verify functionality from the bottom up
  • Verify approach and it’s fit into the turf_dart project
  • Write benchmarks

Copy link
Member

@lukas-h lukas-h left a 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/

Comment on lines 3 to 8
class BoundingBox {
Point ll; // Lower left point
Point ur; // Upper right point

BoundingBox(this.ll, this.ur);
}
Copy link
Member

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

Comment on lines 10 to 36
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));
}
Copy link
Member

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) {

Copy link
Author

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?

Copy link
Author

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?

Copy link
Member

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

Copy link
Author

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?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

lib/src/polygon_clipping/geom_in.dart Outdated Show resolved Hide resolved
Comment on lines 211 to 221
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;
}
Copy link
Member

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

Copy link
Author

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.

Comment on lines 4 to 14
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);
Copy link
Member

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) {
Copy link
Member

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';

Copy link
Member

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:

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)

Copy link
Author

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?

Copy link
Member

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';

Copy link
Member

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

Comment on lines 6 to 25
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;
Copy link
Member

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 Positions

Copy link
Author

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.

Copy link
Member

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

lib/src/polygon_clipping/vector_extension.dart Outdated Show resolved Hide resolved
@aardrop
Copy link
Author

aardrop commented Feb 17, 2024

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.

@aardrop
Copy link
Author

aardrop commented Feb 20, 2024

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/

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.

@lukas-h
Copy link
Member

lukas-h commented Feb 20, 2024

written. I'm happy to switch, but I'd probably need support from someone who understands that package better.

We can start with what you already have. Maybe @jsiedentop would be a good conversation partner to talk about different sweepline algo implementations.

@jsiedentop
Copy link
Contributor

@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.

@jsiedentop
Copy link
Contributor

@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?

@aardrop
Copy link
Author

aardrop commented Feb 28, 2024

@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!

@jsiedentop
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants