Select to view content in your preferred language

Decode compressed geometry in iOS and Android client sdks

1181
8
08-11-2022 01:25 AM
JulianBissekkou
New Contributor III

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?

 

0 Kudos
8 Replies
Nicholas-Furness
Esri Regular Contributor

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:

 

0 Kudos
JulianBissekkou
New Contributor III

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.

 

0 Kudos
Nicholas-Furness
Esri Regular Contributor

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.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

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.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

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()
    }
}

 

JulianBissekkou
New Contributor III

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?

0 Kudos
Nicholas-Furness
Esri Regular Contributor

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?

0 Kudos
JulianBissekkou
New Contributor III

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.

 

0 Kudos