YAC 3.7.0
Yet Another Coupler
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 inline double XYZtoLon(double a[3]) {
214 return atan2(a[1] , a[0]);
215}
216
218 double base_vec[3], double a[3], double b[3], double * middle_lat) {
219
220 //-------------------------------------------------------------------
221 // compute the area that is enclosed between a great circle edges and
222 // a lat circle edge using that go through the points a and b
223 //-------------------------------------------------------------------
224
225 double const tol = 1e-8;
226
228 fabs(a[2]-b[2]) <= tol,
229 "ERROR(lat_edge_correction_): "
230 "latitude of both corners is not identical")
231
232 double h = fabs(a[2]);
233
234 // if we are at the equator or at a pole -> area is zero
235 if (h < tol || fabs(1.0 - h) < tol) return 0.0;
236
237 // compute the norm vector of the plane going through a and b
238 // (if the angle between a and b is to small to compute a norm vector, then
239 // the area is negligible)
240 double norm_ab[3];
241 if (compute_norm_vector(a, b, norm_ab)) return 0.0;
242
243 // compute the area of a triangle consisting of the lat edge and
244 // the reference pole
245 double lat_area = fabs((1.0 - h) * get_angle(XYZtoLon(a), XYZtoLon(b)));
246
247 // compute the same area, but use a gc edge instead of a lat edge
248 double pole[3] = {0, 0, (a[2] >= 0.0)?1.0:-1.0};
249 double gc_area = tri_area(a, b, pole);
250
251 // the difference between the two triangle areas is the area enclosed
252 // between the points a and b using a lat and a gc edge
253 double correction = MAX(lat_area - gc_area, 0.0);
254
255 //-------------------------------------------------------------------
256 // now we have to decide whether this correction needs to be added or
257 // subtracted
258 //-------------------------------------------------------------------
259
260 // compute the middle point of the lat edge
261 middle_lat[0] = a[0]+b[0];
262 middle_lat[1] = a[1]+b[1];
263 middle_lat[2] = a[2];
264 double scale = sqrt(middle_lat[0]*middle_lat[0]+middle_lat[1]*middle_lat[1]);
265 YAC_ASSERT(fabs(scale) >= 1e-18, "internal error")
266 scale = sqrt(1.0 - a[2]*a[2]) / scale;
267 middle_lat[0] *= scale;
268 middle_lat[1] *= scale;
269
270 // compute the cosine between norm vector and the base vector
271 // --> their sign indicates the ordering of vertices
272 double scalar_base = scalar_product(norm_ab, base_vec);
273
274 int negative_correction_flag;
275
276 // if the base point is on the same plane as a and b
277 if (fabs(scalar_base) < 1e-11) {
278
279 // compute the norm vector of the lat edge middle point and the associated
280 // pole
281 // (if the angle between lat edge middle point and the pols is to small to
282 // compute a norm vector, then the area is negligible)
283 double norm_middle[3];
284 if (compute_norm_vector(middle_lat, pole, norm_middle)) return 0.0;
285
286 double scalar_a = scalar_product(norm_middle, a);
287 negative_correction_flag = scalar_a <= 0;
288
289 } else {
290
291 // compute the cosine between the edge norm vector and the lat edge middle
292 // point
293 double scalar_middle_lat = scalar_product(norm_ab, middle_lat);
294 negative_correction_flag = scalar_middle_lat >= 0;
295 }
296
297 return negative_correction_flag?-correction:correction;
298}
299
301 double base_vec[3], double a[3], double b[3]) {
302
303 double middle_lat[3];
304 return lat_edge_correction_(base_vec, a, b, middle_lat);
305}
306
308 double base_vec[3], double a[3], double b[3],
309 double * barycenter, double sign) {
310
311 double middle_lat[3];
312 double correction = lat_edge_correction_(base_vec, a, b, middle_lat);
313
314 if (correction == 0.0) return 0.0;
315
316 double middle_gc[3] = {a[0] + b[0], a[1] + b[1], a[2] + b[2]};
317 normalise_vector(middle_gc);
318
319 double temp_barycenter[3] =
320 {middle_lat[0] + middle_gc[0],
321 middle_lat[1] + middle_gc[1],
322 middle_lat[2] + middle_gc[2]};
323 normalise_vector(temp_barycenter);
324
325 barycenter[0] += temp_barycenter[0] * correction * sign;
326 barycenter[1] += temp_barycenter[1] * correction * sign;
327 barycenter[2] += temp_barycenter[2] * correction * sign;
328
329 return correction * sign;
330}
331
332double yac_pole_area(struct yac_grid_cell cell) {
333
334 size_t const M = cell.num_corners;
335
336 double coordinates_x[M];
337 double coordinates_y[M];
338
339 for (size_t i = 0; i < M; ++i)
340 XYZtoLL(cell.coordinates_xyz[i], &(coordinates_x[i]), &(coordinates_y[i]));
341
342 double area = 0.0;
343
344 if (M < 2) return 0.0;
345
346 int closer_to_south_pole = coordinates_y[0] < 0;
347
348 double pole_vec[3] = {0, 0, (closer_to_south_pole)?-1.0:1.0};
349
350 // it would also be possible to use the equator instead
351 // of the poles as the baseline
352 // this could be used as special case for cell close
353 // the equator (were the other method is probably most
354 // inaccurate)
355
356 for (size_t i = 0; i < M; ++i) {
357
358 // if one of the points it at the pole
359 if (fabs(fabs(coordinates_y[i]) - M_PI_2) < 1e-12) continue;
360 if (fabs(fabs(coordinates_y[(i+1)%M]) - M_PI_2) < 1e-12) {
361 ++i; // we can skip the next edge
362 continue;
363 }
364
366 (cell.edge_type[i] == YAC_GREAT_CIRCLE_EDGE) ||
367 (cell.edge_type[i] == YAC_LON_CIRCLE_EDGE) ||
368 (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE),
369 "ERROR: unsupported edge type")
370
371 if (cell.edge_type[i] == YAC_GREAT_CIRCLE_EDGE ||
372 cell.edge_type[i] == YAC_LON_CIRCLE_EDGE) {
373
374 double * a;
375 double * b;
376
377 a = cell.coordinates_xyz[i];
378 b = cell.coordinates_xyz[(i+1)%M];
379
380 double edge_direction = a[0]*b[1]-a[1]*b[0]; // 3. component of cross product
381
382 // if the edge is nearly on a circle of longitude
383 if (fabs(edge_direction) < 1e-12) continue;
384
385 double tmp_area = tri_area(a, b, pole_vec);
386
387 // or the other way round
388 if (edge_direction > 0) area -= tmp_area;
389 else area += tmp_area;
390
391 } else {
392
393 // the area of a sphere cap is:
394 // A = 2 * PI * r * h (where r == 1 and h == 1 - cos(d_lat))
395 // scaled with the longitude angle this is:
396 // A' = (d_lon / (2 * PI)) * A
397 // => A' = d_lon * (1 - cos(d_lat))
398
399 double d_lon = get_angle(coordinates_x[i], coordinates_x[(i+1)%M]);
400 double d_lat = M_PI_2;
401
402 if (closer_to_south_pole)
403 d_lat += coordinates_y[i];
404 else
405 d_lat -= coordinates_y[i];
406
407 double h = 1 - cos(d_lat);
408
409 area += d_lon * h;
410
411 }
412 }
413 // return fabs(area) * YAC_EARTH_RADIUS * YAC_EARTH_RADIUS;
414 return fabs(area);
415}
416
418
419 /*
420 * source code is originally based on http://gaim.umbc.edu/2010/06/03/polygon-area/
421 *
422 */
423
424 double norm[3] = {0,0,0};
425 size_t M = cell.num_corners;
426
427 if (M < 3) return 0.0;
428
429 for (size_t i0 = M - 1, i1 = 0; i1 < M; i0 = i1, ++i1) {
430 norm[0] += cell.coordinates_xyz[i0][1]*cell.coordinates_xyz[i1][2] -
431 cell.coordinates_xyz[i1][1]*cell.coordinates_xyz[i0][2];
432 norm[1] += cell.coordinates_xyz[i0][2]*cell.coordinates_xyz[i1][0] -
433 cell.coordinates_xyz[i1][2]*cell.coordinates_xyz[i0][0];
434 norm[2] += cell.coordinates_xyz[i0][0]*cell.coordinates_xyz[i1][1] -
435 cell.coordinates_xyz[i1][0]*cell.coordinates_xyz[i0][1];
436 };
437
438 return 0.5 * sqrt(norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]);
439}
440
441 /*
442 * source code is originally based on code by Robert Oehmke of Earth System Modeling
443 * Framework (www.earthsystemmodeling.org)
444 *
445 * it has be extended to support YAC data structures and two types of
446 * grid cell edges (great circle and circle of latitude)
447 *
448 */
449double yac_huiliers_area (struct yac_grid_cell cell) {
450
451 size_t M = cell.num_corners;
452
453 if (M < 2) return 0.0;
454
455 int lat_flag = 0;
456
457 for (size_t i = 0; i < M; i++)
458 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
459
460 if (M == 3 && !lat_flag)
461 return tri_area(cell.coordinates_xyz[0],
462 cell.coordinates_xyz[1],
463 cell.coordinates_xyz[2]);
464
465 // sum areas around cell
466 double area = 0.0;
467
468 for (size_t i = 2; i < M; ++i) {
469
470 double tmp_area = tri_area(cell.coordinates_xyz[0],
471 cell.coordinates_xyz[i-1],
472 cell.coordinates_xyz[i]);
473
474 double norm[3];
475
477 cell.coordinates_xyz[i], norm)) continue;
478
479 double scalar_base = scalar_product(norm, cell.coordinates_xyz[0]);
480
481 if (scalar_base > 0) area += tmp_area;
482 else area -= tmp_area;
483 }
484
485 // if there is at least one latitude circle edge
486 if (lat_flag) {
487
488 for (size_t i = 0; i < M; ++i) {
489
490 if (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE) {
491
492 size_t i_ = (i+1)%cell.num_corners;
493
494 area += lat_edge_correction(cell.coordinates_xyz[0],
495 cell.coordinates_xyz[i],
496 cell.coordinates_xyz[i_]);
497 }
498 }
499 }
500
501 return fabs(area);
502}
503
504static double tri_area_info(
505 double ref[3], double a[3], double b[3],
506 double * barycenter, double sign) {
507
508 double * corners[3] = {b, ref, a};
509 double cross[3][3];
510 struct sin_cos_angle angles[3];
511 int zero_angle_flag = 0;
512 for (int i = 0, j = 2; i < 3; j = i++) {
513 crossproduct_kahan(corners[j], corners[i], cross[i]);
514 angles[i] =
515 sin_cos_angle_new(sqrt(cross[i][0]*cross[i][0] +
516 cross[i][1]*cross[i][1] +
517 cross[i][2]*cross[i][2]),
518 scalar_product(corners[j], corners[i]));
519 zero_angle_flag |= angles[i].sin == 0.0;
520 }
521
522 if (zero_angle_flag) return 0.0;
523
524 double area = tri_area_(angles[0], angles[1], angles[2]);
525
526 // the barycenter of the triangle is given by the sum edge norm vector
527 // scaled by half of the associated edge length
528 for (int i = 0; i < 3; ++i) {
529 double scale = 0.5 * compute_angle(angles[i]) / angles[i].sin * sign;
530 for (int j = 0; j < 3; ++j)
531 barycenter[j] += cross[i][j] * scale;
532 }
533
534 if (scalar_product(ref, cross[0]) < 0.0) area = -area;
535
536 return area * sign;
537}
538
540 struct yac_grid_cell cell, double * barycenter, double sign) {
541
542 size_t M = cell.num_corners;
543
544 if (M < 2) return 0.0;
545
546 int lat_flag = 0;
547
548 for (size_t i = 0; i < M; i++)
549 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
550
551 if (M == 3 && !lat_flag)
552 return tri_area_info(cell.coordinates_xyz[0],
553 cell.coordinates_xyz[1],
554 cell.coordinates_xyz[2],
555 barycenter, sign);
556
557 // sum areas around cell
558 double area = 0.0;
559
560 for (size_t i = 2; i < M; ++i) {
561 area +=
563 cell.coordinates_xyz[0],
564 cell.coordinates_xyz[i-1],
565 cell.coordinates_xyz[i],
566 barycenter, sign);
567 }
568
569 // if there is at least one latitude circle edge
570 if (lat_flag) {
571
572 for (size_t i = 0; i < M; ++i) {
573
574 if (cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE) {
575
576 size_t i_ = (i+1)%cell.num_corners;
577
579 cell.coordinates_xyz[0],
580 cell.coordinates_xyz[i],
581 cell.coordinates_xyz[i_],
582 barycenter, sign);
583 }
584 }
585 }
586
587 return area;
588}
589
590/* ----------------------------------- */
591
592static inline double scalar_product(double a[], double b[]) {
593 return (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
594}
595
596/* ----------------------------------- */
597
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:592
static double tri_area_info(double ref[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:504
static double lat_edge_correction_info(double base_vec[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:307
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:449
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:332
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:417
static double lat_edge_correction_(double base_vec[3], double a[3], double b[3], double *middle_lat)
Definition area.c:217
static double lat_edge_correction(double base_vec[3], double a[3], double b[3])
Definition area.c:300
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:539
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:397
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:318
static struct sin_cos_angle quarter_angle(struct sin_cos_angle angle)
Definition geometry.h:599
static int compare_angles(struct sin_cos_angle a, struct sin_cos_angle b)
Definition geometry.h:410
static double compute_angle(struct sin_cos_angle angle)
return angles in the range of [0;2PI[
Definition geometry.h:554
#define yac_angle_tol
Definition geometry.h:34
static void normalise_vector(double v[])
Definition geometry.h:671
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:387
static void XYZtoLL(double const p_in[], double *lon, double *lat)
Definition geometry.h:304
@ 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:16