Skip to content

Commit

Permalink
Implement lineOverlap, booleanOverlap, refactoring and bug fixes (#174)
Browse files Browse the repository at this point in the history
* implement lineOverlap, booleanOverlap, refactoring and bugFixes

* minor updates

* clearing up nisses mischief

* update dependency: turf_equality
  • Loading branch information
jsiedentop authored Feb 29, 2024
1 parent dafa3cd commit abc17f5
Show file tree
Hide file tree
Showing 26 changed files with 1,665 additions and 151 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [ ] lineArc
- [ ] lineChunk
- [ ] [lineIntersect](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_intersect.dart)
- [ ] lineOverlap
- [x] [lineOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_overlap.dart)
- [x] [lineSegment](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_segment.dart)
- [x] [lineSlice](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice.dart)
- [ ] lineSliceAlong
Expand Down Expand Up @@ -233,7 +233,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
- [x] [booleanDisjoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_disjoint.dart)
- [x] [booleanEqual](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_equal.dart)
- [x] [booleanIntersects](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_intersects.dart)
- [ ] booleanOverlap
- [x] [booleanOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_overlap.dart)
- [x] [booleanParallel](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_parallel.dart)
- [x] [booleanPointInPolygon](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_point_in_polygon.dart)
- [x] [booleanPointOnLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_point_on_line.dart)
Expand Down
2 changes: 1 addition & 1 deletion lib/boolean.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export 'src/booleans/boolean_crosses.dart';
export 'src/booleans/boolean_disjoint.dart';
export 'src/booleans/boolean_equal.dart';
export 'src/booleans/boolean_intersects.dart';
// export 'src/booleans/boolean_overlap.dart';
export 'src/booleans/boolean_overlap.dart';
export 'src/booleans/boolean_parallel.dart';
export 'src/booleans/boolean_point_in_polygon.dart';
export 'src/booleans/boolean_point_on_line.dart';
Expand Down
3 changes: 3 additions & 0 deletions lib/line_overlap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
library turf_line_overlap;

export "src/line_overlap.dart";
13 changes: 5 additions & 8 deletions lib/src/booleans/boolean_contains.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import 'package:turf/src/invariant.dart';
import 'package:turf/turf.dart';

import 'boolean_point_in_polygon.dart';
import 'boolean_point_on_line.dart';
import 'boolean_helper.dart';

/// [booleanContains] returns [true] if the second geometry is completely contained
Expand Down Expand Up @@ -32,15 +29,15 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
if (geom2 is Point) {
return coords1 == coords2;
} else {
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
} else if (geom1 is MultiPoint) {
if (geom2 is Point) {
return isPointInMultiPoint(geom2, geom1);
} else if (geom2 is MultiPoint) {
return isMultiPointInMultiPoint(geom2, geom1);
} else {
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
} else if (geom1 is LineString) {
if (geom2 is Point) {
Expand All @@ -50,7 +47,7 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
} else if (geom2 is MultiPoint) {
return isMultiPointOnLine(geom2, geom1);
} else {
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
} else if (geom1 is Polygon) {
if (geom2 is Point) {
Expand All @@ -63,10 +60,10 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
} else if (geom2 is MultiPoint) {
return isMultiPointInPolygon(geom2, geom1);
} else {
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
} else {
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
}

Expand Down
15 changes: 11 additions & 4 deletions lib/src/booleans/boolean_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import 'package:turf/src/bbox.dart';
import 'boolean_point_on_line.dart';
import 'boolean_point_in_polygon.dart';

class FeatureNotSupported implements Exception {
class GeometryNotSupported implements Exception {
final GeometryObject geometry;
GeometryNotSupported(this.geometry);

@override
String toString() => "geometry not supported ($geometry).";
}

class GeometryCombinationNotSupported implements Exception {
final GeometryObject geometry1;
final GeometryObject geometry2;

FeatureNotSupported(this.geometry1, this.geometry2);
GeometryCombinationNotSupported(this.geometry1, this.geometry2);

@override
String toString() =>
"feature geometry not supported ($geometry1, $geometry2).";
String toString() => "geometry not supported ($geometry1, $geometry2).";
}

bool isPointInMultiPoint(Point point, MultiPoint multipoint) {
Expand Down
179 changes: 179 additions & 0 deletions lib/src/booleans/boolean_overlap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import 'package:turf/helpers.dart';
import 'package:turf/line_overlap.dart';
import 'package:turf/line_segment.dart';
import 'package:turf/src/invariant.dart';
import 'package:turf/src/line_intersect.dart';
import 'package:turf_equality/turf_equality.dart';
import 'boolean_helper.dart';

/// Takes two geometries [firstFeature] and [secondFeature] and checks if they
/// share an common area but are not completely contained by each other.
///
/// Supported Geometries are `Feature<MultiPoint>`, `Feature<LineString>`,
/// `Feature<MultiLineString>`, `Feature<Polygon>`, `Feature<MultiPolygon>`.
/// Features must be of the same type. LineString/MultiLineString and
/// Polygon/MultiPolygon combinations are supported. If the Geometries are not
/// supported an [GeometryNotSupported] or [GeometryCombinationNotSupported]
/// error is thrown.
///
/// Returns false if [firstFeature] and [secondFeature] are equal.
/// - MultiPoint: returns Returns true if the two MultiPoints share any point.
/// - LineString: returns true if the two Lines share any line segment.
/// - Polygon: returns true if the two Polygons intersect.
///
/// Example:
/// ```dart
/// final first = Polygon(coordinates: [
/// [
/// Position(0, 0),
/// Position(0, 5),
/// Position(5, 5),
/// Position(5, 0),
/// Position(0, 0)
/// ]
/// ]);
/// final second = Polygon(coordinates: [
/// [
/// Position(1, 1),
/// Position(1, 6),
/// Position(6, 6),
/// Position(6, 1),
/// Position(1, 1)
/// ]
/// ]);
/// final third = Polygon(coordinates: [
/// [
/// Position(10, 10),
/// Position(10, 15),
/// Position(15, 15),
/// Position(15, 10),
/// Position(10, 10)
/// ]
/// ]);
///
/// final isOverlapping = booleanOverlap(first, second);
/// final isNotOverlapping = booleanOverlap(second, third);
/// ```
bool booleanOverlap(
Feature firstFeature,
Feature secondFeature,
) {
final first = getGeom(firstFeature);
final second = getGeom(secondFeature);

_checkIfGeometryCombinationIsSupported(first, second);

final eq = Equality(
reversedGeometries: true,
shiftedPolygons: true,
);
if (eq.compare(first, second)) {
return false;
}

switch (first.runtimeType) {
case MultiPoint:
switch (second.runtimeType) {
case MultiPoint:
return _isMultiPointOverlapping(
first as MultiPoint,
second as MultiPoint,
);
default:
throw GeometryCombinationNotSupported(first, second);
}
case MultiLineString:
case LineString:
switch (second.runtimeType) {
case LineString:
case MultiLineString:
return _isLineOverlapping(first, second);
default:
throw GeometryCombinationNotSupported(first, second);
}
case MultiPolygon:
case Polygon:
switch (second.runtimeType) {
case Polygon:
case MultiPolygon:
return _isPolygonOverlapping(first, second);
default:
throw GeometryCombinationNotSupported(first, second);
}
default:
throw GeometryCombinationNotSupported(first, second);
}
}

bool _isGeometrySupported(GeometryObject geometry) =>
geometry is MultiPoint ||
geometry is LineString ||
geometry is MultiLineString ||
geometry is Polygon ||
geometry is MultiPolygon;

void _checkIfGeometryCombinationIsSupported(
GeometryObject first,
GeometryObject second,
) {
if (!_isGeometrySupported(first) || !_isGeometrySupported(second)) {
throw GeometryCombinationNotSupported(first, second);
}
}

void _checkIfGeometryIsSupported(GeometryObject geometry) {
if (!_isGeometrySupported(geometry)) {
throw GeometryNotSupported(geometry);
}
}

List<Feature<LineString>> _segmentsOfGeometry(GeometryObject geometry) {
_checkIfGeometryIsSupported(geometry);
List<Feature<LineString>> segments = [];
segmentEach(
geometry,
(Feature<LineString> segment, _, __, ___, ____) {
segments.add(segment);
},
);
return segments;
}

bool _isLineOverlapping(GeometryObject firstLine, GeometryObject secondLine) {
for (final firstSegment in _segmentsOfGeometry(firstLine)) {
for (final secondSegment in _segmentsOfGeometry(secondLine)) {
if (lineOverlap(firstSegment, secondSegment).features.isNotEmpty) {
return true;
}
}
}
return false;
}

bool _isPolygonOverlapping(
GeometryObject firstPolygon,
GeometryObject secondPolygon,
) {
for (final firstSegment in _segmentsOfGeometry(firstPolygon)) {
for (final secondSegment in _segmentsOfGeometry(secondPolygon)) {
if (lineIntersect(firstSegment, secondSegment).features.isNotEmpty) {
return true;
}
}
}
return false;
}

bool _isMultiPointOverlapping(
MultiPoint first,
MultiPoint second,
) {
for (final firstPoint in first.coordinates) {
for (final secondPoint in second.coordinates) {
if (firstPoint == secondPoint) {
return true;
}
}
}
return false;
}
13 changes: 6 additions & 7 deletions lib/src/booleans/boolean_within.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ import 'boolean_helper.dart';
/// Position.of([1, 4])
/// ],
/// );
/// booleanWithin(point, line);
/// //=true
/// final isWithin = booleanWithin(point, line); // true
/// ```
bool booleanWithin(
GeoJSONObject feature1,
Expand All @@ -43,7 +42,7 @@ bool booleanWithin(
case MultiPolygon:
return isPointInMultiPolygon(point, geom2 as MultiPolygon);
default:
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
case MultiPoint:
final multipoint = geom1 as MultiPoint;
Expand All @@ -57,7 +56,7 @@ bool booleanWithin(
case MultiPolygon:
return isMultiPointInMultiPolygon(multipoint, geom2 as MultiPolygon);
default:
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
case LineString:
final line = geom1 as LineString;
Expand All @@ -69,7 +68,7 @@ bool booleanWithin(
case MultiPolygon:
return isLineInMultiPolygon(line, geom2 as MultiPolygon);
default:
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
case Polygon:
final polygon = geom1 as Polygon;
Expand All @@ -79,9 +78,9 @@ bool booleanWithin(
case MultiPolygon:
return isPolygonInMultiPolygon(polygon, geom2 as MultiPolygon);
default:
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
default:
throw FeatureNotSupported(geom1, geom2);
throw GeometryCombinationNotSupported(geom1, geom2);
}
}
Loading

0 comments on commit abc17f5

Please sign in to comment.