Skip to content

Commit

Permalink
#13: boolean-intersects, boolean-disjoint, polygon-to-line (#40)
Browse files Browse the repository at this point in the history
* #13: boolean-intersects, boolean-disjoint, polygon-to-line
  • Loading branch information
trasch authored Dec 11, 2023
1 parent c991330 commit 0452ba2
Show file tree
Hide file tree
Showing 32 changed files with 560 additions and 26 deletions.
237 changes: 237 additions & 0 deletions Sources/GISTools/Algorithms/BooleanDisjoint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#if !os(Linux)
import CoreLocation
#endif
import Foundation

// Ported from https://github.com/Turfjs/turf/tree/master/packages/turf-boolean-disjoint

extension GeoJson {

/// Compares two geometries and returns true if they are disjoint.
///
/// - Parameters:
/// - other: The other geometry
///
/// - Returns: *true* if the geometries don't overlap, *false* otherwise.
public func isDisjoint(with other: GeoJson) -> Bool {
if let otherBoundingBox = other.boundingBox ?? other.calculateBoundingBox(),
!intersects(otherBoundingBox)
{
return true
}

return switch self {
case let point as PointGeometry:
point.isPointDisjoint(with: other)

case let lineString as LineStringGeometry:
lineString.isLineStringDisjoint(with: other)

case let polygon as PolygonGeometry:
polygon.isPolygonDisjoint(with: other)

case let geometryCollection as GeometryCollection:
geometryCollection.geometries.allSatisfy { $0.isDisjoint(with: other) }

case let feature as Feature:
feature.geometry.isDisjoint(with: other)

case let featureCollection as FeatureCollection:
featureCollection.features.allSatisfy { $0.isDisjoint(with: other) }

default:
true
}
}

}

// MARK: - Private helpers

extension PointGeometry {

fileprivate func isPointDisjoint(with other: GeoJson) -> Bool {
let other = other.projected(to: projection)

switch other {
case let otherPoint as PointGeometry:
for coordinate in allCoordinates {
if otherPoint.allCoordinates.contains(coordinate) {
return false
}
}

case let otherLineString as LineStringGeometry:
for coordinate in allCoordinates {
if otherLineString.checkIsOnLine(coordinate) {
return false
}
}

case let otherPolygon as PolygonGeometry:
for coordinate in allCoordinates {
if otherPolygon.contains(coordinate, ignoreBoundary: false) {
return false
}
}

case let otherGeometryCollection as GeometryCollection:
return otherGeometryCollection.geometries.allSatisfy { $0.isDisjoint(with: self) }

case let otherFeature as Feature:
return otherFeature.geometry.isDisjoint(with: self)

case let otherFeatureCollection as FeatureCollection:
return otherFeatureCollection.features.allSatisfy { $0.isDisjoint(with: self) }

default:
return true
}

return true
}

}

extension LineStringGeometry {

private func intersects(_ other: LineStringGeometry) -> Bool {
let other = other.projected(to: projection)

for lineSegment in lineSegments {
for otherLineSegment in other.lineSegments {
if lineSegment.intersects(otherLineSegment) {
return true
}
}
}

return false
}

fileprivate func isLineStringDisjoint(with other: GeoJson) -> Bool {
let other = other.projected(to: projection)

switch other {
case let otherPoint as PointGeometry:
for lineString in lineStrings {
guard otherPoint.isDisjoint(with: lineString) else {
return false
}
}

case let otherLineString as LineStringGeometry:
for lineString in lineStrings {
if lineString.intersects(otherLineString) {
return false
}
}

case let otherPolygon as PolygonGeometry:
// Any point inside the polygon
for coordinate in allCoordinates {
if otherPolygon.contains(coordinate, ignoreBoundary: false) {
return false
}
}

// Any line crosses the polygon
for lineString in lineStrings {
for otherLineString in otherPolygon.polygons.compactMap(\.outerRing?.lineString) {
if lineString.intersects(otherLineString) {
return false
}
}
}

case let otherGeometryCollection as GeometryCollection:
return otherGeometryCollection.geometries.allSatisfy { $0.isDisjoint(with: self) }

case let otherFeature as Feature:
return otherFeature.geometry.isDisjoint(with: self)

case let otherFeatureCollection as FeatureCollection:
return otherFeatureCollection.features.allSatisfy { $0.isDisjoint(with: self) }

default:
return true
}

return true
}

}

extension PolygonGeometry {

fileprivate func isPolygonDisjoint(with other: GeoJson) -> Bool {
let other = other.projected(to: projection)

switch other {
case let otherPoint as PointGeometry:
for polygon in polygons {
guard otherPoint.isDisjoint(with: polygon) else {
return false
}
}

case let otherLineString as LineStringGeometry:
for polygon in polygons {
guard otherLineString.isDisjoint(with: polygon) else {
return false
}
}

case let otherPolygon as PolygonGeometry:
// Any point inside the polygon
for polygon in polygons {
guard let coordinates = polygon.outerRing?.coordinates else {
continue
}

for coordinate in coordinates {
if otherPolygon.contains(coordinate, ignoreBoundary: false) {
return false
}
}
}

for polygon in otherPolygon.polygons {
guard let coordinates = polygon.outerRing?.coordinates else {
continue
}

for coordinate in coordinates {
if self.contains(coordinate, ignoreBoundary: false) {
return false
}
}
}

// Any line crosses the polygon
for polygon in polygons {
guard let lineString = polygon.outerRing?.lineString else {
continue
}
guard lineString.isDisjoint(with: otherPolygon) else {
return false
}
}

case let otherGeometryCollection as GeometryCollection:
return otherGeometryCollection.geometries.allSatisfy { $0.isDisjoint(with: self) }

case let otherFeature as Feature:
return otherFeature.geometry.isDisjoint(with: self)

case let otherFeatureCollection as FeatureCollection:
return otherFeatureCollection.features.allSatisfy { $0.isDisjoint(with: self) }

default:
return true
}

return true
}

}
14 changes: 13 additions & 1 deletion Sources/GISTools/Algorithms/BooleanIntersects.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,16 @@ import Foundation

