1 | /* |
2 | * Copyright 2016-2017 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 geoCoord.c |
17 | * @brief Functions for working with lat/lon coordinates. |
18 | */ |
19 | |
20 | #include "geoCoord.h" |
21 | #include <math.h> |
22 | #include <stdbool.h> |
23 | #include "constants.h" |
24 | #include "h3api.h" |
25 | |
26 | /** |
27 | * Normalizes radians to a value between 0.0 and two PI. |
28 | * |
29 | * @param rads The input radians value. |
30 | * @return The normalized radians value. |
31 | */ |
32 | double _posAngleRads(double rads) { |
33 | double tmp = ((rads < 0.0L) ? rads + M_2PI : rads); |
34 | if (rads >= M_2PI) tmp -= M_2PI; |
35 | return tmp; |
36 | } |
37 | |
38 | /** |
39 | * Determines if the components of two spherical coordinates are within some |
40 | * threshold distance of each other. |
41 | * |
42 | * @param p1 The first spherical coordinates. |
43 | * @param p2 The second spherical coordinates. |
44 | * @param threshold The threshold distance. |
45 | * @return Whether or not the two coordinates are within the threshold distance |
46 | * of each other. |
47 | */ |
48 | bool geoAlmostEqualThreshold(const GeoCoord* p1, const GeoCoord* p2, |
49 | double threshold) { |
50 | return fabs(p1->lat - p2->lat) < threshold && |
51 | fabs(p1->lon - p2->lon) < threshold; |
52 | } |
53 | |
54 | /** |
55 | * Determines if the components of two spherical coordinates are within our |
56 | * standard epsilon distance of each other. |
57 | * |
58 | * @param p1 The first spherical coordinates. |
59 | * @param p2 The second spherical coordinates. |
60 | * @return Whether or not the two coordinates are within the epsilon distance |
61 | * of each other. |
62 | */ |
63 | bool geoAlmostEqual(const GeoCoord* p1, const GeoCoord* p2) { |
64 | return geoAlmostEqualThreshold(p1, p2, EPSILON_RAD); |
65 | } |
66 | |
67 | /** |
68 | * Set the components of spherical coordinates in decimal degrees. |
69 | * |
70 | * @param p The spherical coodinates. |
71 | * @param latDegs The desired latitidue in decimal degrees. |
72 | * @param lonDegs The desired longitude in decimal degrees. |
73 | */ |
74 | void setGeoDegs(GeoCoord* p, double latDegs, double lonDegs) { |
75 | _setGeoRads(p, H3_EXPORT(degsToRads)(latDegs), |
76 | H3_EXPORT(degsToRads)(lonDegs)); |
77 | } |
78 | |
79 | /** |
80 | * Set the components of spherical coordinates in radians. |
81 | * |
82 | * @param p The spherical coodinates. |
83 | * @param latRads The desired latitidue in decimal radians. |
84 | * @param lonRads The desired longitude in decimal radians. |
85 | */ |
86 | void _setGeoRads(GeoCoord* p, double latRads, double lonRads) { |
87 | p->lat = latRads; |
88 | p->lon = lonRads; |
89 | } |
90 | |
91 | /** |
92 | * Convert from decimal degrees to radians. |
93 | * |
94 | * @param degrees The decimal degrees. |
95 | * @return The corresponding radians. |
96 | */ |
97 | double H3_EXPORT(degsToRads)(double degrees) { return degrees * M_PI_180; } |
98 | |
99 | /** |
100 | * Convert from radians to decimal degrees. |
101 | * |
102 | * @param radians The radians. |
103 | * @return The corresponding decimal degrees. |
104 | */ |
105 | double H3_EXPORT(radsToDegs)(double radians) { return radians * M_180_PI; } |
106 | |
107 | /** |
108 | * constrainLat makes sure latitudes are in the proper bounds |
109 | * |
110 | * @param lat The original lat value |
111 | * @return The corrected lat value |
112 | */ |
113 | double constrainLat(double lat) { |
114 | while (lat > M_PI_2) { |
115 | lat = lat - M_PI; |
116 | } |
117 | return lat; |
118 | } |
119 | |
120 | /** |
121 | * constrainLng makes sure longitudes are in the proper bounds |
122 | * |
123 | * @param lng The origin lng value |
124 | * @return The corrected lng value |
125 | */ |
126 | double constrainLng(double lng) { |
127 | while (lng > M_PI) { |
128 | lng = lng - (2 * M_PI); |
129 | } |
130 | while (lng < -M_PI) { |
131 | lng = lng + (2 * M_PI); |
132 | } |
133 | return lng; |
134 | } |
135 | |
136 | /** |
137 | * Find the great circle distance in radians between two spherical coordinates. |
138 | * |
139 | * @param p1 The first spherical coordinates. |
140 | * @param p2 The second spherical coordinates. |
141 | * @return The great circle distance in radians between p1 and p2. |
142 | */ |
143 | double _geoDistRads(const GeoCoord* p1, const GeoCoord* p2) { |
144 | // use spherical triangle with p1 at A, p2 at B, and north pole at C |
145 | double bigC = fabs(p2->lon - p1->lon); |
146 | if (bigC > M_PI) // assume we want the complement |
147 | { |
148 | // note that in this case they can't both be negative |
149 | double lon1 = p1->lon; |
150 | if (lon1 < 0.0L) lon1 += 2.0L * M_PI; |
151 | double lon2 = p2->lon; |
152 | if (lon2 < 0.0L) lon2 += 2.0L * M_PI; |
153 | |
154 | bigC = fabs(lon2 - lon1); |
155 | } |
156 | |
157 | double b = M_PI_2 - p1->lat; |
158 | double a = M_PI_2 - p2->lat; |
159 | |
160 | // use law of cosines to find c |
161 | double cosc = cos(a) * cos(b) + sin(a) * sin(b) * cos(bigC); |
162 | if (cosc > 1.0L) cosc = 1.0L; |
163 | if (cosc < -1.0L) cosc = -1.0L; |
164 | |
165 | return acos(cosc); |
166 | } |
167 | |
168 | /** |
169 | * Find the great circle distance in kilometers between two spherical |
170 | * coordinates. |
171 | * |
172 | * @param p1 The first spherical coordinates. |
173 | * @param p2 The second spherical coordinates. |
174 | * @return The distance in kilometers between p1 and p2. |
175 | */ |
176 | double _geoDistKm(const GeoCoord* p1, const GeoCoord* p2) { |
177 | return EARTH_RADIUS_KM * _geoDistRads(p1, p2); |
178 | } |
179 | |
180 | /** |
181 | * Determines the azimuth to p2 from p1 in radians. |
182 | * |
183 | * @param p1 The first spherical coordinates. |
184 | * @param p2 The second spherical coordinates. |
185 | * @return The azimuth in radians from p1 to p2. |
186 | */ |
187 | double _geoAzimuthRads(const GeoCoord* p1, const GeoCoord* p2) { |
188 | return atan2(cos(p2->lat) * sin(p2->lon - p1->lon), |
189 | cos(p1->lat) * sin(p2->lat) - |
190 | sin(p1->lat) * cos(p2->lat) * cos(p2->lon - p1->lon)); |
191 | } |
192 | |
193 | /** |
194 | * Computes the point on the sphere a specified azimuth and distance from |
195 | * another point. |
196 | * |
197 | * @param p1 The first spherical coordinates. |
198 | * @param az The desired azimuth from p1. |
199 | * @param distance The desired distance from p1, must be non-negative. |
200 | * @param p2 The spherical coordinates at the desired azimuth and distance from |
201 | * p1. |
202 | */ |
203 | void _geoAzDistanceRads(const GeoCoord* p1, double az, double distance, |
204 | GeoCoord* p2) { |
205 | if (distance < EPSILON) { |
206 | *p2 = *p1; |
207 | return; |
208 | } |
209 | |
210 | double sinlat, sinlon, coslon; |
211 | |
212 | az = _posAngleRads(az); |
213 | |
214 | // check for due north/south azimuth |
215 | if (az < EPSILON || fabs(az - M_PI) < EPSILON) { |
216 | if (az < EPSILON) // due north |
217 | p2->lat = p1->lat + distance; |
218 | else // due south |
219 | p2->lat = p1->lat - distance; |
220 | |
221 | if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole |
222 | { |
223 | p2->lat = M_PI_2; |
224 | p2->lon = 0.0L; |
225 | } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole |
226 | { |
227 | p2->lat = -M_PI_2; |
228 | p2->lon = 0.0L; |
229 | } else |
230 | p2->lon = constrainLng(p1->lon); |
231 | } else // not due north or south |
232 | { |
233 | sinlat = sin(p1->lat) * cos(distance) + |
234 | cos(p1->lat) * sin(distance) * cos(az); |
235 | if (sinlat > 1.0L) sinlat = 1.0L; |
236 | if (sinlat < -1.0L) sinlat = -1.0L; |
237 | p2->lat = asin(sinlat); |
238 | if (fabs(p2->lat - M_PI_2) < EPSILON) // north pole |
239 | { |
240 | p2->lat = M_PI_2; |
241 | p2->lon = 0.0L; |
242 | } else if (fabs(p2->lat + M_PI_2) < EPSILON) // south pole |
243 | { |
244 | p2->lat = -M_PI_2; |
245 | p2->lon = 0.0L; |
246 | } else { |
247 | sinlon = sin(az) * sin(distance) / cos(p2->lat); |
248 | coslon = (cos(distance) - sin(p1->lat) * sin(p2->lat)) / |
249 | cos(p1->lat) / cos(p2->lat); |
250 | if (sinlon > 1.0L) sinlon = 1.0L; |
251 | if (sinlon < -1.0L) sinlon = -1.0L; |
252 | if (coslon > 1.0L) sinlon = 1.0L; |
253 | if (coslon < -1.0L) sinlon = -1.0L; |
254 | p2->lon = constrainLng(p1->lon + atan2(sinlon, coslon)); |
255 | } |
256 | } |
257 | } |
258 | |
259 | /* |
260 | * The following functions provide meta information about the H3 hexagons at |
261 | * each zoom level. Since there are only 16 total levels, these are current |
262 | * handled with hardwired static values, but it may be worthwhile to put these |
263 | * static values into another file that can be autogenerated by source code in |
264 | * the future. |
265 | */ |
266 | |
267 | double H3_EXPORT(hexAreaKm2)(int res) { |
268 | static const double areas[] = { |
269 | 4250546.848, 607220.9782, 86745.85403, 12392.26486, |
270 | 1770.323552, 252.9033645, 36.1290521, 5.1612932, |
271 | 0.7373276, 0.1053325, 0.0150475, 0.0021496, |
272 | 0.0003071, 0.0000439, 0.0000063, 0.0000009}; |
273 | return areas[res]; |
274 | } |
275 | |
276 | double H3_EXPORT(hexAreaM2)(int res) { |
277 | static const double areas[] = { |
278 | 4.25055E+12, 6.07221E+11, 86745854035, 12392264862, |
279 | 1770323552, 252903364.5, 36129052.1, 5161293.2, |
280 | 737327.6, 105332.5, 15047.5, 2149.6, |
281 | 307.1, 43.9, 6.3, 0.9}; |
282 | return areas[res]; |
283 | } |
284 | |
285 | double H3_EXPORT(edgeLengthKm)(int res) { |
286 | static const double lens[] = { |
287 | 1107.712591, 418.6760055, 158.2446558, 59.81085794, |
288 | 22.6063794, 8.544408276, 3.229482772, 1.220629759, |
289 | 0.461354684, 0.174375668, 0.065907807, 0.024910561, |
290 | 0.009415526, 0.003559893, 0.001348575, 0.000509713}; |
291 | return lens[res]; |
292 | } |
293 | |
294 | double H3_EXPORT(edgeLengthM)(int res) { |
295 | static const double lens[] = { |
296 | 1107712.591, 418676.0055, 158244.6558, 59810.85794, |
297 | 22606.3794, 8544.408276, 3229.482772, 1220.629759, |
298 | 461.3546837, 174.3756681, 65.90780749, 24.9105614, |
299 | 9.415526211, 3.559893033, 1.348574562, 0.509713273}; |
300 | return lens[res]; |
301 | } |
302 | |
303 | /** @brief Number of unique valid H3Indexes at given resolution. */ |
304 | int64_t H3_EXPORT(numHexagons)(int res) { |
305 | static const int64_t nums[] = {122L, |
306 | 842L, |
307 | 5882L, |
308 | 41162L, |
309 | 288122L, |
310 | 2016842L, |
311 | 14117882L, |
312 | 98825162L, |
313 | 691776122L, |
314 | 4842432842L, |
315 | 33897029882L, |
316 | 237279209162L, |
317 | 1660954464122L, |
318 | 11626681248842L, |
319 | 81386768741882L, |
320 | 569707381193162L}; |
321 | return nums[res]; |
322 | } |
323 | |