diff --git a/cpp/openlocationcode.cc b/cpp/openlocationcode.cc index a7b364a8..718bdbeb 100644 --- a/cpp/openlocationcode.cc +++ b/cpp/openlocationcode.cc @@ -92,6 +92,19 @@ double adjust_latitude(double latitude_degrees, size_t code_length) { return latitude_degrees - precision / 2; } +// Remove the separator and padding characters from the code. +std::string clean_code_chars(const std::string &code) { + std::string clean_code(code); + clean_code.erase( + std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator), + clean_code.end()); + if (clean_code.find(internal::kPaddingCharacter)) { + clean_code = clean_code.substr( + 0, clean_code.find(internal::kPaddingCharacter)); + } + return clean_code; +} + // Encodes positive range lat,lng into a sequence of OLC lat/lng pairs. // This uses pairs of characters (latitude and longitude in that order) to @@ -198,15 +211,10 @@ std::string Encode(const LatLng &location) { } CodeArea Decode(const std::string &code) { - // Make a copy that doesn't have the separator and stops at the first padding - // character. - std::string clean_code(code); - clean_code.erase( - std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator), - clean_code.end()); - if (clean_code.find(internal::kPaddingCharacter)) { - clean_code = clean_code.substr(0, - clean_code.find(internal::kPaddingCharacter)); + std::string clean_code = clean_code_chars(code); + // Constrain to the maximum length. + if (clean_code.size() > internal::kMaximumDigitCount) { + clean_code = clean_code.substr(0, internal::kMaximumDigitCount); } double resolution_degrees = internal::kEncodingBase; double latitude = 0.0; @@ -242,9 +250,8 @@ CodeArea Decode(const std::string &code) { // With a grid, the latitude and longitude resolutions are no longer equal. double latitude_resolution = resolution_degrees; double longitude_resolution = resolution_degrees; - // Decode only up to the maximum digit count. - for (size_t i = internal::kPairCodeLength; - i < std::min(internal::kMaximumDigitCount, clean_code.size()); i++) { + // Decode grid square characters. + for (size_t i = internal::kPairCodeLength; i < clean_code.size(); i++) { // Get the value of the character at i and convert it to the degree value. size_t value = get_alphabet_position(clean_code[i]); size_t row = value / internal::kGridColumns; @@ -264,7 +271,7 @@ CodeArea Decode(const std::string &code) { longitude - internal::kLongitudeMaxDegrees, latitude_high - internal::kLatitudeMaxDegrees, longitude_high - internal::kLongitudeMaxDegrees, - CodeLength(code)); + clean_code.size()); } std::string Shorten(const std::string &code, const LatLng &reference_location) { @@ -454,15 +461,7 @@ bool IsFull(const std::string &code) { } size_t CodeLength(const std::string &code) { - // Remove the separator and any padding characters. - std::string clean_code(code); - clean_code.erase( - std::remove(clean_code.begin(), clean_code.end(), internal::kSeparator), - clean_code.end()); - if (clean_code.find(internal::kPaddingCharacter)) { - clean_code = clean_code.substr( - 0, clean_code.find(internal::kPaddingCharacter)); - } + std::string clean_code = clean_code_chars(code); return clean_code.size(); } diff --git a/js/closure/openlocationcode.js b/js/closure/openlocationcode.js index fe029c31..4c4d01b8 100644 --- a/js/closure/openlocationcode.js +++ b/js/closure/openlocationcode.js @@ -141,6 +141,11 @@ var GRID_SIZE_DEGREES = 0.000125; */ var MIN_TRIMMABLE_CODE_LEN = 6; +/** + * Maximum length of a code. + */ +var MAX_CODE_LEN = 15; + /** * Returns the characters used to produce the codes. * @return {string} the OLC alphabet. @@ -311,6 +316,7 @@ function encode(latitude, longitude, opt_length) { (opt_length < PAIR_CODE_LENGTH && opt_length % 2 == 1)) { throw 'IllegalArgumentException: Invalid Open Location Code length'; } + opt_length = Math.min(opt_length, MAX_CODE_LEN); // Ensure that latitude and longitude are valid. latitude = clipLatitude(latitude); longitude = normalizeLongitude(longitude); @@ -351,6 +357,10 @@ function decode(code) { code = code.replace(SEPARATOR, ''); code = code.replace(new RegExp(PADDING_CHARACTER + '+'), ''); code = code.toUpperCase(); + if (code.length > MAX_CODE_LEN) { + code = code.substring(0, MAX_CODE_LEN); + } + var /** @type {number} */ precision = ENCODING_BASE; var latitude = 0.0; var longitude = 0.0; diff --git a/python/openlocationcode.py b/python/openlocationcode.py index 0cf60a47..65d2613c 100644 --- a/python/openlocationcode.py +++ b/python/openlocationcode.py @@ -103,6 +103,9 @@ #Minimum length of a code that can be shortened. MIN_TRIMMABLE_CODE_LEN_ = 6 +#The maximum significant digits in a plus code. +MAX_CODE_LENGTH = 15 + SP = '+0' @@ -213,6 +216,7 @@ def isFull(code): def encode(latitude, longitude, codeLength=PAIR_CODE_LENGTH_): if codeLength < 2 or (codeLength < PAIR_CODE_LENGTH_ and codeLength % 2 == 1): raise ValueError('Invalid Open Location Code length - ' + str(codeLength)) + codeLength = min(codeLength, MAX_CODE_LENGTH) # Ensure that latitude and longitude are valid. latitude = clipLatitude(latitude) longitude = normalizeLongitude(longitude) @@ -240,10 +244,12 @@ def decode(code): if not isFull(code): raise ValueError('Passed Open Location Code is not a valid full code - ' + str(code)) # Strip out separator character (we've already established the code is - # valid so the maximum is one), padding characters and convert to upper - # case. + # valid so the maximum is one), and padding characters. Convert to upper + # case and constrain to the maximum number of digits. code = re.sub('[+0]','',code) code = code.upper() + code = code[:MAX_CODE_LENGTH] + # Decode the lat/lng pair component. codeArea = decodePairs(code[0:PAIR_CODE_LENGTH_]) if len(code) <= PAIR_CODE_LENGTH_: diff --git a/rust/src/interface.rs b/rust/src/interface.rs index 78716675..9dd6de6a 100644 --- a/rust/src/interface.rs +++ b/rust/src/interface.rs @@ -159,10 +159,13 @@ pub fn decode(_code: &str) -> Result { if !is_full(_code) { return Err(format!("Code must be a valid full code: {}", _code)); } - let code = _code.to_string() + let mut code = _code.to_string() .replace(SEPARATOR, "") .replace(PADDING_CHAR_STR, "") .to_uppercase(); + if code.len() > MAX_CODE_LENGTH { + code = code.chars().take(MAX_CODE_LENGTH).collect(); + } let mut lat = -LATITUDE_MAX; let mut lng = -LONGITUDE_MAX; diff --git a/test_data/decoding.csv b/test_data/decoding.csv index aa071c79..067466ea 100644 --- a/test_data/decoding.csv +++ b/test_data/decoding.csv @@ -24,3 +24,6 @@ CFX30000+,4,89,1,90,2 CFX3X2X2+X2,10,89.9998750,1,90,1.0001250 # Test non-precise latitude/longitude value 6FH56C22+22,10,1.2000000000000028,3.4000000000000057,1.2001249999999999,3.4001250000000027 +# Validate that digits after the first 15 are ignored when decoding +849VGJQF+VX7QR3J,15,37.5396691200,-122.3750698242,37.5396691600,-122.3750697021 +849VGJQF+VX7QR3J7QR3J,15,37.5396691200,-122.3750698242,37.5396691600,-122.3750697021 diff --git a/test_data/encoding.csv b/test_data/encoding.csv index 8fb3a03b..bb5f6a91 100644 --- a/test_data/encoding.csv +++ b/test_data/encoding.csv @@ -24,3 +24,8 @@ 90,1,10,CFX3X2X2+X2 # Test non-precise latitude/longitude value 1.2,3.4,10,6FH56C22+22 +# Validate that codes generated with a length exceeding 15 significant digits +# return a 15-digit code +37.539669125,-122.375069724,15,849VGJQF+VX7QR3J +37.539669125,-122.375069724,16,849VGJQF+VX7QR3J +37.539669125,-122.375069724,100,849VGJQF+VX7QR3J diff --git a/test_data/validityTests.csv b/test_data/validityTests.csv index 1cda63c9..6eb7eee3 100644 --- a/test_data/validityTests.csv +++ b/test_data/validityTests.csv @@ -23,3 +23,9 @@ G+,false,false,false WC2300+G6g,false,false,false WC2345+G,false,false,false WC2300+,false,false,false +# Validate that codes at and exceeding 15 digits are still valid when all their +# digits are valid, and invalid when not. +849VGJQF+VX7QR3J,true,false,true +849VGJQF+VX7QR3U,false,false,false +849VGJQF+VX7QR3JW,true,false,true +849VGJQF+VX7QR3JU,false,false,false