diff --git a/packages/turf-line-overlap/index.ts b/packages/turf-line-overlap/index.ts index 2d503ff9ab..94c30acc54 100644 --- a/packages/turf-line-overlap/index.ts +++ b/packages/turf-line-overlap/index.ts @@ -2,6 +2,7 @@ import rbush from "@turf/geojson-rbush"; import lineSegment from "@turf/line-segment"; import nearestPointOnLine from "@turf/nearest-point-on-line"; import booleanPointOnLine from "@turf/boolean-point-on-line"; +import booleanEqual from "@turf/boolean-equal"; import { getCoords } from "@turf/invariant"; import { featureEach, segmentEach } from "@turf/meta"; import { @@ -13,7 +14,7 @@ import { MultiPolygon, GeoJsonProperties, } from "geojson"; -import { featureCollection, isObject } from "@turf/helpers"; +import { featureCollection, isObject, lineString, point } from "@turf/helpers"; import equal from "deep-equal"; /** @@ -56,94 +57,95 @@ function lineOverlap< // To-Do -- HACK way to support typescript const line: any = lineSegment(line1); tree.load(line); - var overlapSegment: Feature | undefined; - let additionalSegments: Feature[] = []; - - // Line Intersection // Iterate over line segments segmentEach(line2, function (segment) { - var doesOverlaps = false; - if (!segment) { return; } // Iterate over each segments which falls within the same bounds featureEach(tree.search(segment), function (match) { - if (doesOverlaps === false) { - var coordsSegment = getCoords(segment).sort(); - var coordsMatch: any = getCoords(match).sort(); + var coordsSegment = getCoords(segment).sort(); + var coordsMatch = getCoords(match).sort(); - // Segment overlaps feature - if (equal(coordsSegment, coordsMatch)) { - doesOverlaps = true; - // Overlaps already exists - only append last coordinate of segment - if (overlapSegment) { - overlapSegment = - concatSegment(overlapSegment, segment) || overlapSegment; - } else overlapSegment = segment; - // Match segments which don't share nodes (Issue #901) - } else if ( - tolerance === 0 - ? booleanPointOnLine(coordsSegment[0], match) && - booleanPointOnLine(coordsSegment[1], match) - : nearestPointOnLine(match, coordsSegment[0]).properties.dist! <= - tolerance && - nearestPointOnLine(match, coordsSegment[1]).properties.dist! <= - tolerance - ) { - doesOverlaps = true; - if (overlapSegment) { - overlapSegment = - concatSegment(overlapSegment, segment) || overlapSegment; - } else overlapSegment = segment; - } else if ( - tolerance === 0 - ? booleanPointOnLine(coordsMatch[0], segment) && - booleanPointOnLine(coordsMatch[1], segment) - : nearestPointOnLine(segment, coordsMatch[0]).properties.dist! <= - tolerance && - nearestPointOnLine(segment, coordsMatch[1]).properties.dist! <= - tolerance - ) { - // Do not define (doesOverlap = true) since more matches can occur within the same segment - // doesOverlaps = true; - if (overlapSegment) { - const combinedSegment = concatSegment(overlapSegment, match); - if (combinedSegment) { - overlapSegment = combinedSegment; - } else { - additionalSegments.push(match); + // Segment overlaps feature + if (booleanEqual(segment, match)) { + concatSegmentToFeatures(features, segment); + } else { + const segmentStartOnMatch = tolerance + ? nearestPointOnLine(match, coordsSegment[0]).properties.dist <= + tolerance + : booleanPointOnLine(coordsSegment[0], match); + const segmentEndOnMatch = tolerance + ? nearestPointOnLine(match, coordsSegment[1]).properties.dist <= + tolerance + : booleanPointOnLine(coordsSegment[1], match); + const matchStartOnSegment = tolerance + ? nearestPointOnLine(segment, coordsMatch[0]).properties.dist <= + tolerance + : booleanPointOnLine(coordsMatch[0], segment); + const matchEndOnSegment = tolerance + ? nearestPointOnLine(segment, coordsMatch[1]).properties.dist <= + tolerance + : booleanPointOnLine(coordsMatch[1], segment); + if (segmentStartOnMatch && segmentEndOnMatch) { + concatSegmentToFeatures(features, segment); + } else if (matchStartOnSegment && matchEndOnSegment) { + concatSegmentToFeatures(features, match); + } else { + const from = segmentStartOnMatch + ? coordsSegment[0] + : segmentEndOnMatch + ? coordsSegment[1] + : undefined; + const to = matchStartOnSegment + ? coordsMatch[0] + : matchEndOnSegment + ? coordsMatch[1] + : undefined; + if (from && to) { + const isSame = tolerance + ? nearestPointOnLine(lineString([from, from]), to).properties + .dist <= tolerance + : booleanEqual(point(from), point(to)); + if (!isSame) { + concatSegmentToFeatures(features, lineString([from, to])); } - } else overlapSegment = match; + } } } }); - - // Segment doesn't overlap - add overlaps to results & reset - if (doesOverlaps === false && overlapSegment) { - features.push(overlapSegment); - if (additionalSegments.length) { - features = features.concat(additionalSegments); - additionalSegments = []; - } - overlapSegment = undefined; - } }); - // Add last segment if exists - if (overlapSegment) features.push(overlapSegment); - return featureCollection(features); } +/** + * Add segment to list of linestrings + * + * @private + * @param {Feature[]} features Existing list + * @param {Feature} segment 2-vertex LineString to add to list + */ +function concatSegmentToFeatures( + features: Feature[], + segment: Feature +): void { + for (let i = features.length - 1; i >= 0; i--) { + if (concatSegment(features[i], segment)) { + return; + } + } + features.push(segment); +} + /** * Concat Segment * * @private * @param {Feature} line LineString * @param {Feature} segment 2-vertex LineString - * @returns {Feature} concat linestring + * @returns {Feature | void} concat linestring or nothing if no suitable place could be found in linestring */ function concatSegment( line: Feature, diff --git a/packages/turf-line-overlap/test/in/polygons-with-holes.geojson b/packages/turf-line-overlap/test/in/polygons-with-holes.geojson new file mode 100644 index 0000000000..7e060b92cb --- /dev/null +++ b/packages/turf-line-overlap/test/in/polygons-with-holes.geojson @@ -0,0 +1,59 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 10, + "stroke-opacity": 1, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [10, 10], + [10, 0], + [0, 0] + ], + [ + [4, 2], + [8, 2], + [8, 6], + [4, 2] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 3, + "stroke-opacity": 1, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [2, 0], + [6, 4], + [6, 0], + [2, 0] + ], + [ + [4, 2], + [5, 2], + [5, 0], + [4, 2] + ] + ] + } + } + ] +} diff --git a/packages/turf-line-overlap/test/out/issue-#901.geojson b/packages/turf-line-overlap/test/out/issue-#901.geojson index 09d13e5e25..4b1409509d 100644 --- a/packages/turf-line-overlap/test/out/issue-#901.geojson +++ b/packages/turf-line-overlap/test/out/issue-#901.geojson @@ -11,30 +11,7 @@ "geometry": { "type": "LineString", "coordinates": [ - [-113.33698987543352, 53.53214475018778], [-113.33690471442213, 53.53212132654082], - [-113.33698987543352, 53.53214475018778], - [-113.33704111881613, 53.53215959791441] - ] - }, - "bbox": [ - -113.33704111881613, - 53.53214475018778, - -113.33698987543352, - 53.53215959791441 - ], - "id": 13 - }, - { - "type": "Feature", - "properties": { - "stroke": "#0F0", - "fill": "#0F0", - "stroke-width": 25 - }, - "geometry": { - "type": "LineString", - "coordinates": [ [-113.33698987543352, 53.53214475018778], [-113.33690471442213, 53.53212132654082], [-113.33698987543352, 53.53214475018778], @@ -59,34 +36,11 @@ "geometry": { "type": "LineString", "coordinates": [ - [-113.33832502043951, 53.52244398828247], - [-113.3384152645109, 53.52244409344282], [-113.33847575084239, 53.52244416392682], - [-113.3384152645109, 53.52244409344282] - ] - }, - "bbox": [ - -113.3384152645109, - 53.52244398828247, - -113.33832502043951, - 53.52244409344282 - ], - "id": 20 - }, - { - "type": "Feature", - "properties": { - "stroke": "#0F0", - "fill": "#0F0", - "stroke-width": 25 - }, - "geometry": { - "type": "LineString", - "coordinates": [ + [-113.3384152645109, 53.52244409344282], [-113.33832502043951, 53.52244398828247], [-113.3384152645109, 53.52244409344282], - [-113.33847575084239, 53.52244416392682], - [-113.3384152645109, 53.52244409344282] + [-113.33847575084239, 53.52244416392682] ] }, "bbox": [ diff --git a/packages/turf-line-overlap/test/out/polygons-with-holes.geojson b/packages/turf-line-overlap/test/out/polygons-with-holes.geojson new file mode 100644 index 0000000000..4c408ad70e --- /dev/null +++ b/packages/turf-line-overlap/test/out/polygons-with-holes.geojson @@ -0,0 +1,90 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "stroke": "#0F0", + "fill": "#0F0", + "stroke-width": 25 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [6, 4], + [4, 2], + [5, 2] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#0F0", + "fill": "#0F0", + "stroke-width": 25 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [2, 0], + [6, 0] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#F00", + "fill": "#F00", + "stroke-width": 10, + "stroke-opacity": 1, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0, 0], + [10, 10], + [10, 0], + [0, 0] + ], + [ + [4, 2], + [8, 2], + [8, 6], + [4, 2] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#00F", + "fill": "#00F", + "stroke-width": 3, + "stroke-opacity": 1, + "fill-opacity": 0.1 + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [2, 0], + [6, 4], + [6, 0], + [2, 0] + ], + [ + [4, 2], + [5, 2], + [5, 0], + [4, 2] + ] + ] + } + } + ] +}