Group Group Group Group Group Group Group Group Group

Bamboozled: index-range issue with decoding a polyline

  • this works just fine when the encodedPolyline provides [lat, lon]
  • but when the parameters of a GET are setup to provide [lat, lon, elevation] (please see decodePolylineWithElevation); it breaks due to index out of range elsewhere (connected to this)
  • connected issue is: I try to split up the coordinates based on xyz for a specific format compatibility
if let first = step.coordinatesStartIndex, let last = step.coordinatesEndIndex {
  // last is 62 whilst coordinates' count at this juncture is 56 ISSUE is here
  // why would this happen only when I also request elevation to be included in the encodedPolyline? Am I not decoding it properly? :/ 
  let myCoordinates = Array(coordinates[first...last])
  stepGeometry.append(myCoordinates)
}

References for decoding: graphhopper/WebHelper.java at d70b63660ac5200b03c38ba3406b8f93976628a6 · graphhopper/graphhopper · GitHub

public func decodePolyline(_ encodedPolyline: String, precision: Double = 1e5) -> [LocationCoordinate2D]? {
  let data = encodedPolyline.data(using: .utf8)!
  return data.withUnsafeBytes { byteArray -> [LocationCoordinate2D]? in
    let length = data.count
    var position = 0
    
    var decodedCoordinates = [LocationCoordinate2D]()
    
    var lat = 0.0
    var lon = 0.0
    
    while position < length {
      do {
        let resultingLat = try decodeSingleCoordinate(byteArray: byteArray, length: length, position: &position, precision: precision)
        lat += resultingLat
        
        let resultingLon = try decodeSingleCoordinate(byteArray: byteArray, length: length, position: &position, precision: precision)
        lon += resultingLon
      } catch {
        return nil
      }
      
      decodedCoordinates.append(LocationCoordinate2D(latitude: lat, longitude: lon))
    }
    
    return decodedCoordinates
  }
}
public struct CustomPoint {
  var coordinate: LocationCoordinate2D
  var elevation: Double
}

public func decodePolylineWithElevation(_ encodedPolyline: String, precision: Double = 1e5) -> [CustomPoint]? {
  let data = encodedPolyline.data(using: .utf8)!
  return data.withUnsafeBytes { byteArray -> [BMPoint]? in
    let length = data.count
    var position = 0
    
    var decodedPoints = [BMPoint]()
    
    var lat = 0.0
    var lon = 0.0
    var elevation = 0.0
    
    while position < length {
      do {
        let resultingLat = try decodeSingleCoordinate(byteArray: byteArray, length: length, position: &position, precision: precision)
        lat += resultingLat
        
        let resultingLon = try decodeSingleCoordinate(byteArray: byteArray, length: length, position: &position, precision: precision)
        lon += resultingLon
        
        let resultingElevation = try decodeSingleCoordinate(byteArray: byteArray, length: length, position: &position, precision: precision)
        elevation += resultingElevation
      } catch {
        return nil
      }
      
      decodedPoints.append(CustomPoint(coordinate: LocationCoordinate2D(latitude: lat, longitude: lon), elevation: elevation))
    }
    
    return decodedPoints
  }
} 
private func decodeSingleCoordinate(byteArray: UnsafeRawBufferPointer, length: Int, position: inout Int, precision: Double = 1e5) throws -> Double {
  
  guard position < length else { throw PolylineError.singleCoordinateDecodingError }
  
  let bitMask = Int8(0x1F)
  
  var coordinate: Int32 = 0
  
  var currentChar: Int8
  var componentCounter: Int32 = 0
  var component: Int32 = 0
  
  repeat {
    currentChar = Int8(byteArray[position]) - 63
    component = Int32(currentChar & bitMask)
    coordinate |= (component << (5*componentCounter))
    position += 1
    componentCounter += 1
  } while ((currentChar & 0x20) == 0x20) && (position < length) && (componentCounter < 6)
  
  if (componentCounter == 6) && ((currentChar & 0x20) == 0x20) {
    throw PolylineError.singleCoordinateDecodingError
  }
  
  if (coordinate & 0x01) == 0x01 {
    coordinate = ~(coordinate >> 1)
  } else {
    coordinate = coordinate >> 1
  }
  
  return Double(coordinate) / precision
}