Coverage Report

Created: 2025-06-13 06:18

/src/proj/src/projections/healpix.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 * Project: PROJ.4
3
 * Purpose: Implementation of the HEALPix and rHEALPix projections.
4
 *          For background see
5
 *<http://code.scenzgrid.org/index.php/p/scenzgrid-py/source/tree/master/docs/rhealpix_dggs.pdf>.
6
 * Authors: Alex Raichev (raichev@cs.auckland.ac.nz)
7
 *          Michael Speth (spethm@landcareresearch.co.nz)
8
 * Notes:   Raichev implemented these projections in Python and
9
 *          Speth translated them into C here.
10
 ******************************************************************************
11
 * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a
14
 * copy of this software and associated documentation files (the "Software"),
15
 * to deal in the Software without restriction, including without limitation
16
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17
 * and/or sell copies of the Software, and to permit persons to whom the
18
 * Software is furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included
21
 * in all copies or substcounteral portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
27
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
28
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
29
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
 * SOFTWARE.
31
 *****************************************************************************/
32
33
#include <errno.h>
34
#include <math.h>
35
36
#include "proj.h"
37
#include "proj_internal.h"
38
39
PROJ_HEAD(healpix, "HEALPix") "\n\tSph&Ell\n\trot_xy=";
40
PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph&Ell\n\tnorth_square= south_square=";
41
42
/* Matrix for counterclockwise rotation by pi/2: */
43
#define R1                                                                     \
44
    {                                                                          \
45
        {0, -1}, { 1, 0 }                                                      \
46
    }
47
/* Matrix for counterclockwise rotation by pi: */
48
#define R2                                                                     \
49
    {                                                                          \
50
        {-1, 0}, { 0, -1 }                                                     \
51
    }
52
/* Matrix for counterclockwise rotation by 3*pi/2:  */
53
#define R3                                                                     \
54
    {                                                                          \
55
        {0, 1}, { -1, 0 }                                                      \
56
    }
57
/* Identity matrix */
58
#define IDENT                                                                  \
59
    {                                                                          \
60
        {1, 0}, { 0, 1 }                                                       \
61
    }
62
/* IDENT, R1, R2, R3, R1 inverse, R2 inverse, R3 inverse:*/
63
#define ROT                                                                    \
64
    { IDENT, R1, R2, R3, R3, R2, R1 }
65
/* Fuzz to handle rounding errors: */
66
0
#define EPS 1e-15
67
68
namespace { // anonymous namespace
69
struct pj_healpix_data {
70
    int north_square;
71
    int south_square;
72
    double rot_xy;
73
    double qp;
74
    double *apa;
75
};
76
} // anonymous namespace
77
78
typedef struct {
79
    int cn;      /* An integer 0--3 indicating the position of the polar cap. */
80
    double x, y; /* Coordinates of the pole point (point of most extreme
81
                    latitude on the polar caps). */
82
    enum Region { north, south, equatorial } region;
83
} CapMap;
84
85
static const double rot[7][2][2] = ROT;
86
87
/**
88
 * Returns the sign of the double.
89
 * @param v the parameter whose sign is returned.
90
 * @return 1 for positive number, -1 for negative, and 0 for zero.
91
 **/
92
0
static double sign(double v) { return v > 0 ? 1 : (v < 0 ? -1 : 0); }
93
94
0
static PJ_XY rotate(PJ_XY p, double angle) {
95
0
    PJ_XY result;
96
0
    result.x = p.x * cos(angle) - p.y * sin(angle);
97
0
    result.y = p.y * cos(angle) + p.x * sin(angle);
98
0
    return result;
99
0
}
100
101
/**
102
 * Return the index of the matrix in ROT.
103
 * @param index ranges from -3 to 3.
104
 */
105
0
static int get_rotate_index(int index) {
106
0
    switch (index) {
107
0
    case 0:
108
0
        return 0;
109
0
    case 1:
110
0
        return 1;
111
0
    case 2:
112
0
        return 2;
113
0
    case 3:
114
0
        return 3;
115
0
    case -1:
116
0
        return 4;
117
0
    case -2:
118
0
        return 5;
119
0
    case -3:
120
0
        return 6;
121
0
    }
122
0
    return 0;
123
0
}
124
125
/**
126
 * Return 1 if point (testx, testy) lies in the interior of the polygon
127
 * determined by the vertices in vert, and return 0 otherwise.
128
 * See http://paulbourke.net/geometry/polygonmesh/ for more details.
129
 * @param nvert the number of vertices in the polygon.
130
 * @param vert the (x, y)-coordinates of the polygon's vertices
131
 **/
