Coverage Report

Created: 2025-10-10 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
 * Surface area in radians^2 of spherical triangle on unit sphere.
358
 *
359
 * For the math, see:
360
 * https://en.wikipedia.org/wiki/Spherical_trigonometry#Area_and_spherical_excess
361
 *
362
 * @param   a  length of triangle side A in radians
363
 * @param   b  length of triangle side B in radians
364
 * @param   c  length of triangle side C in radians
365
 *
366
 * @return     area in radians^2 of triangle on unit sphere
367
 */
368
0
double triangleEdgeLengthsToArea(double a, double b, double c) {
369
0
    double s = (a + b + c) * 0.5;
370
371
0
    a = (s - a) * 0.5;
372
0
    b = (s - b) * 0.5;
373
0
    c = (s - c) * 0.5;
374
0
    s = s * 0.5;
375
376
0
    return 4 * atan(sqrt(tan(s) * tan(a) * tan(b) * tan(c)));
377
0
}
378
379
/**
380
 * Compute area in radians^2 of a spherical triangle, given its vertices.
381
 *
382
 * @param   a  vertex lat/lng in radians
383
 * @param   b  vertex lat/lng in radians
384
 * @param   c  vertex lat/lng in radians
385
 *
386
 * @return     area of triangle on unit sphere, in radians^2
387
 */
388
0
double triangleArea(const LatLng *a, const LatLng *b, const LatLng *c) {
389
0
    return triangleEdgeLengthsToArea(H3_EXPORT(greatCircleDistanceRads)(a, b),
390
0
                                     H3_EXPORT(greatCircleDistanceRads)(b, c),
391
0
                                     H3_EXPORT(greatCircleDistanceRads)(c, a));
392
0
}
393
394
/**
395
 * Area of H3 cell in radians^2.
396
 *
397
 * The area is calculated by breaking the cell into spherical triangles and
398
 * summing up their areas. Note that some H3 cells (hexagons and pentagons)
399
 * are irregular, and have more than 6 or 5 sides.
400
 *
401
 * todo: optimize the computation by re-using the edges shared between triangles
402
 *
403
 * @param   cell  H3 cell
404
 * @param    out  cell area in radians^2
405
 * @return        E_SUCCESS on success, or an error code otherwise
406
 */
407
0
H3Error H3_EXPORT(cellAreaRads2)(H3Index cell, double *out) {
408
0
    LatLng c;
409
0
    CellBoundary cb;
410
0
    H3Error err = H3_EXPORT(cellToLatLng)(cell, &c);
411
0
    if (err) {
412
0
        return err;
413
0
    }
414
0
    err = H3_EXPORT(cellToBoundary)(cell, &cb);
415
0
    if (NEVER(err)) {
416
        // Uncoverable because cellToLatLng will have returned an error already
417
0
        return err;
418
0
    }
419
420
0
    double area = 0.0;
421
0
    for (int i = 0; i < cb.numVerts; i++) {
422
0
        int j = (i + 1) % cb.numVerts;
423
0
        area += triangleArea(&cb.verts[i], &cb.verts[j], &c);
424
0
    }
425
426
0
    *out = area;
427
0
    return E_SUCCESS;
428
0
}
429
430
/**
431
 * Area of H3 cell in kilometers^2.
432
 *
433
 * @param   cell  H3 cell
434
 * @param    out  cell area in kilometers^2
435
 * @return        E_SUCCESS on success, or an error code otherwise
436
 */
437
0
H3Error H3_EXPORT(cellAreaKm2)(H3Index cell, double *out) {
438
0
    H3Error err = H3_EXPORT(cellAreaRads2)(cell, out);
439
0
    if (!err) {
440
0
        *out = *out * EARTH_RADIUS_KM * EARTH_RADIUS_KM;
441
0
    }
442
0
    return err;
443
0
}
444
445
/**
446
 * Area of H3 cell in meters^2.
447
 *
448
 * @param   cell  H3 cell
449
 * @param    out  cell area in meters^2
450
 * @return        E_SUCCESS on success, or an error code otherwise
451
 */
452
0
H3Error H3_EXPORT(cellAreaM2)(H3Index cell, double *out) {
453
0
    H3Error err = H3_EXPORT(cellAreaKm2)(cell, out);
454
0
    if (!err) {
455
0
        *out = *out * 1000 * 1000;
456
0
    }
457
0
    return err;
458
0
}
459
460
/**
461
 * Length of a directed edge in radians.
462
 *
463
 * @param   edge  H3 directed edge
464
 * @param    length  length in radians
465
 * @return        E_SUCCESS on success, or an error code otherwise
466
 */
467
0
H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length) {
468
0
    CellBoundary cb;
469
470
0
    H3Error err = H3_EXPORT(directedEdgeToBoundary)(edge, &cb);
471
0
    if (err) {
472
0
        return err;
473
0
    }
474
475
0
    *length = 0.0;
476
0
    for (int i = 0; i < cb.numVerts - 1; i++) {
477
0
        *length +=
478
0
            H3_EXPORT(greatCircleDistanceRads)(&cb.verts[i], &cb.verts[i + 1]);
479
0
    }
480
481
0
    return E_SUCCESS;
482
0
}
483
484
/**
485
 * Length of a directed edge in kilometers.
486
 *
487
 * @param   edge  H3 directed edge
488
 * @param    length  length in kilometers
489
 * @return        E_SUCCESS on success, or an error code otherwise
490
 */
491
0
H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length) {
492
0
    H3Error err = H3_EXPORT(edgeLengthRads)(edge, length);
493
0
    *length = *length * EARTH_RADIUS_KM;
494
0
    return err;
495
0
}
496
497
/**
498
 * Length of a directed edge in meters.
499
 *
500
 * @param   edge  H3 directed edge
501
 * @param    length  length in meters
502
 * @return        E_SUCCESS on success, or an error code otherwise
503
 */
504
0
H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length) {
505
0
    H3Error err = H3_EXPORT(edgeLengthKm)(edge, length);
506
0
    *length = *length * 1000;
507
0
    return err;
508
0
}