Update ci.yml
[inav.git] / src / main / common / olc.c
blobca31bc0c5ac20477f1b631dbef4350e41ea665d0
1 #include <math.h>
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
17 #define GRID_COLS 4
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;
33 if (inited) {
34 return;
36 inited = 1;
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)
53 // Magic numbers!
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)
63 if (lat < -LAT_MAX) {
64 lat = -LAT_MAX;
66 if (lat > LAT_MAX) {
67 lat = LAT_MAX;
69 if (lat >= LAT_MAX) {
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;
72 lat -= precision / 2;
74 return lat;
77 static olc_coord_t normalize_longitude(olc_coord_t lon)
79 while (lon < -LON_MAX) {
80 lon += LON_MAX;
81 lon += LON_MAX;
83 while (lon >= LON_MAX) {
84 lon -= LON_MAX;
85 lon -= LON_MAX;
87 return lon;
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
93 // the previous code.
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) {
97 buf[0] = '\0';
98 return 0;
101 unsigned pos = 0;
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) {
107 size_t digit_value;
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;
132 buf[pos] = '\0';
133 return pos;
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:
141 // R V W X
142 // J M P Q
143 // C F G H
144 // 6 7 8 9
145 // 2 3 4 5
147 // This allows default accuracy OLC codes to be refined with just a single
148 // character.
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) {
153 buf[0] = '\0';
154 return 0;
157 int pos = 0;
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.
172 break;
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];
184 buf[pos] = '\0';
185 return pos;
188 int olc_encode(olc_coord_t lat, olc_coord_t lon, size_t length, char *buf, size_t bufsize)
190 int pos = 0;
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;
198 init_constants();
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);
205 buf[pos] = '\0';
206 return pos;