Skip to content

Instantly share code, notes, and snippets.

@nixta
Last active December 9, 2022 18:06
Show Gist options
  • Save nixta/d8b61aac11c001075c8e7acc84704415 to your computer and use it in GitHub Desktop.
Save nixta/d8b61aac11c001075c8e7acc84704415 to your computer and use it in GitHub Desktop.
Read polyline from ArcGIS Route Service compressed geometry.
extension AGSGeometry {
static func decodeCompressedGeometry(_ compressedString: String, spatialReference sr: AGSSpatialReference) -> AGSPolyline? {
// Break the string up into signed parts.
// The first part is a coefficient.
// Subsequent pairs of parts make up the remaining coordinates, encoded.
let pattern = #"((\+|\-)[^\+\-]+)"#
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
return nil
}
let compressedParts = regex.matches(
in: compressedString,
range: NSRange(compressedString.startIndex ..< compressedString.endIndex,
in: compressedString)
).lazy.compactMap {
Range($0.range(at: 0), in: compressedString)
}.compactMap { range -> Double? in
Double(Int(compressedString[range], radix: 32) ?? 0)
}
guard let coefficient = compressedParts.first else {
preconditionFailure("Invalid compressed geometry. Needs coefficient.")
}
guard compressedParts.count >= 5,
(compressedParts.count - 1) % 2 == 0 else {
preconditionFailure("Invalid compressed geometry. Needs even number of subsequent parts, and at least 2 subsequent parts!")
}
struct XYVector {
var x: Double
var y: Double
mutating func applyDelta(_ delta: XYVector) {
x += delta.x
y += delta.y
}
}
let deltas = compressedParts.dropFirst().reduce([[Double]]()) { partialResult, nextVal in
if let lastCoordinate = partialResult.last,
lastCoordinate.count < 2 {
return partialResult.dropLast() + [lastCoordinate + [nextVal]]
} else {
return partialResult + [[nextVal]]
}
}.map( { XYVector(x: $0[0], y: $0[1]) } )
var cumulativeCoordinate = XYVector(x: 0, y: 0)
let builder = deltas.reduce(AGSPolylineBuilder(spatialReference: sr)) { builder, delta in
// Each coordinate pair is a delta from the last point, so we just keep a cumulative last coordinate.
cumulativeCoordinate.applyDelta(delta)
builder.add(AGSPoint(x: cumulativeCoordinate.x / coefficient,
y: cumulativeCoordinate.y / coefficient,
spatialReference: sr))
return builder
}
return builder.toGeometry()
}
}
// See https://github.com/Esri/terraformer-arcgis-parser/pull/15/files for algorithm.
// and https://help.arcgis.com/en/sdk/10.0/java_ao_adf/api/arcobjects/com/esri/arcgis/networkanalyst/INACompactStreetDirectionProxy.html#getCompressedGeometry()
// Example
var sampleCompressedGeometry = "+1m91-66os4+1poms+1+91+3+3j"
let line = AGSGeometry.decodeCompressedGeometry(sampleCompressedGeometry, spatialReference: .wgs84())
print("Got a line: \(line)")
var decodedCoordinates = [
[ -117.1816137447153, 34.057461545380946 ],[ -117.18159575425025, 34.06266078978142 ], [ -117.18154178285509, 34.06472969326257 ]
].map { AGSPointMakeWGS84($0[1], $0[0]) }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment