3 #include "common/maths.h"
4 #include "common/olc.h"
6 // This is a port of https://github.com/google/open-location-code/blob/master/c/olc.c
7 // to avoid double floating point math and use integer math as much as possible.
9 #define SEPARATOR_CHAR '+'
10 #define SEPARATOR_POS 8
11 #define PADDING_CHAR '0'
13 #define ENCODING_BASE 20
14 #define PAIR_CODE_LEN 10u
15 #define CODE_LEN_MAX 15u
18 #define GRID_ROWS (ENCODING_BASE / GRID_COLS)
21 #define LAT_MAX (90 * OLC_DEG_MULTIPLIER)
22 #define LON_MAX (180 * OLC_DEG_MULTIPLIER)
24 static const char alphabet
[] = "23456789CFGHJMPQRVWX";
26 // Initialized via init_constants()
27 static olc_coord_t grid_size
;
28 static olc_coord_t initial_resolution
;
30 static void init_constants(void)
32 static bool inited
= 0;
38 // Work out the encoding base exponent necessary to represent 360 degrees.
39 olc_coord_t initial_exponent
= floorf(logf(2 * (LON_MAX
/ OLC_DEG_MULTIPLIER
)) / logf(ENCODING_BASE
));
41 // Work out the enclosing resolution (in degrees) for the grid algorithm.
42 grid_size
= (1 / powf(ENCODING_BASE
, PAIR_CODE_LEN
/ 2 - (initial_exponent
+ 1))) * OLC_DEG_MULTIPLIER
;
44 // Work out the initial resolution
45 initial_resolution
= powf(ENCODING_BASE
, initial_exponent
) * OLC_DEG_MULTIPLIER
;
48 // Compute the latitude precision value for a given code length. Lengths <= 10
49 // have the same precision for latitude and longitude, but lengths > 10 have
50 // different precisions due to the grid method having fewer columns than rows.
51 static float compute_precision_for_length(int length
)
54 if (length
<= (int)PAIR_CODE_LEN
) {
55 return powf(ENCODING_BASE
, floorf((length
/ -2) + 2));
58 return powf(ENCODING_BASE
, -3) / powf(5, length
- (int)PAIR_CODE_LEN
);
61 static olc_coord_t
adjust_latitude(olc_coord_t lat
, size_t code_len
)
70 // Subtract half the code precision to get the latitude into the code area.
71 olc_coord_t precision
= compute_precision_for_length(code_len
) * OLC_DEG_MULTIPLIER
;
77 static olc_coord_t
normalize_longitude(olc_coord_t lon
)
79 while (lon
< -LON_MAX
) {
83 while (lon
>= LON_MAX
) {
90 // Encodes positive range lat,lon into a sequence of OLC lat/lon pairs. This
91 // uses pairs of characters (latitude and longitude in that order) to represent
92 // each step in a 20x20 grid. Each code, therefore, has 1/400th the area of
94 static unsigned encode_pairs(uolc_coord_t lat
, uolc_coord_t lon
, size_t length
, char *buf
, size_t bufsize
)
96 if ((length
+ 1) >= bufsize
) {
102 olc_coord_t resolution
= initial_resolution
;
103 // Add two digits on each pass.
104 for (size_t digit_count
= 0;
105 digit_count
< length
;
106 digit_count
+= 2, resolution
/= ENCODING_BASE
) {
109 // Do the latitude - gets the digit for this place and subtracts that
110 // for the next digit.
111 digit_value
= lat
/ resolution
;
112 lat
-= digit_value
* resolution
;
113 buf
[pos
++] = alphabet
[digit_value
];
115 // Do the longitude - gets the digit for this place and subtracts that
116 // for the next digit.
117 digit_value
= lon
/ resolution
;
118 lon
-= digit_value
* resolution
;
119 buf
[pos
++] = alphabet
[digit_value
];
121 // Should we add a separator here?
122 if (pos
== SEPARATOR_POS
&& pos
< length
) {
123 buf
[pos
++] = SEPARATOR_CHAR
;
126 while (pos
< SEPARATOR_POS
) {
127 buf
[pos
++] = PADDING_CHAR
;
129 if (pos
== SEPARATOR_POS
) {
130 buf
[pos
++] = SEPARATOR_CHAR
;
136 // Encodes a location using the grid refinement method into an OLC string. The
137 // grid refinement method divides the area into a grid of 4x5, and uses a
138 // single character to refine the area. The grid squares use the OLC
139 // characters in order to number the squares as follows:
147 // This allows default accuracy OLC codes to be refined with just a single
149 static int encode_grid(uolc_coord_t lat
, uolc_coord_t lon
, size_t length
,
150 char *buf
, size_t bufsize
)
152 if ((length
+ 1) >= bufsize
) {
159 olc_coord_t lat_grid_size
= grid_size
;
160 olc_coord_t lon_grid_size
= grid_size
;
162 lat
%= lat_grid_size
;
163 lon
%= lon_grid_size
;
165 for (size_t i
= 0; i
< length
; i
++) {
166 olc_coord_t lat_div
= lat_grid_size
/ GRID_ROWS
;
167 olc_coord_t lon_div
= lon_grid_size
/ GRID_COLS
;
169 if (lat_div
== 0 || lon_div
== 0) {
170 // This case happens when OLC_DEG_MULTIPLIER doesn't have enough
171 // precision for the requested length.
175 // Work out the row and column.
176 size_t row
= lat
/ lat_div
;
177 size_t col
= lon
/ lon_div
;
178 lat_grid_size
/= GRID_ROWS
;
179 lon_grid_size
/= GRID_COLS
;
180 lat
-= row
* lat_grid_size
;
181 lon
-= col
* lon_grid_size
;
182 buf
[pos
++] = alphabet
[row
* GRID_COLS
+ col
];
188 int olc_encode(olc_coord_t lat
, olc_coord_t lon
, size_t length
, char *buf
, size_t bufsize
)
192 length
= MIN(length
, CODE_LEN_MAX
);
194 // Adjust latitude and longitude so they fall into positive ranges.
195 uolc_coord_t alat
= adjust_latitude(lat
, length
) + LAT_MAX
;
196 uolc_coord_t alon
= normalize_longitude(lon
) + LON_MAX
;
200 pos
+= encode_pairs(alat
, alon
, MIN(length
, PAIR_CODE_LEN
), buf
+ pos
, bufsize
- pos
);
201 // If the requested length indicates we want grid refined codes.
202 if (length
> PAIR_CODE_LEN
) {
203 pos
+= encode_grid(alat
, alon
, length
- PAIR_CODE_LEN
, buf
+ pos
, bufsize
- pos
);