Skip to content

Commit abc17f5

Browse files
authored
Implement lineOverlap, booleanOverlap, refactoring and bug fixes (#174)
* implement lineOverlap, booleanOverlap, refactoring and bugFixes * minor updates * clearing up nisses mischief * update dependency: turf_equality
1 parent dafa3cd commit abc17f5

26 files changed

+1665
-151
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
147147
- [ ] lineArc
148148
- [ ] lineChunk
149149
- [ ] [lineIntersect](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_intersect.dart)
150-
- [ ] lineOverlap
150+
- [x] [lineOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_overlap.dart)
151151
- [x] [lineSegment](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_segment.dart)
152152
- [x] [lineSlice](https://github.com/dartclub/turf_dart/blob/main/lib/src/line_slice.dart)
153153
- [ ] lineSliceAlong
@@ -233,7 +233,7 @@ Any new benchmarks must be named `*_benchmark.dart` and reside in the
233233
- [x] [booleanDisjoint](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_disjoint.dart)
234234
- [x] [booleanEqual](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_equal.dart)
235235
- [x] [booleanIntersects](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_intersects.dart)
236-
- [ ] booleanOverlap
236+
- [x] [booleanOverlap](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_overlap.dart)
237237
- [x] [booleanParallel](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_parallel.dart)
238238
- [x] [booleanPointInPolygon](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_point_in_polygon.dart)
239239
- [x] [booleanPointOnLine](https://github.com/dartclub/turf_dart/blob/main/lib/src/booleans/boolean_point_on_line.dart)

lib/boolean.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export 'src/booleans/boolean_crosses.dart';
77
export 'src/booleans/boolean_disjoint.dart';
88
export 'src/booleans/boolean_equal.dart';
99
export 'src/booleans/boolean_intersects.dart';
10-
// export 'src/booleans/boolean_overlap.dart';
10+
export 'src/booleans/boolean_overlap.dart';
1111
export 'src/booleans/boolean_parallel.dart';
1212
export 'src/booleans/boolean_point_in_polygon.dart';
1313
export 'src/booleans/boolean_point_on_line.dart';

lib/line_overlap.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
library turf_line_overlap;
2+
3+
export "src/line_overlap.dart";

lib/src/booleans/boolean_contains.dart

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import 'package:turf/src/invariant.dart';
22
import 'package:turf/turf.dart';
3-
4-
import 'boolean_point_in_polygon.dart';
5-
import 'boolean_point_on_line.dart';
63
import 'boolean_helper.dart';
74

85
/// [booleanContains] returns [true] if the second geometry is completely contained
@@ -32,15 +29,15 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
3229
if (geom2 is Point) {
3330
return coords1 == coords2;
3431
} else {
35-
throw FeatureNotSupported(geom1, geom2);
32+
throw GeometryCombinationNotSupported(geom1, geom2);
3633
}
3734
} else if (geom1 is MultiPoint) {
3835
if (geom2 is Point) {
3936
return isPointInMultiPoint(geom2, geom1);
4037
} else if (geom2 is MultiPoint) {
4138
return isMultiPointInMultiPoint(geom2, geom1);
4239
} else {
43-
throw FeatureNotSupported(geom1, geom2);
40+
throw GeometryCombinationNotSupported(geom1, geom2);
4441
}
4542
} else if (geom1 is LineString) {
4643
if (geom2 is Point) {
@@ -50,7 +47,7 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
5047
} else if (geom2 is MultiPoint) {
5148
return isMultiPointOnLine(geom2, geom1);
5249
} else {
53-
throw FeatureNotSupported(geom1, geom2);
50+
throw GeometryCombinationNotSupported(geom1, geom2);
5451
}
5552
} else if (geom1 is Polygon) {
5653
if (geom2 is Point) {
@@ -63,10 +60,10 @@ bool booleanContains(GeoJSONObject feature1, GeoJSONObject feature2) {
6360
} else if (geom2 is MultiPoint) {
6461
return isMultiPointInPolygon(geom2, geom1);
6562
} else {
66-
throw FeatureNotSupported(geom1, geom2);
63+
throw GeometryCombinationNotSupported(geom1, geom2);
6764
}
6865
} else {
69-
throw FeatureNotSupported(geom1, geom2);
66+
throw GeometryCombinationNotSupported(geom1, geom2);
7067
}
7168
}
7269

lib/src/booleans/boolean_helper.dart

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@ import 'package:turf/src/bbox.dart';
44
import 'boolean_point_on_line.dart';
55
import 'boolean_point_in_polygon.dart';
66

7-
class FeatureNotSupported implements Exception {
7+
class GeometryNotSupported implements Exception {
8+
final GeometryObject geometry;
9+
GeometryNotSupported(this.geometry);
10+
11+
@override
12+
String toString() => "geometry not supported ($geometry).";
13+
}
14+
15+
class GeometryCombinationNotSupported implements Exception {
816
final GeometryObject geometry1;
917
final GeometryObject geometry2;
1018

11-
FeatureNotSupported(this.geometry1, this.geometry2);
19+
GeometryCombinationNotSupported(this.geometry1, this.geometry2);
1220

1321
@override
14-
String toString() =>
15-
"feature geometry not supported ($geometry1, $geometry2).";
22+
String toString() => "geometry not supported ($geometry1, $geometry2).";
1623
}
1724

1825
bool isPointInMultiPoint(Point point, MultiPoint multipoint) {

lib/src/booleans/boolean_overlap.dart

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import 'package:turf/helpers.dart';
2+
import 'package:turf/line_overlap.dart';
3+
import 'package:turf/line_segment.dart';
4+
import 'package:turf/src/invariant.dart';
5+
import 'package:turf/src/line_intersect.dart';
6+
import 'package:turf_equality/turf_equality.dart';
7+
import 'boolean_helper.dart';
8+
9+
/// Takes two geometries [firstFeature] and [secondFeature] and checks if they
10+
/// share an common area but are not completely contained by each other.
11+
///
12+
/// Supported Geometries are `Feature<MultiPoint>`, `Feature<LineString>`,
13+
/// `Feature<MultiLineString>`, `Feature<Polygon>`, `Feature<MultiPolygon>`.
14+
/// Features must be of the same type. LineString/MultiLineString and
15+
/// Polygon/MultiPolygon combinations are supported. If the Geometries are not
16+
/// supported an [GeometryNotSupported] or [GeometryCombinationNotSupported]
17+
/// error is thrown.
18+
///
19+
/// Returns false if [firstFeature] and [secondFeature] are equal.
20+
/// - MultiPoint: returns Returns true if the two MultiPoints share any point.
21+
/// - LineString: returns true if the two Lines share any line segment.
22+
/// - Polygon: returns true if the two Polygons intersect.
23+
///
24+
/// Example:
25+
/// ```dart
26+
/// final first = Polygon(coordinates: [
27+
/// [
28+
/// Position(0, 0),
29+
/// Position(0, 5),
30+
/// Position(5, 5),
31+
/// Position(5, 0),
32+
/// Position(0, 0)
33+
/// ]
34+
/// ]);
35+
/// final second = Polygon(coordinates: [
36+
/// [
37+
/// Position(1, 1),
38+
/// Position(1, 6),
39+
/// Position(6, 6),
40+
/// Position(6, 1),
41+
/// Position(1, 1)
42+
/// ]
43+
/// ]);
44+
/// final third = Polygon(coordinates: [
45+
/// [
46+
/// Position(10, 10),
47+
/// Position(10, 15),
48+
/// Position(15, 15),
49+
/// Position(15, 10),
50+
/// Position(10, 10)
51+
/// ]
52+
/// ]);
53+
///
54+
/// final isOverlapping = booleanOverlap(first, second);
55+
/// final isNotOverlapping = booleanOverlap(second, third);
56+
/// ```
57+
bool booleanOverlap(
58+
Feature firstFeature,
59+
Feature secondFeature,
60+
) {
61+
final first = getGeom(firstFeature);
62+
final second = getGeom(secondFeature);
63+
64+
_checkIfGeometryCombinationIsSupported(first, second);
65+
66+
final eq = Equality(
67+
reversedGeometries: true,
68+
shiftedPolygons: true,
69+
);
70+
if (eq.compare(first, second)) {
71+
return false;
72+
}
73+
74+
switch (first.runtimeType) {
75+
case MultiPoint:
76+
switch (second.runtimeType) {
77+
case MultiPoint:
78+
return _isMultiPointOverlapping(
79+
first as MultiPoint,
80+
second as MultiPoint,
81+
);
82+
default:
83+
throw GeometryCombinationNotSupported(first, second);
84+
}
85+
case MultiLineString:
86+
case LineString:
87+
switch (second.runtimeType) {
88+
case LineString:
89+
case MultiLineString:
90+
return _isLineOverlapping(first, second);
91+
default:
92+
throw GeometryCombinationNotSupported(first, second);
93+
}
94+
case MultiPolygon:
95+
case Polygon:
96+
switch (second.runtimeType) {
97+
case Polygon:
98+
case MultiPolygon:
99+
return _isPolygonOverlapping(first, second);
100+
default:
101+
throw GeometryCombinationNotSupported(first, second);
102+
}
103+
default:
104+
throw GeometryCombinationNotSupported(first, second);
105+
}
106+
}
107+
108+
bool _isGeometrySupported(GeometryObject geometry) =>
109+
geometry is MultiPoint ||
110+
geometry is LineString ||
111+
geometry is MultiLineString ||
112+
geometry is Polygon ||
113+
geometry is MultiPolygon;
114+
115+
void _checkIfGeometryCombinationIsSupported(
116+
GeometryObject first,
117+
GeometryObject second,
118+
) {
119+
if (!_isGeometrySupported(first) || !_isGeometrySupported(second)) {
120+
throw GeometryCombinationNotSupported(first, second);
121+
}
122+
}
123+
124+
void _checkIfGeometryIsSupported(GeometryObject geometry) {
125+
if (!_isGeometrySupported(geometry)) {
126+
throw GeometryNotSupported(geometry);
127+
}
128+
}
129+
130+
List<Feature<LineString>> _segmentsOfGeometry(GeometryObject geometry) {
131+
_checkIfGeometryIsSupported(geometry);
132+
List<Feature<LineString>> segments = [];
133+
segmentEach(
134+
geometry,
135+
(Feature<LineString> segment, _, __, ___, ____) {
136+
segments.add(segment);
137+
},
138+
);
139+
return segments;
140+
}
141+
142+
bool _isLineOverlapping(GeometryObject firstLine, GeometryObject secondLine) {
143+
for (final firstSegment in _segmentsOfGeometry(firstLine)) {
144+
for (final secondSegment in _segmentsOfGeometry(secondLine)) {
145+
if (lineOverlap(firstSegment, secondSegment).features.isNotEmpty) {
146+
return true;
147+
}
148+
}
149+
}
150+
return false;
151+
}
152+
153+
bool _isPolygonOverlapping(
154+
GeometryObject firstPolygon,
155+
GeometryObject secondPolygon,
156+
) {
157+
for (final firstSegment in _segmentsOfGeometry(firstPolygon)) {
158+
for (final secondSegment in _segmentsOfGeometry(secondPolygon)) {
159+
if (lineIntersect(firstSegment, secondSegment).features.isNotEmpty) {
160+
return true;
161+
}
162+
}
163+
}
164+
return false;
165+
}
166+
167+
bool _isMultiPointOverlapping(
168+
MultiPoint first,
169+
MultiPoint second,
170+
) {
171+
for (final firstPoint in first.coordinates) {
172+
for (final secondPoint in second.coordinates) {
173+
if (firstPoint == secondPoint) {
174+
return true;
175+
}
176+
}
177+
}
178+
return false;
179+
}

lib/src/booleans/boolean_within.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ import 'boolean_helper.dart';
2020
/// Position.of([1, 4])
2121
/// ],
2222
/// );
23-
/// booleanWithin(point, line);
24-
/// //=true
23+
/// final isWithin = booleanWithin(point, line); // true
2524
/// ```
2625
bool booleanWithin(
2726
GeoJSONObject feature1,
@@ -43,7 +42,7 @@ bool booleanWithin(
4342
case MultiPolygon:
4443
return isPointInMultiPolygon(point, geom2 as MultiPolygon);
4544
default:
46-
throw FeatureNotSupported(geom1, geom2);
45+
throw GeometryCombinationNotSupported(geom1, geom2);
4746
}
4847
case MultiPoint:
4948
final multipoint = geom1 as MultiPoint;
@@ -57,7 +56,7 @@ bool booleanWithin(
5756
case MultiPolygon:
5857
return isMultiPointInMultiPolygon(multipoint, geom2 as MultiPolygon);
5958
default:
60-
throw FeatureNotSupported(geom1, geom2);
59+
throw GeometryCombinationNotSupported(geom1, geom2);
6160
}
6261
case LineString:
6362
final line = geom1 as LineString;
@@ -69,7 +68,7 @@ bool booleanWithin(
6968
case MultiPolygon:
7069
return isLineInMultiPolygon(line, geom2 as MultiPolygon);
7170
default:
72-
throw FeatureNotSupported(geom1, geom2);
71+
throw GeometryCombinationNotSupported(geom1, geom2);
7372
}
7473
case Polygon:
7574
final polygon = geom1 as Polygon;
@@ -79,9 +78,9 @@ bool booleanWithin(
7978
case MultiPolygon:
8079
return isPolygonInMultiPolygon(polygon, geom2 as MultiPolygon);
8180
default:
82-
throw FeatureNotSupported(geom1, geom2);
81+
throw GeometryCombinationNotSupported(geom1, geom2);
8382
}
8483
default:
85-
throw FeatureNotSupported(geom1, geom2);
84+
throw GeometryCombinationNotSupported(geom1, geom2);
8685
}
8786
}

0 commit comments

Comments
 (0)