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

Implements booleanOverlap and line_overlap function #141

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 4 additions & 2 deletions lib/src/bbox.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ BBox bbox(GeoJSONObject geoJson, {bool recompute = false}) {
}

var result = BBox.named(
lat1: double.infinity,
// min x & y
lng1: double.infinity,
lat2: double.negativeInfinity,
lat1: double.infinity,
// max x & y
lng2: double.negativeInfinity,
lat2: double.negativeInfinity,
);

coordEach(
Expand Down
147 changes: 147 additions & 0 deletions lib/src/booleans/boolean_overlap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import 'package:turf/helpers.dart';
import 'package:turf/line_segment.dart';
import 'package:turf/src/invariant.dart';
import 'package:turf/src/line_intersect.dart';
import 'package:turf/src/line_overlap.dart';
import 'package:turf_equality/turf_equality.dart';

/// Compares two geometries of the same dimension and returns [true] if their
/// intersection Set results in a geometry different from both but of the same
/// dimension. It applies to [Polygon]/[Polygon], [LineString]/[LineString], [MultiPoint]/
/// [MultiPoint], [MultiLineString]/[MultiLineString] and [MultiPolygon]/[MultiPolygon].
/// In other words, it returns [true] if the two geometries overlap, provided that
/// neither completely contains the other.
/// Takes [feature1] and [feature2] which could be [Feature]<[LineString]|
/// [MultiLineString]|[Polygon]|[MultiPolygon]>
/// example
/// ```dart
/// var poly1 = Polygon(
/// coordinates: [
/// [
/// Position.of([0, 0]),
/// Position.of([0, 5]),
/// Position.of([5, 5]),
/// Position.of([5, 0]),
/// Position.of([0, 0])
/// ]
/// ],
/// );
/// var poly2 = Polygon(
/// coordinates: [
/// [
/// Position.of([1, 1]),
/// Position.of([1, 6]),
/// Position.of([6, 6]),
/// Position.of([6, 1]),
/// Position.of([1, 1])
/// ]
/// ],
/// );
/// var poly3 = Polygon(
/// coordinates: [
/// [
/// Position.of([10, 10]),
/// Position.of([10, 15]),
/// Position.of([15, 15]),
/// Position.of([15, 10]),
/// Position.of([10, 10])
/// ]
/// ],
/// );
/// booleanOverlap(poly1, poly2);
/// //=true
/// booleanOverlap(poly2, poly3);
/// //=false
/// ```
bool booleanOverlap(GeoJSONObject feature1, GeoJSONObject feature2) {
var geom1 = getGeom(feature1);
var geom2 = getGeom(feature2);

if ((feature1 is MultiPoint && feature2 is! MultiPoint) ||
((feature1 is LineString || feature1 is MultiLineString) &&
feature2 is! LineString &&
feature2 is! MultiLineString) ||
((feature1 is Polygon || feature1 is MultiPolygon) &&
feature2 is! Polygon &&
feature2 is! MultiPolygon)) {
throw Exception("features must be of the same type");
}
if (feature1 is Point) throw Exception("Point geometry not supported");

// features must be not equal
var equality = Equality(precision: 6);
if (equality.compare(feature1, feature2)) {
return false;
}

var overlap = 0;

if (geom1 is MultiPoint) {
for (var i = 0; i < geom1.coordinates.length; i++) {
for (var j = 0; j < (geom2 as MultiPoint).coordinates.length; j++) {
if (geom1.coordinates[i] == geom2.coordinates[j]) {
return true;
}
}
}
return false;
} else if (feature1 is MultiLineString) {
segmentEach(
feature1,
(
Feature<LineString> currentSegment,
int featureIndex,
int? multiFeatureIndex,
int? geometryIndex,
int segmentIndex,
) {
segmentEach(
feature2,
(
Feature<LineString> currentSegment1,
int featureIndex,
int? multiFeatureIndex,
int? geometryIndex,
int segmentIndex,
) {
if (lineOverlap(currentSegment, currentSegment1)
.features
.isNotEmpty) {
overlap++;
}
},
);
},
);
} else if (feature1 is Polygon || feature1 is MultiPolygon) {
segmentEach(
feature1,
(
Feature<LineString> currentSegment,
int featureIndex,
int? multiFeatureIndex,
int? geometryIndex,
int segmentIndex,
) {
segmentEach(
feature2,
(
Feature<LineString> currentSegment1,
int featureIndex,
int? multiFeatureIndex,
int? geometryIndex,
int segmentIndex,
) {
if (lineIntersect(currentSegment, currentSegment1)
.features
.isNotEmpty) {
overlap++;
}
},
);
},
);
}

return overlap > 0;
}
195 changes: 195 additions & 0 deletions lib/src/line_overlap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import 'package:rbush/rbush.dart';
import 'package:turf/bbox.dart';
import 'package:turf/helpers.dart';
import 'package:turf/line_segment.dart';
import 'package:turf/nearest_point_on_line.dart';
import 'package:turf/src/booleans/boolean_point_on_line.dart';
import 'package:turf/src/invariant.dart';
import 'package:turf/src/meta/feature.dart';
import 'package:turf_equality/turf_equality.dart';

/// Takes any [LineString] or [Polygon] and returns the overlapping [LineString]s
/// between both [Feature]s. [line1] is a [Feature]<[LineString]|[MultiLineString]
/// |[Polygon]|[MultiPolygon]> or any [LineString] or [Polygon], [line2] is a
/// [Feature]<[LineString]|[MultiLineString]|[Polygon]|[MultiPolygon]> or any
/// [LineString] or [Polygon]. [tolerance=0] Tolerance distance to match
/// overlapping line segments (in kilometers) returns a [FeatureCollection]<[LineString]>
/// lines(s) that are overlapping between both [Feature]s.
/// example
/// ```dart
/// var line1 = LineString(
/// coordinates: [
/// Position.of([115, -35]),
/// Position.of([125, -30]),
/// Position.of([135, -30]),
/// Position.of([145, -35])
/// ],
/// );
/// var line2 = LineString(
/// coordinates: [
/// Position.of([115, -25]),
/// Position.of([125, -30]),
/// Position.of([135, -30]),
/// Position.of([145, -25])
/// ],
/// );
/// var overlapping = lineOverlap(line1, line2);
/// //addToMap
/// var addToMap = [line1, line2, overlapping]
///```
FeatureCollection<LineString> lineOverlap(
GeoJSONObject line1, GeoJSONObject line2,
{num tolerance = 0}) {
RBushBox _toRBBox(Feature<LineString> feature) {
return RBushBox.fromList(bbox(feature).toList());
}

// Containers
var features = <Feature<LineString>>[];

// Create Spatial Index
var tree = RBushBase<Feature<LineString>>(
maxEntries: 4,
getMinX: (Feature<LineString> feature) => bbox(feature).lng1.toDouble(),
getMinY: (Feature<LineString> feature) => bbox(feature).lat1.toDouble(),
toBBox: _toRBBox,
);

FeatureCollection<LineString> line = lineSegment(line1);
tree.load(line.features);
Feature<LineString>? overlapSegment;
List<Feature<LineString>> additionalSegments = [];

// Line Intersection

// Iterate over line segments
segmentEach(line2, (Feature<LineString> currentSegment, int featureIndex,
int? multiFeatureIndex, int? geometryIndex, int segmentIndex) {
bool doesOverlap = false;
var list = tree.search(_toRBBox(currentSegment));
// Iterate over each segments which falls within the same bounds
featureEach(FeatureCollection<LineString>(features: list),
(Feature currentFeature, int featureIndex) {
if (!doesOverlap) {
var coords = getCoords(currentSegment) as List<Position>;
var coordsMatch = getCoords(currentFeature) as List<Position>;

coords.sort(((a, b) {
return a.lng < b.lng
? -1
: a.lng > b.lng
? 1
: 0;
}));

coordsMatch.sort(((a, b) {
return a.lng < b.lng
? -1
: a.lng > b.lng
? 1
: 0;
}));

Equality eq = Equality();
// Segment overlaps feature - with dummy LineStrings just to use eq.
if (eq.compare(LineString(coordinates: coords),
LineString(coordinates: coordsMatch))) {
doesOverlap = true;
// Overlaps already exists - only append last coordinate of segment
if (overlapSegment != null) {
overlapSegment = concatSegment(overlapSegment!, currentSegment) ??
overlapSegment;
} else {
overlapSegment = currentSegment;
}
// Match segments which don't share nodes (Issue #901)
} else if (tolerance == 0
? booleanPointOnLine(Point(coordinates: coords[0]), currentFeature.geometry as LineString) &&
booleanPointOnLine(Point(coordinates: coords[1]),
currentFeature.geometry as LineString)
: nearestPointOnLine(currentFeature.geometry as LineString, Point(coordinates: coords[0]))
.properties!['dist'] <=
tolerance &&
nearestPointOnLine(currentFeature.geometry as LineString, Point(coordinates: coords[1]))
.properties!['dist'] <=
tolerance) {
doesOverlap = true;
if (overlapSegment != null) {
overlapSegment = concatSegment(overlapSegment!, currentSegment) ??
overlapSegment;
} else {
overlapSegment = currentSegment;
}
} else if (tolerance == 0
? booleanPointOnLine(Point(coordinates: coordsMatch[0]),
currentSegment.geometry as LineString) &&
booleanPointOnLine(Point(coordinates: coordsMatch[1]),
currentSegment.geometry as LineString)
: nearestPointOnLine(currentSegment.geometry as LineString,
Point(coordinates: coordsMatch[0]))
.properties!['dist'] <=
tolerance &&
nearestPointOnLine(
currentSegment.geometry as LineString, Point(coordinates: coordsMatch[1]))
.properties!['dist'] <=
tolerance) {
// Do not define doesOverlap = true since more matches can occur
// within the same segment
// doesOverlaps = true;
if (overlapSegment != null) {
Feature<LineString>? combinedSegment = concatSegment(
overlapSegment!, currentFeature as Feature<LineString>);
if (combinedSegment != null) {
overlapSegment = combinedSegment;
} else {
additionalSegments.add(currentFeature);
}
} else {
overlapSegment = currentFeature as Feature<LineString>;
}
}
}
});

// Segment doesn't overlap - add overlaps to results & reset
if (doesOverlap == false && overlapSegment != null) {
features.add(overlapSegment!);
if (additionalSegments.isNotEmpty) {
features.addAll(additionalSegments);
additionalSegments = [];
}
overlapSegment = null;
}
});
// Add last segment if exists
if (overlapSegment != null) features.add(overlapSegment!);

return FeatureCollection(features: features);
}

Feature<LineString>? concatSegment(
Feature<LineString> line,
Feature<LineString> segment,
) {
var coords = getCoords(segment) as List<Position>;
var lineCoords = getCoords(line) as List<Position>;
Position start = lineCoords[0];
Position end = lineCoords[lineCoords.length - 1];
List<Position> positions = (line.geometry as LineString).clone().coordinates;

if (coords[0] == start) {
positions.insert(0, coords[1]);
} else if (coords[0] == end) {
positions.add(coords[1]);
} else if (coords[1] == start) {
positions.insert(0, coords[0]);
} else if (coords[1] == end) {
positions.add(coords[0]);
} else {
return null;
} // If the overlap leaves the segment unchanged, return null so that this can be
// identified.

// Otherwise return the mutated line.
return Feature(geometry: LineString(coordinates: positions));
}
Loading
Loading