在 Swift 中解码 HERE REST API 折线?
Decoding HERE REST API polyline in Swift?
我正在使用 HERE REST API 作为 Swift 中转路线。
通常当我在 Swift 中解码折线时,我会使用这个很棒的库 https://github.com/raphaelmor/Polyline/,它适用于 OpenTripPlanner、GoogleDirections、Graphhopper(无高程)等
HERE 折线的编码方式似乎不同
func testDecodePolyline() throws {
let herePolyline = "BHwp7v0W0_uykO89CkU-yIs8B28K89CgkHq8BkmE6a-gF-uBquFooBqvKm3Cg1FooByzEmoB6-Hs8Bm6EooB0kDkUquFq8B4_MqrDi5MykD2jU0nF47F21BzZqwF"
let coordinates: [CLLocationCoordinate2D]? = decodePolyline(herePolyline)
XCTAssertNotNil(coordinates)
}
使用提到的库不起作用。
编码问题有答案:,
实施此文档:https://developer.here.com/documentation/places/dev_guide/topics/location-contexts.html#location-contexts__here-polyline-encoding
HERE 也有一个图书馆,用于多种语言,但 Swift 或 Objective-C 没有:https://github.com/heremaps/flexible-polyline
如何从 Swift 中的 REST API 解码 HERE 折线?
我更喜欢不使用 iOS HERE SDK 的解决方案。如果一定要安装,我会安装一个 iOS HERE SDK。
截至目前,swift 中尚无现成的多段线编码库,仅支持以下语言。 https://github.com/heremaps/flexible-polyline
我们无法对任何第三方库发表评论,但是我们可以与工程部门核实此开发是否在进行中。
我将 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
}
}
我正在使用 HERE REST API 作为 Swift 中转路线。
通常当我在 Swift 中解码折线时,我会使用这个很棒的库 https://github.com/raphaelmor/Polyline/,它适用于 OpenTripPlanner、GoogleDirections、Graphhopper(无高程)等
HERE 折线的编码方式似乎不同
func testDecodePolyline() throws {
let herePolyline = "BHwp7v0W0_uykO89CkU-yIs8B28K89CgkHq8BkmE6a-gF-uBquFooBqvKm3Cg1FooByzEmoB6-Hs8Bm6EooB0kDkUquFq8B4_MqrDi5MykD2jU0nF47F21BzZqwF"
let coordinates: [CLLocationCoordinate2D]? = decodePolyline(herePolyline)
XCTAssertNotNil(coordinates)
}
使用提到的库不起作用。
编码问题有答案:
HERE 也有一个图书馆,用于多种语言,但 Swift 或 Objective-C 没有:https://github.com/heremaps/flexible-polyline
如何从 Swift 中的 REST API 解码 HERE 折线?
我更喜欢不使用 iOS HERE SDK 的解决方案。如果一定要安装,我会安装一个 iOS HERE SDK。
截至目前,swift 中尚无现成的多段线编码库,仅支持以下语言。 https://github.com/heremaps/flexible-polyline
我们无法对任何第三方库发表评论,但是我们可以与工程部门核实此开发是否在进行中。
我将 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
}
}