在Swift中解码HERE REST API折线?

u91tlkcl  于 2023-02-28  发布在  Swift
关注(0)|答案(3)|浏览(182)

我正在Swift中使用HERE REST API用于中转路线。
通常当我在Swift中解码折线时,我使用这个伟大的库https://github.com/raphaelmor/Polyline/,它适用于OpenTripPlanner、GoogleDirections、Graphhopper(没有高程)等
此处折线的编码方式似乎有所不同

func testDecodePolyline() throws {
    let herePolyline = "BHwp7v0W0_uykO89CkU-yIs8B28K89CgkHq8BkmE6a-gF-uBquFooBqvKm3Cg1FooByzEmoB6-Hs8Bm6EooB0kDkUquFq8B4_MqrDi5MykD2jU0nF47F21BzZqwF"
    let coordinates: [CLLocationCoordinate2D]? = decodePolyline(herePolyline)
    XCTAssertNotNil(coordinates)
}

使用上述库不起作用。
编码问题有一个答案:HERE Polyline Encoding: JavaScript -> Swift,实施本文档:https://developer.here.com/documentation/places/dev_guide/topics/location-contexts.html#location-contexts__here-polyline-encoding
这里还有一个库,支持多种语言,但不支持Swift或Objective-C:https://github.com/heremaps/flexible-polyline
如何从Swift中的REST API解码HERE折线?
我更喜欢不使用iOS HERE SDK的解决方案。如果必须的话,我会安装iOS HERE SDK。

gdrx4gfi

gdrx4gfi1#

使用eiprol的版本,他改进了我的版本

我把https://github.com/heremaps/flexible-polyline/blob/master/java/src/com/here/flexpolyline/PolylineEncoderDecoder.java从Java翻译成了Swift:

public class HEREPolylineEncoderDecoder {
    public static let FORMAT_VERSION: Int64 = 1;

    public enum PolylineEncoderDecoderError: Error {
        case IllegalArgumentException(_ cause: String)
    }

    //Base64 URL-safe characters
    public static let ENCODING_TABLE: [Character]  = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")

    public static let DECODING_TABLE: [Int64] = [
                                            62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1,
                                            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                                            22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                            36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
        ]
    /**
     * Encode the list of coordinate triples.<BR><BR>
     * The third dimension value will be eligible for encoding only when ThirdDimension is other than ABSENT.
     * This is lossy compression based on precision accuracy.
     *
     * @param coordinates {@link List} of coordinate triples that to be encoded.
     * @param precision   Floating point precision of the coordinate to be encoded.
     * @param thirdDimension {@link ThirdDimension} which may be a level, altitude, elevation or some other custom value
     * @param thirdDimPrecision Floating point precision for thirdDimension value
     * @return URL-safe encoded {@link String} for the given coordinates.
     */
    public static func encode(coordinates: [LatLngZ], precision: Int64, thirdDimension: ThirdDimension, thirdDimPrecision: Int64) throws -> String {
        if (coordinates.count == 0) {
            throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid coordinates!")
        }
        let enc: Encoder = try Encoder(precision, thirdDimension, thirdDimPrecision)
        for coordinate in coordinates {
            enc.add(coordinate)
        }
        return enc.getEncoded()
    }

    /**
     * Decode the encoded input {@link String} to {@link List} of coordinate triples.<BR><BR>
     * @param encoded URL-safe encoded {@link String}
     * @return {@link List} of coordinate triples that are decoded from input
     *
     * @see PolylineDecoder#getThirdDimension(String) getThirdDimension
     * @see LatLngZ
     */
    public static func decode(_ encoded: String) throws -> [LatLngZ] {
        
        if (encoded.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) {
            throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid argument!")
        }
        var result = [LatLngZ]()
        let dec = try Decoder(encoded)
        var lat: Double = 0.0
        var lng: Double = 0.0
        var z  : Double = 0.0
        
        while (try dec.decodeOne(&lat, &lng, &z)) {
            result.append(LatLngZ(lat, lng, z))
            lat = 0.0
            lng = 0.0
            z   = 0.0
        }
        return result
    }

