I get compressed geometry from an arcgis server when requesting a route. I would like to draw the route on the clients map.
Is there an API on how to uncompress the geometry and create a Path (or any other data structure) from it?
Hi.
Are you making REST requests yourself against the routing service? If you're using the Runtime SDK, simply use the AGSRouteTask. It will handle unpacking the response into Runtime geometry objects. See these links:
No, in my case a server calls the routing service and returns the client a json with some additional information.
This is why I have to deal with the compressed geometry instead of using the AGSRouteTask.
I see. Interesting. I'll do some digging and will let you know what I find out.
In the meantime, does your server specify returnRoutes=true? My understanding (though I'm slightly guessing at this point) is than maneuver geometries come back compressed, but the overall route geometry should be a full Esri JSON geometry that you could pass to AGSPolyline.fromJSON.
So, I have some information, but it means you'll have to write some code yourself 🙂
Take a look at this code form Terraformer, which shows how to decompress this geometry using JavaScript. You'd have to translate that to Swift of course but it should show how to do it.
function decompressGeometry(str) {
var xDiffPrev = 0;
var yDiffPrev = 0;
var points = [];
var x, y;
var strings;
var coefficient;
// Split the string into an array on the + and - characters
strings = str.match(/((\+|\-)[^\+\-]+)/g);
// The first value is the coefficient in base 32
coefficient = parseInt(strings[0], 32);
for (var j = 1; j < strings.length; j += 2) {
// j is the offset for the x value
// Convert the value from base 32 and add the previous x value
x = (parseInt(strings[j], 32) + xDiffPrev);
xDiffPrev = x;
// j+1 is the offset for the y value
// Convert the value from base 32 and add the previous y value
y = (parseInt(strings[j + 1], 32) + yDiffPrev);
yDiffPrev = y;
points.push([x / coefficient, y / coefficient]);
}
return points;
}
This pull request also includes a test that could help.
If you come up with a Swift version of this, please share! I won't have time to try until next week, but it should be achievable.
For reference, here is some really old documentation that explains the process.
FYI, I built this from the Terraformer code. Hope that works for you!
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.
let pattern = #"((\+|\-)[^\+\-]+)"#
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {
return nil
}
let range = NSRange(compressedString.startIndex ..< compressedString.endIndex,
in: compressedString)
let components = regex.matches(in: compressedString, range: range).compactMap {
Range($0.range(at: 0), in: compressedString)
}.compactMap { range -> Double? in
let val = Int(compressedString[range], radix: 32) ?? 0
return Double(val)
}
guard let coefficient = components.first,
(components.count - 1) % 2 == 0 else {
preconditionFailure("Invalid compressed geometry. Needs coefficient plus even number of subsequent parts!")
}
var coordinatePairs = [(Double,Double)]()
for i in stride(from: 1, through: components.count - 1, by: 2) {
let pair = (components[i],components[i+1])
coordinatePairs.append(pair)
}
var xDiffPrev = 0.0
var yDiffPrev = 0.0
var x = 0.0
var y = 0.0
let builder = coordinatePairs.reduce(AGSPolylineBuilder(spatialReference: sr)) { builder, pair in
x = pair.0 + xDiffPrev
xDiffPrev = x
y = pair.1 + yDiffPrev
yDiffPrev = y
let newPoint = AGSPoint(x: x / coefficient, y: y / coefficient, spatialReference: sr)
builder.add(newPoint)
return builder
}
return builder.toGeometry()
}
}
Thanks a lot for sharing. I also implemented the decompression in kotlin and it works for now.
Are you planning to add support for this in the official sdk?
It's not planned, no.
Most people will use Runtime directly against the service.
In your case do you need to parse out just the compressed geometry, or would it be helpful to be able to parse the entire route result into an AGSRouteResult?
The server annotates different parts of my route with additional information. Every part of the route has compressed geometry that is then used to draw the route.
I think we won't be able to parse the result into an AGSRouteResult because of the custom strucutre.