YAC 3.15.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 "geometry.h"
12#include "utils_core.h"
13#include "ensure_array_size.h"
14
15static inline double scalar_product(double a[], double b[]);
16
48static double tri_area(double const * restrict a,
49 double const * restrict b,
50 double const * restrict c) {
51
52 /* --------------------------------------------------
53 * Compensated cross product b x c
54 * -------------------------------------------------- */
55
56 double bc[3]; // b x c
57 double bc_e[3]; // error from computation of bc
58 det2_error(b[1], b[2], c[1], c[2], &bc[0], &bc_e[0]);
59 det2_error(b[2], b[0], c[2], c[0], &bc[1], &bc_e[1]);
60 det2_error(b[0], b[1], c[0], c[1], &bc[2], &bc_e[2]);
61
62 /* --------------------------------------------------
63 * Compensated scalar triple product
64 * T = a * (b x c)
65 * -------------------------------------------------- */
66
67 double abc[3] = // a * bc
68 {a[0]*bc[0], a[1]*bc[1], a[2]*bc[2]};
69 double abc_e[3] = // error from computation of abc
70 {fma(a[0], bc[0], -abc[0]) + a[0] * bc_e[0],
71 fma(a[1], bc[1], -abc[1]) + a[1] * bc_e[1],
72 fma(a[2], bc[2], -abc[2]) + a[2] * bc_e[2]};
73
74 double triple = 0.0, triple_e = 0.0;
75
76 accu_eft(abc_e[0], &triple, &triple_e);
77 accu_eft(abc_e[1], &triple, &triple_e);
78 accu_eft(abc_e[2], &triple, &triple_e);
79 accu_eft(abc[0], &triple, &triple_e);
80 accu_eft(abc[1], &triple, &triple_e);
81 accu_eft(abc[2], &triple, &triple_e);
82
83 /* --------------------------------------------------
84 * Compensated denominator
85 * D = 1 + a·b + b·c + c·a
86 * -------------------------------------------------- */
87
88 double D = 1.0; // denominator
89 double D_e = 0.0; // error from computing D
90
91 accu_product_eft(a[0], b[0], &D, &D_e);
92 accu_product_eft(a[1], b[1], &D, &D_e);
93 accu_product_eft(a[2], b[2], &D, &D_e);
94
95 accu_product_eft(b[0], c[0], &D, &D_e);
96 accu_product_eft(b[1], c[1], &D, &D_e);
97 accu_product_eft(b[2], c[2], &D, &D_e);
98
99 accu_product_eft(c[0], a[0], &D, &D_e);
100 accu_product_eft(c[1], a[1], &D, &D_e);
101 accu_product_eft(c[2], a[2], &D, &D_e);
102
103 /* --------------------------------------------------
104 * Signed spherical excess
105 * -------------------------------------------------- */
106
107 return 2.0 * atan2(triple + triple_e, D + D_e);
108}
109
110/* ----------------------------------- */
111
112static inline int compute_norm_vector(double a[], double b[], double norm[]) {
113
114 crossproduct_kahan(a, b, norm);
115
116 double scale = sqrt(norm[0]*norm[0] + norm[1]*norm[1] + norm[2]*norm[2]);
117
118 if (scale <= yac_angle_tol) return 1;
119
120 scale = 1.0 / scale;
121
122 norm[0] *= scale;
123 norm[1] *= scale;
124 norm[2] *= scale;
125
126 return 0;
127}
128
129static inline double XYZtoLon(double a[3]) {
130 return atan2(a[1] , a[0]);
131}
132
134 double base_vec[3], double a[3], double b[3], double * middle_lat) {
135
136 //-------------------------------------------------------------------
137 // compute the area that is enclosed between a great circle edges and
138 // a lat circle edge using that go through the points a and b
139 //-------------------------------------------------------------------
140
141 double const tol = 1e-8;
142
144 fabs(a[2]-b[2]) <= tol,
145 "ERROR(lat_edge_correction_): "
146 "latitude of both corners is not identical")
147
148 double h = fabs(a[2]);
149
150 // if we are at the equator or at a pole -> area is zero
151 if (h < tol || fabs(1.0 - h) < tol) return 0.0;
152
153 // compute the norm vector of the plane going through a and b
154 // (if the angle between a and b is to small to compute a norm vector, then
155 // the area is negligible)
156 double norm_ab[3];
157 if (compute_norm_vector(a, b, norm_ab)) return 0.0;
158
159 // compute the area of a triangle consisting of the lat edge and
160 // the reference pole
161 double lat_area = fabs((1.0 - h) * get_angle(XYZtoLon(a), XYZtoLon(b)));
162
163 // compute the same area, but use a gc edge instead of a lat edge
164 double pole[3] = {0, 0, copysign(1.0, a[2])};
165 double gc_area = fabs(tri_area(a, b, pole));
166
167 // the difference between the two triangle areas is the area enclosed
168 // between the points a and b using a lat and a gc edge
169 double correction = MAX(lat_area - gc_area, 0.0);
170
171 //-------------------------------------------------------------------
172 // now we have to decide whether this correction needs to be added or
173 // subtracted
174 //-------------------------------------------------------------------
175
176 // compute the middle point of the lat edge
177 middle_lat[0] = a[0]+b[0];
178 middle_lat[1] = a[1]+b[1];
179 middle_lat[2] = a[2];
180 double scale = sqrt(middle_lat[0]*middle_lat[0]+middle_lat[1]*middle_lat[1]);
181 YAC_ASSERT(fabs(scale) >= 1e-18, "internal error")
182 scale = sqrt(1.0 - a[2]*a[2]) / scale;
183 middle_lat[0] *= scale;
184 middle_lat[1] *= scale;
185
186 // compute the cosine between norm vector and the base vector
187 // --> their sign indicates the ordering of vertices
188 double scalar_base = scalar_product(norm_ab, base_vec);
189
190 int negative_correction_flag;
191
192 // if the base point is on the same plane as a and b
193 if (fabs(scalar_base) < 1e-11) {
194
195 // compute the norm vector of the lat edge middle point and the associated
196 // pole
197 // (if the angle between lat edge middle point and the pols is to small to
198 // compute a norm vector, then the area is negligible)
199 double norm_middle[3];
200 if (compute_norm_vector(middle_lat, pole, norm_middle)) return 0.0;
201
202 double scalar_a = scalar_product(norm_middle, a);
203 negative_correction_flag = scalar_a <= 0;
204
205 } else {
206
207 // compute the cosine between the edge norm vector and the lat edge middle
208 // point
209 double scalar_middle_lat = scalar_product(norm_ab, middle_lat);
210 negative_correction_flag = scalar_middle_lat >= 0;
211 }
212
213 return negative_correction_flag?-correction:correction;
214}
215
217 double base_vec[3], double a[3], double b[3]) {
218
219 double middle_lat[3];
220 return lat_edge_correction_(base_vec, a, b, middle_lat);
221}
222
224 double base_vec[3], double a[3], double b[3],
225 double * barycenter, double sign) {
226
227 double middle_lat[3];
228 double correction = lat_edge_correction_(base_vec, a, b, middle_lat);
229
230 if (correction == 0.0) return 0.0;
231
232 double middle_gc[3] = {a[0] + b[0], a[1] + b[1], a[2] + b[2]};
233 normalise_vector(middle_gc);
234
235 double temp_barycenter[3] =
236 {middle_lat[0] + middle_gc[0],
237 middle_lat[1] + middle_gc[1],
238 middle_lat[2] + middle_gc[2]};
239 normalise_vector(temp_barycenter);
240
241 barycenter[0] += temp_barycenter[0] * correction * sign;
242 barycenter[1] += temp_barycenter[1] * correction * sign;
243 barycenter[2] += temp_barycenter[2] * correction * sign;
244
245 return correction * sign;
246}
247
271double yac_grid_cell_area (struct yac_grid_cell cell) {
272
273 size_t M = cell.num_corners;
274
275 if (M < 2) return 0.0;
276
277 // determine whether the cell contains at least one lat circle edge
278 int lat_flag = 0;
279 for (size_t i = 0; i < M; i++)
280 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
281
282 // if this is a basic triangle only conisting of great circle edges
283 // (e.g. ICON cell)
284 if (M == 3 && !lat_flag) {
285
286 // in case of triangle only consisting of great circle edges, return
287 // absolut value of the signed area returned by tri_area
288 return
289 fabs(
291 cell.coordinates_xyz[1],
292 cell.coordinates_xyz[2]));
293 }
294
295 // loop over all subtriangles generated by triangulation using
296 // a fan approach
297 double area = 0.0;
298 for (size_t i = 1; i < M - 1; ++i) {
299
300 // signed area summation, this ensures that this routine
301 // also works for concave cells
302 area += tri_area(cell.coordinates_xyz[0],
303 cell.coordinates_xyz[i],
304 cell.coordinates_xyz[i+1]);
305 }
306
307 // if there is at least one latitude circle edge
308 if (lat_flag) {
309
310 // loop over all edges of the polygon
311 for (size_t i = 0, j = M - 1; i < M; j = i++) {
312
313 if (cell.edge_type[j] == YAC_LAT_CIRCLE_EDGE) {
314
315 // add correction for the area difference between the great circle
316 // edge and the latitude circle edge from vertex j to vertex i
317 // (uses vertex 0 as a reference to determine area sign)
318 area += lat_edge_correction(cell.coordinates_xyz[0],
319 cell.coordinates_xyz[j],
320 cell.coordinates_xyz[i]);
321 }
322 }
323 }
324
325 return fabs(area);
326}
327
348static double tri_area_info(
349 double ref[3], double a[3], double b[3],
350 double * barycenter, double sign) {
351
352 // the barycenter of the triangle is given by the sum edge norm vector
353 // scaled by half of the associated edge length
354 // This approach is exact for spherical triangles. The weighted normalized
355 // sum of the triangle vertices works for the planar triangle.
356 double * corners[3] = {b, ref, a};
357 for (int i = 0, j = 2; i < 3; j = i++) {
358
359 double cross[3];
360 crossproduct_kahan(corners[j], corners[i], cross);
361 double sin_angle =
362 sqrt(cross[0]*cross[0] + cross[1]*cross[1] + cross[2]*cross[2]);
363
364 // if the edge is too short
365 if (sin_angle < yac_angle_tol) continue;
366
367 double angle = asin(sin_angle);
368
369 double scale = 0.5 * angle / sin_angle * sign;
370 for (int k = 0; k < 3; ++k) barycenter[k] += cross[k] * scale;
371 }
372
373 // return signed area
374 return tri_area(ref, a, b) * sign;
375}
376
404 struct yac_grid_cell cell, double * barycenter, double sign) {
405
406 size_t M = cell.num_corners;
407
408 if (M < 2) return 0.0;
409
410 // determine whether the cell contains at least one lat circle edge
411 int lat_flag = 0;
412 for (size_t i = 0; i < M; i++)
413 lat_flag |= cell.edge_type[i] == YAC_LAT_CIRCLE_EDGE;
414
415 // if this is a basic triangle only conisting of great circle edges
416 // (e.g. ICON cell)
417 if (M == 3 && !lat_flag) {
418
419 // in case of triangle only consisting of great circle edges, return
420 // value of the signed area returned by tri_area_info and update
421 // barycenter
422 return tri_area_info(cell.coordinates_xyz[0],
423 cell.coordinates_xyz[1],
424 cell.coordinates_xyz[2],
425 barycenter, sign);
426 }
427
428 // loop over all subtriangles generated by triangulation using
429 // a fan approach
430 double area = 0.0;
431 for (size_t i = 1; i < M - 1; ++i) {
432
433 // signed area summation, this ensures that this routine
434 // also works for concave cells
435 area +=
437 cell.coordinates_xyz[0],
438 cell.coordinates_xyz[i],
439 cell.coordinates_xyz[i+1],
440 barycenter, sign);
441 }
442
443 // if there is at least one latitude circle edge
444 if (lat_flag) {
445
446 // loop over all edges of the polygon
447 for (size_t i = 0, j = M - 1; i < M; j = i++) {
448
449 if (cell.edge_type[j] == YAC_LAT_CIRCLE_EDGE) {
450
451 // Add correction for the area difference between the great circle
452 // edge and the latitude circle edge from vertex j to vertex i
453 // (uses vertex 0 as a reference to determine area sign).
454 // Also updates the barycenter accumulator with the contribution
455 // from the latitude circle edge correction.
457 cell.coordinates_xyz[0],
458 cell.coordinates_xyz[j],
459 cell.coordinates_xyz[i],
460 barycenter, sign);
461 }
462 }
463 }
464
465 return area;
466}
467
468/* ----------------------------------- */
469
470static inline double scalar_product(double a[], double b[]) {
471 return (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
472}
473
474/* ----------------------------------- */
475
#define YAC_ASSERT(exp, msg)
static int compute_norm_vector(double a[], double b[], double norm[])
Definition area.c:112
double yac_grid_cell_area(struct yac_grid_cell cell)
Area calculation of a spherical cell.
Definition area.c:271
static double scalar_product(double a[], double b[])
Definition area.c:470
static double tri_area_info(double ref[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:348
static double lat_edge_correction_info(double base_vec[3], double a[3], double b[3], double *barycenter, double sign)
Definition area.c:223
double yac_grid_cell_area_info(struct yac_grid_cell cell, double *barycenter, double sign)
Definition area.c:403
static double XYZtoLon(double a[3])
Definition area.c:129
static double lat_edge_correction_(double base_vec[3], double a[3], double b[3], double *middle_lat)
Definition area.c:133
static double lat_edge_correction(double base_vec[3], double a[3], double b[3])
Definition area.c:216
static double tri_area(double const *restrict a, double const *restrict b, double const *restrict c)
Definition area.c:48
Structs and interfaces for area calculations.
static double const tol
double scale
static void det2_error(double a, double b, double c, double d, double *restrict det, double *restrict det_error)
Definition geometry.h:382
static void crossproduct_kahan(double const a[], double const b[], double cross[])
Definition geometry.h:405
static void accu_product_eft(double a, double b, double *restrict accu, double *restrict accu_error)
Definition geometry.h:357
#define yac_angle_tol
Definition geometry.h:26
static void normalise_vector(double v[])
Definition geometry.h:728
static double get_angle(double a_lon, double b_lon)
Definition geometry.h:110
static void accu_eft(double a, double *restrict accu, double *restrict error)
Definition geometry.h:334
@ YAC_LAT_CIRCLE_EDGE
latitude circle
Definition grid_cell.h:14
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)