    /**
     * ThirdDimension type from the encoded input {@link String}
     * @param encoded URL-safe encoded coordinate triples {@link String}
     * @return type of {@link ThirdDimension}
     */
    public func getThirdDimension(encoded: String) throws -> ThirdDimension {
        var index: Int64 = 0
        var header: Int64 = 0
        try Decoder.decodeHeaderFromString(encoded, &index, &header)
        guard let td =  ThirdDimension(rawValue: (header >> 4) & 7) else {
            throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimPrecision out of range")
        }
        return td
    }
    
    public func getVersion() -> Int64 {
        return HEREPolylineEncoderDecoder.FORMAT_VERSION
    }
    
    /*
     * Single instance for configuration, validation and encoding for an input request.
     */
    private class Encoder {

        private var result: String
        private let latConveter: Converter
        private let lngConveter: Converter
        private let zConveter: Converter
        private let thirdDimension: ThirdDimension

        public init(_ precision: Int64, _ thirdDimension: ThirdDimension, _ thirdDimPrecision: Int64) throws {
            self.latConveter = Converter(precision)
            self.lngConveter = Converter(precision)
            self.zConveter = Converter(thirdDimPrecision)
            self.thirdDimension = thirdDimension
            self.result = ""
            try encodeHeader(precision, self.thirdDimension.rawValue, thirdDimPrecision);
        }

        private func encodeHeader(_ precision: Int64, _ thirdDimensionValue: Int64, _ thirdDimPrecision: Int64) throws {
            /*
             * Encode the `precision`, `third_dim` and `third_dim_precision` into one encoded char
             */
            if (precision < 0 || precision > 15) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("precision out of range")
            }

            if (thirdDimPrecision < 0 || thirdDimPrecision > 15) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimPrecision out of range")
            }

