/src/h3/src/h3lib/lib/latLng.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2016-2023 Uber Technologies, Inc. |
3 | | * |
4 | | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | * you may not use this file except in compliance with the License. |
6 | | * You may obtain a copy of the License at |
7 | | * |
8 | | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | | * |
10 | | * Unless required by applicable law or agreed to in writing, software |
11 | | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | * See the License for the specific language governing permissions and |
14 | | * limitations under the License. |
15 | | */ |
16 | | /** @file latLng.c |
17 | | * @brief Functions for working with lat/lng coordinates. |
18 | | */ |
19 | | |
20 | | #include "latLng.h" |
21 | | |
22 | | #include <math.h> |
23 | | #include <stdbool.h> |
24 | | |
25 | | #include "constants.h" |
26 | | #include "h3Assert.h" |
27 | | #include "h3api.h" |
28 | | #include "mathExtensions.h" |
29 | | |
30 | | /** |
31 | | * Normalizes radians to a value between 0.0 and two PI. |
32 | | * |
33 | | * @param rads The input radians value. |
34 | | * @return The normalized radians value. |
35 | | */ |
36 | 0 | double _posAngleRads(double rads) { |
37 | 0 | double tmp = ((rads < 0.0) ? rads + M_2PI : rads); |
38 | 0 | if (rads >= M_2PI) tmp -= M_2PI; |
39 | 0 | return tmp; |
40 | 0 | } |
41 | | |
42 | | /** |
43 | | * Determines if the components of two spherical coordinates are within some |
44 | | * threshold distance of each other. |
45 | | * |
46 | | * @param p1 The first spherical coordinates. |
47 | | * @param p2 The second spherical coordinates. |
48 | | * @param threshold The threshold distance. |
49 | | * @return Whether or not the two coordinates are within the threshold distance |
50 | | * of each other. |
51 | | */ |
52 | | bool geoAlmostEqualThreshold(const LatLng *p1, const LatLng *p2, |
53 | 0 | double threshold) { |
54 | 0 | return fabs(p1->lat - p2->lat) < threshold && |
55 | 0 | fabs(p1->lng - p2->lng) < threshold; |
56 | 0 | } |
57 | | |
58 | | /** |
59 | | * Determines if the components of two spherical coordinates are within our |
60 | | * standard epsilon distance of each other. |
61 | | * |
62 | | * @param p1 The first spherical coordinates. |
63 | | * @param p2 The second spherical coordinates. |
64 | | * @return Whether or not the two coordinates are within the epsilon distance |
65 | | * of each other. |
66 | | */ |
67 | 0 | bool geoAlmostEqual(const LatLng *p1, const LatLng *p2) { |
68 | 0 | return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD); |
69 | 0 | } |
70 | | |
71 | | /** |
72 | | * Set the components of spherical coordinates in decimal degrees. |
73 | | * |
74 | | * @param p The spherical coordinates. |
75 | | * @param latDegs The desired latitude in decimal degrees. |
76 | | * @param lngDegs The desired longitude in decimal degrees. |
77 | | */ |
78 | 0 | void setGeoDegs(LatLng *p, double latDegs, double lngDegs) { |
79 | 0 | _setGeoRads(p, H3_EXPORT(degsToRads)(latDegs), |
80 | 0 | H3_EXPORT(degsToRads)(lngDegs)); |
81 | 0 | } |
82 | | |
83 | | /** |
84 | | * Set the components of spherical coordinates in radians. |
85 | | * |
86 | | * @param p The spherical coordinates. |
87 | | * @param latRads The desired latitude in decimal radians. |
88 | | * @param lngRads The desired longitude in decimal radians. |
89 | | */ |
90 | 0 | void _setGeoRads(LatLng *p, double latRads, double lngRads) { |
91 | 0 | p->lat = latRads; |
92 | 0 | p->lng = lngRads; |
93 | 0 | } |
94 | | |
95 | | /** |
96 | | * Convert from decimal degrees to radians. |
97 | | * |
98 | | * @param degrees The decimal degrees. |
99 | | * @return The corresponding radians. |
100 | | */ |
101 | 0 | double H3_EXPORT(degsToRads)(double degrees) { return degrees * M_PI_180; } |
102 | | |
103 | | /** |
104 | | * Convert from radians to decimal degrees. |
105 | | * |
106 | | * @param radians The radians. |
107 | | * @return The corresponding decimal degrees. |
108 | | */ |
109 | 0 | double H3_EXPORT(radsToDegs)(double radians) { return radians * M_180_PI; } |
110 | | |
111 | | /** |
112 | | * constrainLat makes sure latitudes are in the proper bounds |
113 | | * |
114 | | * @param lat The original lat value |
115 | | * @return The corrected lat value |
116 | | */ |
117 | 0 | double constrainLat(double lat) { |
118 | 0 | while (lat > M_PI_2) { |
119 | 0 | lat = lat - M_PI; |
120 | 0 | } |
121 | 0 | return lat; |
122 | 0 | } |
123 | | |
124 | | /** |
125 | | * constrainLng makes sure longitudes are in the proper bounds |
126 | | * |
127 | | * @param lng The origin lng value |
128 | | * @return The corrected lng value |
129 | | */ |
130 | 0 | double constrainLng(double lng) { |
131 | 0 | while (lng > M_PI) { |
132 | 0 | lng = lng - (2 * M_PI); |
133 | 0 | } |
134 | 0 | while (lng < -M_PI) { |
135 | 0 | lng = lng + (2 * M_PI); |
136 | 0 | } |
137 | 0 | return lng; |
138 | 0 | } |
139 | | |
140 | | /** |
141 | | * Normalize an input longitude according to the specified normalization |
142 | | * @param lng Input longitude |
143 | | * @param normalization Longitude normalization strategy |
144 | | * @return Normalized longitude |
145 | | */ |
146 | | double normalizeLng(const double lng, |
147 | 0 | const LongitudeNormalization normalization) { |
148 | 0 | switch (normalization) { |
149 | 0 | case NORMALIZE_EAST: |
150 | 0 | return lng < 0 ? lng + (double)M_2PI : lng; |
151 | 0 | case NORMALIZE_WEST: |
152 | 0 | return lng > 0 ? lng - (double)M_2PI : lng; |
153 | 0 | default: |
154 | 0 | return lng; |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | /** |
159 | | * The great circle distance in radians between two spherical coordinates. |
160 | | * |
161 | | * This function uses the Haversine formula. |
162 | | * For math details, see: |
163 | | * https://en.wikipedia.org/wiki/Haversine_formula |
164 | | * https://www.movable-type.co.uk/scripts/latlong.html |
165 | | * |
166 | | * @param a the first lat/lng pair (in radians) |
167 | | * @param b the second lat/lng pair (in radians) |
168 | | * |
169 | | * @return the great circle distance in radians between a and b |
170 | | */ |
171 | 0 | double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a, const LatLng *b) { |
172 | 0 | double sinLat = sin((b->lat - a->lat) * 0.5); |
173 | 0 | double sinLng = sin((b->lng - a->lng) * 0.5); |
174 | |
|
175 | 0 | double A = sinLat * sinLat + cos(a->lat) * cos(b->lat) * sinLng * sinLng; |
176 | |
|
177 | 0 | return 2 * atan2(sqrt(A), sqrt(1 - A)); |
178 | 0 | } |
179 | | |
180 | | /** |
181 | | * The great circle distance in kilometers between two spherical coordinates. |
182 | | * |
183 | | * @param a the first lat/lng pair (in radians) |
184 | | * @param b the second lat/lng pair (in radians) |
185 | | * |
186 | | * @return the great circle distance in kilometers between a and b |
187 | | */ |
188 | 0 | double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a, const LatLng *b) { |
189 | 0 | return H3_EXPORT(greatCircleDistanceRads)(a, b) * EARTH_RADIUS_KM; |
190 | 0 | } |
191 | | |
192 | | /** |
193 | | * The great circle distance in meters between two spherical coordinates. |
194 | | * |
195 | | * @param a the first lat/lng pair (in radians) |
196 | | * @param b the second lat/lng pair (in radians) |
197 | | * |
198 | | * @return the great circle distance in meters between a and b |
199 | | */ |
200 | 0 | double H3_EXPORT(greatCircleDistanceM)(const LatLng *a, const LatLng *b) { |
201 | 0 | return H3_EXPORT(greatCircleDistanceKm)(a, b) * 1000; |
202 | 0 | } |
203 | | |
204 | | /** |
205 | | * Determines the azimuth to p2 from p1 in radians. |
206 | | * |
207 | | * @param p1 The first spherical coordinates. |
208 | | * @param p2 The second spherical coordinates. |
209 | | * @return The azimuth in radians from p1 to p2. |
210 | | */ |
211 | 0 | double _geoAzimuthRads(const LatLng *p1, const LatLng *p2) { |
212 | 0 | return atan2(cos(p2->lat) * sin(p2->lng - p1->lng), |
213 | 0 | cos(p1->lat) * sin(p2->lat) - |
214 | 0 | sin(p1->lat) * cos(p2->lat) * cos(p2->lng - p1->lng)); |
215 | 0 | } |
216 | | |
217 | | /** |
218 | | * Computes the point on the sphere a specified azimuth and distance from |
219 | | * another point. |
220 | | * |
221 | | * @param p1 The first spherical coordinates. |
222 | | * @param az The desired azimuth from p1. |
223 | | * @param distance The desired distance from p1, must be non-negative. |
224 | | * @param p2 The spherical coordinates at the desired azimuth and distance from |
225 | | * p1. |
226 | | */ |
227 | | void _geoAzDistanceRads(const LatLng *p1, double az, double distance, |
228 | 0 | LatLng *p2) { |
229 | 0 | if (distance < EPSILON) { |
230 | 0 | *p2 = *p1; |
231 | 0 | return; |
232 | 0 | } |
233 | | |
234 | 0 | double sinlat, sinlng, coslng; |
235 | |
|
236 | 0 | az = _posAngleRads(az); |
237 | | |
238 | | // check for due north/south azimuth |
239 | 0 | if (az < EPSILON || fabs(az - M_PI) < EPSILON) { |
240 | 0 | if (az < EPSILON) // due north |
241 | 0 | p2->lat = p1->lat + distance; |
242 | 0 | else // due south |
243 | 0 | p2->lat = p1->lat - distance; |
244 | |
|
245 | 0 | if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole |
246 | 0 | { |
247 | 0 | p2->lat = M_PI_2; |
248 | 0 | p2->lng = 0.0; |
249 | 0 | } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole |
250 | 0 | { |
251 | 0 | p2->lat = -M_PI_2; |
252 | 0 | p2->lng = 0.0; |
253 | 0 | } else |
254 | 0 | p2->lng = constrainLng(p1->lng); |
255 | 0 | } else // not due north or south |
256 | 0 | { |
257 | 0 | sinlat = sin(p1->lat) * cos(distance) + |
258 | 0 | cos(p1->lat) * sin(distance) * cos(az); |
259 | 0 | if (sinlat > 1.0) sinlat = 1.0; |
260 | 0 | if (sinlat < -1.0) sinlat = -1.0; |
261 | 0 | p2->lat = asin(sinlat); |
262 | 0 | if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole |
263 | 0 | { |
264 | 0 | p2->lat = M_PI_2; |
265 | 0 | p2->lng = 0.0; |
266 | 0 | } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole |
267 | 0 | { |
268 | 0 | p2->lat = -M_PI_2; |
269 | 0 | p2->lng = 0.0; |
270 | 0 | } else { |
271 | 0 | double invcosp2lat = 1.0 / cos(p2->lat); |
272 | 0 | sinlng = sin(az) * sin(distance) * invcosp2lat; |
273 | 0 | coslng = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / |
274 | 0 | cos(p1->lat) * invcosp2lat; |
275 | 0 | if (sinlng > 1.0) sinlng = 1.0; |
276 | 0 | if (sinlng < -1.0) sinlng = -1.0; |
277 | 0 | if (coslng > 1.0) coslng = 1.0; |
278 | 0 | if (coslng < -1.0) coslng = -1.0; |
279 | 0 | p2->lng = constrainLng(p1->lng + atan2(sinlng, coslng)); |
280 | 0 | } |
281 | 0 | } |
282 | 0 | } |
283 | | |
284 | | /* |
285 | | * The following functions provide meta information about the H3 hexagons at |
286 | | * each zoom level. Since there are only 16 total levels, these are current |
287 | | * handled with hardwired static values, but it may be worthwhile to put these |
288 | | * static values into another file that can be autogenerated by source code in |
289 | | * the future. |
290 | | */ |
291 | | |
292 | 0 | H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out) { |
293 | 0 | static const double areas[] = { |
294 | 0 | 4.357449416078383e+06, 6.097884417941332e+05, 8.680178039899720e+04, |
295 | 0 | 1.239343465508816e+04, 1.770347654491307e+03, 2.529038581819449e+02, |
296 | 0 | 3.612906216441245e+01, 5.161293359717191e+00, 7.373275975944177e-01, |
297 | 0 | 1.053325134272067e-01, 1.504750190766435e-02, 2.149643129451879e-03, |
298 | 0 | 3.070918756316060e-04, 4.387026794728296e-05, 6.267181135324313e-06, |
299 | 0 | 8.953115907605790e-07}; |
300 | 0 | if (res < 0 || res > MAX_H3_RES) { |
301 | 0 | return E_RES_DOMAIN; |
302 | 0 | } |
303 | 0 | *out = areas[res]; |
304 | 0 | return E_SUCCESS; |
305 | 0 | } |
306 | | |
307 | 0 | H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out) { |
308 | 0 | static const double areas[] = { |
309 | 0 | 4.357449416078390e+12, 6.097884417941339e+11, 8.680178039899731e+10, |
310 | 0 | 1.239343465508818e+10, 1.770347654491309e+09, 2.529038581819452e+08, |
311 | 0 | 3.612906216441250e+07, 5.161293359717198e+06, 7.373275975944188e+05, |
312 | 0 | 1.053325134272069e+05, 1.504750190766437e+04, 2.149643129451882e+03, |
313 | 0 | 3.070918756316063e+02, 4.387026794728301e+01, 6.267181135324322e+00, |
314 | 0 | 8.953115907605802e-01}; |
315 | 0 | if (res < 0 || res > MAX_H3_RES) { |
316 | 0 | return E_RES_DOMAIN; |
317 | 0 | } |
318 | 0 | *out = areas[res]; |
319 | 0 | return E_SUCCESS; |
320 | 0 | } |
321 | | |
322 | 0 | H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out) { |
323 | 0 | static const double lens[] = { |
324 | 0 | 1281.256011, 483.0568391, 182.5129565, 68.97922179, |
325 | 0 | 26.07175968, 9.854090990, 3.724532667, 1.406475763, |
326 | 0 | 0.531414010, 0.200786148, 0.075863783, 0.028663897, |
327 | 0 | 0.010830188, 0.004092010, 0.001546100, 0.000584169}; |
328 | 0 | if (res < 0 || res > MAX_H3_RES) { |
329 | 0 | return E_RES_DOMAIN; |
330 | 0 | } |
331 | 0 | *out = lens[res]; |
332 | 0 | return E_SUCCESS; |
333 | 0 | } |
334 | | |
335 | 0 | H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out) { |
336 | 0 | static const double lens[] = { |
337 | 0 | 1281256.011, 483056.8391, 182512.9565, 68979.22179, |
338 | 0 | 26071.75968, 9854.090990, 3724.532667, 1406.475763, |
339 | 0 | 531.4140101, 200.7861476, 75.86378287, 28.66389748, |
340 | 0 | 10.83018784, 4.092010473, 1.546099657, 0.584168630}; |
341 | 0 | if (res < 0 || res > MAX_H3_RES) { |
342 | 0 | return E_RES_DOMAIN; |
343 | 0 | } |
344 | 0 | *out = lens[res]; |
345 | 0 | return E_SUCCESS; |
346 | 0 | } |
347 | | |
348 | 0 | H3Error H3_EXPORT(getNumCells)(int res, int64_t *out) { |
349 | 0 | if (res < 0 || res > MAX_H3_RES) { |
350 | 0 | return E_RES_DOMAIN; |
351 | 0 | } |
352 | 0 | *out = 2 + 120 * _ipow(7, res); |
353 | 0 | return E_SUCCESS; |
354 | 0 | } |
355 | | |
356 | | /** |
357 | | * Length of a directed edge in radians. |
358 | | * |
359 | | * @param edge H3 directed edge |
360 | | * @param length length in radians |
361 | | * @return E_SUCCESS on success, or an error code otherwise |
362 | | */ |
363 | 0 | H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length) { |
364 | 0 | CellBoundary cb; |
365 | |
|
366 | 0 | H3Error err = H3_EXPORT(directedEdgeToBoundary)(edge, &cb); |
367 | 0 | if (err) { |
368 | 0 | return err; |
369 | 0 | } |
370 | | |
371 | 0 | *length = 0.0; |
372 | 0 | for (int i = 0; i < cb.numVerts - 1; i++) { |
373 | 0 | *length += |
374 | 0 | H3_EXPORT(greatCircleDistanceRads)(&cb.verts[i], &cb.verts[i + 1]); |
375 | 0 | } |
376 | |
|
377 | 0 | return E_SUCCESS; |
378 | 0 | } |
379 | | |
380 | | /** |
381 | | * Length of a directed edge in kilometers. |
382 | | * |
383 | | * @param edge H3 directed edge |
384 | | * @param length length in kilometers |
385 | | * @return E_SUCCESS on success, or an error code otherwise |
386 | | */ |
387 | 0 | H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length) { |
388 | 0 | H3Error err = H3_EXPORT(edgeLengthRads)(edge, length); |
389 | 0 | *length = *length * EARTH_RADIUS_KM; |
390 | 0 | return err; |
391 | 0 | } |
392 | | |
393 | | /** |
394 | | * Length of a directed edge in meters. |
395 | | * |
396 | | * @param edge H3 directed edge |
397 | | * @param length length in meters |
398 | | * @return E_SUCCESS on success, or an error code otherwise |
399 | | */ |
400 | 0 | H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length) { |
401 | 0 | H3Error err = H3_EXPORT(edgeLengthKm)(edge, length); |
402 | 0 | *length = *length * 1000; |
403 | 0 | return err; |
404 | 0 | } |