Skip to content

Commit dcff153

Browse files
authored
python: use floor for conversion, add integer tests (#733)
1 parent 2afe39f commit dcff153

File tree

2 files changed

+55
-17
lines changed

2 files changed

+55
-17
lines changed

python/openlocationcode/openlocationcode.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ def isFull(code):
226226
return False
227227
return True
228228

229+
229230
def locationToIntegers(latitude, longitude):
230231
"""
231232
Convert location in degrees into the integer representations.
@@ -239,14 +240,14 @@ def locationToIntegers(latitude, longitude):
239240
Return:
240241
A tuple of the [latitude, longitude] values as integers.
241242
"""
242-
latVal = int(round(latitude * FINAL_LAT_PRECISION_))
243+
latVal = int(math.floor(latitude * FINAL_LAT_PRECISION_))
243244
latVal += LATITUDE_MAX_ * FINAL_LAT_PRECISION_
244245
if latVal < 0:
245246
latVal = 0
246247
elif latVal >= 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_:
247248
latVal = 2 * LATITUDE_MAX_ * FINAL_LAT_PRECISION_ - 1
248249

249-
lngVal = int(round(longitude * FINAL_LNG_PRECISION_))
250+
lngVal = int(math.floor(longitude * FINAL_LNG_PRECISION_))
250251
lngVal += LONGITUDE_MAX_ * FINAL_LNG_PRECISION_
251252
if lngVal < 0:
252253
# Python's % operator differs from other languages in that it returns
@@ -257,6 +258,7 @@ def locationToIntegers(latitude, longitude):
257258
lngVal = lngVal % (2 * LONGITUDE_MAX_ * FINAL_LNG_PRECISION_)
258259
return (latVal, lngVal)
259260

261+
260262
def encode(latitude, longitude, codeLength=PAIR_CODE_LENGTH_):
261263
"""
262264
Encode a location into an Open Location Code.
@@ -274,21 +276,22 @@ def encode(latitude, longitude, codeLength=PAIR_CODE_LENGTH_):
274276
codeLength: The number of significant digits in the output code, not
275277
including any separator characters.
276278
"""
277-
if codeLength < MIN_DIGIT_COUNT_ or (codeLength < PAIR_CODE_LENGTH_ and
278-
codeLength % 2 == 1):
279-
raise ValueError('Invalid Open Location Code length - ' +
280-
str(codeLength))
281-
codeLength = min(codeLength, MAX_DIGIT_COUNT_)
282279
(latInt, lngInt) = locationToIntegers(latitude, longitude)
283280
return encodeIntegers(latInt, lngInt, codeLength)
284281

282+
285283
def encodeIntegers(latVal, lngVal, codeLength):
286284
"""
287285
Encode a location, as two integer values, into a code.
288286
289287
This function is exposed for testing purposes and should not be called
290288
directly.
291289
"""
290+
if codeLength < MIN_DIGIT_COUNT_ or (codeLength < PAIR_CODE_LENGTH_ and
291+
codeLength % 2 == 1):
292+
raise ValueError('Invalid Open Location Code length - ' +
293+
str(codeLength))
294+
codeLength = min(codeLength, MAX_DIGIT_COUNT_)
292295
# Initialise the code string.
293296
code = ''
294297

python/openlocationcode_test.py

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,14 @@ class TestEncoding(unittest.TestCase):
8686

8787
def setUp(self):
8888
self.testdata = []
89-
headermap = {0: 'lat', 1: 'lng', 2: 'length', 3: 'code'}
89+
headermap = {
90+
0: 'lat',
91+
1: 'lng',
92+
2: 'latInt',
93+
3: 'lngInt',
94+
4: 'length',
95+
5: 'code'
96+
}
9097
tests_fn = _TEST_DATA + '/encoding.csv'
9198
with open(tests_fn, mode='r', encoding='utf-8') as fin:
9299
for line in fin:
@@ -95,20 +102,48 @@ def setUp(self):
95102
td = line.strip().split(',')
96103
assert len(td) == len(
97104
headermap), 'Wrong format of testing data: {0}'.format(line)
98-
# first 3 keys should be numbers
99-
for i in range(0, 3):
100-
td[i] = float(td[i])
105+
# First two columns are floats, next three are integers.
106+
td[0] = float(td[0])
107+
td[1] = float(td[1])
108+
td[2] = int(td[2])
109+
td[3] = int(td[3])
110+
td[4] = int(td[4])
101111
self.testdata.append({
102112
headermap[i]: v for i, v in enumerate(td)
103113
})
104114

105-
def test_encoding(self):
115+
def test_converting_degrees(self):
116+
for td in self.testdata:
117+
got = olc.locationToIntegers(td['lat'], td['lng'])
118+
# Due to floating point precision limitations, we may get values 1 less than expected.
119+
self.assertTrue(
120+
td['latInt'] - 1 <= got[0] <= td['latInt'],
121+
f'Latitude conversion {td["lat"]}: want {td["latInt"]} got {got[0]}'
122+
)
123+
self.assertTrue(
124+
td['lngInt'] - 1 <= got[1] <= td['lngInt'],
125+
f'Longitude conversion {td["lng"]}: want {td["lngInt"]} got {got[1]}'
126+
)
127+
128+
def test_encoding_degrees(self):
129+
# Allow a small proportion of errors due to floating point.
130+
allowedErrorRate = 0.05
131+
errors = 0
132+
for td in self.testdata:
133+
got = olc.encode(td['lat'], td['lng'], td['length'])
134+
if got != td['code']:
135+
print(
136+
f'olc.encode({td["lat"]}, {td["lng"]}, {td["length"]}) want {td["code"]}, got {got}'
137+
)
138+
errors += 1
139+
self.assertLessEqual(errors / len(self.testdata), allowedErrorRate,
140+
"olc.encode error rate too high")
141+
142+
def test_encoding_integers(self):
106143
for td in self.testdata:
107-
codelength = len(td['code']) - 1
108-
if '0' in td['code']:
109-
codelength = td['code'].index('0')
110-
self.assertEqual(td['code'],
111-
olc.encode(td['lat'], td['lng'], codelength))
144+
self.assertEqual(
145+
td['code'],
146+
olc.encodeIntegers(td['latInt'], td['lngInt'], td['length']))
112147

113148

114149
class TestDecoding(unittest.TestCase):

0 commit comments

Comments
 (0)