Skip to content

Commit 6fa2965

Browse files
authored
Merge pull request #90 from google/recovery-issue
Fix short code recovery near poles.
2 parents 219aa2d + 7f02f4c commit 6fa2965

File tree

9 files changed

+115
-102
lines changed

9 files changed

+115
-102
lines changed

go/olc_test.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type (
4545
code string
4646
lat, lng float64
4747
short string
48+
tType string
4849
}
4950
)
5051

@@ -83,6 +84,7 @@ func init() {
8384
code: string(cols[0]),
8485
lat: mustFloat(cols[1]), lng: mustFloat(cols[2]),
8586
short: string(cols[3]),
87+
tType: string(cols[4]),
8688
})
8789
}
8890
}()
@@ -139,24 +141,28 @@ func TestDecode(t *testing.T) {
139141

140142
func TestShorten(t *testing.T) {
141143
for i, elt := range shorten {
142-
got, err := Shorten(elt.code, elt.lat, elt.lng)
143-
if err != nil {
144-
t.Errorf("%d. shorten %q: %v", i, elt.code, err)
145-
t.FailNow()
146-
}
147-
if got != elt.short {
148-
t.Errorf("%d. shorten got %q, awaited %q.", i, got, elt.short)
149-
t.FailNow()
144+
if elt.tType == "B" || elt.tType == "S" {
145+
got, err := Shorten(elt.code, elt.lat, elt.lng)
146+
if err != nil {
147+
t.Errorf("%d. shorten %q: %v", i, elt.code, err)
148+
t.FailNow()
149+
}
150+
if got != elt.short {
151+
t.Errorf("%d. shorten got %q, awaited %q.", i, got, elt.short)
152+
t.FailNow()
153+
}
150154
}
151155

152-
got, err = RecoverNearest(got, elt.lat, elt.lng)
153-
if err != nil {
154-
t.Errorf("%d. nearest %q: %v", i, got, err)
155-
t.FailNow()
156-
}
157-
if got != elt.code {
158-
t.Errorf("%d. nearest got %q, awaited %q.", i, got, elt.code)
159-
t.FailNow()
156+
if elt.tType == "B" || elt.tType == "R" {
157+
got, err := RecoverNearest(elt.short, elt.lat, elt.lng)
158+
if err != nil {
159+
t.Errorf("%d. nearest %q: %v", i, got, err)
160+
t.FailNow()
161+
}
162+
if got != elt.code {
163+
t.Errorf("%d. nearest got %q, awaited %q.", i, got, elt.code)
164+
t.FailNow()
165+
}
160166
}
161167
}
162168
}

go/shorten.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func RecoverNearest(code string, lat, lng float64) (string, error) {
114114
resolution := math.Pow(20, float64(2-(padLen/2)))
115115

116116
// Distance from the center to an edge (in degrees).
117-
areaToEdge := float64(resolution) / 2
117+
halfRes := float64(resolution) / 2
118118

119119
// Use the reference location to pad the supplied short code and decode it.
120120
area, err := Decode(Encode(lat, lng, 0)[:padLen] + code)
@@ -123,24 +123,23 @@ func RecoverNearest(code string, lat, lng float64) (string, error) {
123123
}
124124

125125
// How many degrees latitude is the code from the reference? If it is more
126-
// than half the resolution, we need to move it east or west.
126+
// than half the resolution, we need to move it south or north but keep it
127+
// within -90 to 90 degrees.
127128
centerLat, centerLng := area.Center()
128-
degDiff := centerLat - lat
129-
if degDiff > areaToEdge {
130-
// If the center of the short code is more than half a cell east,
131-
// then the best match will be one position west.
129+
if lat + halfRes < centerLat && centerLat - resolution >= -latMax {
130+
// If the proposed code is more than half a cell north of the reference location,
131+
// it's too far, and the best match will be one cell south.
132132
centerLat -= resolution
133-
} else if degDiff < -areaToEdge {
134-
// If the center of the short code is more than half a cell west,
135-
// then the best match will be one position east.
133+
} else if lat - halfRes > centerLat && centerLat + resolution <= latMax {
134+
// If the proposed code is more than half a cell south of the reference location,
135+
// it's too far, and the best match will be one cell north.
136136
centerLat += resolution
137137
}
138138

139139
// How many degrees longitude is the code from the reference?
140-
degDiff = centerLng - lng
141-
if degDiff > areaToEdge {
140+
if lng + halfRes < centerLng {
142141
centerLng -= resolution
143-
} else if degDiff < -areaToEdge {
142+
} else if lng - halfRes > centerLng {
144143
centerLng += resolution
145144
}
146145

js/src/openlocationcode.js

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -384,30 +384,31 @@
384384
// The resolution (height and width) of the padded area in degrees.
385385
var resolution = Math.pow(20, 2 - (paddingLength / 2));
386386
// Distance from the center to an edge (in degrees).
387-
var areaToEdge = resolution / 2.0;
387+
var halfResolution = resolution / 2.0;
388388

389389
// Use the reference location to pad the supplied short code and decode it.
390390
var codeArea = decode(
391391
encode(referenceLatitude, referenceLongitude).substr(0, paddingLength)
392392
+ shortCode);
393393
// How many degrees latitude is the code from the reference? If it is more
394-
// than half the resolution, we need to move it east or west.
395-
var degreesDifference = codeArea.latitudeCenter - referenceLatitude;
396-
if (degreesDifference > areaToEdge) {
397-
// If the center of the short code is more than half a cell east,
398-
// then the best match will be one position west.
394+
// than half the resolution, we need to move it north or south but keep it
395+
// within -90 to 90 degrees.
396+
if (referenceLatitude + halfResolution < codeArea.latitudeCenter &&
397+
codeArea.latitudeCenter - resolution >= -LATITUDE_MAX_) {
398+
// If the proposed code is more than half a cell north of the reference location,
399+
// it's too far, and the best match will be one cell south.
399400
codeArea.latitudeCenter -= resolution;
400-
} else if (degreesDifference < -areaToEdge) {
401-
// If the center of the short code is more than half a cell west,
402-
// then the best match will be one position east.
401+
} else if (referenceLatitude - halfResolution > codeArea.latitudeCenter &&
402+
codeArea.latitudeCenter + resolution <= LATITUDE_MAX_) {
403+
// If the proposed code is more than half a cell south of the reference location,
404+
// it's too far, and the best match will be one cell north.
403405
codeArea.latitudeCenter += resolution;
404406
}
405407

406408
// How many degrees longitude is the code from the reference?
407-
degreesDifference = codeArea.longitudeCenter - referenceLongitude;
408-
if (degreesDifference > areaToEdge) {
409+
if (referenceLongitude + halfResolution < codeArea.longitudeCenter) {
409410
codeArea.longitudeCenter -= resolution;
410-
} else if (degreesDifference < -areaToEdge) {
411+
} else if (referenceLongitude - halfResolution > codeArea.longitudeCenter) {
411412
codeArea.longitudeCenter += resolution;
412413
}
413414

js/test/test.html

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -129,25 +129,20 @@
129129
// Convert the string numbers to number numbers.
130130
testFields[1] = parseFloat(testFields[1]);
131131
testFields[2] = parseFloat(testFields[2]);
132-
// Shorten the full length code.
133-
var shorten = OpenLocationCode.shorten(
134-
testFields[0], testFields[1], testFields[2]);
135-
//var shortenBy4 = OpenLocationCode.shortenBy4(
136-
// testFields[0], testFields[1], testFields[2]);
137-
//var shortenBy6 = OpenLocationCode.shortenBy6(
138-
// testFields[0], testFields[1], testFields[2]);
139-
// Confirm we got what we expected.
140-
var coords = ' with ' + testFields[1] + ',' + testFields[2];
141-
assert.strictEqual(shorten, testFields[3], testFields[0] + ' shorten ' + coords);
142-
//strictEqual(shortenBy4, testFields[3], testFields[0] + ' shortenBy4 ' + coords);
143-
//strictEqual(shortenBy6, testFields[4], testFields[0] + ' shortenBy6 ' + coords);
144-
// Now try expanding the shortened code.
145-
var expanded = OpenLocationCode.recoverNearest(shorten, testFields[1], testFields[2]);
146-
assert.strictEqual(expanded, testFields[0], testFields[0] + ' expanding shorten ' + coords);
147-
//var expanded = OpenLocationCode.recoverNearest(shortenBy4, testFields[1], testFields[2]);
148-
//strictEqual(expanded, testFields[0], testFields[0] + ' expanding shortenBy4 ' + coords);
149-
//expanded = OpenLocationCode.recoverNearest(shortenBy6, testFields[1], testFields[2]);
150-
//strictEqual(expanded, testFields[0], testFields[0] + ' expanding shortenBy6 ' + coords);
132+
var coords = testFields[1] + ',' + testFields[2];
133+
134+
if (testFields[4] == "B" || testFields[4] == "S") {
135+
// Shorten the full length code.
136+
var shorten = OpenLocationCode.shorten(
137+
testFields[0], testFields[1], testFields[2]);
138+
// Confirm we got what we expected.
139+
assert.strictEqual(shorten, testFields[3], 'shorten ' + testFields[0] + ' with ' + coords);
140+
}
141+
if (testFields[4] == "B" || testFields[4] == "R") {
142+
// Now try expanding the shortened code.
143+
var expanded = OpenLocationCode.recoverNearest(testFields[3], testFields[1], testFields[2]);
144+
assert.strictEqual(expanded, testFields[0], 'recover ' + testFields[3] + ' with ' + coords);
145+
}
151146
}
152147

153148
done();

python/openlocationcode.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -293,25 +293,26 @@ def recoverNearest(shortcode, referenceLatitude, referenceLongitude):
293293
# The resolution (height and width) of the padded area in degrees.
294294
resolution = pow(20, 2 - (paddingLength / 2))
295295
# Distance from the center to an edge (in degrees).
296-
areaToEdge = resolution / 2.0
296+
halfResolution = resolution / 2.0
297297
# Use the reference location to pad the supplied short code and decode it.
298298
codeArea = decode(encode(referenceLatitude, referenceLongitude)[0:paddingLength] + shortcode)
299299
# How many degrees latitude is the code from the reference? If it is more
300-
# than half the resolution, we need to move it east or west.
301-
degreesDifference = codeArea.latitudeCenter - referenceLatitude
302-
if degreesDifference > areaToEdge:
303-
# If the center of the short code is more than half a cell east,
304-
# then the best match will be one position west.
300+
# than half the resolution, we need to move it north or south but keep it
301+
# within -90 to 90 degrees.
302+
if (referenceLatitude + halfResolution < codeArea.latitudeCenter and
303+
codeArea.latitudeCenter - resolution >= -LATITUDE_MAX_):
304+
# If the proposed code is more than half a cell north of the reference location,
305+
# it's too far, and the best match will be one cell south.
305306
codeArea.latitudeCenter -= resolution
306-
elif degreesDifference < -areaToEdge:
307-
# If the center of the short code is more than half a cell west,
308-
# then the best match will be one position east.
307+
elif (referenceLatitude - halfResolution > codeArea.latitudeCenter and
308+
codeArea.latitudeCenter + resolution <= LATITUDE_MAX_):
309+
# If the proposed code is more than half a cell south of the reference location,
310+
# it's too far, and the best match will be one cell north.
309311
codeArea.latitudeCenter += resolution
310-
# How many degrees longitude is the code from the reference?
311-
degreesDifference = codeArea.longitudeCenter - referenceLongitude
312-
if degreesDifference > areaToEdge:
312+
# Adjust longitude if necessary.
313+
if referenceLongitude + halfResolution < codeArea.longitudeCenter:
313314
codeArea.longitudeCenter -= resolution
314-
elif degreesDifference < -areaToEdge:
315+
elif referenceLongitude - halfResolution > codeArea.longitudeCenter:
315316
codeArea.longitudeCenter += resolution
316317
return encode(codeArea.latitudeCenter, codeArea.longitudeCenter, codeArea.codeLength)
317318

python/openlocationcode_test.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_shortcodes(self):
3636
class TestShorten(unittest.TestCase):
3737
def setUp(self):
3838
self.testdata = []
39-
headermap = {0: 'fullcode', 1: 'lat', 2: 'lng', 3: 'shortcode'}
39+
headermap = {0: 'fullcode', 1: 'lat', 2: 'lng', 3: 'shortcode', 4:'testtype'}
4040
tests_fn = 'test_data/shortCodeTests.csv'
4141
with open(tests_fn, "r") as fin:
4242
for line in fin:
@@ -50,7 +50,10 @@ def setUp(self):
5050

5151
def test_full2short(self):
5252
for td in self.testdata:
53-
self.assertEqual(td['shortcode'], olc.shorten(td['fullcode'], td['lat'], td['lng']), td)
53+
if td['testtype'] == 'B' or td['testtype'] == 'S':
54+
self.assertEqual(td['shortcode'], olc.shorten(td['fullcode'], td['lat'], td['lng']), td)
55+
if td['testtype'] == 'B' or td['testtype'] == 'R':
56+
self.assertEqual(td['fullcode'], olc.recoverNearest(td['shortcode'], td['lat'], td['lng']), td)
5457

5558

5659
class TestEncoding(unittest.TestCase):

ruby/lib/plus_codes/open_location_code.rb

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,21 @@ def recover_nearest(short_code, reference_latitude, reference_longitude)
121121
code = prefix_by_reference(ref_lat, ref_lng, prefix_len) << short_code
122122
code_area = decode(code)
123123

124-
area_range = precision_by_length(prefix_len)
125-
area_edge = area_range / 2
124+
resolution = precision_by_length(prefix_len)
125+
half_res = resolution / 2
126126

127127
latitude = code_area.latitude_center
128-
latitude_diff = latitude - ref_lat
129-
if (latitude_diff > area_edge)
130-
latitude -= area_range
131-
elsif (latitude_diff < -area_edge)
132-
latitude += area_range
128+
if (ref_lat + half_res < latitude && latitude - resolution >= -90)
129+
latitude -= resolution
130+
elsif (ref_lat - half_res > latitude && latitude + resolution <= 90)
131+
latitude += resolution
133132
end
134133

135134
longitude = code_area.longitude_center
136-
longitude_diff = longitude - ref_lng
137-
if (longitude_diff > area_edge)
138-
longitude -= area_range
139-
elsif (longitude_diff < -area_edge)
140-
longitude += area_range
135+
if (ref_lng + half_res < longitude)
136+
longitude -= resolution
137+
elsif (ref_lng - half_res > longitude)
138+
longitude += resolution
141139
end
142140

143141
encode(latitude, longitude, code.length - SEPARATOR.length)

ruby/test/plus_codes_test.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ def test_shorten
4747
lat = cols[1].to_f
4848
lng = cols[2].to_f
4949
short_code = cols[3]
50-
short = @olc.shorten(code, lat, lng)
51-
assert_equal(short_code, short)
52-
expanded = @olc.recover_nearest(short, lat, lng)
53-
assert_equal(code, expanded)
50+
test_type = cols[4]
51+
if test_type == 'B' || test_type == 'S'
52+
short = @olc.shorten(code, lat, lng)
53+
assert_equal(short_code, short)
54+
end
55+
if test_type == 'B' || test_type == 'R'
56+
expanded = @olc.recover_nearest(short_code, lat, lng)
57+
assert_equal(code, expanded)
58+
end
5459
end
5560
@olc.shorten('9C3W9QCJ+2VX', 60.3701125, 10.202665625)
5661
end

test_data/shortCodeTests.csv

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
# Test shortening and extending codes.
22
#
33
# Format:
4-
# full code,lat,lng,shortcode
5-
9C3W9QCJ+2VX,51.3701125,-1.217765625,+2VX
4+
# full code,lat,lng,shortcode,test_type
5+
# test_type is R for recovery only, S for shorten only, or B for both.
6+
9C3W9QCJ+2VX,51.3701125,-1.217765625,+2VX,B
67
# Adjust so we can't trim by 8 (+/- .000755)
7-
9C3W9QCJ+2VX,51.3708675,-1.217765625,CJ+2VX
8-
9C3W9QCJ+2VX,51.3693575,-1.217765625,CJ+2VX
9-
9C3W9QCJ+2VX,51.3701125,-1.218520625,CJ+2VX
10-
9C3W9QCJ+2VX,51.3701125,-1.217010625,CJ+2VX
8+
9C3W9QCJ+2VX,51.3708675,-1.217765625,CJ+2VX,B
9+
9C3W9QCJ+2VX,51.3693575,-1.217765625,CJ+2VX,B
10+
9C3W9QCJ+2VX,51.3701125,-1.218520625,CJ+2VX,B
11+
9C3W9QCJ+2VX,51.3701125,-1.217010625,CJ+2VX,B
1112
# Adjust so we can't trim by 6 (+/- .0151)
12-
9C3W9QCJ+2VX,51.3852125,-1.217765625,9QCJ+2VX
13-
9C3W9QCJ+2VX,51.3550125,-1.217765625,9QCJ+2VX
14-
9C3W9QCJ+2VX,51.3701125,-1.232865625,9QCJ+2VX
15-
9C3W9QCJ+2VX,51.3701125,-1.202665625,9QCJ+2VX
13+
9C3W9QCJ+2VX,51.3852125,-1.217765625,9QCJ+2VX,B
14+
9C3W9QCJ+2VX,51.3550125,-1.217765625,9QCJ+2VX,B
15+
9C3W9QCJ+2VX,51.3701125,-1.232865625,9QCJ+2VX,B
16+
9C3W9QCJ+2VX,51.3701125,-1.202665625,9QCJ+2VX,B
1617
# Added to detect error in recoverNearest functionality
17-
8FJFW222+,42.899,9.012,22+
18-
796RXG22+,14.95125,-23.5001,22+
18+
8FJFW222+,42.899,9.012,22+,B
19+
796RXG22+,14.95125,-23.5001,22+,B
20+
# Added to detect errors recovering codes near the poles.
21+
# This tests recovery function, but these codes won't shorten.
22+
CFX22222+22,89.6,0.0,2222+22,R
23+
2CXXXXXX+XX,-81.0,0.0,XXXXXX+XX,R

0 commit comments

Comments
 (0)