Skip to content

Commit

Permalink
Ported the turf-boolean-point-in-polygon function to Turf Swift.
Browse files Browse the repository at this point in the history
  • Loading branch information
Sandy Chapman authored and frederoni committed Sep 18, 2018
1 parent 5f35431 commit d679d07
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Turf.js | Turf-swift
----|----
[turf-along](https://github.com/Turfjs/turf/tree/master/packages/turf-along/) | `LineString.coordinateFromStart(distance:)`
[turf-area](https://github.com/Turfjs/turf/blob/master/packages/turf-area/) | `Polygon.area`
[turf-boolean-point-in-polygon](https://github.com/Turfjs/turf/tree/master/packages/turf-boolean-point-in-polygon) | `Polygon.contains(point:ignoreBoundary:)`
[turf-destination](https://github.com/Turfjs/turf/tree/master/packages/turf-destination/) | `CLLocationCoordinate2D.coordinate(at:facing:)`<br>`RadianCoordinate2D.coordinate(at:facing:)`
[turf-distance](https://github.com/Turfjs/turf/tree/master/packages/turf-distance/) | `CLLocationCoordinate2D.distance(to:)`<br>`RadianCoordinate2D.distance(to:)`
[turf-helpers#polygon](https://github.com/Turfjs/turf/tree/master/packages/turf-helpers/#polygon) | `Polygon(outerRing:innerRings:)`
Expand Down
32 changes: 32 additions & 0 deletions Sources/Turf/BoundingBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
#if !os(Linux)
import CoreLocation
#endif

public class BoundingBox {

public init?(points: [CLLocationCoordinate2D]?) {
guard points?.count ?? 0 > 0 else {
return nil
}
(minLat, maxLat, minLon, maxLon) = points!
.reduce((minLat: 0, maxLat: 0, minLon: 0, maxLon: 0)) { (result, coordinate) -> (minLat: Double, maxLat: Double, minLon: Double, maxLon: Double) in
let minLat = min(coordinate.latitude, result.0)
let maxLat = max(coordinate.latitude, result.1)
let minLon = min(coordinate.longitude, result.2)
let maxLon = max(coordinate.longitude, result.3)
return (minLat: minLat, maxLat: maxLat, minLon: minLon, maxLon: maxLon)
}
}

public func contains(point: CLLocationCoordinate2D) -> Bool {
return minLat < point.latitude && maxLat > point.latitude && minLon < point.longitude && maxLon > point.longitude
}

// MARK: - Private

let minLat: Double
let maxLat: Double
let minLon: Double
let maxLon: Double
}
29 changes: 29 additions & 0 deletions Sources/Turf/Polygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,32 @@ extension Polygon {
.reduce(0, +)
}
}

extension Polygon {

/**
* Determines if the given point falls within the polygon and outside of its interior rings.
* The optional parameter `ignoreBoundary` will result in the method returning true if the given point
* lies on the boundary line of the polygon or its interior rings.
*
* Ported from: https://github.com/Turfjs/turf/blob/e53677b0931da9e38bb947da448ee7404adc369d/packages/turf-boolean-point-in-polygon/index.ts#L31-L75
*/
public func contains(point: CLLocationCoordinate2D, ignoreBoundary: Bool = false) -> Bool {
let bbox = BoundingBox(points: self.coordinates.first)
guard bbox?.contains(point: point) ?? false else {
return false
}
guard self.outerRing.contains(point: point, ignoreBoundary: ignoreBoundary) else {
return false
}
if let innerRings = innerRings {
for ring in innerRings {
if ring.contains(point: point, ignoreBoundary: ignoreBoundary) {
return false
}
}
}
return true
}
}

41 changes: 41 additions & 0 deletions Sources/Turf/Ring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,44 @@ public struct Ring {
return area
}
}

extension Ring {
/**
* Determines if the given point falls within the ring.
* The optional parameter `ignoreBoundary` will result in the method returning true if the given point
* lies on the boundary line of the ring.
*
* Ported from: https://github.com/Turfjs/turf/blob/e53677b0931da9e38bb947da448ee7404adc369d/packages/turf-boolean-point-in-polygon/index.ts#L77-L108
*/
public func contains(point: CLLocationCoordinate2D, ignoreBoundary: Bool = false) -> Bool {
var ring: ArraySlice<CLLocationCoordinate2D>!
var isInside = false
if coordinates.first == coordinates.last {
ring = coordinates.prefix(coordinates.count - 1)
}
else {
ring = coordinates.prefix(coordinates.count)
}
var i = 0
var j = ring.count - 1
while i < ring.count {
let xi = ring[i].longitude
let yi = ring[i].latitude
let xj = ring[j].longitude
let yj = ring[j].latitude
let onBoundary = (point.latitude * (xi - xj) + yi * (xj - point.longitude) + yj * (point.longitude - xi) == 0) &&
((xi - point.longitude) * (xj - point.longitude) <= 0) && ((yi - point.latitude) * (yj - point.latitude) <= 0)
if onBoundary {
return !ignoreBoundary
}
let intersect = ((yi > point.latitude) != (yj > point.latitude)) &&
(point.longitude < (xj - xi) * (point.latitude - yi) / (yj - yi) + xi);
if (intersect) {
isInside = !isInside;
}
j = i
i = i + 1
}
return isInside
}
}
45 changes: 45 additions & 0 deletions Tests/TurfTests/PolygonTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,49 @@ class PolygonTests: XCTestCase {
XCTAssert(decoded.geometry.outerRing.coordinates.count == 5)
XCTAssert(decoded.geometry.innerRings!.first?.coordinates.count == 5)
}

func testPolygonContains() {
let coordinate = CLLocationCoordinate2D(latitude: 44, longitude: -77)
let polygon = Polygon([[
CLLocationCoordinate2D(latitude: 41, longitude: -81),
CLLocationCoordinate2D(latitude: 47, longitude: -81),
CLLocationCoordinate2D(latitude: 47, longitude: -72),
CLLocationCoordinate2D(latitude: 41, longitude: -72),
CLLocationCoordinate2D(latitude: 41, longitude: -81),
]])
XCTAssertTrue(polygon.contains(point: coordinate))
}

func testPolygonDoesNotContain() {
let coordinate = CLLocationCoordinate2D(latitude: 44, longitude: -77)
let polygon = Polygon([[
CLLocationCoordinate2D(latitude: 41, longitude: -51),
CLLocationCoordinate2D(latitude: 47, longitude: -51),
CLLocationCoordinate2D(latitude: 47, longitude: -42),
CLLocationCoordinate2D(latitude: 41, longitude: -42),
CLLocationCoordinate2D(latitude: 41, longitude: -51),
]])
XCTAssertFalse(polygon.contains(point: coordinate))
}

func testPolygonDoesNotContainWithHole() {
let coordinate = CLLocationCoordinate2D(latitude: 44, longitude: -77)
let polygon = Polygon([
[
CLLocationCoordinate2D(latitude: 41, longitude: -81),
CLLocationCoordinate2D(latitude: 47, longitude: -81),
CLLocationCoordinate2D(latitude: 47, longitude: -72),
CLLocationCoordinate2D(latitude: 41, longitude: -72),
CLLocationCoordinate2D(latitude: 41, longitude: -81),
],
[
CLLocationCoordinate2D(latitude: 43, longitude: -76),
CLLocationCoordinate2D(latitude: 43, longitude: -78),
CLLocationCoordinate2D(latitude: 45, longitude: -78),
CLLocationCoordinate2D(latitude: 45, longitude: -76),
CLLocationCoordinate2D(latitude: 43, longitude: -76),
],
])
XCTAssertFalse(polygon.contains(point: coordinate))
}
}
10 changes: 10 additions & 0 deletions Turf.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@
C46809D721241B5100BAD5E1 /* featurecollection-no-properties.geojson in Resources */ = {isa = PBXBuildFile; fileRef = C46809D52124199500BAD5E1 /* featurecollection-no-properties.geojson */; };
C46809D821249E8F00BAD5E1 /* featurecollection-no-properties.geojson in Resources */ = {isa = PBXBuildFile; fileRef = C46809D52124199500BAD5E1 /* featurecollection-no-properties.geojson */; };
C46809D921249E9700BAD5E1 /* featurecollection-no-properties.geojson in Resources */ = {isa = PBXBuildFile; fileRef = C46809D52124199500BAD5E1 /* featurecollection-no-properties.geojson */; };
CE2EB998214C246A00915A30 /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2EB997214C246A00915A30 /* BoundingBox.swift */; };
CE2EB999214C247100915A30 /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2EB997214C246A00915A30 /* BoundingBox.swift */; };
CE2EB99A214C247100915A30 /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2EB997214C246A00915A30 /* BoundingBox.swift */; };
CE2EB99B214C247200915A30 /* BoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE2EB997214C246A00915A30 /* BoundingBox.swift */; };
DA39EB6E20101F99004D87F7 /* Turf.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA39EB6520101F98004D87F7 /* Turf.framework */; };
DA39EB7C20101FB9004D87F7 /* Turf.h in Headers */ = {isa = PBXBuildFile; fileRef = 3547ECEF200C3C78009DA062 /* Turf.h */; settings = {ATTRIBUTES = (Public, ); }; };
DA39EB7D20101FCD004D87F7 /* CoreLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3547ECF0200C3C78009DA062 /* CoreLocation.swift */; };
Expand Down Expand Up @@ -197,6 +201,7 @@
35ECAF2E20974A1800DC3BC3 /* Geometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geometry.swift; sourceTree = "<group>"; };
35ECAF352099EC0700DC3BC3 /* FeatureIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureIdentifier.swift; sourceTree = "<group>"; };
C46809D52124199500BAD5E1 /* featurecollection-no-properties.geojson */ = {isa = PBXFileReference; lastKnownFileType = text; path = "featurecollection-no-properties.geojson"; sourceTree = "<group>"; };
CE2EB997214C246A00915A30 /* BoundingBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoundingBox.swift; sourceTree = "<group>"; };
DA39EB6520101F98004D87F7 /* Turf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Turf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA39EB6D20101F99004D87F7 /* TurfTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TurfTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DA94249B2010283900CDB4E6 /* Turf.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Turf.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -263,6 +268,7 @@
children = (
3547ECEF200C3C78009DA062 /* Turf.h */,
3547ECF2200C3C78009DA062 /* Info.plist */,
CE2EB997214C246A00915A30 /* BoundingBox.swift */,
3502F795201F53FC00399EFE /* Codable.swift */,
3547ECF0200C3C78009DA062 /* CoreLocation.swift */,
35B56E9120DAF66A00C4923D /* FeatureCollection.swift */,
Expand Down Expand Up @@ -679,6 +685,7 @@
35B56E7520DAF47D00C4923D /* Polygon.swift in Sources */,
35B56E7F20DAF4E700C4923D /* LineString.swift in Sources */,
35B56E7A20DAF4BF00C4923D /* Point.swift in Sources */,
CE2EB999214C247100915A30 /* BoundingBox.swift in Sources */,
35B56E7020DAF41F00C4923D /* Ring.swift in Sources */,
3502F797201F53FC00399EFE /* Codable.swift in Sources */,
35B56E8E20DAF59700C4923D /* MultiPolygon.swift in Sources */,
Expand Down Expand Up @@ -717,6 +724,7 @@
35B56E7420DAF47D00C4923D /* Polygon.swift in Sources */,
35B56E7E20DAF4E700C4923D /* LineString.swift in Sources */,
35B56E7920DAF4BF00C4923D /* Point.swift in Sources */,
CE2EB998214C246A00915A30 /* BoundingBox.swift in Sources */,
35B56E6F20DAF41F00C4923D /* Ring.swift in Sources */,
3502F796201F53FC00399EFE /* Codable.swift in Sources */,
35B56E8D20DAF59700C4923D /* MultiPolygon.swift in Sources */,
Expand Down Expand Up @@ -755,6 +763,7 @@
35B56E7620DAF47D00C4923D /* Polygon.swift in Sources */,
35B56E8020DAF4E700C4923D /* LineString.swift in Sources */,
35B56E7B20DAF4BF00C4923D /* Point.swift in Sources */,
CE2EB99A214C247100915A30 /* BoundingBox.swift in Sources */,
35B56E7120DAF41F00C4923D /* Ring.swift in Sources */,
3502F798201F53FC00399EFE /* Codable.swift in Sources */,
35B56E8F20DAF59700C4923D /* MultiPolygon.swift in Sources */,
Expand Down Expand Up @@ -793,6 +802,7 @@
35B56E7720DAF47D00C4923D /* Polygon.swift in Sources */,
35B56E8120DAF4E700C4923D /* LineString.swift in Sources */,
35B56E7C20DAF4BF00C4923D /* Point.swift in Sources */,
CE2EB99B214C247200915A30 /* BoundingBox.swift in Sources */,
35B56E7220DAF41F00C4923D /* Ring.swift in Sources */,
3502F799201F53FC00399EFE /* Codable.swift in Sources */,
35B56E9020DAF59700C4923D /* MultiPolygon.swift in Sources */,
Expand Down

0 comments on commit d679d07

Please sign in to comment.