diff --git a/README.md b/README.md index b47d56ed..ac08df76 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Turf.js | Turf for Swift [turf-length#length](https://turfjs.org/docs/#length) | `LineString.distance(from:to:)` [turf-line-intersect#lineIntersect](https://turfjs.org/docs/#lineIntersect) | `intersection(_:_:)` [turf-line-slice#lineSlice](https://turfjs.org/docs/#lineSlice) | `LineString.sliced(from:to:)` -[turf-line-slice-along#lineSliceAlong](https://turfjs.org/docs/#lineSliceAlong) | `LineString.trimmed(from:distance:)` +[turf-line-slice-along#lineSliceAlong](https://turfjs.org/docs/#lineSliceAlong) | `LineString.trimmed(from:to:)` [turf-midpoint#midpoint](https://turfjs.org/docs/#midpoint) | `mid(_:_:)` [turf-nearest-point-on-line#nearestPointOnLine](https://turfjs.org/docs/#nearestPointOnLine) | `LineString.closestCoordinate(to:)` [turf-polygon-to-line#polygonToLine](https://turfjs.org/docs/#polygonToLine) | `LineString(_:)`
`MultiLineString(_:)` diff --git a/Sources/Turf/Geometries/LineString.swift b/Sources/Turf/Geometries/LineString.swift index 5e6a3871..09afb02b 100644 --- a/Sources/Turf/Geometries/LineString.swift +++ b/Sources/Turf/Geometries/LineString.swift @@ -78,9 +78,65 @@ extension LineString { } /** - Returns the portion of the line string that begins at the given coordinate and extends the given distance along the line string. + Returns the portion of the line string that begins at the given start distance and extends the given stop distance along the line string. - This method is roughly equivalent to the [turf-line-slice-along](https://turfjs.org/docs/#lineSliceAlong) package of Turf.js ([source code](https://github.com/Turfjs/turf/tree/master/packages/turf-line-slice-along/)), except that it accepts a starting position instead of a starting distance along the line string. + This method is equivalent to the [turf-line-slice-along](https://turfjs.org/docs/#lineSliceAlong) package of Turf.js ([source code](https://github.com/Turfjs/turf/tree/master/packages/turf-line-slice-along/)). + */ + public func trimmed(from startDistance: LocationDistance, to stopDistance: LocationDistance) -> LineString? { + // The method is porting from https://github.com/Turfjs/turf/blob/5375941072b90d489389db22b43bfe809d5e451e/packages/turf-line-slice-along/index.js + guard startDistance >= 0.0 && stopDistance >= startDistance else { return nil } + let coordinates = self.coordinates + var traveled: LocationDistance = 0 + var slice = [LocationCoordinate2D]() + + for i in 0..= traveled && i == coordinates.endIndex - 1 { + break + } else if traveled > startDistance && slice.isEmpty { + let overshoot = startDistance - traveled + if overshoot == 0.0 { + slice.append(coordinates[i]) + return LineString(slice) + } + let direction = coordinates[i].direction(to: coordinates[i - 1]) - 180 + let interpolated = coordinates[i].coordinate(at: overshoot, facing: direction) + slice.append(interpolated) + } + + if traveled >= stopDistance { + let overshoot = stopDistance - traveled + if overshoot == 0.0 { + slice.append(coordinates[i]) + return LineString(slice) + } + let direction = coordinates[i].direction(to: coordinates[i - 1]) - 180 + let interpolated = coordinates[i].coordinate(at: overshoot, facing: direction) + slice.append(interpolated) + return LineString(slice) + } + + if traveled >= startDistance { + slice.append(coordinates[i]) + } + + if i == coordinates.count - 1 { + return LineString(slice) + } + + traveled += distance(from: coordinates[i], to: coordinates[i + 1]) ?? 0.0 + } + + if traveled < startDistance { return nil } + + if let last = coordinates.last { + return LineString([last, last]) + } + + return nil + } + + /** + Returns the portion of the line string that begins at the given coordinate and extends the given distance along the line string. */ public func trimmed(from coordinate: LocationCoordinate2D, distance: LocationDistance) -> LineString? { let startVertex = closestCoordinate(to: coordinate) diff --git a/Tests/TurfTests/LineStringTests.swift b/Tests/TurfTests/LineStringTests.swift index 6c98a261..8446925b 100644 --- a/Tests/TurfTests/LineStringTests.swift +++ b/Tests/TurfTests/LineStringTests.swift @@ -350,4 +350,64 @@ class LineStringTests: XCTestCase { XCTAssertEqual(slicedCoordinates?.count, 2, "no duplicated coords") XCTAssertNotEqual(slicedCoordinates?.first, slicedCoordinates?.last, "vertical slice should not collapse to first coordinate") } + + func testTrimmed() { + // https://github.com/Turfjs/turf/blob/5375941072b90d489389db22b43bfe809d5e451e/packages/turf-line-slice-along/test.js + + // turf-line-slice-along -- line1 + let coordinates = [ + [113.99414062499999, 22.350075806124867], + [116.76269531249999, 23.241346102386135], + [117.7734375, 24.367113562651276], + [118.828125, 25.20494115356912], + [119.794921875, 26.78484736105119], + [120.80566406250001, 28.110748760633534], + [121.59667968749999, 29.49698759653577], + [121.59667968749999, 31.12819929911196], + [120.84960937499999, 32.84267363195431], + [119.83886718750001, 34.125447565116126], + [118.69628906249999, 35.31736632923788], + [121.4208984375, 36.80928470205937], + [122.82714843749999, 37.37015718405753] + ] + let line1 = LineString(coordinates.map{ + CLLocationCoordinate2D(latitude: $0.last!, longitude: $0.first!) + }) + + var startDistance = 804672.0 + var stopDistance = 1207008.0 + + var startPoint = line1.coordinateFromStart(distance: startDistance) + var endPoint = line1.coordinateFromStart(distance: stopDistance) + var sliced = line1.trimmed(from: startDistance, to: stopDistance) + XCTAssertNotNil(sliced, "should return valid lineString") + XCTAssertEqual(sliced!.coordinates.first!, startPoint) + XCTAssertEqual(sliced!.coordinates.last!, endPoint) + + stopDistance = 2414016.0 + endPoint = line1.coordinateFromStart(distance: stopDistance) + sliced = line1.trimmed(from: startDistance, to: stopDistance) + XCTAssertNotNil(sliced, "should return valid lineString") + XCTAssertEqual(sliced!.coordinates.first!, startPoint) + XCTAssertEqual(sliced!.coordinates.last!, endPoint) + + startDistance = 8046720 + stopDistance = 12874752.0 + sliced = line1.trimmed(from: startDistance, to: stopDistance) + XCTAssertNil(sliced, "should return nil") + + startDistance = line1.distance()! + stopDistance = startDistance + 100.0 + startPoint = line1.coordinateFromStart(distance: startDistance) + endPoint = line1.coordinateFromStart(distance: stopDistance) + sliced = line1.trimmed(from: startDistance, to: stopDistance) + XCTAssertNotNil(sliced, "should return valid lineString") + XCTAssertEqual(sliced!.coordinates.first!, startPoint) + XCTAssertEqual(sliced!.coordinates.last!, endPoint) + + startDistance = -0.376 + stopDistance = 543.0 + sliced = line1.trimmed(from: startDistance, to: stopDistance) + XCTAssertNil(sliced, "should return nil") + } }