Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions lib/src/polygon_clipping/bbox.dart
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);
}
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


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

30 changes: 30 additions & 0 deletions lib/src/polygon_clipping/flp.dart
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;
}
142 changes: 142 additions & 0 deletions lib/src/polygon_clipping/geom_in.dart
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;

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

Choose a reason for hiding this comment

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

should be a MultiPolygon* or Polygoninstead of just aList`

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;
}
Copy link
Member

Choose a reason for hiding this comment

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

if that's a sweepline-intersections implementation, we already have a package that we ported: https://github.com/dartclub/sweepline_intersections/

}

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

Choose a reason for hiding this comment

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

should be a MultiPolygon or Polygon instead of just a List<dynamic>

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

Choose a reason for hiding this comment

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

List<of what> as a return type?

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;
}
}
Loading