Skip to content

Commit 8bfbdfb

Browse files
authored
Merge pull request #300 from WilliamDenniss/maxlength
Add max code length tests, fix behavior for Python, Rust and C++
2 parents c4d78b4 + af246f0 commit 8bfbdfb

File tree

7 files changed

+57
-25
lines changed

7 files changed

+57
-25
lines changed

cpp/openlocationcode.cc

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,19 @@ double adjust_latitude(double latitude_degrees, size_t code_length) {
9292
return latitude_degrees - precision / 2;
9393
}
9494

95+
// Remove the separator and padding characters from the code.
96+
std::string clean_code_chars(const std::string &code) {
97+
std::string clean_code(code);
98+
clean_code.erase(
99+
std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator),
100+
clean_code.end());
101+
if (clean_code.find(internal::kPaddingCharacter)) {
102+
clean_code = clean_code.substr(
103+
0, clean_code.find(internal::kPaddingCharacter));
104+
}
105+
return clean_code;
106+
}
107+
95108

96109
// Encodes positive range lat,lng into a sequence of OLC lat/lng pairs.
97110
// This uses pairs of characters (latitude and longitude in that order) to
@@ -198,15 +211,10 @@ std::string Encode(const LatLng &location) {
198211
}
199212

200213
CodeArea Decode(const std::string &code) {
201-
// Make a copy that doesn't have the separator and stops at the first padding
202-
// character.
203-
std::string clean_code(code);
204-
clean_code.erase(
205-
std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator),
206-
clean_code.end());
207-
if (clean_code.find(internal::kPaddingCharacter)) {
208-
clean_code = clean_code.substr(0,
209-
clean_code.find(internal::kPaddingCharacter));
214+
std::string clean_code = clean_code_chars(code);
215+
// Constrain to the maximum length.
216+
if (clean_code.size() > internal::kMaximumDigitCount) {
217+
clean_code = clean_code.substr(0, internal::kMaximumDigitCount);
210218
}
211219
double resolution_degrees = internal::kEncodingBase;
212220
double latitude = 0.0;
@@ -242,9 +250,8 @@ CodeArea Decode(const std::string &code) {
242250
// With a grid, the latitude and longitude resolutions are no longer equal.
243251
double latitude_resolution = resolution_degrees;
244252
double longitude_resolution = resolution_degrees;
245-
// Decode only up to the maximum digit count.
246-
for (size_t i = internal::kPairCodeLength;
247-
i < std::min(internal::kMaximumDigitCount, clean_code.size()); i++) {
253+
// Decode grid square characters.
254+
for (size_t i = internal::kPairCodeLength; i < clean_code.size(); i++) {
248255
// Get the value of the character at i and convert it to the degree value.
249256
size_t value = get_alphabet_position(clean_code[i]);
250257
size_t row = value / internal::kGridColumns;
@@ -264,7 +271,7 @@ CodeArea Decode(const std::string &code) {
264271
longitude - internal::kLongitudeMaxDegrees,
265272
latitude_high - internal::kLatitudeMaxDegrees,
266273
longitude_high - internal::kLongitudeMaxDegrees,
267-
CodeLength(code));
274+
clean_code.size());
268275
}
269276

270277
std::string Shorten(const std::string &code, const LatLng &reference_location) {
@@ -454,15 +461,7 @@ bool IsFull(const std::string &code) {
454461
}
455462

456463
size_t CodeLength(const std::string &code) {
457-
// Remove the separator and any padding characters.
458-
std::string clean_code(code);
459-
clean_code.erase(
460-
std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator),
461-
clean_code.end());
462-
if (clean_code.find(internal::kPaddingCharacter)) {
463-
clean_code = clean_code.substr(
464-
0, clean_code.find(internal::kPaddingCharacter));
465-
}
464+
std::string clean_code = clean_code_chars(code);
466465
return clean_code.size();
467466
}
468467

js/closure/openlocationcode.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ var GRID_SIZE_DEGREES = 0.000125;
141141
*/
142142
var MIN_TRIMMABLE_CODE_LEN = 6;
143143

144+
/**
145+
* Maximum length of a code.
146+
*/
147+
var MAX_CODE_LEN = 15;
148+
144149
/**
145150
* Returns the characters used to produce the codes.
146151
* @return {string} the OLC alphabet.
@@ -311,6 +316,7 @@ function encode(latitude, longitude, opt_length) {
311316
(opt_length < PAIR_CODE_LENGTH && opt_length % 2 == 1)) {
312317
throw 'IllegalArgumentException: Invalid Open Location Code length';
313318
}
319+
opt_length = Math.min(opt_length, MAX_CODE_LEN);
314320
// Ensure that latitude and longitude are valid.
315321
latitude = clipLatitude(latitude);
316322
longitude = normalizeLongitude(longitude);
@@ -351,6 +357,10 @@ function decode(code) {
351357
code = code.replace(SEPARATOR, '');
352358
code = code.replace(new RegExp(PADDING_CHARACTER + '+'), '');
353359
code = code.toUpperCase();
360+
if (code.length > MAX_CODE_LEN) {
361+
code = code.substring(0, MAX_CODE_LEN);
362+
}
363+
354364
var /** @type {number} */ precision = ENCODING_BASE;
355365
var latitude = 0.0;
356366
var longitude = 0.0;

python/openlocationcode.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@
103103
#Minimum length of a code that can be shortened.
104104
MIN_TRIMMABLE_CODE_LEN_ = 6
105105

106+
#The maximum significant digits in a plus code.
107+
MAX_CODE_LENGTH = 15
108+
106109
SP = '+0'
107110

108111

@@ -213,6 +216,7 @@ def isFull(code):
213216
def encode(latitude, longitude, codeLength=PAIR_CODE_LENGTH_):
214217
if codeLength < 2 or (codeLength < PAIR_CODE_LENGTH_ and codeLength % 2 == 1):
215218
raise ValueError('Invalid Open Location Code length - ' + str(codeLength))
219+
codeLength = min(codeLength, MAX_CODE_LENGTH)
216220
# Ensure that latitude and longitude are valid.
217221
latitude = clipLatitude(latitude)
218222
longitude = normalizeLongitude(longitude)
@@ -240,10 +244,12 @@ def decode(code):
240244
if not isFull(code):
241245
raise ValueError('Passed Open Location Code is not a valid full code - ' + str(code))
242246
# Strip out separator character (we've already established the code is
243-
# valid so the maximum is one), padding characters and convert to upper
244-
# case.
247+
# valid so the maximum is one), and padding characters. Convert to upper
248+
# case and constrain to the maximum number of digits.
245249
code = re.sub('[+0]','',code)
246250
code = code.upper()
251+
code = code[:MAX_CODE_LENGTH]
252+
247253
# Decode the lat/lng pair component.
248254
codeArea = decodePairs(code[0:PAIR_CODE_LENGTH_])
249255
if len(code) <= PAIR_CODE_LENGTH_:

rust/src/interface.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,13 @@ pub fn decode(_code: &str) -> Result<CodeArea, String> {
159159
if !is_full(_code) {
160160
return Err(format!("Code must be a valid full code: {}", _code));
161161
}
162-
let code = _code.to_string()
162+
let mut code = _code.to_string()
163163
.replace(SEPARATOR, "")
164164
.replace(PADDING_CHAR_STR, "")
165165
.to_uppercase();
166+
if code.len() > MAX_CODE_LENGTH {
167+
code = code.chars().take(MAX_CODE_LENGTH).collect();
168+
}
166169

167170
let mut lat = -LATITUDE_MAX;
168171
let mut lng = -LONGITUDE_MAX;

test_data/decoding.csv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ CFX30000+,4,89,1,90,2
2424
CFX3X2X2+X2,10,89.9998750,1,90,1.0001250
2525
# Test non-precise latitude/longitude value
2626
6FH56C22+22,10,1.2000000000000028,3.4000000000000057,1.2001249999999999,3.4001250000000027
27+
# Validate that digits after the first 15 are ignored when decoding
28+
849VGJQF+VX7QR3J,15,37.5396691200,-122.3750698242,37.5396691600,-122.3750697021
29+
849VGJQF+VX7QR3J7QR3J,15,37.5396691200,-122.3750698242,37.5396691600,-122.3750697021

test_data/encoding.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@
2424
90,1,10,CFX3X2X2+X2
2525
# Test non-precise latitude/longitude value
2626
1.2,3.4,10,6FH56C22+22
27+
# Validate that codes generated with a length exceeding 15 significant digits
28+
# return a 15-digit code
29+
37.539669125,-122.375069724,15,849VGJQF+VX7QR3J
30+
37.539669125,-122.375069724,16,849VGJQF+VX7QR3J
31+
37.539669125,-122.375069724,100,849VGJQF+VX7QR3J

test_data/validityTests.csv

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,9 @@ G+,false,false,false
2323
WC2300+G6g,false,false,false
2424
WC2345+G,false,false,false
2525
WC2300+,false,false,false
26+
# Validate that codes at and exceeding 15 digits are still valid when all their
27+
# digits are valid, and invalid when not.
28+
849VGJQF+VX7QR3J,true,false,true
29+
849VGJQF+VX7QR3U,false,false,false
30+
849VGJQF+VX7QR3JW,true,false,true
31+
849VGJQF+VX7QR3JU,false,false,false

0 commit comments

Comments
 (0)