            if (thirdDimensionValue < 0 || thirdDimensionValue > 7) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimensionValue out of range")
            }
            let res: Int64 = (thirdDimPrecision << 7) | (thirdDimensionValue << 4) | precision
            Converter.encodeUnsignedVarint(HEREPolylineEncoderDecoder.FORMAT_VERSION, &result)
            Converter.encodeUnsignedVarint(res, &result)
        }

        private func add(_ lat: Double, _ lng: Double) {
            latConveter.encodeValue(lat, &result);
            lngConveter.encodeValue(lng, &result);
        }

        private func add(_ lat: Double, _ lng: Double, _ z: Double) {
            add(lat, lng);
            if (self.thirdDimension != ThirdDimension.ABSENT) {
                zConveter.encodeValue(z, &result);
            }
        }

        fileprivate func add(_ tuple: LatLngZ) {
            add(tuple.lat, tuple.lng, tuple.z);
        }

        fileprivate func getEncoded() -> String {
            return self.result
        }
    }
    
    /*
     * Single instance for decoding an input request.
     */
    private class Decoder {

        private let encoded: String
        private var index: Int64
        private let latConveter: Converter
        private let lngConveter: Converter
        private let zConveter: Converter
        
        private let precision: Int64
        private let thirdDimPrecision: Int64
        private let thirdDimension: ThirdDimension

        public init(_ encoded: String) throws {
            self.encoded = encoded;
            self.index = 0

            // decodeHeader():
            var header: Int64 = 0
            try HEREPolylineEncoderDecoder.Decoder.decodeHeaderFromString(encoded, &index, &header);
            self.precision = (header & 15); // we pick the first 4 bits only
            header = (header >> 4);
            guard let td = ThirdDimension(rawValue: header & 7) else {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimensionValue out of range")
            }
            self.thirdDimension = td
            self.thirdDimPrecision = ((header >> 3) & 15);
            // end decodeHeader()

            self.latConveter = Converter(precision)
            self.lngConveter = Converter(precision)
            self.zConveter = Converter(thirdDimPrecision)
        }

        private func hasThirdDimension() -> Bool {
            return thirdDimension != ThirdDimension.ABSENT
        }
        
        fileprivate static func decodeHeaderFromString(_ encoded: String, _ index: inout Int64, _ header: inout Int64) throws {
            var value: Int64 = 0

            // Decode the header version
            if(!Converter.decodeUnsignedVarint(Array(encoded), &index, &value)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (value != FORMAT_VERSION) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid format version")
            }
            // Decode the polyline header
            if(!Converter.decodeUnsignedVarint(Array(encoded), &index, &value)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            header = value
        }
        

        fileprivate func decodeOne(_ lat: inout Double,
                               _ lng: inout Double,
                               _ z: inout Double) throws -> Bool {
            if (index == encoded.count) {
                return false
            }
            if (!latConveter.decodeValue(encoded, &index, &lat)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (!lngConveter.decodeValue(encoded, &index, &lng)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (hasThirdDimension()) {
                if (!zConveter.decodeValue(encoded, &index, &z)) {
                    throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
                }
            }
            return true;
        }
    }
    
    //Decode a single char to the corresponding value
    private static func decodeChar(_ charValue: Character) -> Int64 {
        let pos: Int = Int(charValue.asciiValue ?? 0) - 45;
        if (pos < 0 || pos > 77) {
            return -1;
        }
        return DECODING_TABLE[pos];
    }

    /*
     * Stateful instance for encoding and decoding on a sequence of Coordinates part of an request.
     * Instance should be specific to type of coordinates (e.g. Lat, Lng)
     * so that specific type delta is computed for encoding.
     * Lat0 Lng0 3rd0 (Lat1-Lat0) (Lng1-Lng0) (3rdDim1-3rdDim0)
     */
    public class Converter {

        private var multiplier: Double = 0;
        private var lastValue: Int64 = 0;

        public init(_ precision: Int64) {
            // could be replaced by iterative inter muliplication, only calculated once
            self.multiplier = pow(10.0, Double(precision))
        }

        fileprivate static func encodeUnsignedVarint(_ val: Int64, _ result: inout String) {
            var value = val // make parameter mutable
            while (value > 0x1F) {
                let pos: Int =  Int((value & 0x1F) | 0x20);
                result.append(ENCODING_TABLE[pos]);
                // value >>= 5;
                value = value >> 5
            }
            result.append(ENCODING_TABLE[Int(value)]);
        }

        func encodeValue(_ value: Double, _ result: inout String) {
            /*
             * Round-half-up
             * round(-1.4) --> -1
             * round(-1.5) --> -2
             * round(-2.5) --> -3
             */
            let scaledValue: Int64 = Int64(abs(value * multiplier).rounded() * value.signum().rounded())
            var delta: Int64 = scaledValue - lastValue
            let negative: Bool = delta < 0

            lastValue = scaledValue

            // make room on lowest bit
            delta = delta << 1

            // invert bits if the value is negative
            if (negative) {
                delta = ~delta;
            }
            HEREPolylineEncoderDecoder.Converter.encodeUnsignedVarint(delta, &result);
        }

        fileprivate static func decodeUnsignedVarint(_ encoded: [Character],
                                                 _ index: inout Int64,
                                                 _ result: inout Int64) -> Bool {
            var shift: Int16 = 0
            var delta: Int64 = 0
            var value: Int64

            while (index < encoded.count) {
                value = decodeChar(encoded[Int(index)])
                if (value < 0) {
                    return false;
                }
                index = index + 1
                delta |= (value & 0x1F) << shift;
                if ((value & 0x20) == 0) {
                    result = delta
                    return true;
                } else {
                    shift += 5;
                }
            }

            if (shift > 0) {
                return false;
            }
            return true;
        }

        //Decode single coordinate (say lat|lng|z) starting at index
        func decodeValue(_ encoded: String,
                         _ index: inout Int64,
                         _ coordinate: inout Double) -> Bool {
            var delta: Int64 = 0
            if (!HEREPolylineEncoderDecoder.Converter.decodeUnsignedVarint(Array(encoded), &index, &delta)) {
                return false;
            }
            if ((delta & 1) != 0) {
                delta = ~delta
            }
            delta = delta >> 1
            lastValue = lastValue + delta
            coordinate = (Double(lastValue) / multiplier)
            return true;
        }
    } // class Converter

    
    /**
     *     3rd dimension specification.
     *  Example a level, altitude, elevation or some other custom value.
     *  ABSENT is default when there is no third dimension en/decoding required.
     */
    public enum ThirdDimension: Int64 {
        case ABSENT // (0),
        case LEVEL // (1),
        case ALTITUDE // (2),
        case ELEVATION // (3),
        case RESERVED1 // (4),
        case RESERVED2 // (5),
        case CUSTOM1 // (6),
        case CUSTOM2 // (7);
    }

    /**
     * Coordinate triple
     */
    public class LatLngZ: CustomStringConvertible, Equatable {
        public let lat: Double
        public let lng: Double
        public let z: Double

        init(_ latitude: Double,_ longitude: Double, _ thirdDimension: Double = 0.0) {
            self.lat = latitude
            self.lng = longitude
            self.z   = thirdDimension
        }

        public func toString() -> String {
            return description
        }

        public var description: String {
            return "LatLngZ [lat=\(lat), lng=\(lng), z=\(z)]"
        }
        public static func == (lhs: HEREPolylineEncoderDecoder.LatLngZ, rhs: HEREPolylineEncoderDecoder.LatLngZ) -> Bool {
            return lhs.lat == rhs.lat
                && lhs.lng == rhs.lng
                && lhs.z   == rhs.z
        }
    } // inner class LatLngZ
} // class HEREPolylineEncoderDecoder

extension FloatingPoint {
  @inlinable
  func signum( ) -> Self {
    if self < 0 { return -1 }
    if self > 0 { return 1 }
    return 0
  }
}
50pmv0ei

50pmv0ei2#

非常感谢Gerd。由于某种原因,我注意到它在更大的折线上速度慢得令人难以置信(例如,从巴塞罗那到马德里的路线需要超过120秒才能解码,这太疯狂了!),所以我尝试改变了一些东西,看看它是否有所改善:

  • 只要可能,就用Int 64替换Int,比如在var索引上(解码器内部),假设不需要更大的Int。(还必须将一些方法从Int 64更改为Int,以适应新的索引大小)
  • 我将encoded字符串的大小缓存到了encodedCount中,这样我们就不用在每次检查索引时都调用encoded.count
  • 在创建decoder时,我将提供的encoded string保存到一个新的类型为Char Array的var中,以避免每次解码编码字符串的一个字符时都创建新的char数组。

问题是(希望我没有打破任何东西),现在走得更快了。以防万一有人需要它!

import Foundation

public class HEREPolylineEncoderDecoder {
    public static let FORMAT_VERSION: Int64 = 1;

    public enum PolylineEncoderDecoderError: Error {
        case IllegalArgumentException(_ cause: String)
    }

    //Base64 URL-safe characters
    public static let ENCODING_TABLE: [Character]  = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_")

    public static let DECODING_TABLE: [Int64] = [
                                            62, -1, -1, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1,
                                            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
                                            22, 23, 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
                                            36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
        ]
    /**
     * Encode the list of coordinate triples.<BR><BR>
     * The third dimension value will be eligible for encoding only when ThirdDimension is other than ABSENT.
     * This is lossy compression based on precision accuracy.
     *
     * @param coordinates {@link List} of coordinate triples that to be encoded.
     * @param precision   Floating point precision of the coordinate to be encoded.
     * @param thirdDimension {@link ThirdDimension} which may be a level, altitude, elevation or some other custom value
     * @param thirdDimPrecision Floating point precision for thirdDimension value
     * @return URL-safe encoded {@link String} for the given coordinates.
     */
    public static func encode(coordinates: [LatLngZ], precision: Int64, thirdDimension: ThirdDimension, thirdDimPrecision: Int64) throws -> String {
        if (coordinates.count == 0) {
            throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid coordinates!")
        }
        let enc: Encoder = try Encoder(precision, thirdDimension, thirdDimPrecision)
        for coordinate in coordinates {
            enc.add(coordinate)
        }
        return enc.getEncoded()
    }

    /**
     * Decode the encoded input {@link String} to {@link List} of coordinate triples.<BR><BR>
     * @param encoded URL-safe encoded {@link String}
     * @return {@link List} of coordinate triples that are decoded from input
     *
     * @see PolylineDecoder#getThirdDimension(String) getThirdDimension
     * @see LatLngZ
     */
    private static func decode(_ encoded: String) throws -> [LatLngZ] {

        if (encoded.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) {
            throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid argument!")
        }
        var result = [LatLngZ]()
        let dec = try Decoder(encoded)
        var lat: Double = 0.0
        var lng: Double = 0.0
        var z  : Double = 0.0
                
        while (try dec.decodeOne(&lat, &lng, &z)) {
            result.append(LatLngZ(lat, lng, z))
            lat = 0.0
            lng = 0.0
            z   = 0.0
        }
        return result
    }
    
    //External method to avoid try catch
    public static func decodePolyline(_ encoded: String) -> (error: Error?, points: [LatLngZ]?)
    {
        do {
            // Create JSON Encoder
            let decodedPolyline = try self.decode(encoded)
            
            //print("decoded polyline (steps:\(decodedPolyline.count) \(decodedPolyline)")
            return (error: nil, points: decodedPolyline)
        } catch {
            print("Unable to Encode RestGenericRequest (\(error))")
            return (error: error, points: nil)
        }
    }

    /**
     * ThirdDimension type from the encoded input {@link String}
     * @param encoded URL-safe encoded coordinate triples {@link String}
     * @return type of {@link ThirdDimension}
     */
    public func getThirdDimension(encoded: String) throws -> ThirdDimension {
        var index: Int = 0
        var header: Int64 = 0
        try Decoder.decodeHeaderFromString(encoded, &index, &header)
        guard let td =  ThirdDimension(rawValue: (header >> 4) & 7) else {
            throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimPrecision out of range")
        }
        return td
    }

    public func getVersion() -> Int64 {
        return HEREPolylineEncoderDecoder.FORMAT_VERSION
    }

    /*
     * Single instance for configuration, validation and encoding for an input request.
     */
    private class Encoder {

        private var result: String
        private let latConveter: Converter
        private let lngConveter: Converter
        private let zConveter: Converter
        private let thirdDimension: ThirdDimension

        public init(_ precision: Int64, _ thirdDimension: ThirdDimension, _ thirdDimPrecision: Int64) throws {
            self.latConveter = Converter(precision)
            self.lngConveter = Converter(precision)
            self.zConveter = Converter(thirdDimPrecision)
            self.thirdDimension = thirdDimension
            self.result = ""
            try encodeHeader(precision, self.thirdDimension.rawValue, thirdDimPrecision);
        }

        private func encodeHeader(_ precision: Int64, _ thirdDimensionValue: Int64, _ thirdDimPrecision: Int64) throws {
            /*
             * Encode the `precision`, `third_dim` and `third_dim_precision` into one encoded char
             */
            if (precision < 0 || precision > 15) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("precision out of range")
            }

            if (thirdDimPrecision < 0 || thirdDimPrecision > 15) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimPrecision out of range")
            }

            if (thirdDimensionValue < 0 || thirdDimensionValue > 7) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimensionValue out of range")
            }
            let res: Int64 = (thirdDimPrecision << 7) | (thirdDimensionValue << 4) | precision
            Converter.encodeUnsignedVarint(HEREPolylineEncoderDecoder.FORMAT_VERSION, &result)
            Converter.encodeUnsignedVarint(res, &result)
        }

        private func add(_ lat: Double, _ lng: Double) {
            latConveter.encodeValue(lat, &result);
            lngConveter.encodeValue(lng, &result);
        }

        private func add(_ lat: Double, _ lng: Double, _ z: Double) {
            add(lat, lng);
            if (self.thirdDimension != ThirdDimension.ABSENT) {
                zConveter.encodeValue(z, &result);
            }
        }

        fileprivate func add(_ tuple: LatLngZ) {
            add(tuple.lat, tuple.lng, tuple.z);
        }

        fileprivate func getEncoded() -> String {
            return self.result
        }
    }

    /*
     * Single instance for decoding an input request.
     */
    private class Decoder {

        private let encoded: String
        private let encodedArray: Array<Character>
        private let encodedCount: Int
        private var index: Int
        private let latConveter: Converter
        private let lngConveter: Converter
        private let zConveter: Converter

        private let precision: Int64
        private let thirdDimPrecision: Int64
        private let thirdDimension: ThirdDimension

        public init(_ encoded: String) throws {
            self.encoded = encoded;
            self.index = 0
            
            //NEW TO IMPROVE PERFORMANCE
            self.encodedArray = Array(encoded)
            self.encodedCount = self.encodedArray.count

            // decodeHeader():
            var header: Int64 = 0
            try HEREPolylineEncoderDecoder.Decoder.decodeHeaderFromString(encoded, &index, &header);
            self.precision = (header & 15); // we pick the first 4 bits only
            header = (header >> 4);
            guard let td = ThirdDimension(rawValue: header & 7) else {
                throw PolylineEncoderDecoderError.IllegalArgumentException("thirdDimensionValue out of range")
            }
            self.thirdDimension = td
            self.thirdDimPrecision = ((header >> 3) & 15);
            // end decodeHeader()
            
            self.latConveter = Converter(precision)
            self.lngConveter = Converter(precision)
            self.zConveter = Converter(thirdDimPrecision)
        }

        private func hasThirdDimension() -> Bool {
            return thirdDimension != ThirdDimension.ABSENT
        }

        fileprivate static func decodeHeaderFromString(_ encoded: String, _ index: inout Int, _ header: inout Int64) throws {
            var value: Int64 = 0

            // Decode the header version
            if(!Converter.decodeUnsignedVarint(Array(encoded), &index, &value)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (value != FORMAT_VERSION) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid format version")
            }
            // Decode the polyline header
            if(!Converter.decodeUnsignedVarint(Array(encoded), &index, &value)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            header = value
        }

        fileprivate func decodeOne(_ lat: inout Double,
                               _ lng: inout Double,
                               _ z: inout Double) throws -> Bool {
            if (index >= encodedCount) {
                return false
            }
            
            if (!latConveter.decodeValue(encodedArray, &index, &lat)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (!lngConveter.decodeValue(encodedArray, &index, &lng)) {
                throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
            }
            if (hasThirdDimension()) {
                if (!zConveter.decodeValue(encodedArray, &index, &z)) {
                    throw PolylineEncoderDecoderError.IllegalArgumentException("Invalid encoding")
                }
            }
            return true;
        }
    }

    //Decode a single char to the corresponding value
    private static func decodeChar(_ charValue: Character) -> Int64 {
        let pos: Int = Int(charValue.asciiValue ?? 0) - 45;
        if (pos < 0 || pos > 77) {
            return -1;
        }
        return DECODING_TABLE[pos];
    }

    /*
     * Stateful instance for encoding and decoding on a sequence of Coordinates part of an request.
     * Instance should be specific to type of coordinates (e.g. Lat, Lng)
     * so that specific type delta is computed for encoding.
     * Lat0 Lng0 3rd0 (Lat1-Lat0) (Lng1-Lng0) (3rdDim1-3rdDim0)
     */
    public class Converter {

        private var multiplier: Double = 0;
        private var lastValue: Int64 = 0;

        public init(_ precision: Int64) {
            // could be replaced by iterative inter muliplication, only calculated once
            self.multiplier = pow(10.0, Double(precision))
        }

        fileprivate static func encodeUnsignedVarint(_ val: Int64, _ result: inout String) {
            var value = val // make parameter mutable
            while (value > 0x1F) {
                let pos: Int =  Int((value & 0x1F) | 0x20);
                result.append(ENCODING_TABLE[pos]);
                // value >>= 5;
                value = value >> 5
            }
            result.append(ENCODING_TABLE[Int(value)]);
        }

        func encodeValue(_ value: Double, _ result: inout String) {
            /*
             * Round-half-up
             * round(-1.4) --> -1
             * round(-1.5) --> -2
             * round(-2.5) --> -3
             */
            let scaledValue: Int64 = Int64(abs(value * multiplier).rounded() * value.signum().rounded())
            var delta: Int64 = scaledValue - lastValue
            let negative: Bool = delta < 0

            lastValue = scaledValue

            // make room on lowest bit
            delta = delta << 1

            // invert bits if the value is negative
            if (negative) {
                delta = ~delta;
            }
            HEREPolylineEncoderDecoder.Converter.encodeUnsignedVarint(delta, &result);
        }

        fileprivate static func decodeUnsignedVarint(_ encoded: [Character],
                                                 _ index: inout Int,
                                                 _ result: inout Int64) -> Bool {
            var shift: Int16 = 0
            var delta: Int64 = 0
            var value: Int64

            while (index < encoded.count) {
                value = decodeChar(encoded[index])
                if (value < 0) {
                    return false;
                }
                index = index + 1
                delta |= (value & 0x1F) << shift;
                if ((value & 0x20) == 0) {
                    result = delta
                    return true;
                } else {
                    shift += 5;
                }
            }

            if (shift > 0) {
                return false;
            }
            return true;
        }

        //Decode single coordinate (say lat|lng|z) starting at index
        func decodeValue(_ encodedArray: Array<Character>,
                         _ index: inout Int,
                         _ coordinate: inout Double) -> Bool {
            var delta: Int64 = 0
            if (!HEREPolylineEncoderDecoder.Converter.decodeUnsignedVarint(encodedArray, &index, &delta)) {
                return false;
            }
            if ((delta & 1) != 0) {
                delta = ~delta
            }
            delta = delta >> 1
            lastValue = lastValue + delta
            coordinate = (Double(lastValue) / multiplier)
            return true;
        }
    } // class Converter

    /**
     *     3rd dimension specification.
     *  Example a level, altitude, elevation or some other custom value.
     *  ABSENT is default when there is no third dimension en/decoding required.
     */
    public enum ThirdDimension: Int64 {
        case ABSENT // (0),
        case LEVEL // (1),
        case ALTITUDE // (2),
        case ELEVATION // (3),
        case RESERVED1 // (4),
        case RESERVED2 // (5),
        case CUSTOM1 // (6),
        case CUSTOM2 // (7);
    }

    /**
     * Coordinate triple
     */
    public class LatLngZ: CustomStringConvertible, Equatable {
        public let lat: Double
        public let lng: Double
        public let z: Double

        init(_ latitude: Double,_ longitude: Double, _ thirdDimension: Double = 0.0) {
            self.lat = latitude
            self.lng = longitude
            self.z   = thirdDimension
        }

        public func toString() -> String {
            return description
        }

        public var description: String {
            return "LatLngZ [lat=\(lat), lng=\(lng), z=\(z)]"
        }
        public static func == (lhs: HEREPolylineEncoderDecoder.LatLngZ, rhs: HEREPolylineEncoderDecoder.LatLngZ) -> Bool {
            return lhs.lat == rhs.lat
                && lhs.lng == rhs.lng
                && lhs.z   == rhs.z
        }
    } // inner class LatLngZ
} // class HEREPolylineEncoderDecoder

extension FloatingPoint {
  @inlinable
  func signum( ) -> Self {
    if self < 0 { return -1 }
    if self > 0 { return 1 }
    return 0
  }
}
q3qa4bjr

q3qa4bjr3#

到目前为止,swift中还没有现成的折线编码库,仅支持以下语言。https://github.com/heremaps/flexible-polyline
我们不能评论任何第三方库,但我们可以检查与工程,如果这个发展是在管道或没有。

相关问题