diff --git a/c/src/olc.c b/c/src/olc.c index 8cdc83cb..9ef5bde6 100644 --- a/c/src/olc.c +++ b/c/src/olc.c @@ -82,8 +82,8 @@ int OLC_IsFull(const char* code, size_t size) { return is_full(&info); } -int OLC_Encode(const OLC_LatLon* location, size_t length, char* code, - int maxlen) { +int OLC_EncodeIntegers(long long int lat, long long int lng, size_t length, + char* code, int maxlen) { // Limit the maximum number of digits in the code. if (length > kMaximumDigitCount) { length = kMaximumDigitCount; @@ -94,51 +94,53 @@ int OLC_Encode(const OLC_LatLon* location, size_t length, char* code, if (length < kPairCodeLength && length % 2 == 1) { length = length + 1; } - // Adjust latitude and longitude so they fall into positive ranges. - double latitude = adjust_latitude(location->lat, length); - double longitude = normalize_longitude(location->lon); + // Convert latitude to positive range and clip. + lat += OLC_kLatMaxDegrees * kGridLatPrecisionInverse; + if (lat < 0) { + lat = 0; + } else if (lat >= 2 * OLC_kLatMaxDegrees * kGridLatPrecisionInverse) { + // Subtract one to bring it just inside 90 degrees lat. + lat = 2 * OLC_kLatMaxDegrees * kGridLatPrecisionInverse - 1; + } + // Convert longitude to positive range and normalise. + lng += OLC_kLonMaxDegrees * kGridLonPrecisionInverse; + while (lng < 0) { + lng += 2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse; + } + while (lng >= 2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse) { + lng -= 2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse; + } // Build up the code here, then copy it to the passed pointer. char fullcode[] = "12345678901234567"; // Compute the code. - // This approach converts each value to an integer after multiplying it by - // the final precision. This allows us to use only integer operations, so - // avoiding any accumulation of floating point representation errors. - - // Multiply values by their precision and convert to positive without any - // floating point operations. - long long int lat_val = kLatMaxDegrees * 2.5e7; - long long int lng_val = kLonMaxDegrees * 8.192e6; - lat_val += latitude * 2.5e7; - lng_val += longitude * 8.192e6; - size_t pos = kMaximumDigitCount; // Compute the grid part of the code if necessary. if (length > kPairCodeLength) { for (size_t i = 0; i < kGridCodeLength; i++) { - int lat_digit = lat_val % kGridRows; - int lng_digit = lng_val % kGridCols; + int lat_digit = lat % kGridRows; + int lng_digit = lng % kGridCols; int ndx = lat_digit * kGridCols + lng_digit; fullcode[pos--] = kAlphabet[ndx]; // Note! Integer division. - lat_val /= kGridRows; - lng_val /= kGridCols; + lat /= kGridRows; + lng /= kGridCols; } } else { - lat_val /= pow(kGridRows, kGridCodeLength); - lng_val /= pow(kGridCols, kGridCodeLength); + lat /= pow(kGridRows, kGridCodeLength); + lng /= pow(kGridCols, kGridCodeLength); } pos = kPairCodeLength; // Compute the pair section of the code. for (size_t i = 0; i < kPairCodeLength / 2; i++) { - int lat_ndx = lat_val % kEncodingBase; - int lng_ndx = lng_val % kEncodingBase; + int lat_ndx = lat % kEncodingBase; + int lng_ndx = lng % kEncodingBase; fullcode[pos--] = kAlphabet[lng_ndx]; fullcode[pos--] = kAlphabet[lat_ndx]; // Note! Integer division. - lat_val /= kEncodingBase; - lng_val /= kEncodingBase; + lat /= kEncodingBase; + lng /= kEncodingBase; if (i == 0) { fullcode[pos--] = kSeparator; } @@ -165,6 +167,16 @@ int OLC_Encode(const OLC_LatLon* location, size_t length, char* code, return char_count; } +int OLC_Encode(const OLC_LatLon* location, size_t length, char* code, + int maxlen) { + // Multiply degrees by precision. Use lround to explicitly round rather than + // truncate, which causes issues when using values like 0.1 that do not have + // precise floating point representations. + long long int lat = lround(location->lat * kGridLatPrecisionInverse); + long long int lng = lround(location->lon * kGridLonPrecisionInverse); + return OLC_EncodeIntegers(lat, lng, length, code, maxlen); +} + int OLC_EncodeDefault(const OLC_LatLon* location, char* code, int maxlen) { return OLC_Encode(location, kPairCodeLength, code, maxlen); } diff --git a/c/src/olc.h b/c/src/olc.h index 4b5def16..5a0ac9d1 100644 --- a/c/src/olc.h +++ b/c/src/olc.h @@ -72,4 +72,4 @@ int OLC_Shorten(const char* code, size_t size, const OLC_LatLon* reference, int OLC_RecoverNearest(const char* short_code, size_t size, const OLC_LatLon* reference, char* code, int maxlen); -#endif +#endif // OLC_OPENLOCATIONCODE_H diff --git a/c/src/olc_private.h b/c/src/olc_private.h index c4ceba6e..dc74a3fe 100644 --- a/c/src/olc_private.h +++ b/c/src/olc_private.h @@ -40,9 +40,9 @@ static const size_t kSeparatorPosition = 8; static const size_t kPairPrecisionInverse = 8000; // Inverse (1/) of the precision of the final grid digits in degrees. // Latitude is kEncodingBase^3 * kGridRows^kGridCodeLength -static const size_t kGridLatPrecisionInverse = 2.5e7; +static const long long int kGridLatPrecisionInverse = 2.5e7; // Longitude is kEncodingBase^3 * kGridColumns^kGridCodeLength -static const size_t kGridLonPrecisionInverse = 8.192e6; +static const long long int kGridLonPrecisionInverse = 8.192e6; // Latitude bounds are -kLatMaxDegrees degrees and +kLatMaxDegrees degrees // which we transpose to 0 and 180 degrees. static const double kLatMaxDegrees = OLC_kLatMaxDegrees; diff --git a/test_data/encoding.csv b/test_data/encoding.csv index af7d2b19..75a004ec 100644 --- a/test_data/encoding.csv +++ b/test_data/encoding.csv @@ -53,9 +53,9 @@ # 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 +37.539669125,-122.375069724,15,849VGJQF+VX7QR3M +37.539669125,-122.375069724,16,849VGJQF+VX7QR3M +37.539669125,-122.375069724,100,849VGJQF+VX7QR3M ################################################################################ # # Test floating point representation/rounding errors. @@ -134,18 +134,18 @@ # ################################################################################ 80.0100000001,58.57,15,CHGW2H6C+2222222 -80.0099999999,58.57,15,CHGW2H5C+X2RRRRR +80.00999996,58.57,15,CHGW2H5C+X2RRRRR -80.0099999999,58.57,15,2HFWXHRC+2222222 --80.0100000001,58.57,15,2HFWXHQC+X2RRRRR +-80.0100000399,58.57,15,2HFWXHQC+X2RRRRR ################################################################################ # # Add a few other examples. # ################################################################################ -47.000000080000000,8.00022229,15,8FVC2222+235235C -68.3500147997595,113.625636875353,15,9PWM9J2G+272FWJV -38.1176000887231,165.441989844555,15,8VC74C9R+2QX445C --28.1217794010122,-154.066811473758,15,5337VWHM+77PR2GR +47.000000080000000,8.00022229,15,8FVC2222+235235F +68.3500147997595,113.625636875353,15,9PWM9J2G+272FWR3 +38.1176000887231,165.441989844555,15,8VC74C9R+2QX445F +-28.1217794010122,-154.066811473758,15,5337VWHM+77PR2P2 ################################################################################ # # Test short length.