132
0
static int pnpoly(int nvert, double vert[][2], double testx, double testy) {
133
0
    int i;
134
0
    int counter = 0;
135
0
    double xinters;
136
0
    PJ_XY p1, p2;
137
138
    /* Check for boundary cases */
139
0
    for (i = 0; i < nvert; i++) {
140
0
        if (testx == vert[i][0] && testy == vert[i][1]) {
141
0
            return 1;
142
0
        }
143
0
    }
144
145
0
    p1.x = vert[0][0];
146
0
    p1.y = vert[0][1];
147
148
0
    for (i = 1; i < nvert; i++) {
149
0
        p2.x = vert[i % nvert][0];
150
0
        p2.y = vert[i % nvert][1];
151
0
        if (testy > MIN(p1.y, p2.y) && testy <= MAX(p1.y, p2.y) &&
152
0
            testx <= MAX(p1.x, p2.x) && p1.y != p2.y) {
153
0
            xinters = (testy - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
154
0
            if (p1.x == p2.x || testx <= xinters)
155
0
                counter++;
156
0
        }
157
0
        p1 = p2;
158
0
    }
159
160
0
    if (counter % 2 == 0) {
161
0
        return 0;
162
0
    } else {
163
0
        return 1;
164
0
    }
165
0
}
166
167
/**
168
 * Return 1 if (x, y) lies in (the interior or boundary of) the image of the
169
 * HEALPix projection (in case proj=0) or in the image the rHEALPix projection
170
 * (in case proj=1), and return 0 otherwise.
171
 * @param north_square the position of the north polar square (rHEALPix only)
172
 * @param south_square the position of the south polar square (rHEALPix only)
173
 **/
174
static int in_image(double x, double y, int proj, int north_square,
175
0
                    int south_square) {
176
0
    if (proj == 0) {
177
0
        double healpixVertsJit[][2] = {{-M_PI - EPS, M_FORTPI},
178
0
                                       {-3 * M_FORTPI, M_HALFPI + EPS},
179
0
                                       {-M_HALFPI, M_FORTPI + EPS},
180
0
                                       {-M_FORTPI, M_HALFPI + EPS},
181
0
                                       {0.0, M_FORTPI + EPS},
182
0
                                       {M_FORTPI, M_HALFPI + EPS},
183
0
                                       {M_HALFPI, M_FORTPI + EPS},
184
0
                                       {3 * M_FORTPI, M_HALFPI + EPS},
185
0
                                       {M_PI + EPS, M_FORTPI},
186
0
                                       {M_PI + EPS, -M_FORTPI},
187
0
                                       {3 * M_FORTPI, -M_HALFPI - EPS},
188
0
                                       {M_HALFPI, -M_FORTPI - EPS},
189
0
                                       {M_FORTPI, -M_HALFPI - EPS},
190
0
                                       {0.0, -M_FORTPI - EPS},
191
0
                                       {-M_FORTPI, -M_HALFPI - EPS},
192
0
                                       {-M_HALFPI, -M_FORTPI - EPS},
193
0
                                       {-3 * M_FORTPI, -M_HALFPI - EPS},
194
0
                                       {-M_PI - EPS, -M_FORTPI},
195
0
                                       {-M_PI - EPS, M_FORTPI}};
196
0
        return pnpoly((int)sizeof(healpixVertsJit) / sizeof(healpixVertsJit[0]),
197
0
                      healpixVertsJit, x, y);
198
0
    } else {
199
        /**
200
         * We need to assign the array this way because the input is
201
         * dynamic (north_square and south_square vars are unknown at
202
         * compile time).
203
         **/
204
0
        double rhealpixVertsJit[][2] = {
205
0
            {-M_PI - EPS, M_FORTPI + EPS},
206
0
            {-M_PI + north_square * M_HALFPI - EPS, M_FORTPI + EPS},
207
0
            {-M_PI + north_square * M_HALFPI - EPS, 3 * M_FORTPI + EPS},
208
0
            {-M_PI + (north_square + 1.0) * M_HALFPI + EPS, 3 * M_FORTPI + EPS},
209
0
            {-M_PI + (north_square + 1.0) * M_HALFPI + EPS, M_FORTPI + EPS},
210
0
            {M_PI + EPS, M_FORTPI + EPS},
211
0
            {M_PI + EPS, -M_FORTPI - EPS},
212
0
            {-M_PI + (south_square + 1.0) * M_HALFPI + EPS, -M_FORTPI - EPS},
213
0
            {-M_PI + (south_square + 1.0) * M_HALFPI + EPS,
214
0
             -3 * M_FORTPI - EPS},
215
0
            {-M_PI + south_square * M_HALFPI - EPS, -3 * M_FORTPI - EPS},
216
0
            {-M_PI + south_square * M_HALFPI - EPS, -M_FORTPI - EPS},
217
0
            {-M_PI - EPS, -M_FORTPI - EPS}};
218
219
0
        return pnpoly((int)sizeof(rhealpixVertsJit) /
220
0
                          sizeof(rhealpixVertsJit[0]),
221
0
                      rhealpixVertsJit, x, y);
222
0
    }
223
0
}
224
225
/**
226
 * Return the authalic latitude of latitude alpha (if inverse=0) or
227
 * return the latitude of authalic latitude alpha (if inverse=1).
228
 * P contains the relevant ellipsoid parameters.
229
 **/
230
0
static double auth_lat(PJ *P, double alpha, int inverse) {
231
0
    const struct pj_healpix_data *Q =
232
0
        static_cast<const struct pj_healpix_data *>(P->opaque);
233
0
    if (inverse == 0) {
234
        /* Authalic latitude from geographic latitude. */
235
0
        return pj_authalic_lat(alpha, sin(alpha), cos(alpha), Q->apa, P, Q->qp);
236
0
    } else {
237
        /* Geographic latitude from authalic latitude. */
238
0
        return pj_authalic_lat_inverse(alpha, Q->apa, P, Q->qp);
239
0
    }
240
0
}
241
242
/**
243
 * Return the HEALPix projection of the longitude-latitude point lp on
244
 * the unit sphere.
245
 **/
246
0
static PJ_XY healpix_sphere(PJ_LP lp) {
247
0
    double lam = lp.lam;
248
0
    double phi = lp.phi;
249
0
    double phi0 = asin(2.0 / 3.0);
250
0
    PJ_XY xy;
251
252
    /* equatorial region */
253
0
    if (fabs(phi) <= phi0) {
254
0
        xy.x = lam;
255
0
        xy.y = 3 * M_PI / 8 * sin(phi);
256
0
    } else {
257
0
        double lamc;
258
0
        double sigma = sqrt(3 * (1 - fabs(sin(phi))));
259
0
        double cn = floor(2 * lam / M_PI + 2);
260
0
        if (cn >= 4) {
261
0
            cn = 3;
262
0
        }
263
0
        lamc = -3 * M_FORTPI + M_HALFPI * cn;
264
0
        xy.x = lamc + (lam - lamc) * sigma;
265
0
        xy.y = sign(phi) * M_FORTPI * (2 - sigma);
266
0
    }
267
0
    return xy;
268
0
}
269
270
/**
271
 * Return the inverse of healpix_sphere().
272
 **/
273
0
static PJ_LP healpix_spherhealpix_e_inverse(PJ_XY xy) {
274
0
    PJ_LP lp;
275
0
    double x = xy.x;
276
0
    double y = xy.y;
277
0
    double y0 = M_FORTPI;
278
279
    /* Equatorial region. */
280
0
    if (fabs(y) <= y0) {
281
0
        lp.lam = x;
282
0
        lp.phi = asin(8 * y / (3 * M_PI));
283
0
    } else if (fabs(y) < M_HALFPI) {
284
0
        double cn = floor(2 * x / M_PI + 2);
285
0
        double xc, tau;
286
0
        if (cn >= 4) {
287
0
            cn = 3;
288
0
        }
289
0
        xc = -3 * M_FORTPI + M_HALFPI * cn;
290
0
        tau = 2.0 - 4 * fabs(y) / M_PI;
291
0
        lp.lam = xc + (x - xc) / tau;
292
0
        lp.phi = sign(y) * asin(1.0 - pow(tau, 2) / 3.0);
293
0
    } else {
294
0
        lp.lam = -M_PI;
295
0
        lp.phi = sign(y) * M_HALFPI;
296
0
    }
297
0
    return (lp);
298
0
}
299
300
/**
301
 * Return the vector sum a + b, where a and b are 2-dimensional vectors.
302
 * @param ret holds a + b.
303
 **/
304
0
static void vector_add(const double a[2], const double b[2], double *ret) {
305
0
    int i;
306
0
    for (i = 0; i < 2; i++) {
307
0
        ret[i] = a[i] + b[i];
308
0
    }
309
0
}
310
311
/**
312
 * Return the vector difference a - b, where a and b are 2-dimensional vectors.
313
 * @param ret holds a - b.
314
 **/
315
0
static void vector_sub(const double a[2], const double b[2], double *ret) {
316
0
    int i;
317
0
    for (i = 0; i < 2; i++) {
318
0
        ret[i] = a[i] - b[i];
319
0
    }
320
0
}
321
322
/**
323
 * Return the 2 x 1 matrix product a*b, where a is a 2 x 2 matrix and
324
 * b is a 2 x 1 matrix.
325
 * @param ret holds a*b.
326
 **/
327
0
static void dot_product(const double a[2][2], const double b[2], double *ret) {
328
0
    int i, j;
329
0
    int length = 2;
330
0
    for (i = 0; i < length; i++) {
331
0
        ret[i] = 0;
332
0
        for (j = 0; j < length; j++) {
333
0
            ret[i] += a[i][j] * b[j];
334
0
        }
335
0
    }
336
0
}
337
338
/**
339
 * Return the number of the polar cap, the pole point coordinates, and
340
 * the region that (x, y) lies in.
341
 * If inverse=0, then assume (x,y) lies in the image of the HEALPix
342
 * projection of the unit sphere.
343
 * If inverse=1, then assume (x,y) lies in the image of the
344
 * (north_square, south_square)-rHEALPix projection of the unit sphere.
345
 **/
346
static CapMap get_cap(double x, double y, int north_square, int south_square,
347
0
                      int inverse) {
348
0
    CapMap capmap;
349
0
    double c;
350
351
0
    capmap.x = x;
352
0
    capmap.y = y;
353
0
    if (inverse == 0) {
354
0
        if (y > M_FORTPI) {
355
0
            capmap.region = CapMap::north;
356
0
            c = M_HALFPI;
357
0
        } else if (y < -M_FORTPI) {
358
0
            capmap.region = CapMap::south;
359
0
            c = -M_HALFPI;
360
0
        } else {
361
0
            capmap.region = CapMap::equatorial;
362
0
            capmap.cn = 0;
363
0
            return capmap;
364
0
        }
365
        /* polar region */
366
0
        if (x < -M_HALFPI) {
367
0
            capmap.cn = 0;
368
0
            capmap.x = (-3 * M_FORTPI);
369
0
            capmap.y = c;
370
0
        } else if (x >= -M_HALFPI && x < 0) {
371
0
            capmap.cn = 1;
372
0
            capmap.x = -M_FORTPI;
373
0
            capmap.y = c;
374
0
        } else if (x >= 0 && x < M_HALFPI) {
375
0
            capmap.cn = 2;
376
0
            capmap.x = M_FORTPI;
377
0
            capmap.y = c;
378
0
        } else {
379
0
            capmap.cn = 3;
380
0
            capmap.x = 3 * M_FORTPI;
381
0
            capmap.y = c;
382
0
        }
383
0
    } else {
384
0
        if (y > M_FORTPI) {
385
0
            capmap.region = CapMap::north;
386
0
            capmap.x = -3 * M_FORTPI + north_square * M_HALFPI;
387
0
            capmap.y = M_HALFPI;
388
0
            x = x - north_square * M_HALFPI;
389
0
        } else if (y < -M_FORTPI) {
390
0
            capmap.region = CapMap::south;
391
0
            capmap.x = -3 * M_FORTPI + south_square * M_HALFPI;
392
0
            capmap.y = -M_HALFPI;
393
0
            x = x - south_square * M_HALFPI;
394
0
        } else {
395
0
            capmap.region = CapMap::equatorial;
396
0
            capmap.cn = 0;
397
0
            return capmap;
398
0
        }
399
        /* Polar Region, find the HEALPix polar cap number that
400
           x, y moves to when rHEALPix polar square is disassembled. */
401
0
        if (capmap.region == CapMap::north) {
402
0
            if (y >= -x - M_FORTPI - EPS && y < x + 5 * M_FORTPI - EPS) {
403
0
                capmap.cn = (north_square + 1) % 4;
404
0
            } else if (y > -x - M_FORTPI + EPS && y >= x + 5 * M_FORTPI - EPS) {
405
0
                capmap.cn = (north_square + 2) % 4;
406
0
            } else if (y <= -x - M_FORTPI + EPS && y > x + 5 * M_FORTPI + EPS) {
407
0
                capmap.cn = (north_square + 3) % 4;
408
0
            } else {
409
0
                capmap.cn = north_square;
410
0
            }
411
0
        } else /* if (capmap.region == CapMap::south) */ {
412
0
            if (y <= x + M_FORTPI + EPS && y > -x - 5 * M_FORTPI + EPS) {
413
0
                capmap.cn = (south_square + 1) % 4;
414
0
            } else if (y < x + M_FORTPI - EPS && y <= -x - 5 * M_FORTPI + EPS) {
415
0
                capmap.cn = (south_square + 2) % 4;
416
0
            } else if (y >= x + M_FORTPI - EPS && y < -x - 5 * M_FORTPI - EPS) {
417
0
                capmap.cn = (south_square + 3) % 4;
418
0
            } else {
419
0
                capmap.cn = south_square;
420
0
            }
421
0
        }
422
0
    }
423
0
    return capmap;
424
0
}
425
426
/**
427
 * Rearrange point (x, y) in the HEALPix projection by
428
 * combining the polar caps into two polar squares.
429
 * Put the north polar square in position north_square and
430
 * the south polar square in position south_square.
431
 * If inverse=1, then uncombine the polar caps.
432
 * @param north_square integer between 0 and 3.
433
 * @param south_square integer between 0 and 3.
434
 **/
435
static PJ_XY combine_caps(double x, double y, int north_square,
436
0
                          int south_square, int inverse) {
437
0
    PJ_XY xy;
438
0
    double vector[2];
439
0
    double v_min_c[2];
440
0
    double ret_dot[2];
441
0
    const double(*tmpRot)[2];
442
0
    int pole = 0;
443
444
0
    CapMap capmap = get_cap(x, y, north_square, south_square, inverse);
445
0
    if (capmap.region == CapMap::equatorial) {
446
0
        xy.x = capmap.x;
447
0
        xy.y = capmap.y;
448
0
        return xy;
449
0
    }
450
451
0
    double v[] = {x, y};
452
0
    double c[] = {capmap.x, capmap.y};
453
454
0
    if (inverse == 0) {
455
        /* Rotate (x, y) about its polar cap tip and then translate it to
456
           north_square or south_square. */
457
458
0
        if (capmap.region == CapMap::north) {
459
0
            pole = north_square;
460
0
            tmpRot = rot[get_rotate_index(capmap.cn - pole)];
461
0
        } else {
462
0
            pole = south_square;
463
0
            tmpRot = rot[get_rotate_index(-1 * (capmap.cn - pole))];
464
0
        }
465
0
    } else {
466
        /* Inverse function.
467
         Unrotate (x, y) and then translate it back. */
468
469
        /* disassemble */
470
0
        if (capmap.region == CapMap::north) {
471
0
            pole = north_square;
472
0
            tmpRot = rot[get_rotate_index(-1 * (capmap.cn - pole))];
473
0
        } else {
474
0
            pole = south_square;
475
0
            tmpRot = rot[get_rotate_index(capmap.cn - pole)];
476
0
        }
477
0
    }
478
479
0
    vector_sub(v, c, v_min_c);
480
0
    dot_product(tmpRot, v_min_c, ret_dot);
481
0
    {
482
0
        double a[] = {-3 * M_FORTPI +
483
0
                          ((inverse == 0) ? pole : capmap.cn) * M_HALFPI,
484
0
                      ((capmap.region == CapMap::north) ? 1 : -1) * M_HALFPI};
485
0
        vector_add(ret_dot, a, vector);
486
0
    }
487
488
0
    xy.x = vector[0];
489
0
    xy.y = vector[1];
490
0
    return xy;
491
0
}
492
493
0
static PJ_XY s_healpix_forward(PJ_LP lp, PJ *P) { /* sphere  */
494
0
    (void)P;
495
0
    struct pj_healpix_data *Q =
496
0
        static_cast<struct pj_healpix_data *>(P->opaque);
497
0
    return rotate(healpix_sphere(lp), -Q->rot_xy);
498
0
}
499
500
0
static PJ_XY e_healpix_forward(PJ_LP lp, PJ *P) { /* ellipsoid  */
501
0
    lp.phi = auth_lat(P, lp.phi, 0);
502
0
    struct pj_healpix_data *Q =
503
0
        static_cast<struct pj_healpix_data *>(P->opaque);
504
0
    return rotate(healpix_sphere(lp), -Q->rot_xy);
505
0
}
506
507
0
static PJ_LP s_healpix_inverse(PJ_XY xy, PJ *P) { /* sphere */
508
0
    struct pj_healpix_data *Q =
509
0
        static_cast<struct pj_healpix_data *>(P->opaque);
510
0
    xy = rotate(xy, Q->rot_xy);
511
512
    /* Check whether (x, y) lies in the HEALPix image */
513
0
    if (in_image(xy.x, xy.y, 0, 0, 0) == 0) {
514
0
        PJ_LP lp;
515
0
        lp.lam = HUGE_VAL;
516
0
        lp.phi = HUGE_VAL;
517
0
        proj_context_errno_set(
518
0
            P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
519
0
        return lp;
520
0
    }
521
0
    return healpix_spherhealpix_e_inverse(xy);
522
0
}
523
524
0
static PJ_LP e_healpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */
525
0
    PJ_LP lp = {0.0, 0.0};
526
0
    struct pj_healpix_data *Q =
527
0
        static_cast<struct pj_healpix_data *>(P->opaque);
528
0
    xy = rotate(xy, Q->rot_xy);
529
530
    /* Check whether (x, y) lies in the HEALPix image. */
531
0
    if (in_image(xy.x, xy.y, 0, 0, 0) == 0) {
532
0
        lp.lam = HUGE_VAL;
533
0
        lp.phi = HUGE_VAL;
534
0
        proj_context_errno_set(
535
0
            P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
536
0
        return lp;
537
0
    }
538
0
    lp = healpix_spherhealpix_e_inverse(xy);
539
0
    lp.phi = auth_lat(P, lp.phi, 1);
540
0
    return lp;
541
0
}
542
543
0
static PJ_XY s_rhealpix_forward(PJ_LP lp, PJ *P) { /* sphere */
544
0
    struct pj_healpix_data *Q =
545
0
        static_cast<struct pj_healpix_data *>(P->opaque);
546
547
0
    PJ_XY xy = healpix_sphere(lp);
548
0
    return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0);
549
0
}
550
551
0
static PJ_XY e_rhealpix_forward(PJ_LP lp, PJ *P) { /* ellipsoid */
552
0
    struct pj_healpix_data *Q =
553
0
        static_cast<struct pj_healpix_data *>(P->opaque);
554
0
    PJ_XY xy;
555
0
    lp.phi = auth_lat(P, lp.phi, 0);
556
0
    xy = healpix_sphere(lp);
557
0
    return combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 0);
558
0
}
559
560
0
static PJ_LP s_rhealpix_inverse(PJ_XY xy, PJ *P) { /* sphere */
561
0
    struct pj_healpix_data *Q =
562
0
        static_cast<struct pj_healpix_data *>(P->opaque);
563
564
    /* Check whether (x, y) lies in the rHEALPix image. */
565
0
    if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) {
566
0
        PJ_LP lp;
567
0
        lp.lam = HUGE_VAL;
568
0
        lp.phi = HUGE_VAL;
569
0
        proj_context_errno_set(
570
0
            P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
571
0
        return lp;
572
0
    }
573
0
    xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1);
