Skip to content

Commit 0f38a2a

Browse files
committed
Refactors implementation to get rid of atomic.* dependencies. Also some code clean-up
Signed-off-by: Hille, Marlon <[email protected]>
1 parent d0f47bf commit 0f38a2a

2 files changed

Lines changed: 72 additions & 128 deletions

File tree

kotlin/src/com/here/flexiblepolyline/FlexiblePolyline.kt

Lines changed: 69 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
*/
77
package 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
*/
2628
object 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
}

kotlin/src/com/here/flexiblepolyline/FlexiblePolylineTest.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,11 @@ class FlexiblePolylineTest {
112112
}
113113

114114
private fun testDecodeConvertValue() {
115-
val encoded = ("h_wqiB").toCharArray()
115+
val encoded = ("h_wqiB").iterator()
116116
val expected = -179.98321
117-
val computed = AtomicReference(0.0)
118117
val conv = Converter(5)
119-
conv.decodeValue(
120-
encoded,
121-
AtomicInteger(0),
122-
computed
123-
)
124-
assertEquals(computed.get(), expected)
118+
val computed = conv.decodeValue(encoded)
119+
assertEquals(computed, expected)
125120
}
126121

127122
private fun testSimpleLatLngDecoding() {

0 commit comments

Comments
 (0)