66 */
77package com.here.flexiblepolyline
88
9- import java.util.concurrent.atomic.AtomicInteger
10- import java.util.concurrent.atomic.AtomicLong
11- import java.util.concurrent.atomic.AtomicReference
9+ import kotlin.math.abs
10+ import kotlin.math.roundToLong
11+ import kotlin.math.sign
12+ import kotlin.math.pow
13+
1214/* *
1315 * The polyline encoding is a lossy compressed representation of a list of coordinate pairs or coordinate triples.
1416 * It achieves that by:
@@ -25,7 +27,7 @@ import java.util.concurrent.atomic.AtomicReference
2527 */
2628object FlexiblePolyline {
2729
28- private const val version : Int = 1
30+ const val VERSION = 1L
2931 // Base64 URL-safe characters
3032 private val ENCODING_TABLE = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" .toCharArray()
3133 private val DECODING_TABLE = intArrayOf(
@@ -63,22 +65,16 @@ object FlexiblePolyline {
6365 * @param encoded URL-safe encoded [String]
6466 * @return [List] of coordinate triples that are decoded from input
6567 *
66- * @see PolylineDecoder. getThirdDimension
68+ * @see getThirdDimension
6769 * @see LatLngZ
6870 */
6971 @JvmStatic
7072 fun decode (encoded : String? ): List <LatLngZ > {
7173 require(! (encoded == null || encoded.trim { it <= ' ' }.isEmpty())) { " Invalid argument!" }
7274 val result: MutableList <LatLngZ > = ArrayList ()
7375 val dec = Decoder (encoded)
74- var lat = AtomicReference (0.0 )
75- var lng = AtomicReference (0.0 )
76- var z = AtomicReference (0.0 )
77- while (dec.decodeOne(lat, lng, z)) {
78- result.add(LatLngZ (lat.get(), lng.get(), z.get()))
79- lat = AtomicReference (0.0 )
80- lng = AtomicReference (0.0 )
81- z = AtomicReference (0.0 )
76+ while (dec.hasNext()) {
77+ result.add(dec.decodeOne())
8278 }
8379 return result
8480 }
@@ -90,10 +86,7 @@ object FlexiblePolyline {
9086 */
9187 @JvmStatic
9288 fun getThirdDimension (encoded : String ): ThirdDimension ? {
93- val index = AtomicInteger (0 )
94- val header = AtomicLong (0 )
95- Decoder .decodeHeaderFromString(encoded.toCharArray(), index, header)
96- return ThirdDimension .fromNum(header.get() shr 4 and 7 )
89+ return Decoder (encoded).thirdDimension
9790 }
9891
9992 // Decode a single char to the corresponding value
@@ -112,6 +105,12 @@ object FlexiblePolyline {
112105 private val latConveter: Converter = Converter (precision)
113106 private val lngConveter: Converter = Converter (precision)
114107 private val zConveter: Converter = Converter (thirdDimPrecision)
108+
109+
110+ init {
111+ encodeHeader(precision, this .thirdDimension.num, thirdDimPrecision)
112+ }
113+
115114 private fun encodeHeader (precision : Int , thirdDimensionValue : Int , thirdDimPrecision : Int ) {
116115 /*
117116 * Encode the `precision`, `third_dim` and `third_dim_precision` into one encoded char
@@ -120,8 +119,8 @@ object FlexiblePolyline {
120119 require(! (thirdDimPrecision < 0 || thirdDimPrecision > 15 )) { " thirdDimPrecision out of range" }
121120 require(! (thirdDimensionValue < 0 || thirdDimensionValue > 7 )) { " thirdDimensionValue out of range" }
122121 val res = (thirdDimPrecision shl 7 or (thirdDimensionValue shl 4 ) or precision).toLong()
123- Converter .encodeUnsignedVarint(version.toLong() , result)
124- Converter .encodeUnsignedVarint (res, result)
122+ Converter .encodeUnsignedVarInt( VERSION , result)
123+ Converter .encodeUnsignedVarInt (res, result)
125124 }
126125
127126 private fun add (lat : Double , lng : Double ) {
@@ -144,71 +143,52 @@ object FlexiblePolyline {
144143 fun getEncoded (): String {
145144 return result.toString()
146145 }
147-
148- init {
149- encodeHeader(precision, this .thirdDimension.num, thirdDimPrecision)
150- }
151146 }
152147
153148 /*
154149 * Single instance for decoding an input request.
155150 */
156151 private class Decoder (encoded : String ) {
157- private val encoded: CharArray = encoded.toCharArray()
158- private val index: AtomicInteger = AtomicInteger (0 )
152+ private val encoded: CharIterator = (encoded).iterator()
159153 private val latConverter: Converter
160154 private val lngConverter: Converter
161155 private val zConverter: Converter
162- private var precision = 0
163- private var thirdDimPrecision = 0
164- private var thirdDimension: ThirdDimension ? = null
156+ var thirdDimension: ThirdDimension ? = null
157+
158+ init {
159+ val header = decodeHeader()
160+ val precision = header and 0x0f
161+ thirdDimension = ThirdDimension .fromNum(((header shr 4 ) and 0x07 ).toLong())
162+ val thirdDimPrecision = ((header shr 7 ) and 0x0f )
163+ latConverter = Converter (precision)
164+ lngConverter = Converter (precision)
165+ zConverter = Converter (thirdDimPrecision)
166+ }
167+
165168 private fun hasThirdDimension (): Boolean {
166169 return thirdDimension != ThirdDimension .ABSENT
167170 }
168171
169- private fun decodeHeader () {
170- val header = AtomicLong (0 )
171- decodeHeaderFromString(encoded, index, header)
172- precision = (header.get() and 15 ).toInt() // we pick the first 4 bits only
173- header.set(header.get() shr 4 )
174- thirdDimension = ThirdDimension .fromNum(header.get() and 7 ) // we pick the first 3 bits only
175- thirdDimPrecision = (header.get() shr 3 and 15 ).toInt()
172+ private fun decodeHeader (): Int {
173+ val version = Converter .decodeUnsignedVarInt(encoded)
174+ require(version == VERSION ) { " Invalid format version :: encoded.$version vs FlexiblePolyline.$VERSION " }
175+ // Decode the polyline header
176+ return Converter .decodeUnsignedVarInt(encoded).toInt()
176177 }
177178
178- fun decodeOne (
179- lat : AtomicReference <Double >,
180- lng : AtomicReference <Double >,
181- z : AtomicReference <Double >
182- ): Boolean {
183- if (index.get() == encoded.size) {
184- return false
185- }
186- require(latConverter.decodeValue(encoded, index, lat)) { " Invalid encoding" }
187- require(lngConverter.decodeValue(encoded, index, lng)) { " Invalid encoding" }
179+ fun decodeOne (): LatLngZ {
180+ val lat = latConverter.decodeValue(encoded)
181+ val lng = lngConverter.decodeValue(encoded)
182+
188183 if (hasThirdDimension()) {
189- require(zConverter.decodeValue(encoded, index, z)) { " Invalid encoding" }
184+ val z = zConverter.decodeValue(encoded)
185+ return LatLngZ (lat, lng, z)
190186 }
191- return true
187+ return LatLngZ (lat, lng)
192188 }
193189
194- companion object {
195- fun decodeHeaderFromString (encoded : CharArray , index : AtomicInteger , header : AtomicLong ) {
196- val value = AtomicLong (0 )
197-
198- // Decode the header version
199- require(Converter .decodeUnsignedVarint(encoded, index, value)) { " Invalid encoding" }
200- require(value.get() == version.toLong()) { " Invalid format version" }
201- // Decode the polyline header
202- require(Converter .decodeUnsignedVarint(encoded, index, value)) { " Invalid encoding" }
203- header.set(value.get())
204- }
205- }
206-
207- init {
208- decodeHeader()
209- latConverter = Converter (precision)
210- lngConverter = Converter (precision)
211- zConverter = Converter (thirdDimPrecision)
190+ fun hasNext (): Boolean {
191+ return encoded.hasNext()
212192 }
213193 }
214194
@@ -219,12 +199,8 @@ object FlexiblePolyline {
219199 * Lat0 Lng0 3rd0 (Lat1-Lat0) (Lng1-Lng0) (3rdDim1-3rdDim0)
220200 */
221201 class Converter (precision : Int ) {
222- private var multiplier: Long = 0
202+ private val multiplier = ( 10.0 .pow(precision.toDouble())).toLong()
223203 private var lastValue: Long = 0
224- private fun setPrecision (precision : Int ) {
225- // multiplier = Math.pow(10.0, java.lang.Double.valueOf(precision.toDouble())).toLong()
226- multiplier = Math .pow(10.0 , precision.toDouble()).toLong()
227- }
228204
229205 fun encodeValue (value : Double , result : StringBuilder ) {
230206 /*
@@ -233,7 +209,7 @@ object FlexiblePolyline {
233209 * round(-1.5) --> -2
234210 * round(-2.5) --> -3
235211 */
236- val scaledValue = Math .round( Math . abs(value * multiplier)) * Math .round( Math .signum( value))
212+ val scaledValue = abs(value * multiplier).roundToLong( ) * sign( value).roundToLong( )
237213 var delta = scaledValue - lastValue
238214 val negative = delta < 0
239215 lastValue = scaledValue
@@ -245,34 +221,23 @@ object FlexiblePolyline {
245221 if (negative) {
246222 delta = delta.inv ()
247223 }
248- encodeUnsignedVarint (delta, result)
224+ encodeUnsignedVarInt (delta, result)
249225 }
250226
251227 // Decode single coordinate (say lat|lng|z) starting at index
252- fun decodeValue (
253- encoded : CharArray ,
254- index : AtomicInteger ,
255- coordinate : AtomicReference <Double >
256- ): Boolean {
257- val delta = AtomicLong ()
258- if (! decodeUnsignedVarint(encoded, index, delta)) {
259- return false
260- }
261- if (delta.get() and 1 != 0L ) {
262- delta.set(delta.get().inv ())
228+ fun decodeValue (encoded : CharIterator ): Double {
229+ var l = decodeUnsignedVarInt(encoded)
230+ if ((l and 1L ) != 0L ) {
231+ l = l.inv ()
263232 }
264- delta.set(delta.get() shr 1 )
265- lastValue + = delta.get()
266- coordinate.set(lastValue.toDouble() / multiplier)
267- return true
233+ l = l shr 1
234+ lastValue + = l
235+
236+ return lastValue.toDouble() / multiplier
268237 }
269238
270239 companion object {
271- fun encodeUnsignedVarint (value : Long , result : StringBuilder ) {
272- // TODO: check performance impact
273- /* val estimatedCapacity = 10000 // Adjust as needed
274- result.ensureCapacity(estimatedCapacity)*/
275- // end TODO
240+ fun encodeUnsignedVarInt (value : Long , result : StringBuilder ) {
276241 var number = value
277242 while (number > 0x1F ) {
278243 val pos = (number and 0x1F or 0x20 ).toByte()
@@ -282,40 +247,25 @@ object FlexiblePolyline {
282247 result.append(ENCODING_TABLE [number.toByte().toInt()])
283248 }
284249
285- fun decodeUnsignedVarint (
286- encoded : CharArray ,
287- index : AtomicInteger ,
288- result : AtomicLong
289- ): Boolean {
250+ fun decodeUnsignedVarInt (encoded : CharIterator ): Long {
290251 var shift: Short = 0
291- var delta : Long = 0
292- var value : Long
293- while (index.get() < encoded.size) {
294- value = decodeChar(encoded[index.get()] ).toLong()
252+ var result : Long = 0
253+ while ( encoded.hasNext() ) {
254+ val c = encoded.next()
255+ val value = decodeChar(c ).toLong()
295256 if (value < 0 ) {
296- return false
257+ throw IllegalArgumentException ( " Unexpected value found :: ' $c " )
297258 }
298- index.incrementAndGet()
299- delta = delta or (value and 0x1F shl shift.toInt())
300- if (value and 0x20 == 0L ) {
301- result.set(delta)
302- return true
259+ result = result or ((value and 0x1FL ) shl shift.toInt())
260+ if ((value and 0x20L ) == 0L ) {
261+ return result
303262 } else {
304263 shift = (shift + 5 ).toShort()
305264 }
306- // TODO: Check performance and tests
307- /* if (shift <= 0) {
308- return true
309- }*/
310- // end TODO
311265 }
312- return shift <= 0
266+ return result
313267 }
314268 }
315-
316- init {
317- setPrecision(precision)
318- }
319269 }
320270
321271 /* *
@@ -328,7 +278,7 @@ object FlexiblePolyline {
328278
329279 companion object {
330280 fun fromNum (value : Long ): ThirdDimension ? {
331- for (dim in values() ) {
281+ for (dim in entries ) {
332282 if (dim.num.toLong() == value) {
333283 return dim
334284 }
@@ -351,8 +301,7 @@ object FlexiblePolyline {
351301 return true
352302 }
353303 if (other is LatLngZ ) {
354- val passed = other
355- if (passed.lat == lat && passed.lng == lng && passed.z == z) {
304+ if (other.lat == lat && other.lng == lng && other.z == z) {
356305 return true
357306 }
358307 }
0 commit comments