Skip to content

Commit 5d6004c

Browse files
authored
Split encoding implementations into integer encoding method (#721)
* Split encoding implementations into integer encoding method * Update C implementation to expose integer conversion and encoding functions. * expose C++ integer encoding and conversion functions * clang formatting * expose Dart integer encoding and conversion functions * expose Java integer encoding and conversion functions * formatting * expose js/closure integer encoding and conversion functions, formatting * correct js/closure function name * expose js integer encoding and conversion functions * expose plpgsql integer encoding and conversion functions * expose python integer encoding and conversion functions * expose ruby integer encoding and conversion functions * expose rust integer encoding and conversion functions * expose visualbasic integer encoding and conversion functions * fix rust formatting error
1 parent fa6d7f9 commit 5d6004c

File tree

16 files changed

+756
-424
lines changed

16 files changed

+756
-424
lines changed

c/src/olc.c

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -82,23 +82,13 @@ int OLC_IsFull(const char* code, size_t size) {
8282
return is_full(&info);
8383
}
8484

85-
int OLC_Encode(const OLC_LatLon* location, size_t length, char* code,
86-
int maxlen) {
85+
void OLC_LocationToIntegers(const OLC_LatLon* degrees,
86+
OLC_LatLonIntegers* integers) {
8787
// Multiply degrees by precision. Use lround to explicitly round rather than
8888
// truncate, which causes issues when using values like 0.1 that do not have
8989
// precise floating point representations.
90-
long long int lat = lround(location->lat * kGridLatPrecisionInverse);
91-
long long int lng = lround(location->lon * kGridLonPrecisionInverse);
92-
// Limit the maximum number of digits in the code.
93-
if (length > kMaximumDigitCount) {
94-
length = kMaximumDigitCount;
95-
}
96-
if (length < kMinimumDigitCount) {
97-
length = kMinimumDigitCount;
98-
}
99-
if (length < kPairCodeLength && length % 2 == 1) {
100-
length = length + 1;
101-
}
90+
long long int lat = lround(degrees->lat * kGridLatPrecisionInverse);
91+
long long int lon = lround(degrees->lon * kGridLonPrecisionInverse);
10292
// Convert latitude to positive range (0..2*degrees*precision) and clip.
10393
lat += OLC_kLatMaxDegrees * kGridLatPrecisionInverse;
10494
if (lat < 0) {
@@ -108,16 +98,35 @@ int OLC_Encode(const OLC_LatLon* location, size_t length, char* code,
10898
lat = 2 * OLC_kLatMaxDegrees * kGridLatPrecisionInverse - 1;
10999
}
110100
// Convert longitude to the positive range and normalise.
111-
lng += OLC_kLonMaxDegrees * kGridLonPrecisionInverse;
112-
if (lng < 0) {
101+
lon += OLC_kLonMaxDegrees * kGridLonPrecisionInverse;
102+
if (lon < 0) {
113103
// If after adding 180 it is still less than zero, do integer division
114104
// on a full longitude (360) and add the remainder.
115-
lng = lng % (2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse) +
105+
lon = lon % (2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse) +
116106
(2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse);
117-
} else if (lng >= 2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse) {
107+
} else if (lon >= 2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse) {
118108
// If it's greater than 360, just get the integer division remainder.
119-
lng = lng % (2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse);
109+
lon = lon % (2 * OLC_kLonMaxDegrees * kGridLonPrecisionInverse);
120110
}
111+
integers->lat = lat;
112+
integers->lon = lon;
113+
}
114+
115+
int OLC_EncodeIntegers(const OLC_LatLonIntegers* location, size_t length,
116+
char* code, int maxlen) {
117+
// Limit the maximum number of digits in the code.
118+
if (length > kMaximumDigitCount) {
119+
length = kMaximumDigitCount;
120+
}
121+
if (length < kMinimumDigitCount) {
122+
length = kMinimumDigitCount;
123+
}
124+
if (length < kPairCodeLength && length % 2 == 1) {
125+
length = length + 1;
126+
}
127+
128+
long long int lat = location->lat;
129+
long long int lon = location->lon;
121130

122131
// Reserve characters for the code digits, the separator and the null
123132
// terminator.
@@ -129,30 +138,30 @@ int OLC_Encode(const OLC_LatLon* location, size_t length, char* code,
129138
if (length > kPairCodeLength) {
130139
for (size_t i = kMaximumDigitCount - kPairCodeLength; i >= 1; i--) {
131140
int lat_digit = lat % kGridRows;
132-
int lng_digit = lng % kGridCols;
141+
int lng_digit = lon % kGridCols;
133142
fullcode[kSeparatorPosition + 2 + i] =
134143
kAlphabet[lat_digit * kGridCols + lng_digit];
135144
lat /= kGridRows;
136-
lng /= kGridCols;
145+
lon /= kGridCols;
137146
}
138147
} else {
139148
lat /= pow(kGridRows, kGridCodeLength);
140-
lng /= pow(kGridCols, kGridCodeLength);
149+
lon /= pow(kGridCols, kGridCodeLength);
141150
}
142151

143152
// Add the pair after the separator.
144153
fullcode[kSeparatorPosition + 1] = kAlphabet[lat % kEncodingBase];
145-
fullcode[kSeparatorPosition + 2] = kAlphabet[lng % kEncodingBase];
154+
fullcode[kSeparatorPosition + 2] = kAlphabet[lon % kEncodingBase];
146155
lat /= kEncodingBase;
147-
lng /= kEncodingBase;
156+
lon /= kEncodingBase;
148157

149158
// Compute the pair section before the separator in reverse order.
150159
// Even indices contain latitude and odd contain longitude.
151160
for (int i = (kPairCodeLength / 2 + 1); i >= 0; i -= 2) {
152161
fullcode[i] = kAlphabet[lat % kEncodingBase];
153-
fullcode[i + 1] = kAlphabet[lng % kEncodingBase];
162+
fullcode[i + 1] = kAlphabet[lon % kEncodingBase];
154163
lat /= kEncodingBase;
155-
lng /= kEncodingBase;
164+
lon /= kEncodingBase;
156165
}
157166
// Replace digits with padding if necessary.
158167
if (length < kSeparatorPosition) {
@@ -170,6 +179,13 @@ int OLC_Encode(const OLC_LatLon* location, size_t length, char* code,
170179
return length;
171180
}
172181

182+
int OLC_Encode(const OLC_LatLon* location, size_t length, char* code,
183+
int maxlen) {
184+
OLC_LatLonIntegers integers;
185+
OLC_LocationToIntegers(location, &integers);
186+
return OLC_EncodeIntegers(&integers, length, code, maxlen);
187+
}
188+
173189
int OLC_EncodeDefault(const OLC_LatLon* location, char* code, int maxlen) {
174190
return OLC_Encode(location, kPairCodeLength, code, maxlen);
175191
}

c/src/olc.h

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,53 +22,71 @@
2222
#define OLC_MAKE_VERSION_STR(major, minor, patch) \
2323
OLC_MAKE_VERSION_STR_IMPL(major, minor, patch)
2424

25-
// Current version, as a number and a string
25+
// Current version, as a number and a string.
2626
#define OLC_VERSION_NUM \
2727
OLC_MAKE_VERSION_NUM(OLC_VERSION_MAJOR, OLC_VERSION_MINOR, OLC_VERSION_PATCH)
2828
#define OLC_VERSION_STR \
2929
OLC_MAKE_VERSION_STR(OLC_VERSION_MAJOR, OLC_VERSION_MINOR, OLC_VERSION_PATCH)
3030

31-
// A pair of doubles representing latitude / longitude
31+
// A pair of doubles representing latitude and longitude.
3232
typedef struct OLC_LatLon {
3333
double lat;
3434
double lon;
3535
} OLC_LatLon;
3636

37-
// An area defined by two corners (lo and hi) and a code length
37+
// A pair of clipped, normalised positive range integers representing latitude
38+
// and longitude. This is used internally in the encoding methods to avoid
39+
// floating-point precision issues.
40+
typedef struct OLC_LatLonIntegers {
41+
long long int lat;
42+
long long int lon;
43+
} OLC_LatLonIntegers;
44+
45+
// Convert a location in degrees into the integer values necessary to reliably
46+
// encode it.
47+
void OLC_LocationToIntegers(const OLC_LatLon* degrees,
48+
OLC_LatLonIntegers* integers);
49+
50+
// An area defined by two corners (lo and hi) and a code length.
3851
typedef struct OLC_CodeArea {
3952
OLC_LatLon lo;
4053
OLC_LatLon hi;
4154
size_t len;
4255
} OLC_CodeArea;
4356

44-
// Get the center coordinates for an area
57+
// Get the center coordinates for an area.
4558
void OLC_GetCenter(const OLC_CodeArea* area, OLC_LatLon* center);
4659

47-
// Get the effective length for a code
60+
// Get the effective length for a code.
4861
size_t OLC_CodeLength(const char* code, size_t size);
4962

5063
// Check for the three obviously-named conditions
5164
int OLC_IsValid(const char* code, size_t size);
5265
int OLC_IsShort(const char* code, size_t size);
5366
int OLC_IsFull(const char* code, size_t size);
5467

55-
// Encode location with given code length (indicates precision) into an OLC
56-
// Return the string length of the code
68+
// Encode location with given code length (indicates precision) into an OLC.
69+
// Return the string length of the code.
5770
int OLC_Encode(const OLC_LatLon* location, size_t code_length, char* code,
5871
int maxlen);
5972

60-
// Encode location with default code length into an OLC
61-
// Return the string length of the code
73+
// Encode using integer values. This is only exposed for testing and should
74+
// not be called from client code.
75+
int OLC_EncodeIntegers(const OLC_LatLonIntegers* location, size_t code_length,
76+
char* code, int maxlen);
77+
78+
// Encode location with default code length into an OLC.
79+
// Return the string length of the code.
6280
int OLC_EncodeDefault(const OLC_LatLon* location, char* code, int maxlen);
6381

64-
// Decode OLC into the original location
82+
// Decode OLC into the original location.
6583
int OLC_Decode(const char* code, size_t size, OLC_CodeArea* decoded);
6684

67-
// Compute a (shorter) OLC for a given code and a reference location
85+
// Compute a (shorter) OLC for a given code and a reference location.
6886
int OLC_Shorten(const char* code, size_t size, const OLC_LatLon* reference,
6987
char* buf, int maxlen);
7088

71-
// Given shorter OLC and reference location, compute original (full length) OLC
89+
// Given shorter OLC and reference location, compute original (full length) OLC.
7290
int OLC_RecoverNearest(const char* short_code, size_t size,
7391
const OLC_LatLon* reference, char* code, int maxlen);
7492

cpp/openlocationcode.cc

Lines changed: 75 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,78 @@ const int64_t kLongitudeMaxDegrees = 180;
4747
const int kPositionLUT['X' - 'C' + 1] = {8, -1, -1, 9, 10, 11, -1, 12,
4848
-1, -1, 13, -1, -1, 14, 15, 16,
4949
-1, -1, -1, 17, 18, 19};
50+
51+
int64_t latitudeToInteger(double latitude) {
52+
int64_t lat = round(latitude * kGridLatPrecisionInverse);
53+
lat += kLatitudeMaxDegrees * kGridLatPrecisionInverse;
54+
if (lat < 0) {
55+
lat = 0;
56+
} else if (lat >= 2 * kLatitudeMaxDegrees * kGridLatPrecisionInverse) {
57+
lat = 2 * kLatitudeMaxDegrees * kGridLatPrecisionInverse - 1;
58+
}
59+
return lat;
60+
}
61+
62+
int64_t longitudeToInteger(double longitude) {
63+
int64_t lng = round(longitude * kGridLngPrecisionInverse);
64+
lng += kLongitudeMaxDegrees * kGridLngPrecisionInverse;
65+
if (lng <= 0) {
66+
lng = lng % (2 * kLongitudeMaxDegrees * kGridLngPrecisionInverse) +
67+
2 * kLongitudeMaxDegrees * kGridLngPrecisionInverse;
68+
} else if (lng >= 2 * kLongitudeMaxDegrees * kGridLngPrecisionInverse) {
69+
lng = lng % (2 * kLongitudeMaxDegrees * kGridLngPrecisionInverse);
70+
}
71+
return lng;
72+
}
73+
74+
std::string encodeIntegers(int64_t lat_val, int64_t lng_val,
75+
size_t code_length) {
76+
// Reserve characters for the code digits and the separator.
77+
std::string code = "1234567890abcdef";
78+
// Add the separator character.
79+
code[internal::kSeparatorPosition] = internal::kSeparator;
80+
81+
// Compute the grid part of the code if necessary.
82+
if (code_length > internal::kPairCodeLength) {
83+
for (size_t i = internal::kGridCodeLength; i >= 1; i--) {
84+
int lat_digit = lat_val % internal::kGridRows;
85+
int lng_digit = lng_val % internal::kGridColumns;
86+
code[internal::kSeparatorPosition + 2 + i] =
87+
internal::kAlphabet[lat_digit * internal::kGridColumns + lng_digit];
88+
lat_val /= internal::kGridRows;
89+
lng_val /= internal::kGridColumns;
90+
}
91+
} else {
92+
lat_val /= pow(internal::kGridRows, internal::kGridCodeLength);
93+
lng_val /= pow(internal::kGridColumns, internal::kGridCodeLength);
94+
}
95+
96+
// Add the pair after the separator.
97+
code[internal::kSeparatorPosition + 1] =
98+
internal::kAlphabet[lat_val % internal::kEncodingBase];
99+
code[internal::kSeparatorPosition + 2] =
100+
internal::kAlphabet[lng_val % internal::kEncodingBase];
101+
lat_val /= internal::kEncodingBase;
102+
lng_val /= internal::kEncodingBase;
103+
104+
// Compute the pair section before the separator in reverse order.
105+
// Even indices contain latitude and odd contain longitude.
106+
for (int i = (internal::kPairCodeLength / 2 + 1); i >= 0; i -= 2) {
107+
code[i] = internal::kAlphabet[lat_val % internal::kEncodingBase];
108+
code[i + 1] = internal::kAlphabet[lng_val % internal::kEncodingBase];
109+
lat_val /= internal::kEncodingBase;
110+
lng_val /= internal::kEncodingBase;
111+
}
112+
// Replace digits with padding if necessary.
113+
if (code_length < internal::kSeparatorPosition) {
114+
for (size_t i = code_length; i < internal::kSeparatorPosition; i++) {
115+
code[i] = internal::kPaddingCharacter;
116+
}
117+
code_length = internal::kSeparatorPosition;
118+
}
119+
// Return the code up to and including the separator.
120+
return code.substr(0, code_length + 1);
121+
}
50122
} // namespace internal
51123

52124
namespace {
@@ -129,78 +201,9 @@ std::string Encode(const LatLng &location, size_t code_length) {
129201
code_length = code_length + 1;
130202
}
131203
// Convert latitude and longitude into integer values.
132-
int64_t lat_val =
133-
round(location.latitude * internal::kGridLatPrecisionInverse);
134-
int64_t lng_val =
135-
round(location.longitude * internal::kGridLngPrecisionInverse);
136-
137-
// Adjust latitude and longitude so that they are normalized/clipped.
138-
lat_val += internal::kLatitudeMaxDegrees * internal::kGridLatPrecisionInverse;
139-
if (lat_val < 0) {
140-
lat_val = 0;
141-
} else if (lat_val >= 2 * internal::kLatitudeMaxDegrees *
142-
internal::kGridLatPrecisionInverse) {
143-
lat_val =
144-
2 * internal::kLatitudeMaxDegrees * internal::kGridLatPrecisionInverse -
145-
1;
146-
}
147-
lng_val +=
148-
internal::kLongitudeMaxDegrees * internal::kGridLngPrecisionInverse;
149-
if (lng_val <= 0) {
150-
lng_val =
151-
lng_val % (2 * internal::kLongitudeMaxDegrees *
152-
internal::kGridLngPrecisionInverse) +
153-
2 * internal::kLongitudeMaxDegrees * internal::kGridLngPrecisionInverse;
154-
} else if (lng_val >= 2 * internal::kLongitudeMaxDegrees *
155-
internal::kGridLngPrecisionInverse) {
156-
lng_val = lng_val % (2 * internal::kLongitudeMaxDegrees *
157-
internal::kGridLngPrecisionInverse);
158-
}
159-
// Reserve characters for the code digits and the separator.
160-
std::string code = "1234567890abcdef";
161-
// Add the separator character.
162-
code[internal::kSeparatorPosition] = internal::kSeparator;
163-
164-
// Compute the grid part of the code if necessary.
165-
if (code_length > internal::kPairCodeLength) {
166-
for (size_t i = internal::kGridCodeLength; i >= 1; i--) {
167-
int lat_digit = lat_val % internal::kGridRows;
168-
int lng_digit = lng_val % internal::kGridColumns;
169-
code[internal::kSeparatorPosition + 2 + i] =
170-
internal::kAlphabet[lat_digit * internal::kGridColumns + lng_digit];
171-
lat_val /= internal::kGridRows;
172-
lng_val /= internal::kGridColumns;
173-
}
174-
} else {
175-
lat_val /= pow(internal::kGridRows, internal::kGridCodeLength);
176-
lng_val /= pow(internal::kGridColumns, internal::kGridCodeLength);
177-
}
178-
179-
// Add the pair after the separator.
180-
code[internal::kSeparatorPosition + 1] =
181-
internal::kAlphabet[lat_val % internal::kEncodingBase];
182-
code[internal::kSeparatorPosition + 2] =
183-
internal::kAlphabet[lng_val % internal::kEncodingBase];
184-
lat_val /= internal::kEncodingBase;
185-
lng_val /= internal::kEncodingBase;
186-
187-
// Compute the pair section before the separator in reverse order.
188-
// Even indices contain latitude and odd contain longitude.
189-
for (int i = (internal::kPairCodeLength / 2 + 1); i >= 0; i -= 2) {
190-
code[i] = internal::kAlphabet[lat_val % internal::kEncodingBase];
191-
code[i + 1] = internal::kAlphabet[lng_val % internal::kEncodingBase];
192-
lat_val /= internal::kEncodingBase;
193-
lng_val /= internal::kEncodingBase;
194-
}
195-
// Replace digits with padding if necessary.
196-
if (code_length < internal::kSeparatorPosition) {
197-
for (size_t i = code_length; i < internal::kSeparatorPosition; i++) {
198-
code[i] = internal::kPaddingCharacter;
199-
}
200-
code_length = internal::kSeparatorPosition;
201-
}
202-
// Return the code up to and including the separator.
203-
return code.substr(0, code_length + 1);
204+
int64_t lat_val = internal::latitudeToInteger(location.latitude);
205+
int64_t lng_val = internal::longitudeToInteger(location.longitude);
206+
return internal::encodeIntegers(lat_val, lng_val, code_length);
204207
}
205208

206209
std::string Encode(const LatLng &location) {

cpp/openlocationcode.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ extern const size_t kInitialExponent;
106106
// Size of the initial grid in degrees. This is the size of the area represented
107107
// by a 10 character code, and is kEncodingBase ^ (2 - kPairCodeLength / 2).
108108
extern const double kGridSizeDegrees;
109+
// Internal method to convert latitude in degrees to the integer value for
110+
// encoding.
111+
int64_t latitudeToInteger(double latitude);
112+
// Internal method to convert longitude in degrees to the integer value for
113+
// encoding.
114+
int64_t longitudeToInteger(double longitude);
115+
// Internal method to encode using the integer values to avoid floating-point
116+
// precision errors.
117+
std::string encodeIntegers(int64_t latitude, int64_t longitude,
118+
size_t code_length);
109119
} // namespace internal
110120

111121
} // namespace openlocationcode

0 commit comments

Comments
 (0)