YetAnotherCoupler 3.2.0_a
Loading...
Searching...
No Matches
area.c
Go to the documentation of this file.
1// Copyright (c) 2024 The YAC Authors
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5#include <stdlib.h>
6#include <stdio.h>
7#include <math.h>
8#include <float.h>
9
10#include "area.h"
11#include "basic_grid.h"
12#include "clipping.h"
13#include "geometry.h"
14#include "utils_core.h"
15#include "ensure_array_size.h"
16
17static inline double scalar_product(double a[], double b[]);
18
19/* ----------------------------------- */
20
21double yac_triangle_area ( struct yac_grid_cell cell ) {
22
23 /* taken from the ICON code, mo_base_geometry.f90
24 provided by Luis Kornblueh, MPI-M. */
25
26 const double tol = 1e-18;
27
28 double s01, s12, s20;
29 double ca1, ca2, ca3;
30 double a1, a2, a3;
31
32 double * triangle[3];
33 double u01[3], u12[3], u20[3];
34
36 cell.num_corners == 3, "ERROR(yac_triangle_area): cell is not a triangle")
37
38 triangle[0] = cell.coordinates_xyz[0];
39 triangle[1] = cell.coordinates_xyz[1];
40 triangle[2] = cell.coordinates_xyz[2];
41
42 /* First, compute cross products Uij = Vi x Vj. */
43
44 crossproduct_kahan(triangle[0], triangle[1], u01);
45 crossproduct_kahan(triangle[1], triangle[2], u12);
46 crossproduct_kahan(triangle[2], triangle[0], u20);
47
48 /* Normalize Uij to unit vectors. */
49
50 s01 = scalar_product(u01, u01);
51 s12 = scalar_product(u12, u12);
52 s20 = scalar_product(u20, u20);
53
54 /* Test for a degenerated triangle associated with collinear vertices. */
55
56 if ( fabs(s01) < tol ||
57 fabs(s12) < tol ||
58 fabs(s20) < tol )
59 return 0.0;
60
61 s01 = sqrt(s01);
62 s12 = sqrt(s12);
63 s20 = sqrt(s20);
64
65 for (int m = 0; m < 3; m++ ) {
66 u01[m] = u01[m]/s01;
67 u12[m] = u12[m]/s12;
68 u20[m] = u20[m]/s20;
69 }
70
71 /* Compute interior angles Ai as the dihedral angles between planes:
72
73 CA1 = cos(A1) = -<U01,U20>
74 CA2 = cos(A2) = -<U12,U01>
75 CA3 = cos(A3) = -<U20,U12>
76
77 */
78
79 ca1 = -u01[0]*u20[0]-u01[1]*u20[1]-u01[2]*u20[2];
80 ca2 = -u12[0]*u01[0]-u12[1]*u01[1]-u12[2]*u01[2];
81 ca3 = -u20[0]*u12[0]-u20[1]*u12[1]-u20[2]*u12[2];
82
83 if ( ca1 < -1.0 ) ca1 = -1.0;
84 if ( ca1 > 1.0 ) ca1 = 1.0;
85 if ( ca2 < -1.0 ) ca2 = -1.0;
86 if ( ca2 > 1.0 ) ca2 = 1.0;
87 if ( ca3 < -1.0 ) ca3 = -1.0;
88 if ( ca3 > 1.0 ) ca3 = 1.0;
89
90 a1 = acos(ca1);
91 a2 = acos(ca2);
92 a3 = acos(ca3);
93
94 /* Compute areas = a1 + a2 + a3 - pi.
95
96 here for a unit sphere: */
97
98 // return MAX(( a1+a2+a3-M_PI ) * YAC_EARTH_RADIUS * YAC_EARTH_RADIUS, 0.0);
99 return MAX( (a1+a2+a3-M_PI) , 0.0 );
100}
101
102/* ----------------------------------- */
103
104static inline struct sin_cos_angle
106
107 // the angle passed to this routine should be in the range [0;3*PI/4],
108 // in case the angle is outside this, we have to assume that this is due to
109 // numerical inaccuracy or
110 // tri_area was called for a too big triangle...has to be really big...
111
112 if (compare_angles(angle, SIN_COS_7_M_PI_4) >= 0) return SIN_COS_ZERO;
113 else return quarter_angle(angle);
114}
115
134static double tri_area_(
135 struct sin_cos_angle angle_a,
136 struct sin_cos_angle angle_b,
137 struct sin_cos_angle angle_c) {
138
139 if (compare_angles(angle_a, SIN_COS_LOW_TOL) < 0) return 0.0;
140 if (compare_angles(angle_b, SIN_COS_LOW_TOL) < 0) return 0.0;
141 if (compare_angles(angle_c, SIN_COS_LOW_TOL) < 0) return 0.0;
142
143 double sin_sin = angle_a.sin * angle_b.sin;
144 double sin_cos = angle_a.sin * angle_b.cos;
145 double cos_sin = angle_a.cos * angle_b.sin;
146 double cos_cos = angle_a.cos * angle_b.cos;
147
148 double sin_sin_sin = sin_sin * angle_c.sin;
149 double sin_sin_cos = sin_sin * angle_c.cos;
150 double sin_cos_sin = sin_cos * angle_c.sin;
151 double sin_cos_cos = sin_cos * angle_c.cos;
152 double cos_sin_sin = cos_sin * angle_c.sin;
153 double cos_sin_cos = cos_sin * angle_c.cos;
154 double cos_cos_sin = cos_cos * angle_c.sin;
155 double cos_cos_cos = cos_cos * angle_c.cos;
156
157 double t_sin_a = sin_sin_sin - sin_cos_cos;
158 double t_sin_b = cos_sin_cos + cos_cos_sin;
159 double t_sin_c = sin_sin_sin + sin_cos_cos;
160 double t_sin_d = cos_sin_cos - cos_cos_sin;
161 double t_cos_a = cos_cos_cos - cos_sin_sin;
162 double t_cos_b = sin_sin_cos + sin_cos_sin;
163 double t_cos_c = cos_cos_cos + cos_sin_sin;
164 double t_cos_d = sin_sin_cos - sin_cos_sin;
165
166 struct sin_cos_angle t_angle[4] = {
168 (struct sin_cos_angle){.sin = - t_sin_a + t_sin_b,
169 .cos = + t_cos_a - t_cos_b}),
171 (struct sin_cos_angle){.sin = + t_sin_a + t_sin_b,
172 .cos = + t_cos_a + t_cos_b}),
174 (struct sin_cos_angle){.sin = + t_sin_c - t_sin_d,
175 .cos = + t_cos_c + t_cos_d}),
177 (struct sin_cos_angle){.sin = + t_sin_c + t_sin_d,
178 .cos = + t_cos_c - t_cos_d})};
179
180 double t = (t_angle[0].sin*t_angle[1].sin*t_angle[2].sin*t_angle[3].sin) /
181 (t_angle[0].cos*t_angle[1].cos*t_angle[2].cos*t_angle[3].cos);
182
183 return 4.0 * atan(sqrt(fabs(t)));
184}
185
186static double tri_area(double u[3], double v[3], double w[3]) {
187
188 return
191 get_vector_angle_2(w,v));
192}
193
194/* ----------------------------------- */
195
196static inline int compute_norm_vector(double a[], double b[], double norm[]) {
197
198 crossproduct_kahan(a, b, norm);
199
200 double scale = sqrt(norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]);
201
202 if (scale <= yac_angle_tol) return 1;
203
204 scale = 1.0 / scale;
205
206 norm[0] *= scale;
207 norm[1] *= scale;
208 norm[2] *= scale;
209
210 return 0;
211}
212
213static double inline XYZtoLon(double a[3]) {
214 return atan2(a[1] , a[0]);
215}
216
217static double
218lat_edge_correction(double base_vec[3], double a[3], double b[3]) {
219
220 double const tol = 1e-8;
221
223 fabs(a[2]-b[2]) <= tol, "ERROR: latitude of both corners is not identical")
224
225 double h = fabs(a[2]);
226
227 // if we are at the equator or at a pole
228 if (h < tol || fabs(1.0 - h) < tol)
229 return 0.0;
230
231 double lat_area = fabs((1.0 - h) * get_angle(XYZtoLon(a), XYZtoLon(b)));
232
233 double pole[3] = {0, 0, (a[2] >= 0.0)?1.0:-1.0};
234 double gc_area = tri_area(a, b, pole);
235
236 double correction = MAX(lat_area - gc_area, 0.0);
237
238 double middle_lat[3] = {a[0]+b[0], a[1]+b[1], a[2]};
239 double scale = sqrt(middle_lat[0]*middle_lat[0]+middle_lat[1]*middle_lat[1]);
240
241 YAC_ASSERT(fabs(scale) >= 1e-18, "internal error")
242
243 scale = sqrt(1.0 - a[2]*a[2]) / scale;
244
245 middle_lat[0] *= scale;
246 middle_lat[1] *= scale;
247
248 double norm_ab[3];
249
250 // if the angle between a and b is to small to compute a norm vector
251 if (compute_norm_vector(a, b, norm_ab)) return 0.0;
252
253 double scalar_base = scalar_product(norm_ab, base_vec);
254 double scalar_middle_lat = scalar_product(norm_ab, middle_lat);
255
256 // if the base point is on the same plane as a and b
257 if (fabs(scalar_base) < 1e-11) {
258
259 double norm_middle[3];
260
261 if (compute_norm_vector(middle_lat, pole, norm_middle)) return 0.0;
262
263 double scalar_a = scalar_product(norm_middle, a);
264
265 if (scalar_a > 0)
266 return correction;
267 else
268 return - correction;
269
270 } else {
271
272 if (scalar_middle_lat < 0)
273 return correction;
274 else
275 return - correction;
276 }
277}
278
279double yac_pole_area(struct yac_grid_cell cell) {
280
281 size_t const M = cell.num_corners;
282
283 double coordinates_x[M];
284 double coordinates_y[M];
285
286 for (size_t i = 0; i < M; ++i)
287 XYZtoLL(cell.coordinates_xyz[i], &(coordinates_x[i]), &(coordinates_y[i]));
288
289 double area = 0.0;
290
291 if (M < 2) return 0.0;
292
293 int closer_to_south_pole = coordinates_y[0] < 0;
294
295 double pole_vec[3] = {0, 0, (closer_to_south_pole)?-1.0:1.0};
296
297 // it would also be possible to use the equator instead
298 // of the poles as the baseline
299 // this could be used as special case for cell close
300 // the equator (were the other method is probably most
301 // inaccurate)
302
303 for (size_t i = 0; i < M; ++i) {
304
305 // if one of the points it at the pole
306 if (fabs(fabs(coordinates_y[i]) - M_PI_2) < 1e-12) continue;
307 if (fabs(fabs(coordinates_y[(i+1)%M]) - M_PI_2) < 1e-12) {
308 ++i; // we can skip the next edge
309 continue;
310 }
311
313 (cell.edge_type[i] == YAC_GREAT_CIRCLE_EDGE) ||
314 (cell.edge_type[i] == YAC_LON_CIRCLE_EDGE) ||
315 (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE),
316 "ERROR: unsupported edge type")
317
318 if (cell.edge_type[i] == YAC_GREAT_CIRCLE_EDGE ||
319 cell.edge_type[i] == YAC_LON_CIRCLE_EDGE) {
320
321 double * a;
322 double * b;
323
324 a = cell.coordinates_xyz[i];
325 b = cell.coordinates_xyz[(i+1)%M];
326
327 double edge_direction = a[0]*b[1]-a[1]*b[0]; // 3. component of cross product
328
329 // if the edge is nearly on a circle of longitude
330 if (fabs(edge_direction) < 1e-12) continue;
331
332 double tmp_area = tri_area(a, b, pole_vec);
333
334 // or the other way round
335 if (edge_direction > 0) area -= tmp_area;
336 else area += tmp_area;
337
338 } else {
339
340 // the area of a sphere cap is:
341 // A = 2 * PI * r * h (where r == 1 and h == 1 - cos(d_lat))
342 // scaled with the longitude angle this is:
343 // A' = (d_lon / (2 * PI)) * A
344 // => A' = d_lon * (1 - cos(d_lat))
345
346 double d_lon = get_angle(coordinates_x[i], coordinates_x[(i+1)%M]);
347 double d_lat = M_PI_2;
348
349 if (closer_to_south_pole)
350 d_lat += coordinates_y[i];
351 else
352 d_lat -= coordinates_y[i];
353
354 double h = 1 - cos(d_lat);
355
356 area += d_lon * h;
357
358 }
359 }
360 // return fabs(area) * YAC_EARTH_RADIUS * YAC_EARTH_RADIUS;
361 return fabs(area);
362}
363
365
366 /*
367 * source code is originally based on http://gaim.umbc.edu/2010/06/03/polygon-area/
368 *
369 */
370
371 double norm[3] = {0,0,0};
372 size_t M = cell.num_corners;
373
374 if (M < 3) return 0.0;
375
376 for (size_t i0 = M - 1, i1 = 0; i1 < M; i0 = i1, ++i1) {
377 norm[0] += cell.coordinates_xyz[i0][1]*cell.coordinates_xyz[i1][2] -
378 cell.coordinates_xyz[i1][1]*cell.coordinates_xyz[i0][2];
379 norm[1] += cell.coordinates_xyz[i0][2]*cell.coordinates_xyz[i1][0] -
380 cell.coordinates_xyz[i1][2]*cell.coordinates_xyz[i0][0];
381 norm[2] += cell.coordinates_xyz[i0][0]*cell.coordinates_xyz[i1][1] -
382 cell.coordinates_xyz[i1][0]*cell.coordinates_xyz[i0][1];
383 };
384
385 return 0.5 * sqrt(norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]);
386}
387
388 /*
389 * source code is originally based on code by Robert Oehmke of Earth System Modeling
390 * Framework (www.earthsystemmodeling.org)
391 *
392 * it has be extended to support YAC data structures and two types of
393 * grid cell edges (great circle and circle of latitude)
394 *
395 */
396double yac_huiliers_area (struct yac_grid_cell cell) {
397
398 size_t M = cell.num_corners;
399
400 if (M < 2) return 0.0;
401
402 int lat_flag = 0;
403
404 for (size_t i = 0; i < M; i++)
405 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
406
407 if (M == 3 && !lat_flag)
408 return tri_area(cell.coordinates_xyz[0],
409 cell.coordinates_xyz[1],
410 cell.coordinates_xyz[2]);
411
412 // sum areas around cell
413 double area = 0.0;
414
415 for (size_t i = 2; i < M; ++i) {
416
417 double tmp_area = tri_area(cell.coordinates_xyz[0],
418 cell.coordinates_xyz[i-1],
419 cell.coordinates_xyz[i]);
420
421 double norm[3];
422
424 cell.coordinates_xyz[i], norm)) continue;
425
426 double scalar_base = scalar_product(norm, cell.coordinates_xyz[0]);
427
428 if (scalar_base > 0) area += tmp_area;
429 else area -= tmp_area;
430 }
431
432 // if there is at least one latitude circle edge
433 if (lat_flag) {
434
435 for (size_t i = 0; i < M; ++i) {
436
437 if (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE) {
438
439 size_t i_ = (i+1)%cell.num_corners;
440
441 area += lat_edge_correction(cell.coordinates_xyz[0],
442 cell.coordinates_xyz[i],
443 cell.coordinates_xyz[i_]);
444 }
445 }
446 }
447
448 return fabs(area);
449}
450
451static double tri_area_info(
452 double ref[3], double a[3], double b[3],
453 double * barycenter, double sign) {
454
455 double * corners[3] = {b, ref, a};
456 double cross[3][3];
457 struct sin_cos_angle angles[3];
458 for (int i = 0, j = 2; i < 3; j = i++) {
459 crossproduct_kahan(corners[j], corners[i], cross[i]);
460 angles[i] =
461 sin_cos_angle_new(sqrt(cross[i][0]*cross[i][0] +
462 cross[i][1]*cross[i][1] +
463 cross[i][2]*cross[i][2]),
464 scalar_product(corners[j], corners[i]));
465 }
466
467 double area = tri_area_(angles[0], angles[1], angles[2]);
468
469 // the barycenter of the triangle is given by the sum edge norm vector
470 // scaled by half of the associated edge length
471 for (int i = 0; i < 3; ++i) {
472 double scale = 0.5 * compute_angle(angles[i]) / angles[i].sin * sign;
473 for (int j = 0; j < 3; ++j)
474 barycenter[j] += cross[i][j] * scale;
475 }
476
477 if (scalar_product(ref, cross[0]) < 0.0) area = -area;
478
479 return area * sign;
480}
481
483 struct yac_grid_cell cell, double * barycenter, double sign) {
484
485 size_t M = cell.num_corners;
486
487 if (M < 2) return 0.0;
488
489 int lat_flag = 0;
490
491 for (size_t i = 0; i < M; i++)
492 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
493
494 if (M == 3 && !lat_flag)
495 return tri_area_info(cell.coordinates_xyz[0],
496 cell.coordinates_xyz[1],
497 cell.coordinates_xyz[2],
498 barycenter, sign);
499
500 // sum areas around cell
501 double area = 0.0;
502
503 for (size_t i = 2; i < M; ++i) {
504 area +=
506 cell.coordinates_xyz[0],
507 cell.coordinates_xyz[i-1],
508 cell.coordinates_xyz[i],
509 barycenter, sign);
510 }
511
512 // if there is at least one latitude circle edge
513 if (lat_flag) {
514
515 for (size_t i = 0; i < M; ++i) {
516
517 if (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE) {
518
519 size_t i_ = (i+1)%cell.num_corners;
520
521 area += lat_edge_correction(cell.coordinates_xyz[0],
522 cell.coordinates_xyz[i],
523 cell.coordinates_xyz[i_]) * sign;
524 }
525 }
526 }
527
528 return area;
529}
530
531/* ----------------------------------- */
532
533static inline double scalar_product(double a[], double b[]) {
534 return (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
535}
536
537/* ----------------------------------- */
538
static int compute_norm_vector(double a[], double b[], double norm[])
Definition area.c:196
static double tri_area_(struct sin_cos_angle angle_a, struct sin_cos_angle angle_b, struct sin_cos_angle angle_c)
Definition area.c:134
static double scalar_product(double a[], double b[])
Definition area.c:533
static double tri_area_info(double ref[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:451
double yac_huiliers_area(struct yac_grid_cell cell)
Area calculation on a unit sphere taken from ESMF based on L'Huilier's Theorem.
Definition area.c:396
static double tri_area(double u[3], double v[3], double w[3])
Definition area.c:186
double yac_pole_area(struct yac_grid_cell cell)
Calculate the area of a cell in a 3d plane on a unit sphere.
Definition area.c:279
static double XYZtoLon(double a[3])
Definition area.c:213
double yac_planar_3dcell_area(struct yac_grid_cell cell)
Area calculation on a unit sphere of a planar polygon in 3D.
Definition area.c:364
static double lat_edge_correction(double base_vec[3], double a[3], double b[3])
Definition area.c:218
static struct sin_cos_angle tri_area_quarter_angle(struct sin_cos_angle angle)
Definition area.c:105
double yac_huiliers_area_info(struct yac_grid_cell cell, double *barycenter, double sign)
Definition area.c:482
double yac_triangle_area(struct yac_grid_cell cell)
Calculate the area of a triangle on a unit sphere.
Definition area.c:21
Structs and interfaces for area calculations.
static int edge_direction(double *a, double *b)
static double const tol
static struct sin_cos_angle get_vector_angle_2(double const a[3], double const b[3])
Definition geometry.h:415
static const struct sin_cos_angle SIN_COS_LOW_TOL
Definition geometry.h:46
static const struct sin_cos_angle SIN_COS_7_M_PI_4
Definition geometry.h:50
static const struct sin_cos_angle SIN_COS_ZERO
Definition geometry.h:44
static void crossproduct_kahan(double const a[], double const b[], double cross[])
Definition geometry.h:332
static struct sin_cos_angle quarter_angle(struct sin_cos_angle angle)
Definition geometry.h:617
static int compare_angles(struct sin_cos_angle a, struct sin_cos_angle b)
Definition geometry.h:428
static double compute_angle(struct sin_cos_angle angle)
return angles in the range of [0;2PI[
Definition geometry.h:572
#define yac_angle_tol
Definition geometry.h:34
static double get_angle(double a_lon, double b_lon)
Definition geometry.h:127
static struct sin_cos_angle sin_cos_angle_new(double sin, double cos)
Definition geometry.h:405
static void XYZtoLL(double const p_in[], double *lon, double *lat)
Definition geometry.h:318
@ YAC_GREAT_CIRCLE_EDGE
great circle
Definition grid_cell.h:13
@ YAC_LAT_CIRCLE_EDGE
latitude circle
Definition grid_cell.h:14
@ YAC_LON_CIRCLE_EDGE
longitude circle
Definition grid_cell.h:15
double sin
Definition geometry.h:41
double cos
Definition geometry.h:41
size_t num_corners
Definition grid_cell.h:21
enum yac_edge_type * edge_type
Definition grid_cell.h:20
double(* coordinates_xyz)[3]
Definition grid_cell.h:19
#define MAX(a, b)
#define YAC_ASSERT(exp, msg)
Definition yac_assert.h:15