574
0
    return healpix_spherhealpix_e_inverse(xy);
575
0
}
576
577
0
static PJ_LP e_rhealpix_inverse(PJ_XY xy, PJ *P) { /* ellipsoid */
578
0
    struct pj_healpix_data *Q =
579
0
        static_cast<struct pj_healpix_data *>(P->opaque);
580
0
    PJ_LP lp = {0.0, 0.0};
581
582
    /* Check whether (x, y) lies in the rHEALPix image. */
583
0
    if (in_image(xy.x, xy.y, 1, Q->north_square, Q->south_square) == 0) {
584
0
        lp.lam = HUGE_VAL;
585
0
        lp.phi = HUGE_VAL;
586
0
        proj_context_errno_set(
587
0
            P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN);
588
0
        return lp;
589
0
    }
590
0
    xy = combine_caps(xy.x, xy.y, Q->north_square, Q->south_square, 1);
591
0
    lp = healpix_spherhealpix_e_inverse(xy);
592
0
    lp.phi = auth_lat(P, lp.phi, 1);
593
0
    return lp;
594
0
}
595
596
0
static PJ *pj_healpix_data_destructor(PJ *P, int errlev) { /* Destructor */
597
0
    if (nullptr == P)
598
0
        return nullptr;
599
600
0
    if (nullptr == P->opaque)
601
0
        return pj_default_destructor(P, errlev);
602
603
0
    free(static_cast<struct pj_healpix_data *>(P->opaque)->apa);
604
0
    return pj_default_destructor(P, errlev);
605
0
}
606
607
0
PJ *PJ_PROJECTION(healpix) {
608
0
    struct pj_healpix_data *Q = static_cast<struct pj_healpix_data *>(
609
0
        calloc(1, sizeof(struct pj_healpix_data)));
610
0
    if (nullptr == Q)
611
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
612
0
    P->opaque = Q;
613
0
    P->destructor = pj_healpix_data_destructor;
614
615
0
    double angle = pj_param(P->ctx, P->params, "drot_xy").f;
616
0
    Q->rot_xy = PJ_TORAD(angle);
617
618
0
    if (P->es != 0.0) {
619
0
        Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */
620
0
        if (nullptr == Q->apa)
621
0
            return pj_healpix_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
622
0
        Q->qp =
623
0
            pj_authalic_lat_q(1.0, P); /* For auth_lat(). */
624
0
        P->a = P->a * sqrt(0.5 * Q->qp); /* Set P->a to authalic radius. */
625
0
        pj_calc_ellipsoid_params(
626
0
            P, P->a, P->es); /* Ensure we have a consistent parameter set */
627
0
        P->fwd = e_healpix_forward;
628
0
        P->inv = e_healpix_inverse;
629
0
    } else {
630
0
        P->fwd = s_healpix_forward;
631
0
        P->inv = s_healpix_inverse;
632
0
    }
633
634
0
    return P;
635
0
}
636
637
0
PJ *PJ_PROJECTION(rhealpix) {
638
0
    struct pj_healpix_data *Q = static_cast<struct pj_healpix_data *>(
639
0
        calloc(1, sizeof(struct pj_healpix_data)));
640
0
    if (nullptr == Q)
641
0
        return pj_default_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
642
0
    P->opaque = Q;
643
0
    P->destructor = pj_healpix_data_destructor;
644
645
0
    Q->north_square = pj_param(P->ctx, P->params, "inorth_square").i;
646
0
    Q->south_square = pj_param(P->ctx, P->params, "isouth_square").i;
647
648
    /* Check for valid north_square and south_square inputs. */
649
0
    if (Q->north_square < 0 || Q->north_square > 3) {
650
0
        proj_log_error(
651
0
            P,
652
0
            _("Invalid value for north_square: it should be in [0,3] range."));
653
0
        return pj_healpix_data_destructor(
654
0
            P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
655
0
    }
656
0
    if (Q->south_square < 0 || Q->south_square > 3) {
657
0
        proj_log_error(
658
0
            P,
659
0
            _("Invalid value for south_square: it should be in [0,3] range."));
660
0
        return pj_healpix_data_destructor(
661
0
            P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE);
662
0
    }
663
0
    if (P->es != 0.0) {
664
0
        Q->apa = pj_authalic_lat_compute_coeffs(P->n); /* For auth_lat(). */
665
0
        if (nullptr == Q->apa)
666
0
            return pj_healpix_data_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/);
667
0
        Q->qp =
668
0
            pj_authalic_lat_q(1.0, P); /* For auth_lat(). */
669
0
        P->a = P->a * sqrt(0.5 * Q->qp); /* Set P->a to authalic radius. */
670
0
        P->ra = 1.0 / P->a;
671
0
        P->fwd = e_rhealpix_forward;
672
0
        P->inv = e_rhealpix_inverse;
673
0
    } else {
674
0
        P->fwd = s_rhealpix_forward;
675
0
        P->inv = s_rhealpix_inverse;
676
0
    }
677
678
0
    return P;
679
0
}
680
681
#undef R1
682
#undef R2
683
#undef R3
684
#undef IDENT
685
#undef ROT
686
#undef EPS