Coverage Report

Created: 2026-09-03 06:52

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, 2026 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
 * The following functions provide meta information about the H3 hexagons at
206
 * each zoom level. Since there are only 16 total levels, these are current
207
 * handled with hardwired static values, but it may be worthwhile to put these
208
 * static values into another file that can be autogenerated by source code in
209
 * the future.
210
 */
211
212
0
H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out) {
213
0
    static const double areas[] = {
214
0
        4.357449416078383e+06, 6.097884417941332e+05, 8.680178039899720e+04,
215
0
        1.239343465508816e+04, 1.770347654491307e+03, 2.529038581819449e+02,
216
0
        3.612906216441245e+01, 5.161293359717191e+00, 7.373275975944177e-01,
217
0
        1.053325134272067e-01, 1.504750190766435e-02, 2.149643129451879e-03,
218
0
        3.070918756316060e-04, 4.387026794728296e-05, 6.267181135324313e-06,
219
0
        8.953115907605790e-07};
220
0
    if (res < 0 || res > MAX_H3_RES) {
221
0
        return E_RES_DOMAIN;
222
0
    }
223
0
    *out = areas[res];
224
0
    return E_SUCCESS;
225
0
}
226
227
0
H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out) {
228
0
    static const double areas[] = {
229
0
        4.357449416078390e+12, 6.097884417941339e+11, 8.680178039899731e+10,
230
0
        1.239343465508818e+10, 1.770347654491309e+09, 2.529038581819452e+08,
231
0
        3.612906216441250e+07, 5.161293359717198e+06, 7.373275975944188e+05,
232
0
        1.053325134272069e+05, 1.504750190766437e+04, 2.149643129451882e+03,
233
0
        3.070918756316063e+02, 4.387026794728301e+01, 6.267181135324322e+00,
234
0
        8.953115907605802e-01};
235
0
    if (res < 0 || res > MAX_H3_RES) {
236
0
        return E_RES_DOMAIN;
237
0
    }
238
0
    *out = areas[res];
239
0
    return E_SUCCESS;
240
0
}
241
242
0
H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out) {
243
0
    static const double lens[] = {
244
0
        1281.256011, 483.0568391, 182.5129565, 68.97922179,
245
0
        26.07175968, 9.854090990, 3.724532667, 1.406475763,
246
0
        0.531414010, 0.200786148, 0.075863783, 0.028663897,
247
0
        0.010830188, 0.004092010, 0.001546100, 0.000584169};
248
0
    if (res < 0 || res > MAX_H3_RES) {
249
0
        return E_RES_DOMAIN;
250
0
    }
251
0
    *out = lens[res];
252
0
    return E_SUCCESS;
253
0
}
254
255
0
H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out) {
256
0
    static const double lens[] = {
257
0
        1281256.011, 483056.8391, 182512.9565, 68979.22179,
258
0
        26071.75968, 9854.090990, 3724.532667, 1406.475763,
259
0
        531.4140101, 200.7861476, 75.86378287, 28.66389748,
260
0
        10.83018784, 4.092010473, 1.546099657, 0.584168630};
261
0
    if (res < 0 || res > MAX_H3_RES) {
262
0
        return E_RES_DOMAIN;
263
0
    }
264
0
    *out = lens[res];
265
0
    return E_SUCCESS;
266
0
}
267
268
0
H3Error H3_EXPORT(getNumCells)(int res, int64_t *out) {
269
0
    if (res < 0 || res > MAX_H3_RES) {
270
0
        return E_RES_DOMAIN;
271
0
    }
272
0
    *out = 2 + 120 * _ipow(7, res);
273
0
    return E_SUCCESS;
274
0
}
275
276
/**
277
 * Length of a directed edge in radians.
278
 *
279
 * @param   edge  H3 directed edge
280
 * @param    length  length in radians
281
 * @return        E_SUCCESS on success, or an error code otherwise
282
 */
283
0
H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length) {
284
0
    CellBoundary cb;
285
286
0
    H3Error err = H3_EXPORT(directedEdgeToBoundary)(edge, &cb);
287
0
    if (err) {
288
0
        return err;
289
0
    }
290
291
0
    *length = 0.0;
292
0
    for (int i = 0; i < cb.numVerts - 1; i++) {
293
0
        *length +=
294
0
            H3_EXPORT(greatCircleDistanceRads)(&cb.verts[i], &cb.verts[i + 1]);
295
0
    }
296
297
0
    return E_SUCCESS;
298
0
}
299
300
/**
301
 * Length of a directed edge in kilometers.
302
 *
303
 * @param   edge  H3 directed edge
304
 * @param    length  length in kilometers
305
 * @return        E_SUCCESS on success, or an error code otherwise
306
 */
307
0
H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length) {
308
0
    H3Error err = H3_EXPORT(edgeLengthRads)(edge, length);
309
0
    *length = *length * EARTH_RADIUS_KM;
310
0
    return err;
311
0
}
312
313
/**
314
 * Length of a directed edge in meters.
315
 *
316
 * @param   edge  H3 directed edge
317
 * @param    length  length in meters
318
 * @return        E_SUCCESS on success, or an error code otherwise
319
 */
320
0
H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length) {
321
0
    H3Error err = H3_EXPORT(edgeLengthKm)(edge, length);
322
0
    *length = *length * 1000;
323
0
    return err;
324
0
}