Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split encoding into floating and integer functions #677

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
64 changes: 38 additions & 26 deletions c/src/olc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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);
}
Expand Down
2 changes: 1 addition & 1 deletion c/src/olc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions c/src/olc_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 9 additions & 9 deletions test_data/encoding.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading