Repository: GEOSwift/GEOSwift (swift)

Issue:
Update LineStringConvertible+GEOS extensions for fixed GEOS issues
https://trac.osgeo.org/geos/ticket/972 has been fixed, so we can update `normalizedDistanceFromStart(toProjectionOf:)` and `interpolatedPoint(withFraction:)` to use `GEOSProjectNormalized_r` and `GEOSInterpolateNormalized_r`, respectively.

Pull request description:
* Updates normalizedDistanceFromStart(toProjectionOf:). The geos impl
returns 0 for 0-length lines, whereas the old GEOSwift impl threw
GEOSwiftError.lengthIsZero, which has now been removed.

* Updates interpolatedPoint(withFraction:)

Fixes #220

Patch:
diff --git a/Sources/GEOSwift/GEOS/LineStringConvertible+GEOS.swift b/Sources/GEOSwift/GEOS/LineStringConvertible+GEOS.swift
index 32607234..008c9728 100644
--- a/Sources/GEOSwift/GEOS/LineStringConvertible+GEOS.swift
+++ b/Sources/GEOSwift/GEOS/LineStringConvertible+GEOS.swift
@@ -18,13 +18,15 @@ public extension LineStringConvertible {
     }
 
     func normalizedDistanceFromStart(toProjectionOf point: Point) throws -> Double {
-        // avoiding GEOSProjectNormalized_r because of https://trac.osgeo.org/geos/ticket/972
-        let distance = try distanceFromStart(toProjectionOf: point)
-        let length = try lineString.length()
-        guard length != 0 else {
-            throw GEOSwiftError.lengthIsZero
+        let context = try GEOSContext()
+        let lineStringGeosObject = try lineString.geosObject(with: context)
+        let pointGeosObject = try point.geosObject(with: context)
+        // returns -1 on exception
+        let result = GEOSProjectNormalized_r(context.handle, lineStringGeosObject.pointer, pointGeosObject.pointer)
+        guard result != -1 else {
+            throw GEOSError.libraryError(errorMessages: context.errors)
         }
-        return distance / length
+        return result
     }
 
     /// If distance is negative, the interpolation starts from the end and works backwards
@@ -39,7 +41,11 @@ public extension LineStringConvertible {
 
     /// If fraction is negative, the interpolation starts from the end and works backwards
     func interpolatedPoint(withFraction fraction: Double) throws -> Point {
-        // avoiding GEOSInterpolateNormalized_r because of https://trac.osgeo.org/geos/ticket/972
-        return try interpolatedPoint(withDistance: lineString.length() * fraction)
+        let context = try GEOSContext()
+        let lineStringGeosObject = try lineString.geosObject(with: context)
+        guard let pointer = GEOSInterpolateNormalized_r(context.handle, lineStringGeosObject.pointer, fraction) else {
+            throw GEOSError.libraryError(errorMessages: context.errors)
+        }
+        return try Point(geosObject: GEOSObject(context: context, pointer: pointer))
     }
 }
diff --git a/Sources/GEOSwift/GEOSwiftError.swift b/Sources/GEOSwift/GEOSwiftError.swift
index 3865ed98..e9c353f0 100644
--- a/Sources/GEOSwift/GEOSwiftError.swift
+++ b/Sources/GEOSwift/GEOSwiftError.swift
@@ -7,6 +7,5 @@ public enum GEOSwiftError: Error, Equatable {
     case ringNotClosed
     case tooFewRings
     case invalidFeatureId
-    case lengthIsZero
     case unexpectedEnvelopeResult(Geometry)
 }


Test patch:
diff --git a/Tests/GEOSwiftTests/GEOS/LineStringConvertible+GEOSTests.swift b/Tests/GEOSwiftTests/GEOS/LineStringConvertible+GEOSTests.swift
index d16f18d2..5c3145fd 100644
--- a/Tests/GEOSwiftTests/GEOS/LineStringConvertible+GEOSTests.swift
+++ b/Tests/GEOSwiftTests/GEOS/LineStringConvertible+GEOSTests.swift
@@ -123,34 +123,20 @@ final class LineStringConvertible_GEOSTests: XCTestCase {
         XCTAssertEqual(try? linearRing.normalizedDistanceFromStart(toProjectionOf: point), 1 / 8)
     }
 
-    func testNormalizedDistanceFromStartToProjectionOfPointLineLengthZero_LineString() {
+    func testNormalizedDistanceFromStartToProjectionOfPointLineLengthZero_LineString() throws {
         let lineString = try! LineString(points: Array(repeating: Point(x: 0, y: 0), count: 2))
 
         let point = Point(x: 0, y: 0)
 
-        do {
-            _ = try lineString.normalizedDistanceFromStart(toProjectionOf: point)
-            XCTFail("Expected call to throw, but it did not.")
-        } catch GEOSwiftError.lengthIsZero {
-            // Pass
-        } catch {
-            XCTFail("Threw unexpected error: \(error)")
-        }
+        XCTAssertEqual(try lineString.normalizedDistanceFromStart(toProjectionOf: point), 0)
     }
 
-    func testNormalizedDistanceFromStartToProjectionOfPointLineLengthZero_LinearRing() {
+    func testNormalizedDistanceFromStartToProjectionOfPointLineLengthZero_LinearRing() throws {
         let linearRing = try! Polygon.LinearRing(points: Array(repeating: Point(x: 0, y: 0), count: 4))
 
         let point = Point(x: 0, y: 0)
 
-        do {
-            _ = try linearRing.normalizedDistanceFromStart(toProjectionOf: point)
-            XCTFail("Expected call to throw, but it did not.")
-        } catch GEOSwiftError.lengthIsZero {
-            // Pass
-        } catch {
-            XCTFail("Threw unexpected error: \(error)")
-        }
+        XCTAssertEqual(try linearRing.normalizedDistanceFromStart(toProjectionOf: point), 0)
     }
 
     func testInterpolatedPointWithDistance_LineString() {