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 faceijk.c |
17 | * @brief Functions for working with icosahedral face-centered hex IJK |
18 | * coordinate systems. |
19 | */ |
20 | |
21 | #include "faceijk.h" |
22 | #include <assert.h> |
23 | #include <math.h> |
24 | #include <stdio.h> |
25 | #include <stdlib.h> |
26 | #include <string.h> |
27 | #include "constants.h" |
28 | #include "coordijk.h" |
29 | #include "geoCoord.h" |
30 | #include "h3Index.h" |
31 | #include "vec3d.h" |
32 | |
33 | /** square root of 7 */ |
34 | #define M_SQRT7 2.6457513110645905905016157536392604257102L |
35 | |
36 | /** @brief icosahedron face centers in lat/lon radians */ |
37 | const GeoCoord faceCenterGeo[NUM_ICOSA_FACES] = { |
38 | {0.803582649718989942, 1.248397419617396099}, // face 0 |
39 | {1.307747883455638156, 2.536945009877921159}, // face 1 |
40 | {1.054751253523952054, -1.347517358900396623}, // face 2 |
41 | {0.600191595538186799, -0.450603909469755746}, // face 3 |
42 | {0.491715428198773866, 0.401988202911306943}, // face 4 |
43 | {0.172745327415618701, 1.678146885280433686}, // face 5 |
44 | {0.605929321571350690, 2.953923329812411617}, // face 6 |
45 | {0.427370518328979641, -1.888876200336285401}, // face 7 |
46 | {-0.079066118549212831, -0.733429513380867741}, // face 8 |
47 | {-0.230961644455383637, 0.506495587332349035}, // face 9 |
48 | {0.079066118549212831, 2.408163140208925497}, // face 10 |
49 | {0.230961644455383637, -2.635097066257444203}, // face 11 |
50 | {-0.172745327415618701, -1.463445768309359553}, // face 12 |
51 | {-0.605929321571350690, -0.187669323777381622}, // face 13 |
52 | {-0.427370518328979641, 1.252716453253507838}, // face 14 |
53 | {-0.600191595538186799, 2.690988744120037492}, // face 15 |
54 | {-0.491715428198773866, -2.739604450678486295}, // face 16 |
55 | {-0.803582649718989942, -1.893195233972397139}, // face 17 |
56 | {-1.307747883455638156, -0.604647643711872080}, // face 18 |
57 | {-1.054751253523952054, 1.794075294689396615}, // face 19 |
58 | }; |
59 | |
60 | /** @brief icosahedron face centers in x/y/z on the unit sphere */ |
61 | static const Vec3d faceCenterPoint[NUM_ICOSA_FACES] = { |
62 | {0.2199307791404606, 0.6583691780274996, 0.7198475378926182}, // face 0 |
63 | {-0.2139234834501421, 0.1478171829550703, 0.9656017935214205}, // face 1 |
64 | {0.1092625278784797, -0.4811951572873210, 0.8697775121287253}, // face 2 |
65 | {0.7428567301586791, -0.3593941678278028, 0.5648005936517033}, // face 3 |
66 | {0.8112534709140969, 0.3448953237639384, 0.4721387736413930}, // face 4 |
67 | {-0.1055498149613921, 0.9794457296411413, 0.1718874610009365}, // face 5 |
68 | {-0.8075407579970092, 0.1533552485898818, 0.5695261994882688}, // face 6 |
69 | {-0.2846148069787907, -0.8644080972654206, 0.4144792552473539}, // face 7 |
70 | {0.7405621473854482, -0.6673299564565524, -0.0789837646326737}, // face 8 |
71 | {0.8512303986474293, 0.4722343788582681, -0.2289137388687808}, // face 9 |
72 | {-0.7405621473854481, 0.6673299564565524, 0.0789837646326737}, // face 10 |
73 | {-0.8512303986474292, -0.4722343788582682, 0.2289137388687808}, // face 11 |
74 | {0.1055498149613919, -0.9794457296411413, -0.1718874610009365}, // face 12 |
75 | {0.8075407579970092, -0.1533552485898819, -0.5695261994882688}, // face 13 |
76 | {0.2846148069787908, 0.8644080972654204, -0.4144792552473539}, // face 14 |
77 | {-0.7428567301586791, 0.3593941678278027, -0.5648005936517033}, // face 15 |
78 | {-0.8112534709140971, -0.3448953237639382, -0.4721387736413930}, // face 16 |
79 | {-0.2199307791404607, -0.6583691780274996, -0.7198475378926182}, // face 17 |
80 | {0.2139234834501420, -0.1478171829550704, -0.9656017935214205}, // face 18 |
81 | {-0.1092625278784796, 0.4811951572873210, -0.8697775121287253}, // face 19 |
82 | }; |
83 | |
84 | /** @brief icosahedron face ijk axes as azimuth in radians from face center to |
85 | * vertex 0/1/2 respectively |
86 | */ |
87 | static const double faceAxesAzRadsCII[NUM_ICOSA_FACES][3] = { |
88 | {5.619958268523939882, 3.525563166130744542, |
89 | 1.431168063737548730}, // face 0 |
90 | {5.760339081714187279, 3.665943979320991689, |
91 | 1.571548876927796127}, // face 1 |
92 | {0.780213654393430055, 4.969003859179821079, |
93 | 2.874608756786625655}, // face 2 |
94 | {0.430469363979999913, 4.619259568766391033, |
95 | 2.524864466373195467}, // face 3 |
96 | {6.130269123335111400, 4.035874020941915804, |
97 | 1.941478918548720291}, // face 4 |
98 | {2.692877706530642877, 0.598482604137447119, |
99 | 4.787272808923838195}, // face 5 |
100 | {2.982963003477243874, 0.888567901084048369, |
101 | 5.077358105870439581}, // face 6 |
102 | {3.532912002790141181, 1.438516900396945656, |
103 | 5.627307105183336758}, // face 7 |
104 | {3.494305004259568154, 1.399909901866372864, |
105 | 5.588700106652763840}, // face 8 |
106 | {3.003214169499538391, 0.908819067106342928, |
107 | 5.097609271892733906}, // face 9 |
108 | {5.930472956509811562, 3.836077854116615875, |
109 | 1.741682751723420374}, // face 10 |
110 | {0.138378484090254847, 4.327168688876645809, |
111 | 2.232773586483450311}, // face 11 |
112 | {0.448714947059150361, 4.637505151845541521, |
113 | 2.543110049452346120}, // face 12 |
114 | {0.158629650112549365, 4.347419854898940135, |
115 | 2.253024752505744869}, // face 13 |
116 | {5.891865957979238535, 3.797470855586042958, |
117 | 1.703075753192847583}, // face 14 |
118 | {2.711123289609793325, 0.616728187216597771, |
119 | 4.805518392002988683}, // face 15 |
120 | {3.294508837434268316, 1.200113735041072948, |
121 | 5.388903939827463911}, // face 16 |
122 | {3.804819692245439833, 1.710424589852244509, |
123 | 5.899214794638635174}, // face 17 |
124 | {3.664438879055192436, 1.570043776661997111, |
125 | 5.758833981448388027}, // face 18 |
126 | {2.361378999196363184, 0.266983896803167583, |
127 | 4.455774101589558636}, // face 19 |
128 | }; |
129 | |
130 | /** @brief Definition of which faces neighbor each other. */ |
131 | static const FaceOrientIJK faceNeighbors[NUM_ICOSA_FACES][4] = { |
132 | { |
133 | // face 0 |
134 | {0, {0, 0, 0}, 0}, // central face |
135 | {4, {2, 0, 2}, 1}, // ij quadrant |
136 | {1, {2, 2, 0}, 5}, // ki quadrant |
137 | {5, {0, 2, 2}, 3} // jk quadrant |
138 | }, |
139 | { |
140 | // face 1 |
141 | {1, {0, 0, 0}, 0}, // central face |
142 | {0, {2, 0, 2}, 1}, // ij quadrant |
143 | {2, {2, 2, 0}, 5}, // ki quadrant |
144 | {6, {0, 2, 2}, 3} // jk quadrant |
145 | }, |
146 | { |
147 | // face 2 |
148 | {2, {0, 0, 0}, 0}, // central face |
149 | {1, {2, 0, 2}, 1}, // ij quadrant |
150 | {3, {2, 2, 0}, 5}, // ki quadrant |
151 | {7, {0, 2, 2}, 3} // jk quadrant |
152 | }, |
153 | { |
154 | // face 3 |
155 | {3, {0, 0, 0}, 0}, // central face |
156 | {2, {2, 0, 2}, 1}, // ij quadrant |
157 | {4, {2, 2, 0}, 5}, // ki quadrant |
158 | {8, {0, 2, 2}, 3} // jk quadrant |
159 | }, |
160 | { |
161 | // face 4 |
162 | {4, {0, 0, 0}, 0}, // central face |
163 | {3, {2, 0, 2}, 1}, // ij quadrant |
164 | {0, {2, 2, 0}, 5}, // ki quadrant |
165 | {9, {0, 2, 2}, 3} // jk quadrant |
166 | }, |
167 | { |
168 | // face 5 |
169 | {5, {0, 0, 0}, 0}, // central face |
170 | {10, {2, 2, 0}, 3}, // ij quadrant |
171 | {14, {2, 0, 2}, 3}, // ki quadrant |
172 | {0, {0, 2, 2}, 3} // jk quadrant |
173 | }, |
174 | { |
175 | // face 6 |
176 | {6, {0, 0, 0}, 0}, // central face |
177 | {11, {2, 2, 0}, 3}, // ij quadrant |
178 | {10, {2, 0, 2}, 3}, // ki quadrant |
179 | {1, {0, 2, 2}, 3} // jk quadrant |
180 | }, |
181 | { |
182 | // face 7 |
183 | {7, {0, 0, 0}, 0}, // central face |
184 | {12, {2, 2, 0}, 3}, // ij quadrant |
185 | {11, {2, 0, 2}, 3}, // ki quadrant |
186 | {2, {0, 2, 2}, 3} // jk quadrant |
187 | }, |
188 | { |
189 | // face 8 |
190 | {8, {0, 0, 0}, 0}, // central face |
191 | {13, {2, 2, 0}, 3}, // ij quadrant |
192 | {12, {2, 0, 2}, 3}, // ki quadrant |
193 | {3, {0, 2, 2}, 3} // jk quadrant |
194 | }, |
195 | { |
196 | // face 9 |
197 | {9, {0, 0, 0}, 0}, // central face |
198 | {14, {2, 2, 0}, 3}, // ij quadrant |
199 | {13, {2, 0, 2}, 3}, // ki quadrant |
200 | {4, {0, 2, 2}, 3} // jk quadrant |
201 | }, |
202 | { |
203 | // face 10 |
204 | {10, {0, 0, 0}, 0}, // central face |
205 | {5, {2, 2, 0}, 3}, // ij quadrant |
206 | {6, {2, 0, 2}, 3}, // ki quadrant |
207 | {15, {0, 2, 2}, 3} // jk quadrant |
208 | }, |
209 | { |
210 | // face 11 |
211 | {11, {0, 0, 0}, 0}, // central face |
212 | {6, {2, 2, 0}, 3}, // ij quadrant |
213 | {7, {2, 0, 2}, 3}, // ki quadrant |
214 | {16, {0, 2, 2}, 3} // jk quadrant |
215 | }, |
216 | { |
217 | // face 12 |
218 | {12, {0, 0, 0}, 0}, // central face |
219 | {7, {2, 2, 0}, 3}, // ij quadrant |
220 | {8, {2, 0, 2}, 3}, // ki quadrant |
221 | {17, {0, 2, 2}, 3} // jk quadrant |
222 | }, |
223 | { |
224 | // face 13 |
225 | {13, {0, 0, 0}, 0}, // central face |
226 | {8, {2, 2, 0}, 3}, // ij quadrant |
227 | {9, {2, 0, 2}, 3}, // ki quadrant |
228 | {18, {0, 2, 2}, 3} // jk quadrant |
229 | }, |
230 | { |
231 | // face 14 |
232 | {14, {0, 0, 0}, 0}, // central face |
233 | {9, {2, 2, 0}, 3}, // ij quadrant |
234 | {5, {2, 0, 2}, 3}, // ki quadrant |
235 | {19, {0, 2, 2}, 3} // jk quadrant |
236 | }, |
237 | { |
238 | // face 15 |
239 | {15, {0, 0, 0}, 0}, // central face |
240 | {16, {2, 0, 2}, 1}, // ij quadrant |
241 | {19, {2, 2, 0}, 5}, // ki quadrant |
242 | {10, {0, 2, 2}, 3} // jk quadrant |
243 | }, |
244 | { |
245 | // face 16 |
246 | {16, {0, 0, 0}, 0}, // central face |
247 | {17, {2, 0, 2}, 1}, // ij quadrant |
248 | {15, {2, 2, 0}, 5}, // ki quadrant |
249 | {11, {0, 2, 2}, 3} // jk quadrant |
250 | }, |
251 | { |
252 | // face 17 |
253 | {17, {0, 0, 0}, 0}, // central face |
254 | {18, {2, 0, 2}, 1}, // ij quadrant |
255 | {16, {2, 2, 0}, 5}, // ki quadrant |
256 | {12, {0, 2, 2}, 3} // jk quadrant |
257 | }, |
258 | { |
259 | // face 18 |
260 | {18, {0, 0, 0}, 0}, // central face |
261 | {19, {2, 0, 2}, 1}, // ij quadrant |
262 | {17, {2, 2, 0}, 5}, // ki quadrant |
263 | {13, {0, 2, 2}, 3} // jk quadrant |
264 | }, |
265 | { |
266 | // face 19 |
267 | {19, {0, 0, 0}, 0}, // central face |
268 | {15, {2, 0, 2}, 1}, // ij quadrant |
269 | {18, {2, 2, 0}, 5}, // ki quadrant |
270 | {14, {0, 2, 2}, 3} // jk quadrant |
271 | }}; |
272 | |
273 | /** @brief direction from the origin face to the destination face, relative to |
274 | * the origin face's coordinate system, or -1 if not adjacent. |
275 | */ |
276 | static const int adjacentFaceDir[NUM_ICOSA_FACES][NUM_ICOSA_FACES] = { |
277 | {0, KI, -1, -1, IJ, JK, -1, -1, -1, -1, |
278 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 0 |
279 | {IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, |
280 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 1 |
281 | {-1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, |
282 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 2 |
283 | {-1, -1, IJ, 0, KI, -1, -1, -1, JK, -1, |
284 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 3 |
285 | {KI, -1, -1, IJ, 0, -1, -1, -1, -1, JK, |
286 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 4 |
287 | {JK, -1, -1, -1, -1, 0, -1, -1, -1, -1, |
288 | IJ, -1, -1, -1, KI, -1, -1, -1, -1, -1}, // face 5 |
289 | {-1, JK, -1, -1, -1, -1, 0, -1, -1, -1, |
290 | KI, IJ, -1, -1, -1, -1, -1, -1, -1, -1}, // face 6 |
291 | {-1, -1, JK, -1, -1, -1, -1, 0, -1, -1, |
292 | -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1}, // face 7 |
293 | {-1, -1, -1, JK, -1, -1, -1, -1, 0, -1, |
294 | -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1}, // face 8 |
295 | {-1, -1, -1, -1, JK, -1, -1, -1, -1, 0, |
296 | -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1}, // face 9 |
297 | {-1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, |
298 | 0, -1, -1, -1, -1, JK, -1, -1, -1, -1}, // face 10 |
299 | {-1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, |
300 | -1, 0, -1, -1, -1, -1, JK, -1, -1, -1}, // face 11 |
301 | {-1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, |
302 | -1, -1, 0, -1, -1, -1, -1, JK, -1, -1}, // face 12 |
303 | {-1, -1, -1, -1, -1, -1, -1, -1, IJ, KI, |
304 | -1, -1, -1, 0, -1, -1, -1, -1, JK, -1}, // face 13 |
305 | {-1, -1, -1, -1, -1, KI, -1, -1, -1, IJ, |
306 | -1, -1, -1, -1, 0, -1, -1, -1, -1, JK}, // face 14 |
307 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
308 | JK, -1, -1, -1, -1, 0, IJ, -1, -1, KI}, // face 15 |
309 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
310 | -1, JK, -1, -1, -1, KI, 0, IJ, -1, -1}, // face 16 |
311 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
312 | -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1}, // face 17 |
313 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
314 | -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ}, // face 18 |
315 | {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
316 | -1, -1, -1, -1, JK, IJ, -1, -1, KI, 0} // face 19 |
317 | }; |
318 | |
319 | /** @brief overage distance table */ |
320 | static const int maxDimByCIIres[] = { |
321 | 2, // res 0 |
322 | -1, // res 1 |
323 | 14, // res 2 |
324 | -1, // res 3 |
325 | 98, // res 4 |
326 | -1, // res 5 |
327 | 686, // res 6 |
328 | -1, // res 7 |
329 | 4802, // res 8 |
330 | -1, // res 9 |
331 | 33614, // res 10 |
332 | -1, // res 11 |
333 | 235298, // res 12 |
334 | -1, // res 13 |
335 | 1647086, // res 14 |
336 | -1, // res 15 |
337 | 11529602 // res 16 |
338 | }; |
339 | |
340 | /** @brief unit scale distance table */ |
341 | static const int unitScaleByCIIres[] = { |
342 | 1, // res 0 |
343 | -1, // res 1 |
344 | 7, // res 2 |
345 | -1, // res 3 |
346 | 49, // res 4 |
347 | -1, // res 5 |
348 | 343, // res 6 |
349 | -1, // res 7 |
350 | 2401, // res 8 |
351 | -1, // res 9 |
352 | 16807, // res 10 |
353 | -1, // res 11 |
354 | 117649, // res 12 |
355 | -1, // res 13 |
356 | 823543, // res 14 |
357 | -1, // res 15 |
358 | 5764801 // res 16 |
359 | }; |
360 | |
361 | /** |
362 | * Encodes a coordinate on the sphere to the FaceIJK address of the containing |
363 | * cell at the specified resolution. |
364 | * |
365 | * @param g The spherical coordinates to encode. |
366 | * @param res The desired H3 resolution for the encoding. |
367 | * @param h The FaceIJK address of the containing cell at resolution res. |
368 | */ |
369 | void _geoToFaceIjk(const GeoCoord* g, int res, FaceIJK* h) { |
370 | // first convert to hex2d |
371 | Vec2d v; |
372 | _geoToHex2d(g, res, &h->face, &v); |
373 | |
374 | // then convert to ijk+ |
375 | _hex2dToCoordIJK(&v, &h->coord); |
376 | } |
377 | |
378 | /** |
379 | * Encodes a coordinate on the sphere to the corresponding icosahedral face and |
380 | * containing 2D hex coordinates relative to that face center. |
381 | * |
382 | * @param g The spherical coordinates to encode. |
383 | * @param res The desired H3 resolution for the encoding. |
384 | * @param face The icosahedral face containing the spherical coordinates. |
385 | * @param v The 2D hex coordinates of the cell containing the point. |
386 | */ |
387 | void _geoToHex2d(const GeoCoord* g, int res, int* face, Vec2d* v) { |
388 | Vec3d v3d; |
389 | _geoToVec3d(g, &v3d); |
390 | |
391 | // determine the icosahedron face |
392 | *face = 0; |
393 | double sqd = _pointSquareDist(&faceCenterPoint[0], &v3d); |
394 | for (int f = 1; f < NUM_ICOSA_FACES; f++) { |
395 | double sqdT = _pointSquareDist(&faceCenterPoint[f], &v3d); |
396 | if (sqdT < sqd) { |
397 | *face = f; |
398 | sqd = sqdT; |
399 | } |
400 | } |
401 | |
402 | // cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2 |
403 | double r = acos(1 - sqd / 2); |
404 | |
405 | if (r < EPSILON) { |
406 | v->x = v->y = 0.0L; |
407 | return; |
408 | } |
409 | |
410 | // now have face and r, now find CCW theta from CII i-axis |
411 | double theta = |
412 | _posAngleRads(faceAxesAzRadsCII[*face][0] - |
413 | _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g))); |
414 | |
415 | // adjust theta for Class III (odd resolutions) |
416 | if (isResClassIII(res)) theta = _posAngleRads(theta - M_AP7_ROT_RADS); |
417 | |
418 | // perform gnomonic scaling of r |
419 | r = tan(r); |
420 | |
421 | // scale for current resolution length u |
422 | r /= RES0_U_GNOMONIC; |
423 | for (int i = 0; i < res; i++) r *= M_SQRT7; |
424 | |
425 | // we now have (r, theta) in hex2d with theta ccw from x-axes |
426 | |
427 | // convert to local x,y |
428 | v->x = r * cos(theta); |
429 | v->y = r * sin(theta); |
430 | } |
431 | |
432 | /** |
433 | * Determines the center point in spherical coordinates of a cell given by 2D |
434 | * hex coordinates on a particular icosahedral face. |
435 | * |
436 | * @param v The 2D hex coordinates of the cell. |
437 | * @param face The icosahedral face upon which the 2D hex coordinate system is |
438 | * centered. |
439 | * @param res The H3 resolution of the cell. |
440 | * @param substrate Indicates whether or not this grid is actually a substrate |
441 | * grid relative to the specified resolution. |
442 | * @param g The spherical coordinates of the cell center point. |
443 | */ |
444 | void _hex2dToGeo(const Vec2d* v, int face, int res, int substrate, |
445 | GeoCoord* g) { |
446 | // calculate (r, theta) in hex2d |
447 | double r = _v2dMag(v); |
448 | |
449 | if (r < EPSILON) { |
450 | *g = faceCenterGeo[face]; |
451 | return; |
452 | } |
453 | |
454 | double theta = atan2(v->y, v->x); |
455 | |
456 | // scale for current resolution length u |
457 | for (int i = 0; i < res; i++) r /= M_SQRT7; |
458 | |
459 | // scale accordingly if this is a substrate grid |
460 | if (substrate) { |
461 | r /= 3.0; |
462 | if (isResClassIII(res)) r /= M_SQRT7; |
463 | } |
464 | |
465 | r *= RES0_U_GNOMONIC; |
466 | |
467 | // perform inverse gnomonic scaling of r |
468 | r = atan(r); |
469 | |
470 | // adjust theta for Class III |
471 | // if a substrate grid, then it's already been adjusted for Class III |
472 | if (!substrate && isResClassIII(res)) |
473 | theta = _posAngleRads(theta + M_AP7_ROT_RADS); |
474 | |
475 | // find theta as an azimuth |
476 | theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta); |
477 | |
478 | // now find the point at (r,theta) from the face center |
479 | _geoAzDistanceRads(&faceCenterGeo[face], theta, r, g); |
480 | } |
481 | |
482 | /** |
483 | * Determines the center point in spherical coordinates of a cell given by |
484 | * a FaceIJK address at a specified resolution. |
485 | * |
486 | * @param h The FaceIJK address of the cell. |
487 | * @param res The H3 resolution of the cell. |
488 | * @param g The spherical coordinates of the cell center point. |
489 | */ |
490 | void _faceIjkToGeo(const FaceIJK* h, int res, GeoCoord* g) { |
491 | Vec2d v; |
492 | _ijkToHex2d(&h->coord, &v); |
493 | _hex2dToGeo(&v, h->face, res, 0, g); |
494 | } |
495 | |
496 | /** |
497 | * Generates the cell boundary in spherical coordinates for a pentagonal cell |
498 | * given by a FaceIJK address at a specified resolution. |
499 | * |
500 | * @param h The FaceIJK address of the pentagonal cell. |
501 | * @param res The H3 resolution of the cell. |
502 | * @param g The spherical coordinates of the cell boundary. |
503 | */ |
504 | void _faceIjkPentToGeoBoundary(const FaceIJK* h, int res, GeoBoundary* g) { |
505 | // the vertexes of an origin-centered pentagon in a Class II resolution on a |
506 | // substrate grid with aperture sequence 33r. The aperture 3 gets us the |
507 | // vertices, and the 3r gets us back to Class II. |
508 | // vertices listed ccw from the i-axes |
509 | CoordIJK vertsCII[NUM_PENT_VERTS] = { |
510 | {2, 1, 0}, // 0 |
511 | {1, 2, 0}, // 1 |
512 | {0, 2, 1}, // 2 |
513 | {0, 1, 2}, // 3 |
514 | {1, 0, 2}, // 4 |
515 | }; |
516 | |
517 | // the vertexes of an origin-centered pentagon in a Class III resolution on |
518 | // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the |
519 | // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the |
520 | // i-axes |
521 | CoordIJK vertsCIII[NUM_PENT_VERTS] = { |
522 | {5, 4, 0}, // 0 |
523 | {1, 5, 0}, // 1 |
524 | {0, 5, 4}, // 2 |
525 | {0, 1, 5}, // 3 |
526 | {4, 0, 5}, // 4 |
527 | }; |
528 | |
529 | // get the correct set of substrate vertices for this resolution |
530 | CoordIJK* verts; |
531 | if (isResClassIII(res)) |
532 | verts = vertsCIII; |
533 | else |
534 | verts = vertsCII; |
535 | |
536 | // adjust the center point to be in an aperture 33r substrate grid |
537 | // these should be composed for speed |
538 | FaceIJK centerIJK = *h; |
539 | _downAp3(¢erIJK.coord); |
540 | _downAp3r(¢erIJK.coord); |
541 | |
542 | // if res is Class III we need to add a cw aperture 7 to get to |
543 | // icosahedral Class II |
544 | int adjRes = res; |
545 | if (isResClassIII(res)) { |
546 | _downAp7r(¢erIJK.coord); |
547 | adjRes++; |
548 | } |
549 | |
550 | // The center point is now in the same substrate grid as the origin |
551 | // cell vertices. Add the center point substate coordinates |
552 | // to each vertex to translate the vertices to that cell. |
553 | FaceIJK fijkVerts[NUM_PENT_VERTS]; |
554 | for (int v = 0; v < NUM_PENT_VERTS; v++) { |
555 | fijkVerts[v].face = centerIJK.face; |
556 | _ijkAdd(¢erIJK.coord, &verts[v], &fijkVerts[v].coord); |
557 | _ijkNormalize(&fijkVerts[v].coord); |
558 | } |
559 | |
560 | // convert each vertex to lat/lon |
561 | // adjust the face of each vertex as appropriate and introduce |
562 | // edge-crossing vertices as needed |
563 | g->numVerts = 0; |
564 | FaceIJK lastFijk; |
565 | for (int vert = 0; vert < NUM_PENT_VERTS + 1; vert++) { |
566 | int v = vert % NUM_PENT_VERTS; |
567 | |
568 | FaceIJK fijk = fijkVerts[v]; |
569 | |
570 | int pentLeading4 = 0; |
571 | int overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); |
572 | if (overage == 2) // in a different triangle |
573 | { |
574 | while (1) { |
575 | overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); |
576 | if (overage != 2) // not in a different triangle |
577 | break; |
578 | } |
579 | } |
580 | |
581 | // all Class III pentagon edges cross icosa edges |
582 | // note that Class II pentagons have vertices on the edge, |
583 | // not edge intersections |
584 | if (isResClassIII(res) && vert > 0) { |
585 | // find hex2d of the two vertexes on the last face |
586 | |
587 | FaceIJK tmpFijk = fijk; |
588 | |
589 | Vec2d orig2d0; |
590 | _ijkToHex2d(&lastFijk.coord, &orig2d0); |
591 | |
592 | int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; |
593 | |
594 | const FaceOrientIJK* fijkOrient = |
595 | &faceNeighbors[tmpFijk.face][currentToLastDir]; |
596 | |
597 | tmpFijk.face = fijkOrient->face; |
598 | CoordIJK* ijk = &tmpFijk.coord; |
599 | |
600 | // rotate and translate for adjacent face |
601 | for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); |
602 | |
603 | CoordIJK transVec = fijkOrient->translate; |
604 | _ijkScale(&transVec, unitScaleByCIIres[adjRes] * 3); |
605 | _ijkAdd(ijk, &transVec, ijk); |
606 | _ijkNormalize(ijk); |
607 | |
608 | Vec2d orig2d1; |
609 | _ijkToHex2d(ijk, &orig2d1); |
610 | |
611 | // find the appropriate icosa face edge vertexes |
612 | int maxDim = maxDimByCIIres[adjRes]; |
613 | Vec2d v0 = {3.0 * maxDim, 0.0}; |
614 | Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; |
615 | Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; |
616 | |
617 | Vec2d* edge0; |
618 | Vec2d* edge1; |
619 | switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { |
620 | case IJ: |
621 | edge0 = &v0; |
622 | edge1 = &v1; |
623 | break; |
624 | case JK: |
625 | edge0 = &v1; |
626 | edge1 = &v2; |
627 | break; |
628 | case KI: |
629 | default: |
630 | assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI); |
631 | edge0 = &v2; |
632 | edge1 = &v0; |
633 | break; |
634 | } |
635 | |
636 | // find the intersection and add the lat/lon point to the result |
637 | Vec2d inter; |
638 | _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); |
639 | _hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, |
640 | &g->verts[g->numVerts]); |
641 | g->numVerts++; |
642 | } |
643 | |
644 | // convert vertex to lat/lon and add to the result |
645 | // vert == NUM_PENT_VERTS is only used to test for possible intersection |
646 | // on last edge |
647 | if (vert < NUM_PENT_VERTS) { |
648 | Vec2d vec; |
649 | _ijkToHex2d(&fijk.coord, &vec); |
650 | _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); |
651 | g->numVerts++; |
652 | } |
653 | |
654 | lastFijk = fijk; |
655 | } |
656 | } |
657 | |
658 | /** |
659 | * Generates the cell boundary in spherical coordinates for a cell given by a |
660 | * FaceIJK address at a specified resolution. |
661 | * |
662 | * @param h The FaceIJK address of the cell. |
663 | * @param res The H3 resolution of the cell. |
664 | * @param isPentagon Whether or not the cell is a pentagon. |
665 | * @param g The spherical coordinates of the cell boundary. |
666 | */ |
667 | void _faceIjkToGeoBoundary(const FaceIJK* h, int res, int isPentagon, |
668 | GeoBoundary* g) { |
669 | if (isPentagon) { |
670 | _faceIjkPentToGeoBoundary(h, res, g); |
671 | return; |
672 | } |
673 | |
674 | // the vertexes of an origin-centered cell in a Class II resolution on a |
675 | // substrate grid with aperture sequence 33r. The aperture 3 gets us the |
676 | // vertices, and the 3r gets us back to Class II. |
677 | // vertices listed ccw from the i-axes |
678 | CoordIJK vertsCII[NUM_HEX_VERTS] = { |
679 | {2, 1, 0}, // 0 |
680 | {1, 2, 0}, // 1 |
681 | {0, 2, 1}, // 2 |
682 | {0, 1, 2}, // 3 |
683 | {1, 0, 2}, // 4 |
684 | {2, 0, 1} // 5 |
685 | }; |
686 | |
687 | // the vertexes of an origin-centered cell in a Class III resolution on a |
688 | // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the |
689 | // vertices, and the 3r7r gets us to Class II. |
690 | // vertices listed ccw from the i-axes |
691 | CoordIJK vertsCIII[NUM_HEX_VERTS] = { |
692 | {5, 4, 0}, // 0 |
693 | {1, 5, 0}, // 1 |
694 | {0, 5, 4}, // 2 |
695 | {0, 1, 5}, // 3 |
696 | {4, 0, 5}, // 4 |
697 | {5, 0, 1} // 5 |
698 | }; |
699 | |
700 | // get the correct set of substrate vertices for this resolution |
701 | CoordIJK* verts; |
702 | if (isResClassIII(res)) |
703 | verts = vertsCIII; |
704 | else |
705 | verts = vertsCII; |
706 | |
707 | // adjust the center point to be in an aperture 33r substrate grid |
708 | // these should be composed for speed |
709 | FaceIJK centerIJK = *h; |
710 | _downAp3(¢erIJK.coord); |
711 | _downAp3r(¢erIJK.coord); |
712 | |
713 | // if res is Class III we need to add a cw aperture 7 to get to |
714 | // icosahedral Class II |
715 | int adjRes = res; |
716 | if (isResClassIII(res)) { |
717 | _downAp7r(¢erIJK.coord); |
718 | adjRes++; |
719 | } |
720 | |
721 | // The center point is now in the same substrate grid as the origin |
722 | // cell vertices. Add the center point substate coordinates |
723 | // to each vertex to translate the vertices to that cell. |
724 | FaceIJK fijkVerts[NUM_HEX_VERTS]; |
725 | for (int v = 0; v < NUM_HEX_VERTS; v++) { |
726 | fijkVerts[v].face = centerIJK.face; |
727 | _ijkAdd(¢erIJK.coord, &verts[v], &fijkVerts[v].coord); |
728 | _ijkNormalize(&fijkVerts[v].coord); |
729 | } |
730 | |
731 | // convert each vertex to lat/lon |
732 | // adjust the face of each vertex as appropriate and introduce |
733 | // edge-crossing vertices as needed |
734 | g->numVerts = 0; |
735 | int lastFace = -1; |
736 | int lastOverage = 0; // 0: none; 1: edge; 2: overage |
737 | for (int vert = 0; vert < NUM_HEX_VERTS + 1; vert++) { |
738 | int v = vert % NUM_HEX_VERTS; |
739 | |
740 | FaceIJK fijk = fijkVerts[v]; |
741 | |
742 | int pentLeading4 = 0; |
743 | int overage = _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1); |
744 | |
745 | /* |
746 | Check for edge-crossing. Each face of the underlying icosahedron is a |
747 | different projection plane. So if an edge of the hexagon crosses an |
748 | icosahedron edge, an additional vertex must be introduced at that |
749 | intersection point. Then each half of the cell edge can be projected |
750 | to geographic coordinates using the appropriate icosahedron face |
751 | projection. Note that Class II cell edges have vertices on the face |
752 | edge, with no edge line intersections. |
753 | */ |
754 | if (isResClassIII(res) && vert > 0 && fijk.face != lastFace && |
755 | lastOverage != 1) { |
756 | // find hex2d of the two vertexes on original face |
757 | int lastV = (v + 5) % NUM_HEX_VERTS; |
758 | Vec2d orig2d0; |
759 | _ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0); |
760 | |
761 | Vec2d orig2d1; |
762 | _ijkToHex2d(&fijkVerts[v].coord, &orig2d1); |
763 | |
764 | // find the appropriate icosa face edge vertexes |
765 | int maxDim = maxDimByCIIres[adjRes]; |
766 | Vec2d v0 = {3.0 * maxDim, 0.0}; |
767 | Vec2d v1 = {-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}; |
768 | Vec2d v2 = {-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}; |
769 | |
770 | int face2 = ((lastFace == centerIJK.face) ? fijk.face : lastFace); |
771 | Vec2d* edge0; |
772 | Vec2d* edge1; |
773 | switch (adjacentFaceDir[centerIJK.face][face2]) { |
774 | case IJ: |
775 | edge0 = &v0; |
776 | edge1 = &v1; |
777 | break; |
778 | case JK: |
779 | edge0 = &v1; |
780 | edge1 = &v2; |
781 | break; |
782 | case KI: |
783 | default: |
784 | assert(adjacentFaceDir[centerIJK.face][face2] == KI); |
785 | edge0 = &v2; |
786 | edge1 = &v0; |
787 | break; |
788 | } |
789 | |
790 | // find the intersection and add the lat/lon point to the result |
791 | Vec2d inter; |
792 | _v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter); |
793 | /* |
794 | If a point of intersection occurs at a hexagon vertex, then each |
795 | adjacent hexagon edge will lie completely on a single icosahedron |
796 | face, and no additional vertex is required. |
797 | */ |
798 | bool isIntersectionAtVertex = |
799 | _v2dEquals(&orig2d0, &inter) || _v2dEquals(&orig2d1, &inter); |
800 | if (!isIntersectionAtVertex) { |
801 | _hex2dToGeo(&inter, centerIJK.face, adjRes, 1, |
802 | &g->verts[g->numVerts]); |
803 | g->numVerts++; |
804 | } |
805 | } |
806 | |
807 | // convert vertex to lat/lon and add to the result |
808 | // vert == NUM_HEX_VERTS is only used to test for possible intersection |
809 | // on last edge |
810 | if (vert < NUM_HEX_VERTS) { |
811 | Vec2d vec; |
812 | _ijkToHex2d(&fijk.coord, &vec); |
813 | _hex2dToGeo(&vec, fijk.face, adjRes, 1, &g->verts[g->numVerts]); |
814 | g->numVerts++; |
815 | } |
816 | |
817 | lastFace = fijk.face; |
818 | lastOverage = overage; |
819 | } |
820 | } |
821 | |
822 | /** |
823 | * Adjusts a FaceIJK address in place so that the resulting cell address is |
824 | * relative to the correct icosahedral face. |
825 | * |
826 | * @param fijk The FaceIJK address of the cell. |
827 | * @param res The H3 resolution of the cell. |
828 | * @param pentLeading4 Whether or not the cell is a pentagon with a leading |
829 | * digit 4. |
830 | * @param substrate Whether or not the cell is in a substrate grid. |
831 | * @return 0 if on original face (no overage); 1 if on face edge (only occurs |
832 | * on substrate grids); 2 if overage on new face interior |
833 | */ |
834 | int _adjustOverageClassII(FaceIJK* fijk, int res, int pentLeading4, |
835 | int substrate) { |
836 | int overage = 0; |
837 | |
838 | CoordIJK* ijk = &fijk->coord; |
839 | |
840 | // get the maximum dimension value; scale if a substrate grid |
841 | int maxDim = maxDimByCIIres[res]; |
842 | if (substrate) maxDim *= 3; |
843 | |
844 | // check for overage |
845 | if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge |
846 | overage = 1; |
847 | else if (ijk->i + ijk->j + ijk->k > maxDim) // overage |
848 | { |
849 | overage = 2; |
850 | |
851 | const FaceOrientIJK* fijkOrient; |
852 | if (ijk->k > 0) { |
853 | if (ijk->j > 0) // jk "quadrant" |
854 | fijkOrient = &faceNeighbors[fijk->face][JK]; |
855 | else // ik "quadrant" |
856 | { |
857 | fijkOrient = &faceNeighbors[fijk->face][KI]; |
858 | |
859 | // adjust for the pentagonal missing sequence |
860 | if (pentLeading4) { |
861 | // translate origin to center of pentagon |
862 | CoordIJK origin; |
863 | _setIJK(&origin, maxDim, 0, 0); |
864 | CoordIJK tmp; |
865 | _ijkSub(ijk, &origin, &tmp); |
866 | // rotate to adjust for the missing sequence |
867 | _ijkRotate60cw(&tmp); |
868 | // translate the origin back to the center of the triangle |
869 | _ijkAdd(&tmp, &origin, ijk); |
870 | } |
871 | } |
872 | } else // ij "quadrant" |
873 | fijkOrient = &faceNeighbors[fijk->face][IJ]; |
874 | |
875 | fijk->face = fijkOrient->face; |
876 | |
877 | // rotate and translate for adjacent face |
878 | for (int i = 0; i < fijkOrient->ccwRot60; i++) _ijkRotate60ccw(ijk); |
879 | |
880 | CoordIJK transVec = fijkOrient->translate; |
881 | int unitScale = unitScaleByCIIres[res]; |
882 | if (substrate) unitScale *= 3; |
883 | _ijkScale(&transVec, unitScale); |
884 | _ijkAdd(ijk, &transVec, ijk); |
885 | _ijkNormalize(ijk); |
886 | |
887 | // overage points on pentagon boundaries can end up on edges |
888 | if (substrate && ijk->i + ijk->j + ijk->k == maxDim) // on edge |
889 | overage = 1; |
890 | } |
891 | |
892 | return overage; |
893 | } |
894 | |