Skip to content

Commit 7bd969f

Browse files
authored
Add GeoJSON.LineString.encodedPolyline() (#25)
1 parent 2f3942c commit 7bd969f

3 files changed

Lines changed: 165 additions & 63 deletions

File tree

Sources/GeoJSONKitTurf/GeoJSON+LineString+DecodePolyline.swift

Lines changed: 0 additions & 63 deletions
This file was deleted.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
//
2+
// GeoJSON+LineString+EncodedPolyline.swift
3+
//
4+
// Created by Adrian Schoenig on 18/2/17.
5+
//
6+
//
7+
import Foundation
8+
9+
import GeoJSONKit
10+
11+
extension GeoJSON.LineString {
12+
13+
// MARK: - Decode
14+
15+
public init(encodedPolyline: String) {
16+
let bytes = encodedPolyline.utf8CString
17+
let length = bytes.count - 1 // ignore 0 at end
18+
var idx = 0
19+
20+
var array: [GeoJSON.Position] = []
21+
22+
var latitude = 0.0
23+
var longitude = 0.0
24+
while idx < length {
25+
var byte = 0
26+
var res = 0
27+
var shift = 0
28+
29+
repeat {
30+
if idx > length {
31+
break
32+
}
33+
byte = Int(bytes[idx]) - 63
34+
idx += 1
35+
res |= (byte & 0x1F) << shift
36+
shift += 5
37+
} while byte >= 0x20
38+
39+
let deltaLat = ((res & 1) != 0 ? ~(res >> 1) : (res >> 1));
40+
latitude += Double(deltaLat)
41+
42+
shift = 0
43+
res = 0
44+
45+
repeat {
46+
if idx > length {
47+
break
48+
}
49+
byte = Int(bytes[idx]) - 0x3F
50+
idx += 1
51+
res |= (byte & 0x1F) << shift
52+
shift += 5
53+
} while byte >= 0x20
54+
55+
let deltaLon = ((res & 1) != 0 ? ~(res >> 1) : (res >> 1));
56+
longitude += Double(deltaLon)
57+
58+
let finalLat = latitude * 1E-5
59+
let finalLon = longitude * 1E-5
60+
let coordinate = GeoJSON.Position(latitude: finalLat, longitude: finalLon)
61+
array.append(coordinate)
62+
}
63+
64+
self.init(positions: array)
65+
}
66+
67+
// MARK: - Encode
68+
69+
/// Encodes this `GeoJSON.LineString` to a `String`
70+
///
71+
/// Adopted from https://github.com/raphaelmor/Polyline/blob/master/Sources/Polyline/Polyline.swift
72+
///
73+
/// - parameter precision: The precision used to encode coordinates (default: `1e5`)
74+
/// - returns: A `String` representing the encoded polyline
75+
public func encodedPolyline(precision: Double = 1e5) -> String {
76+
var previousCoordinate = IntegerCoordinates(0, 0)
77+
var encodedPolyline = ""
78+
79+
for position in positions {
80+
let intLatitude = Int(round(position.latitude * precision))
81+
let intLongitude = Int(round(position.longitude * precision))
82+
83+
let coordinatesDifference = (intLatitude - previousCoordinate.latitude, intLongitude - previousCoordinate.longitude)
84+
encodedPolyline += Self.encodeCoordinate(coordinatesDifference)
85+
86+
previousCoordinate = (intLatitude, intLongitude)
87+
}
88+
89+
return encodedPolyline
90+
}
91+
92+
private typealias IntegerCoordinates = (latitude: Int, longitude: Int)
93+
94+
private static func encodeCoordinate(_ coordinate: IntegerCoordinates) -> String {
95+
let latitudeString = encodeSingleComponent(coordinate.latitude)
96+
let longitudeString = encodeSingleComponent(coordinate.longitude)
97+
return latitudeString + longitudeString
98+
}
99+
100+
private static func encodeSingleComponent(_ value: Int) -> String {
101+
var intValue = value
102+
if intValue < 0 {
103+
intValue = intValue << 1
104+
intValue = ~intValue
105+
} else {
106+
intValue = intValue << 1
107+
}
108+
return encodeFiveBitComponents(intValue)
109+
}
110+
111+
private static func encodeLevel(_ level: UInt32) -> String {
112+
return encodeFiveBitComponents(Int(level))
113+
}
114+
115+
private static func encodeFiveBitComponents(_ value: Int) -> String {
116+
var remainingComponents = value
117+
118+
var fiveBitComponent = 0
119+
var returnString = String()
120+
121+
repeat {
122+
fiveBitComponent = remainingComponents & 0x1F
123+
124+
if remainingComponents >= 0x20 {
125+
fiveBitComponent |= 0x20
126+
}
127+
128+
fiveBitComponent += 63
129+
130+
let char = UnicodeScalar(fiveBitComponent)!
131+
returnString.append(String(char))
132+
remainingComponents = remainingComponents >> 5
133+
} while (remainingComponents != 0)
134+
135+
return returnString
136+
}
137+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// EncodedPolylineTests.swift
3+
// GeoJSONKitTurf
4+
//
5+
// Created by Adrian Schönig on 14/2/2025.
6+
//
7+
8+
#if canImport(Testing) && swift(>=6)
9+
import Testing
10+
11+
import GeoJSONKit
12+
import GeoJSONKitTurf
13+
14+
struct EncodedPolylineTests {
15+
16+
@Test func testPalermoToRome() async throws {
17+
18+
let polyline = "iasgFkxqpAbbCseDtrBmpDv_BgeEvtB_mDdjB_}Dz|@iuEjc@ihF`YcdFnn@__FjiAowErgAmoEvCwgFqOkbFiPghFoI{hFwNgfFcS}eFa}@e}EpRgyE|UslFfEgcFtWihFvSqhFbZi_Fm_AaoEknA}xDcBwiF}MsgF}IahF{EegFo_@kdFcNshFqYeiFmW}`FoVefF_NslFkyAeeEkrAilEw~AoeEyiAwpEcyBwpD{hAgqEc[weFyc@qbFiy@s|Ea`BkbEaUk`FvcAouEfiAaqE]{hFqf@uaFky@gsEymBavDytBkyDqaCw_DkbBu~De`Au_Fsj@wyEk\\agFrUycFfx@ozEdfBc`EhBarEclBiaEws@mtEgXijFMkjFeEmdFyn@mbFemAomE}fAwyE{yB_cDanDyy@_{C}nBu_DqlByaDqmBegDmuAitDkSknDwo@akAmjEyrAewEcsAavEwqAwfEo|AwlE_}AgbEkoC{iCmuCoyBahDu_BcsDcNcoDys@aqDq\\gwD{EyqD{h@uhD{eAmrDuIwuD^grD~VodDbvAizC`zBm`DncBgbDryBa~C{\\qoDomAwoDmg@euDwJ{nDlp@_fDvtAmeDx{AgoDbhAenDz]{pD~a@}kDf`AqjDdiA_oD~t@ymDlLizDlLicDxgAw_DnoBgmDnn@_sDtEyrDfTulDrl@ymDt_AolD~}@irDb{@ycD`~@ouDnr@agDxr@unD~q@anDbq@skDby@icDh_BmnD|z@meDxm@qpDzr@yxDj_@s|Cdl@krBxzDaqBzwDu{B~yCg_DnxB_gC~jCgpC`eC{cCb{C}qBluDijBlrD_|BxkDuiChlCotB|{CquBt|DutB~wDmeB`oDcbBnlE{mAjlEciC|dAygDhaAeqCvdCk_CljDe{BzcDg~B~`D{zBdeD{}Bh_Ds}BxlDivB|kD_nBvmDueBnaEugBtvDu~Ax}DgcCxdDgsBzeDgkBxyDu}AdcEgt@`wEqr@pgF{^v{E_PngF_W|gF}s@x~E}^tbFef@r`Fu`AhvEay@l}E}o@tdFqlAzgE}z@~xEmjBfvDmnAhmE}bBrvDwcAh_Fyp@h|EewAxbEosA|nDu|BlxCkjBv{D}gB|aEyeB`bEw`B||Dq~Az}D}tBznDy}BnwCmjB~vDoiB|_EigBjxDwgBr~D{_BlcE{uA`gEozAvcEylBhvDa{BhdDioBlqDulBf~D}_BtuDm_CfhDo~CrqAscC`lC_eCfzCuzBffDe|AnbE{mBhuDogClnCquC~pBo_Cr|CeqBhnDeiB`{DudBnzDeqBtnDuoBhpD_qBdrDwcBn}D{aBn`EefBdxDshBtzDumBdxDotAbeEazAzhEm_Bj}DcdB|zDylBpiDadBr|DqdBheEs_Bj~D{~A`cEiuAdlEm`AjsEsv@x`Feu@`mFcf@t}EmCfaF}s@jzDs{CfiBsuB|zCusB|jDedB|zDaxBjiDgNpR"
19+
20+
let decoded = GeoJSON.LineString(encodedPolyline: polyline)
21+
#expect(decoded.positions.count == 256)
22+
23+
#expect(decoded.encodedPolyline() == polyline)
24+
}
25+
26+
}
27+
28+
#endif

0 commit comments

Comments
 (0)