// Ported from https://github.com/Turfjs/turf/blob/master/packages/turf-boolean-intersects

// TODO
extension GeoJson {

/// Compares two geometries and returns true if they intersect.
///
/// - Parameters:
/// - other: The other geometry
///
/// - Returns: *true* if the geometries intersect, *false* otherwise.
public func intersects(with other: GeoJson) -> Bool {
!isDisjoint(with: other)
}

}
34 changes: 26 additions & 8 deletions Sources/GISTools/Algorithms/LineIntersect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,12 @@ extension GeoJson {

/// Returns the intersecting point(s) with the receiver.
///
/// - note: Takes all poygon rings into account, not just the outer ring.
///
/// - Parameter other: The other geometry
public func intersections(other: GeoJson) -> [Point] {
let other = other.projected(to: projection)

if self is Point || self is MultiPoint || other is Point || other is MultiPoint {
return []
}

if let otherBoundingBox = other.boundingBox ?? other.calculateBoundingBox(),
!intersects(otherBoundingBox)
{
Expand All @@ -155,10 +153,30 @@ extension GeoJson {

var result: Set<Coordinate3D> = []

for lineSegment in lineSegments {
for otherLineSegment in other.lineSegments {
if let intersection = lineSegment.intersection(otherLineSegment) {
result.insert(intersection)
if let point = self as? PointGeometry {
if let otherPoint = other as? PointGeometry {
for coordinate in point.allCoordinates
where otherPoint.allCoordinates.contains(coordinate)
{
result.insert(coordinate)
}
}
else {
for coordinate in point.allCoordinates {
for otherLineSegment in other.lineSegments
where otherLineSegment.checkIsOnSegment(coordinate)
{
result.insert(coordinate)
}
}
}
}
else {
for lineSegment in lineSegments {
for otherLineSegment in other.lineSegments {
if let intersection = lineSegment.intersection(otherLineSegment) {
result.insert(intersection)
}
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions Sources/GISTools/Algorithms/PolygonToLine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#if !os(Linux)
import CoreLocation
#endif
import Foundation

extension PolygonGeometry {

/// Returns a MultiLineString for each polygon.
public var lineStrings: [MultiLineString] {
polygons.compactMap { polygon in
let lineStrings = polygon.rings.map(\.lineString)
return MultiLineString(lineStrings)
}
}

}
5 changes: 5 additions & 0 deletions Sources/GISTools/GeoJson/BoundingBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ extension BoundingBox {

extension BoundingBox {

/// All of the bounding box's corner coordinates.
var allCoordinates: [Coordinate3D] {
[southWest, northWest, northEast, southEast]
}

/// Converts the bounding box to a `Polygon` object.
public var boundingBoxPolygon: Polygon {
Polygon([[southWest, northWest, northEast, southEast, southWest]])!
Expand Down
4 changes: 3 additions & 1 deletion Sources/GISTools/GeoJson/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ extension Feature {
}

public func intersects(_ otherBoundingBox: BoundingBox) -> Bool {
if let boundingBox = boundingBox, !boundingBox.intersects(otherBoundingBox) {
if let boundingBox = boundingBox ?? calculateBoundingBox(),
!boundingBox.intersects(otherBoundingBox)
{
return false
}

Expand Down
4 changes: 3 additions & 1 deletion Sources/GISTools/GeoJson/FeatureCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ extension FeatureCollection {
}

public func intersects(_ otherBoundingBox: BoundingBox) -> Bool {
if let boundingBox = boundingBox, !boundingBox.intersects(otherBoundingBox) {
if let boundingBox = boundingBox ?? calculateBoundingBox(),
!boundingBox.intersects(otherBoundingBox)
{
return false
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/GISTools/GeoJson/GeoJson.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public protocol EmptyCreatable {
// MARK: - GeoJsonGeometry

/// GeoJSON geometry objects: `Point`, `MultiPoint`, `LineString`, `MultiLineString`,
/// `Polygon`, `MultiPolygon`, `GeoJsonGeometry`.
/// `Polygon`, `MultiPolygon`, `GeometryCollection`.
public protocol GeoJsonGeometry: GeoJson {}

// MARK: - Point, MultiPoint
Expand Down
2 changes: 1 addition & 1 deletion Sources/GISTools/GeoJson/LineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ extension LineString {
}

public func intersects(_ otherBoundingBox: BoundingBox) -> Bool {
if let boundingBox = boundingBox,
if let boundingBox = boundingBox ?? calculateBoundingBox(),
!boundingBox.intersects(otherBoundingBox)
{
return false
Expand Down
3 changes: 2 additions & 1 deletion Sources/GISTools/GeoJson/MultiLineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,12 @@ extension MultiLineString {
}

public func intersects(_ otherBoundingBox: BoundingBox) -> Bool {
if let boundingBox = boundingBox,
if let boundingBox = boundingBox ?? calculateBoundingBox(),
!boundingBox.intersects(otherBoundingBox)
{
return false
}

return lineStrings.contains { $0.intersects(otherBoundingBox) }
}

Expand Down
Loading

0 comments on commit 0452ba2

Please sign in to comment.