-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from Outdooractive/frechet_distance
#38: Frechet distance
- Loading branch information
Showing
85 changed files
with
293 additions
and
131 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
// swift-tools-version:5.7 | ||
// swift-tools-version:5.9 | ||
|
||
import PackageDescription | ||
|
||
|
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#if !os(Linux) | ||
import CoreLocation | ||
#endif | ||
import Foundation | ||
|
||
/// Distance functions for ```LineString.frechetDistance```. | ||
public enum FrechetDistanceFunction { | ||
|
||
/// Use the eudlidean distance. | ||
case euclidean | ||
/// Use the Haversine formula to account for global curvature. | ||
case haversine | ||
/// Use a rhumb line. | ||
case rhumbLine | ||
/// Use a custom distance function. | ||
case other((Coordinate3D, Coordinate3D) -> CLLocationDistance) | ||
|
||
func distance(between first: Coordinate3D, and second: Coordinate3D) -> CLLocationDistance { | ||
switch self { | ||
case .euclidean: | ||
sqrt(pow(first.longitude - second.longitude, 2.0) + pow(first.latitude - second.latitude, 2.0)) | ||
case .haversine: | ||
first.distance(from: second) | ||
case .rhumbLine: | ||
first.rhumbDistance(from: second) | ||
case let .other(distanceFuntion): | ||
distanceFuntion(first, second) | ||
} | ||
} | ||
|
||
} | ||
|
||
extension LineString { | ||
|
||
/// Fréchet distance between to geometries. | ||
/// | ||
/// - Parameters: | ||
/// - from: The other geometry of equal type. | ||
/// - distanceFunction: The algorithm to use for distance calculations. | ||
/// - segmentLength: Adds coordinates to the lines for improved matching (in meters). | ||
/// | ||
/// - Returns: The frechet distance between the two geometries. | ||
public func frechetDistance( | ||
from other: LineString, | ||
distanceFunction: FrechetDistanceFunction = .haversine, | ||
segmentLength: CLLocationDistance? = nil) | ||
-> Double | ||
{ | ||
var firstLine = self | ||
var secondLine = other.projected(to: projection) | ||
|
||
if let segmentLength { | ||
firstLine = firstLine.evenlyDivided(segmentLength: segmentLength) | ||
secondLine = secondLine.evenlyDivided(segmentLength: segmentLength) | ||
} | ||
|
||
let p = firstLine.allCoordinates | ||
let q = secondLine.allCoordinates | ||
|
||
var ca: [Double] = Array(repeating: -1.0, count: p.count * q.count) | ||
|
||
func index(_ pI: Int, _ qI: Int) -> Int { | ||
(pI * p.count) + qI | ||
} | ||
|
||
for i in 0 ..< p.count { | ||
for j in 0 ..< q.count { | ||
let distance = distanceFunction.distance(between: p[i], and: q[j]) | ||
|
||
ca[index(i, j)] = if i > 0, j > 0 { | ||
max([ca[index(i - 1, j)], ca[index(i - 1, j - 1)], ca[index(i, j - 1)]].min() ?? -1.0, distance) | ||
} | ||
else if i > 0, j == 0 { | ||
max(ca[index(i - 1, 0)], distance) | ||
} | ||
else if i == 0, j > 0 { | ||
max(ca[index(0, j - 1)], distance) | ||
} | ||
else if i == 0, j == 0 { | ||
distance | ||
} | ||
else { | ||
Double.infinity | ||
} | ||
} | ||
} | ||
|
||
return ca[index(p.count - 1, q.count - 1)] | ||
} | ||
|
||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#if !os(Linux) | ||
import CoreLocation | ||
#endif | ||
@testable import GISTools | ||
import XCTest | ||
|
||
final class FrechetDistanceTests: XCTestCase { | ||
|
||
func testFrechetDistance4326() throws { | ||
let point = Point(Coordinate3D(latitude: 0.0, longitude: 0.0)) | ||
let lineArc1 = try XCTUnwrap(point.lineArc(radius: 5000.0, bearing1: 20.0, bearing2: 60.0)) | ||
let lineArc2 = try XCTUnwrap(point.lineArc(radius: 6000.0, bearing1: 20.0, bearing2: 60.0)) | ||
|
||
let distanceHaversine = lineArc1.frechetDistance(from: lineArc2, distanceFunction: .haversine) | ||
let distanceRhumbLine = lineArc1.frechetDistance(from: lineArc2, distanceFunction: .rhumbLine) | ||
|
||
XCTAssertEqual(distanceHaversine, 1000.0, accuracy: 0.0001) | ||
XCTAssertEqual(distanceRhumbLine, 1000.0, accuracy: 0.0001) | ||
} | ||
|
||
func testFrechetDistance3857() throws { | ||
let point = Point(Coordinate3D(latitude: 0.0, longitude: 0.0)).projected(to: .epsg3857) | ||
let lineArc1 = try XCTUnwrap(point.lineArc(radius: 5000.0, bearing1: 20.0, bearing2: 60.0)) | ||
let lineArc2 = try XCTUnwrap(point.lineArc(radius: 6000.0, bearing1: 20.0, bearing2: 60.0)) | ||
|
||
let distanceEucliden = lineArc1.frechetDistance(from: lineArc2, distanceFunction: .euclidean) | ||
XCTAssertEqual(distanceEucliden, 1000.0, accuracy: 2.0) | ||
} | ||
|
||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.