diff --git a/Sources/GISTools/Algorithms/LineOverlap.swift b/Sources/GISTools/Algorithms/LineOverlap.swift index 1244e80..b6c0e49 100644 --- a/Sources/GISTools/Algorithms/LineOverlap.swift +++ b/Sources/GISTools/Algorithms/LineOverlap.swift @@ -1,5 +1,5 @@ #if !os(Linux) -import CoreLocation + import CoreLocation #endif import Foundation @@ -23,8 +23,12 @@ extension LineSegment { /// The other segment partially overlaps with the first segment /// at the first segment's end. case otherPartialOverlapAtEnd + /// One of the segments is shorter than the tolerance + case shortSegment /// The first segment is fully included in the second segment. case thisOnOther + /// One of the segments has zero length and will be skipped + case zeroLengthSegment } /// Checks how the receiver and the other *LineSegment* lie in relation to each other. @@ -32,6 +36,8 @@ extension LineSegment { /// - Note: With `tolerance > 0.0` the segments in the result might not necessarily be /// parallel with each other. The result can then be filtered with ``LineSegment.isParallel``. /// + /// - Note: Altitude values will be ignored. + /// /// - Parameters: /// - other: The other *LineSegment* /// - tolerance: The tolerance, in meters @@ -43,43 +49,66 @@ extension LineSegment { let other = other.projected(to: projection) let tolerance = abs(tolerance) - if first == other.first, second == other.second { + if tolerance > 0.0, + length <= tolerance + || other.length <= tolerance + { + return .shortSegment + } + + if first.equals(other: second, compareAltitude: false) + || other.first.equals(other: other.second, compareAltitude: false) + { + return .zeroLengthSegment + } + + if first.equals(other: other.first, compareAltitude: false), + second.equals(other: other.second, compareAltitude: false) + { return .equal } - else if first == other.second, second == other.first { + + if first.equals(other: other.second, compareAltitude: false), + second.equals(other: other.first, compareAltitude: false) + { return .equalReversed } - else if tolerance == 0.0 { + + if tolerance == 0.0 { if other.checkIsOnSegment(first), other.checkIsOnSegment(second) { return .thisOnOther } - else if checkIsOnSegment(other.first), checkIsOnSegment(other.second) { + + if checkIsOnSegment(other.first), checkIsOnSegment(other.second) { return .otherOnThis } - else if other.second != first, - checkIsOnSegment(other.second), - other.checkIsOnSegment(first) + + if other.second != first, + checkIsOnSegment(other.second), + other.checkIsOnSegment(first) { return .otherPartialOverlapAtStart } - else if other.first != first, - checkIsOnSegment(other.first), - other.checkIsOnSegment(first) + + if other.first != first, + checkIsOnSegment(other.first), + other.checkIsOnSegment(first) { return .otherPartialOverlapAtStart } - else if other.first != second, - checkIsOnSegment(other.first), - other.checkIsOnSegment(second) + + if other.first != second, + checkIsOnSegment(other.first), + other.checkIsOnSegment(second) { return .otherPartialOverlapAtEnd } - else if other.second != second, - checkIsOnSegment(other.second), - other.checkIsOnSegment(second) + + if other.second != second, + checkIsOnSegment(other.second), + other.checkIsOnSegment(second) { return .otherPartialOverlapAtEnd - } } else if tolerance > 0.0 { @@ -88,35 +117,39 @@ extension LineSegment { { return .thisOnOther } - else if nearestCoordinateOnSegment(from: other.first).distance <= tolerance, - nearestCoordinateOnSegment(from: other.second).distance <= tolerance + + if nearestCoordinateOnSegment(from: other.first).distance <= tolerance, + nearestCoordinateOnSegment(from: other.second).distance <= tolerance { return .otherOnThis } - else if other.first.distance(from: second) > tolerance, - nearestCoordinateOnSegment(from: other.first).distance <= tolerance, - other.nearestCoordinateOnSegment(from: second).distance <= tolerance + + if other.first.distance(from: second) > tolerance, + nearestCoordinateOnSegment(from: other.first).distance <= tolerance, + other.nearestCoordinateOnSegment(from: second).distance <= tolerance { return .otherPartialOverlapAtEnd } - else if other.second.distance(from: first) > tolerance, - nearestCoordinateOnSegment(from: other.second).distance <= tolerance, - other.nearestCoordinateOnSegment(from: first).distance <= tolerance + + if other.second.distance(from: first) > tolerance, + nearestCoordinateOnSegment(from: other.second).distance <= tolerance, + other.nearestCoordinateOnSegment(from: first).distance <= tolerance { return .otherPartialOverlapAtStart } - else if other.first.distance(from: first) > tolerance, - nearestCoordinateOnSegment(from: other.first).distance <= tolerance, - other.nearestCoordinateOnSegment(from: first).distance <= tolerance + + if other.first.distance(from: first) > tolerance, + nearestCoordinateOnSegment(from: other.first).distance <= tolerance, + other.nearestCoordinateOnSegment(from: first).distance <= tolerance { return .otherPartialOverlapAtStart } - else if other.second.distance(from: second) > tolerance, - nearestCoordinateOnSegment(from: other.second).distance <= tolerance, - other.nearestCoordinateOnSegment(from: second).distance <= tolerance + + if other.second.distance(from: second) > tolerance, + nearestCoordinateOnSegment(from: other.second).distance <= tolerance, + other.nearestCoordinateOnSegment(from: second).distance <= tolerance { return .otherPartialOverlapAtEnd - } } @@ -138,6 +171,8 @@ extension GeoJson { /// - Note: Every match will be included in the result twice when comparing an object with itself. /// I.e. when A-B overlap, the result will also include B-A. /// + /// - Note: Altitude values will be ignored. + /// /// - Parameters: /// - other: The other geometry, or `nil` for overlapping segments with the receiver itself /// - tolerance: The tolerance, in meters @@ -174,7 +209,10 @@ extension GeoJson { let comparison = segment.compare(other: match, tolerance: tolerance) - guard comparison != .notEqual else { continue } + guard comparison != .notEqual, + comparison != .shortSegment, + comparison != .zeroLengthSegment + else { continue } result.append(SegmentOverlapResult( overlap: comparison, @@ -190,6 +228,8 @@ extension GeoJson { /// /// This implementation is streamlined for finding self-overlaps. /// + /// - Note: Altitude values will be ignored. + /// /// - Parameters: /// - tolerance: The tolerance, in meters /// @@ -234,7 +274,10 @@ extension GeoJson { guard nextMaxY >= currentMinY, nextMinY <= currentMaxY else { continue } let comparison = current.compare(other: next, tolerance: tolerance) - if comparison == .notEqual { continue } + if comparison == .notEqual + || comparison == .shortSegment + || comparison == .zeroLengthSegment + { continue } result.insert(currentIndex) result.insert(nextIndex) diff --git a/Sources/GISTools/GeoJson/Coordinate3D.swift b/Sources/GISTools/GeoJson/Coordinate3D.swift index dfeadcf..b92ab9f 100644 --- a/Sources/GISTools/GeoJson/Coordinate3D.swift +++ b/Sources/GISTools/GeoJson/Coordinate3D.swift @@ -471,6 +471,28 @@ extension Coordinate3D: Equatable { && lhs.altitude == rhs.altitude } + public func equals( + other: Coordinate3D, + compareAltitude: Bool, + equalityDelta: Double = GISTool.equalityDelta) + -> Bool + { + if projection != other.projection + || abs(latitude - other.latitude) >= equalityDelta + || abs(longitude - other.longitude) >= equalityDelta + { + return false + } + + if compareAltitude, + altitude != other.altitude + { + return false + } + + return true + } + } // MARK: - Hashable