1
/******************************************************************************
2
* $Id: PJ_healpix.c 1504 2011-10-18 14:58:57Z landcare $
5
* Purpose: Implementation of the healpix projection.
6
* Definition: http://code.scenzgrid.org/index.php/p/scenzgrid-py/source/tree/master/docs/scenzgrid.pdf
7
* Author: Alex Raichev & Michael Speth , spethm@landcareresearch.co.nz
9
******************************************************************************
10
* Copyright (c) 2001, Thomas Flemming, tf@ttqv.com
12
* Permission is hereby granted, free of charge, to any person obtaining a
13
* copy of this software and associated documentation files (the "Software"),
14
* to deal in the Software without restriction, including without limitation
15
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
* and/or sell copies of the Software, and to permit persons to whom the
17
* Software is furnished to do so, subject to the following conditions:
19
* The above copyright notice and this permission notice shall be included
20
* in all copies or substantial portions of the Software.
22
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
26
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
27
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
28
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30
*****************************************************************************/
32
#define PROJ_PARMS__ \
37
# include <projects.h>
38
PROJ_HEAD(healpix, "HEALPix") "\n\tSph., Ellps.";
39
PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph., Ellps.\n\tnpole= spole=";
41
# define R1 {{ 0,-1},{ 1, 0}} /** Matrix for anticlockwise rotation by pi/2 **/
42
# define R2 {{-1, 0},{ 0,-1}} /** Matrix for anticlockwise rotation by pi (R1 X R1) X = dot product **/
43
# define R3 {{ 0, 1},{-1, 0}} /** Matrix for anticlockwise rotation by 3*pi/2 (R2 X R1) **/
44
# define IDENT {{1,0},{0,1}}
46
* 0 - Identity matrix<br>
47
* 1 - Counter-clockwise rotation by PI/2<br>
48
* 2 - Counter-clockwise rotation by PI<br>
49
* 3 - Counter-clockwise rotation by 3*PI/2<br>
50
* 4 - Counter-clockwise rotation by 3*PI/2<br>
51
* 5 - Counter-clockwise rotation by PI<br>
52
* 6 - Counter-clockwise rotation by PI/2<br>
54
# define ROT { IDENT, R1, R2, R3, R3, R2, R1}
55
# define RFACTOR 3 /** Used for returning the rotation matrix **/
56
/** Used for calculating if a point is within the HEALPix projection for sphere. **/
59
int cn; // the number 0 -> 4 indicating the position of the polar cap.
60
double x,y; // the coordinates of the pole points (point of most extreme latitude on the polar caps).
61
enum Region { north, south, equatorial } region;
66
double rot[7][2][2] = ROT;
69
NOTES: Alex Raichev implemented the math in python and this is a port of his work.
70
The healpix projection is a Lambert cylindrical equal-area projection for
71
equaltorial latitudes and an interrupted Colignon projection for polar
76
* Returns the sign of the double.
77
* @param v the parameter whose sign is returned.
78
* @return 1 for positive number, -1 for negative, and 0 for zero.
80
double sign (double v) {
81
return v > 0 ? 1 : (v < 0 ? -1 : 0);
84
* Scales the number by a factor.
85
* @param num the number to be scaled.
86
* @param factor the factor to scale the number by.
87
* @param isInverse 1 for scaling the number by 1 / factor and 0 for scaling by the factor.
88
* @return the scaled number.
90
double scale_number(double num, double factor, int isInverse){
92
return num * 1.0/factor;
97
* Scales all the items of the array by a factor.
100
void scale_array(XY *array, double k, int inverse){
111
* Given an angle return its equivalent angle.
112
* @param x the angle to convert
113
* @return the equivalent angle such that -PI <= the angle returend <= PI
115
double standardize_lon(double x){
116
if(x < -1*PI || x >= PI){
117
x = x - 2*PI*floor(x/(2*PI));
125
* Given an angle, return its unit-circle equivalent angle.
126
* @param x the angel to convert.
127
* @return the equivalent angle such that -PI/2 <= the angle returned <= PI/2.
129
double standardize_lat(double x){
130
if( x < -PI/2.0 || x > PI/2){
131
x = x-2.0*PI*floor(x/(2.0*PI));
132
if(x > PI/2.0 && x <= 3.0*PI/2){
141
* Calculates if the point lies on or within the polygon.
142
* Very good explination of how this works: http://paulbourke.net/geometry/insidepoly/
143
* @param nvert the number of vertices in the polygon.
144
* @param vert the x,y-coordinates of the polygon's vertices
145
* @param testx the x-coordinate of the test point.
146
* @param testy the y-coordinate of the test point.
147
* @return 1 if on or within the bounds of the polygon, and 0 otherwise.
150
int pnpoly(int nvert, double vert[][2], double testx, double testy){
157
// check for boundrary cases
158
for(i = 0; i < nvert; i++){
159
if(testx == vert[i][0] && testy == vert[i][1]){
168
for(i = 1; i < nvert; i++){
169
p2.x = vert[i % nvert][0];
170
p2.y = vert[i % nvert][1];
172
if(testy > MIN(p1.y,p2.y)){
173
if (testy <= MAX(p1.y,p2.y)) {
174
if (testx <= MAX(p1.x,p2.x)) {
176
xinters = (testy-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x;
177
if (p1.x == p2.x || testx <= xinters){
186
if(counter % 2 == 0){
194
* Calculates if the coordinates are within the image of projection.
195
* @param x the x-coordinate to check.
196
* @param y the y-coordinate to check.
197
* @param proj 0 for healpix and 1 for rhealpix.
198
* @param npole the positions of the polar squares, only used for rhealpix.
199
* @param spole the positions of the polar squares, only used for rhealpix.
200
* @return 1 if the coordinate is within the projection and 0 otherwise.
202
int in_image(double x, double y, int proj, int npole, int spole){
204
double healpixVertsJit[][2] = {
205
{-1.0*PI-EPS ,PI/4.0},
206
{-3.0*PI/4.0 ,PI/2.0+EPS},
207
{-1.0*PI/2.0 ,PI/4.0+EPS},
208
{-1.0*PI/4.0 ,PI/2.0+EPS},
210
{PI/4.0 ,PI/2.0+EPS},
211
{PI/2.0 ,PI/4.0+EPS},
212
{3.0*PI/4.0 ,PI/2.0+EPS},
214
{PI+EPS ,-1.0*PI/4.0},
215
{3.0*PI/4.0 ,-1.0*PI/2.0-EPS},
216
{PI/2.0 ,-1.0*PI/4.0-EPS},
217
{PI/4.0 ,-1.0*PI/2.0-EPS},
218
{0.0 ,-1.0*PI/4.0-EPS},
219
{-1.0*PI/4.0 ,-1.0*PI/2.0-EPS},
220
{-1.0*PI/2.0 ,-1.0*PI/4.0-EPS},
221
{-3.0*PI/4.0 ,-1.0*PI/2.0-EPS},
222
{-1.0*PI-EPS ,-1.0*PI/4.0}};
223
return pnpoly((int)sizeof(healpixVertsJit)/sizeof(healpixVertsJit[0]),
224
healpixVertsJit,x,y);
226
// Used for calculating if a point is within the rHEALPix projection for sphere.
227
double rhealpixVertsJit[][2] = {
228
{-1.0*PI-EPS ,PI/4.0+EPS},
229
{-1.0*PI + npole*PI/2.0-EPS ,PI/4.0+EPS},
230
{-1.0*PI + npole*PI/2.0-EPS ,3*PI/4.0+EPS},
231
{-1.0*PI + (npole + 1.0)*PI/2.0+EPS ,3*PI/4.0+EPS},
232
{-1.0*PI + (npole + 1.0)*PI/2.0+EPS ,PI/4.0+EPS},
233
{PI+EPS ,PI/4.0+EPS},
234
{PI+EPS ,-1.0*PI/4.0-EPS},
235
{-1.0*PI + (spole + 1.0)*PI/2.0+EPS ,-1.0*PI/4.0-EPS},
236
{-1.0*PI + (spole + 1.0)*PI/2.0+EPS ,-3.0*PI/4.0-EPS},
237
{-1.0*PI + spole*PI/2.0-EPS ,-3.0*PI/4.0-EPS},
238
{-1.0*PI + spole*PI/2.0-EPS ,-1.0*PI/4.0-EPS},
239
{-1.0*PI-EPS ,-1.0*PI/4.0-EPS}};
240
return pnpoly((int)sizeof(rhealpixVertsJit)/sizeof(rhealpixVertsJit[0]),
241
rhealpixVertsJit,x,y);
245
* Returns an authalic latitude of the point given a point of geographic
246
* latitude phi on an ellipse of eccentricity e.
247
* pj_authlat is the inverse of the alex's auth_lat.
250
* @param inverse 1 for inverse or 0 otherwise.
251
* @return the authalic latitude of the point.
253
double auth_lat(double phi, double e, int inverse){
255
double q_numerator = ((1.0 - pow(e,2.0)) * sin(phi));
256
double q_demonitor = (1.0 - (pow(e*sin(phi),2.0)));
257
double q_subtractor = - (1.0 - pow(e,2.0)) / (2.0*e) * log((1.0 - e*sin(phi)) / (1.0+e*sin(phi)));
258
double q = ((1.0 - pow(e,2.0)) * sin(phi)) / (1.0 - (pow(e*sin(phi),2.0))) -
259
(1.0 - pow(e,2.0)) / (2.0*e) * log((1.0 - e*sin(phi)) / (1.0+e*sin(phi)));
261
double qp = 1.0 - (1.0-pow(e,2.0)) / (2.0*e)*log((1.0 - e) / (1.0 + e));
264
if( fabsl(ratio) > 1){
269
return phi + (pow(e,2) / 3.0 + 31*pow(e,4) / 180.0 + 517.0*pow(e,6)/5040.0) * sin(2.0*phi)
270
+ (23.0*pow(e,4)/360.0 + 251.0*pow(e,6)/3780.0)*sin(4.0*phi)
271
+ 761.0*pow(e,6)/45360.0 * sin(6.0*phi);
274
* Compute the forward signature functions of the HEALPix
275
* projection of a sphere with radius `R` and central meridian `lon0`.
277
XY healpix_sphere(LP lp, PJ *P){
278
double lam = standardize_lon(lp.lam);
279
double phi = standardize_lat(lp.phi);
280
double phi0 = aasin(P->ctx, 2.0/3.0);
283
if( fabsl(phi) <= phi0) {
285
xy.y = 3.0*PI/8.0*sin(phi);
288
double sigma = sqrt(3.0 * (1 - fabsl(sin(phi))));
289
double cn = floor(2 * lam / PI + 2);
293
lamc = -3*PI/4 + (PI/2)*cn;
294
xy.x = lamc + (lam - lamc) * sigma;
295
xy.y = sign(phi)*PI/4 * (2 - sigma);
297
xy.x = scale_number(xy.x,P->a,0);
298
xy.y = scale_number(xy.y,P->a,0);
302
* Compute the inverse signature functions of the HEALPix
303
* projection of a sphere with radius `R` and central meridian `lon0`.
305
LP healpix_sphere_inv(XY xy, PJ *P){
311
// Scale down to radius 1 sphere
312
x = scale_number(xy.x,P->a,1);
313
y = scale_number(xy.y,P->a,1);
315
// Equatorial region.
318
lp.phi = asin(8.0*y/(3.0*PI));
319
} else if(fabsl(y) < PI/2.0){
320
cn = floor(2.0 * x/PI + 2.0);
324
xc = -3.0 * PI/4.0 + (PI/2.0)*cn;
325
tau = 2.0 - 4.0*fabsl(y)/PI;
326
lp.lam = xc + (x - xc)/tau;
327
lp.phi = sign(y)*asin(1.0 - pow(tau , 2.0)/3.0);
329
lp.lam = -1.0*PI - P->lam0;
330
lp.phi = sign(y)*PI/2.0;
335
* Adds one vector to another of length 2.
336
* @param a the first term.
337
* @param b the second term.
338
* @param ret holds the summation of the vectors.
340
static void vector_add(double a[], double b[],double * ret){
342
for(i = 0; i < 2; i++){
343
ret[i] = a[i] + b[i];
347
* Subs tracts one vector from another of length 2.
348
* @param a the minuend.
349
* @param b the subtrahend.
350
* @param ret the difference of the vectors where the difference is the result of a minus b.
352
static void vector_sub(double a[], double b[], double * ret){
354
for(i = 0; i < 2; i++){
355
ret[i] = a[i] - b[i];
359
* Calculates the dot product of the arrays.
360
* @param a the array that will be used to calculate the dot product.
361
* Must contain the same number of columns as b's rows. Must be a matrix with equal lengthed rows and columns.
362
* @param b the array that will be used to calculate the dot product; must contain the same number of rows as a's columns.
363
* @param length the size of the b array. Note, a's column size must equal b's length.
364
* @param ret the dot product of a and b.
366
static void dot_product(double a[2][2], double b[], double * ret){
369
for(i = 0; i < length; i++){
371
for(j = 0; j < length; j++){
372
ret[i] += a[i][j]*b[i];
377
* Returns the polar cap number, pole point coordinates, and region
378
* for x,y in the HEALPix projection of the sphere of radius R.
379
* @param x coordinate in the HEALPix or rHEALPix.
380
* @param y coordinate in the HEALPix or rHEALPix.
381
* @param npole integer between 0 and 3 indicating the position of the north pole.
382
* @param spole integer between 0 and 3 indicating teh position of the south pole.
383
* @param inverse 1 computes the rHEALPix projection and 0 computes forward.
384
* @return a structure containing the cap poles.
386
static CapMap get_cap(double x, double y, double R, int npole, int spole, int inverse){
395
capmap.region = north;
397
}else if(y < -1*R*PI/4.0){
398
capmap.region = south;
401
capmap.region = equatorial;
408
capmap.x = (-1*R*3.0*PI/4.0);
410
}else if(x >= -1*R*PI/2.0 && x < 0){
412
capmap.x = -1*R*PI/4.0;
414
}else if(x >= 0 && x < R*PI/2.0){
420
capmap.x = R*3.0*PI/4.0;
428
capmap.region = north;
429
capmap.x = -1*R*3.0*PI/4.0 + npole*R*PI/2.0;
431
x = x - npole*R*PI/2.0;
432
}else if(y < -1*R*PI/4.0){
433
capmap.region = south;
434
capmap.x = -1*R*3.0*PI/4.0 + spole*R*PI/2;
435
capmap.y = -1*R*PI/2.0;
436
x = x - spole*R*PI/2.0;
438
capmap.region = equatorial;
442
// Polar Region, find # of HEALPix polar cap number that
443
// x,y moves to when rHEALPix polar square is disassembled.
444
eps = R*1e-15; // Kludge. Fuzz to avoid some rounding errors.
445
if(capmap.region == north){
446
if(y >= -1*x - R*PI/4.0 - eps && y < x + R*5.0*PI/4.0 - eps){
448
}else if(y > -1*x -1*R*PI/4.0 + eps && y >= x + R*5.0*PI/4.0 - eps){
450
}else if(y <= -1*x -1*R*PI/4.0 + eps && y > x + R*5.0*PI/4.0 + eps){
455
}else if(capmap.region == south){
456
if(y <= x + R*PI/4.0 + eps && y > -1*x - R*5.0*PI/4 + eps){
458
}else if(y < x + R*PI/4.0 - eps && y <= -1*x - R*5.0*PI/4.0 + eps){
460
}else if(y >= x + R*PI/4.0 - eps && y < -1*x - R*5.0*PI/4.0 - eps){
470
* Rearrange point x,y in the HEALPix projection by
471
* combining the polar caps into two polar squares.
472
* Put the north polar square in position npole and
473
* the south polar square in position spole.
474
* @param x coordinate in the HEALPix projection of the sphere.
475
* @param y coordinate in the HEALPix projection of the sphere.
476
* @param R - the Sphere's radius.
477
* @param npole integer between 0 and 3 indicating the position
478
* of the north polar square.
479
* @param spole integer between 0 and 3 indicating the position
480
* of the south polar square.
481
* @param inverse 1 to uncombine the polar caps and 0 to combine.
483
static XY combine_caps(double x, double y, double R, int npole, int spole, int inverse){
492
CapMap capmap = get_cap(x,y,R,npole,spole,inverse);
494
if(capmap.region == equatorial){
502
// compute forward function by rotating, translating, and shifting xy.
505
double c[2] = {capmap.x,capmap.y};
506
if(capmap.region == north){
508
tmpRot = rot[capmap.cn];
509
a[0] = R*-3.0*PI/4.0;
513
tmpRot = rot[capmap.cn+RFACTOR];
514
a[0] = R*-3.0*PI/4.0;
518
tmpVect[0] = R*pole*PI/2.0;
520
// translate, rotate, then shift
521
vector_sub(v,c,v_min_c);
522
dot_product(tmpRot,v_min_c,ret_dot);
523
vector_add(a,tmpVect,ret_add);
524
vector_add(ret_dot, ret_add, vector);
529
// compute inverse function.
530
// get the current position of rHEALPix polar squares
531
int pole = floor( (capmap.x + R*3.0*PI/4.0) / (R*PI/2.0));
532
double tmpVect[2] = {R*pole*PI/2.0,0};
533
double coord[2] = {x,y};
536
// translate polar square to position 0
537
vector_sub(coord,tmpVect,v);
539
if(capmap.region == north){
540
cn = capmap.cn + RFACTOR;
548
tmpVect[0] = R*capmap.cn*PI/2.0;
550
// Math: Rotate Matrix * v-a + a + R*CN*{PI/2,0}
551
vector_sub(v,a,v_min_c);
552
dot_product(rot[cn],v_min_c,ret_dot);
553
vector_add(ret_dot,a,ret_add);
554
vector_add(ret_add,tmpVect,vector);
560
FORWARD(e_healpix_forward); /* ellipsoidal */
562
double bet = auth_lat(lp.phi, P->e, 0);
565
return healpix_sphere(lp,P);
567
FORWARD(s_healpix_forward); /* spheroid */
568
return healpix_sphere(lp, P);
570
INVERSE(e_healpix_inverse); /* ellipsoidal */
574
// Scale down to radius 1 sphere before checking x,y
575
x = scale_number(xy.x,P->a,1);
576
y = scale_number(xy.y,P->a,1);
577
// check if the point is in the image
578
if(in_image(x,y,0,0,0) == 0){
581
pj_ctx_set_errno( P->ctx, -15);
585
lp = healpix_sphere_inv(xy, P);
587
lp.phi = auth_lat(lp.phi,P->e,1);
591
INVERSE(s_healpix_inverse); /* spheroid */
594
// Scale down to radius 1 sphere before checking x,y
595
x = scale_number(x,P->a,1);
596
y = scale_number(y,P->a,1);
597
// check if the point is in the image
598
if(in_image(x,y,0,0,0) == 0){
601
pj_ctx_set_errno( P->ctx, -15);
604
return healpix_sphere_inv(xy, P);
606
FORWARD(e_rhealpix_forward); /* ellipsoidal */
607
double bet = auth_lat(lp.phi,P->e,0);
609
xy = healpix_sphere(lp,P);
610
return combine_caps(xy.x, xy.y, P->a, P->npole, P->spole, 0);
612
FORWARD(s_rhealpix_forward); /* spheroid */
613
// Compute forward function.
614
xy = healpix_sphere(lp,P);
615
return combine_caps(xy.x, xy.y, P->a, P->npole, P->spole, 0);
617
INVERSE(e_rhealpix_inverse); /* ellipsoidal */
618
double x = scale_number(xy.x,P->a,1);
619
double y = scale_number(xy.y,P->a,1);
620
// check for out of bounds coordinates
621
if(in_image(x,y,1,P->npole,P->spole) == 0){
624
pj_ctx_set_errno( P->ctx, -15);
628
xy = combine_caps(xy.x,xy.y,P->a,P->npole,P->spole,1);
629
lp = healpix_sphere_inv(xy, P);
630
lp.phi = auth_lat(lp.phi,P->e,1);
633
INVERSE(s_rhealpix_inverse); /* spheroid */
634
double x = scale_number(xy.x,P->a,1);
635
double y = scale_number(xy.y,P->a,1);
636
// check for out of bounds coordinates
637
if(in_image(x,y,1,P->npole,P->spole) == 0){
640
pj_ctx_set_errno( P->ctx, -15);
643
xy = combine_caps(xy.x,xy.y,P->a,P->npole,P->spole,1);
644
return healpix_sphere_inv(xy, P);
653
P->inv = e_healpix_inverse; P->fwd = e_healpix_forward;
655
P->inv = s_healpix_inverse; P->fwd = s_healpix_forward;
659
P->npole = pj_param(P->ctx, P->params,"inpole").i;
660
P->spole = pj_param(P->ctx,P->params,"ispole").i;
662
// check for valid npole and spole inputs
663
if(P->npole < 0 || P->npole > 3){
666
if(P->spole < 0 || P->spole > 3){
671
P->inv = e_rhealpix_inverse; P->fwd = e_rhealpix_forward;
673
P->inv = s_rhealpix_inverse; P->fwd = s_rhealpix_forward;