EVOLUTION-MANAGER
Edit File: PJ_healpix.c
/****************************************************************************** * $Id: PJ_healpix.c 1504 2011-10-18 14:58:57Z landcare $ * * Project: PROJ.4 * Purpose: Implementation of the healpix projection. * Definition: http://code.scenzgrid.org/index.php/p/scenzgrid-py/source/tree/master/docs/scenzgrid.pdf * Author: Alex Raichev & Michael Speth , spethm@landcareresearch.co.nz * ****************************************************************************** * Copyright (c) 2001, Thomas Flemming, tf@ttqv.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *****************************************************************************/ #define PROJ_PARMS__ \ int npole;\ int spole; #define PJ_LIB__ # include <projects.h> PROJ_HEAD(healpix, "HEALPix") "\n\tSph., Ellps."; PROJ_HEAD(rhealpix, "rHEALPix") "\n\tSph., Ellps.\n\tnpole= spole="; # include <stdio.h> # define R1 {{ 0,-1},{ 1, 0}} /** Matrix for anticlockwise rotation by pi/2 **/ # define R2 {{-1, 0},{ 0,-1}} /** Matrix for anticlockwise rotation by pi (R1 X R1) X = dot product **/ # define R3 {{ 0, 1},{-1, 0}} /** Matrix for anticlockwise rotation by 3*pi/2 (R2 X R1) **/ # define IDENT {{1,0},{0,1}} /** * 0 - Identity matrix<br> * 1 - Counter-clockwise rotation by PI/2<br> * 2 - Counter-clockwise rotation by PI<br> * 3 - Counter-clockwise rotation by 3*PI/2<br> * 4 - Counter-clockwise rotation by 3*PI/2<br> * 5 - Counter-clockwise rotation by PI<br> * 6 - Counter-clockwise rotation by PI/2<br> **/ # define ROT { IDENT, R1, R2, R3, R3, R2, R1} # define RFACTOR 3 /** Used for returning the rotation matrix **/ /** Used for calculating if a point is within the HEALPix projection for sphere. **/ # define EPS 1e-12 typedef struct { int cn; // the number 0 -> 4 indicating the position of the polar cap. double x,y; // the coordinates of the pole points (point of most extreme latitude on the polar caps). enum Region { north, south, equatorial } region; } CapMap; typedef struct { double x,y; } Point; double rot[7][2][2] = ROT; /** NOTES: Alex Raichev implemented the math in python and this is a port of his work. The healpix projection is a Lambert cylindrical equal-area projection for equaltorial latitudes and an interrupted Colignon projection for polar latitudes. **/ /** * Returns the sign of the double. * @param v the parameter whose sign is returned. * @return 1 for positive number, -1 for negative, and 0 for zero. **/ double sign (double v) { return v > 0 ? 1 : (v < 0 ? -1 : 0); } /** * Scales the number by a factor. * @param num the number to be scaled. * @param factor the factor to scale the number by. * @param isInverse 1 for scaling the number by 1 / factor and 0 for scaling by the factor. * @return the scaled number. **/ double scale_number(double num, double factor, int isInverse){ if(isInverse == 1){ return num * 1.0/factor; } return num * factor; } /** * Scales all the items of the array by a factor. * @param xy **/ void scale_array(XY *array, double k, int inverse){ double c = 0; if (inverse == 1) { c = 1.0/k; }else{ c = k; } array->x *= c; array->y *= c; } /** * Given an angle return its equivalent angle. * @param x the angle to convert * @return the equivalent angle such that -PI <= the angle returend <= PI **/ double standardize_lon(double x){ if(x < -1*PI || x >= PI){ x = x - 2*PI*floor(x/(2*PI)); if(x >= PI){ x = x - 2*PI; } } return x; } /** * Given an angle, return its unit-circle equivalent angle. * @param x the angel to convert. * @return the equivalent angle such that -PI/2 <= the angle returned <= PI/2. **/ double standardize_lat(double x){ if( x < -PI/2.0 || x > PI/2){ x = x-2.0*PI*floor(x/(2.0*PI)); if(x > PI/2.0 && x <= 3.0*PI/2){ x = PI - x; }else{ x = x - 2*PI; } } return x; } /** * Calculates if the point lies on or within the polygon. * Very good explination of how this works: http://paulbourke.net/geometry/insidepoly/ * @param nvert the number of vertices in the polygon. * @param vert the x,y-coordinates of the polygon's vertices * @param testx the x-coordinate of the test point. * @param testy the y-coordinate of the test point. * @return 1 if on or within the bounds of the polygon, and 0 otherwise. **/ static int pnpoly(int nvert, double vert[][2], double testx, double testy){ int i,j,c = 0; int counter = 0; double xinters; Point p1,p2; // check for boundrary cases for(i = 0; i < nvert; i++){ if(testx == vert[i][0] && testy == vert[i][1]){ return 1; } } // initialize p1 p1.x = vert[0][0]; p1.y = vert[0][1]; for(i = 1; i < nvert; i++){ p2.x = vert[i % nvert][0]; p2.y = vert[i % nvert][1]; if(testy > MIN(p1.y,p2.y)){ if (testy <= MAX(p1.y,p2.y)) { if (testx <= MAX(p1.x,p2.x)) { if (p1.y != p2.y) { xinters = (testy-p1.y)*(p2.x-p1.x)/(p2.y-p1.y)+p1.x; if (p1.x == p2.x || testx <= xinters){ counter++; } } } } } p1 = p2; } if(counter % 2 == 0){ return 0; }else{ return 1; } return c; } /** * Calculates if the coordinates are within the image of projection. * @param x the x-coordinate to check. * @param y the y-coordinate to check. * @param proj 0 for healpix and 1 for rhealpix. * @param npole the positions of the polar squares, only used for rhealpix. * @param spole the positions of the polar squares, only used for rhealpix. * @return 1 if the coordinate is within the projection and 0 otherwise. **/ int in_image(double x, double y, int proj, int npole, int spole){ if(proj == 0){ double healpixVertsJit[][2] = { {-1.0*PI-EPS ,PI/4.0}, {-3.0*PI/4.0 ,PI/2.0+EPS}, {-1.0*PI/2.0 ,PI/4.0+EPS}, {-1.0*PI/4.0 ,PI/2.0+EPS}, {0.0 ,PI/4.0+EPS}, {PI/4.0 ,PI/2.0+EPS}, {PI/2.0 ,PI/4.0+EPS}, {3.0*PI/4.0 ,PI/2.0+EPS}, {PI+EPS ,PI/4.0}, {PI+EPS ,-1.0*PI/4.0}, {3.0*PI/4.0 ,-1.0*PI/2.0-EPS}, {PI/2.0 ,-1.0*PI/4.0-EPS}, {PI/4.0 ,-1.0*PI/2.0-EPS}, {0.0 ,-1.0*PI/4.0-EPS}, {-1.0*PI/4.0 ,-1.0*PI/2.0-EPS}, {-1.0*PI/2.0 ,-1.0*PI/4.0-EPS}, {-3.0*PI/4.0 ,-1.0*PI/2.0-EPS}, {-1.0*PI-EPS ,-1.0*PI/4.0}}; return pnpoly((int)sizeof(healpixVertsJit)/sizeof(healpixVertsJit[0]), healpixVertsJit,x,y); }else{ // Used for calculating if a point is within the rHEALPix projection for sphere. double rhealpixVertsJit[][2] = { {-1.0*PI-EPS ,PI/4.0+EPS}, {-1.0*PI + npole*PI/2.0-EPS ,PI/4.0+EPS}, {-1.0*PI + npole*PI/2.0-EPS ,3*PI/4.0+EPS}, {-1.0*PI + (npole + 1.0)*PI/2.0+EPS ,3*PI/4.0+EPS}, {-1.0*PI + (npole + 1.0)*PI/2.0+EPS ,PI/4.0+EPS}, {PI+EPS ,PI/4.0+EPS}, {PI+EPS ,-1.0*PI/4.0-EPS}, {-1.0*PI + (spole + 1.0)*PI/2.0+EPS ,-1.0*PI/4.0-EPS}, {-1.0*PI + (spole + 1.0)*PI/2.0+EPS ,-3.0*PI/4.0-EPS}, {-1.0*PI + spole*PI/2.0-EPS ,-3.0*PI/4.0-EPS}, {-1.0*PI + spole*PI/2.0-EPS ,-1.0*PI/4.0-EPS}, {-1.0*PI-EPS ,-1.0*PI/4.0-EPS}}; return pnpoly((int)sizeof(rhealpixVertsJit)/sizeof(rhealpixVertsJit[0]), rhealpixVertsJit,x,y); } } /** * Returns an authalic latitude of the point given a point of geographic * latitude phi on an ellipse of eccentricity e. * pj_authlat is the inverse of the alex's auth_lat. * @param phi * @param e * @param inverse 1 for inverse or 0 otherwise. * @return the authalic latitude of the point. **/ double auth_lat(double phi, double e, int inverse){ if(inverse == 0){ double q_numerator = ((1.0 - pow(e,2.0)) * sin(phi)); double q_demonitor = (1.0 - (pow(e*sin(phi),2.0))); double q_subtractor = - (1.0 - pow(e,2.0)) / (2.0*e) * log((1.0 - e*sin(phi)) / (1.0+e*sin(phi))); double q = ((1.0 - pow(e,2.0)) * sin(phi)) / (1.0 - (pow(e*sin(phi),2.0))) - (1.0 - pow(e,2.0)) / (2.0*e) * log((1.0 - e*sin(phi)) / (1.0+e*sin(phi))); double qp = 1.0 - (1.0-pow(e,2.0)) / (2.0*e)*log((1.0 - e) / (1.0 + e)); double ratio = q/qp; // Rounding errors if( fabsl(ratio) > 1){ ratio = sign(ratio); } return asin(ratio); } return phi + (pow(e,2) / 3.0 + 31*pow(e,4) / 180.0 + 517.0*pow(e,6)/5040.0) * sin(2.0*phi) + (23.0*pow(e,4)/360.0 + 251.0*pow(e,6)/3780.0)*sin(4.0*phi) + 761.0*pow(e,6)/45360.0 * sin(6.0*phi); } /** * Compute the forward signature functions of the HEALPix * projection of a sphere with radius `R` and central meridian `lon0`. **/ XY healpix_sphere(LP lp, PJ *P){ double lam = standardize_lon(lp.lam); double phi = standardize_lat(lp.phi); double phi0 = aasin(P->ctx, 2.0/3.0); XY xy; // equatorial region if( fabsl(phi) <= phi0) { xy.x = lam; xy.y = 3.0*PI/8.0*sin(phi); } else { double lamc; double sigma = sqrt(3.0 * (1 - fabsl(sin(phi)))); double cn = floor(2 * lam / PI + 2); if (cn >= 4) { cn = 3; } lamc = -3*PI/4 + (PI/2)*cn; xy.x = lamc + (lam - lamc) * sigma; xy.y = sign(phi)*PI/4 * (2 - sigma); } xy.x = scale_number(xy.x,P->a,0); xy.y = scale_number(xy.y,P->a,0); return xy; } /** * Compute the inverse signature functions of the HEALPix * projection of a sphere with radius `R` and central meridian `lon0`. **/ LP healpix_sphere_inv(XY xy, PJ *P){ double x,y,y0; double cn; double xc; double tau; LP lp; // Scale down to radius 1 sphere x = scale_number(xy.x,P->a,1); y = scale_number(xy.y,P->a,1); y0 = PI/4.0; // Equatorial region. if(fabsl(y) <= y0){ lp.lam = x; lp.phi = asin(8.0*y/(3.0*PI)); } else if(fabsl(y) < PI/2.0){ cn = floor(2.0 * x/PI + 2.0); if(cn >= 4){ cn = 3; } xc = -3.0 * PI/4.0 + (PI/2.0)*cn; tau = 2.0 - 4.0*fabsl(y)/PI; lp.lam = xc + (x - xc)/tau; lp.phi = sign(y)*asin(1.0 - pow(tau , 2.0)/3.0); } else { lp.lam = -1.0*PI - P->lam0; lp.phi = sign(y)*PI/2.0; } return (lp); } /** * Adds one vector to another of length 2. * @param a the first term. * @param b the second term. * @param ret holds the summation of the vectors. **/ static void vector_add(double a[], double b[],double * ret){ int i; for(i = 0; i < 2; i++){ ret[i] = a[i] + b[i]; } } /** * Subs tracts one vector from another of length 2. * @param a the minuend. * @param b the subtrahend. * @param ret the difference of the vectors where the difference is the result of a minus b. **/ static void vector_sub(double a[], double b[], double * ret){ int i; for(i = 0; i < 2; i++){ ret[i] = a[i] - b[i]; } } /** * Calculates the dot product of the arrays. * @param a the array that will be used to calculate the dot product. * Must contain the same number of columns as b's rows. Must be a matrix with equal lengthed rows and columns. * @param b the array that will be used to calculate the dot product; must contain the same number of rows as a's columns. * @param length the size of the b array. Note, a's column size must equal b's length. * @param ret the dot product of a and b. **/ static void dot_product(double a[2][2], double b[], double * ret){ int i,j; int length = 2; for(i = 0; i < length; i++){ ret[i] = 0; for(j = 0; j < length; j++){ ret[i] += a[i][j]*b[i]; } } } /** * Returns the polar cap number, pole point coordinates, and region * for x,y in the HEALPix projection of the sphere of radius R. * @param x coordinate in the HEALPix or rHEALPix. * @param y coordinate in the HEALPix or rHEALPix. * @param npole integer between 0 and 3 indicating the position of the north pole. * @param spole integer between 0 and 3 indicating teh position of the south pole. * @param inverse 1 computes the rHEALPix projection and 0 computes forward. * @return a structure containing the cap poles. **/ static CapMap get_cap(double x, double y, double R, int npole, int spole, int inverse){ CapMap capmap; double c; capmap.x = x; capmap.y = y; if(inverse == 0){ if(y > R*PI/4.0){ capmap.region = north; c = R*PI/2.0; }else if(y < -1*R*PI/4.0){ capmap.region = south; c = -1*R*PI/2.0; }else{ capmap.region = equatorial; capmap.cn = 0; return capmap; } // polar region if(x < -1*R*PI/2.0){ capmap.cn = 0; capmap.x = (-1*R*3.0*PI/4.0); capmap.y = c; }else if(x >= -1*R*PI/2.0 && x < 0){ capmap.cn = 1; capmap.x = -1*R*PI/4.0; capmap.y = c; }else if(x >= 0 && x < R*PI/2.0){ capmap.cn = 2; capmap.x = R*PI/4.0; capmap.y = c; }else{ capmap.cn = 3; capmap.x = R*3.0*PI/4.0; capmap.y = c; } return capmap; }else{ double c; double eps; if(y > R*PI/4.0){ capmap.region = north; capmap.x = -1*R*3.0*PI/4.0 + npole*R*PI/2.0; capmap.y = R*PI/2.0; x = x - npole*R*PI/2.0; }else if(y < -1*R*PI/4.0){ capmap.region = south; capmap.x = -1*R*3.0*PI/4.0 + spole*R*PI/2; capmap.y = -1*R*PI/2.0; x = x - spole*R*PI/2.0; }else{ capmap.region = equatorial; capmap.cn = 0; return capmap; } // Polar Region, find # of HEALPix polar cap number that // x,y moves to when rHEALPix polar square is disassembled. eps = R*1e-15; // Kludge. Fuzz to avoid some rounding errors. if(capmap.region == north){ if(y >= -1*x - R*PI/4.0 - eps && y < x + R*5.0*PI/4.0 - eps){ capmap.cn = 1; }else if(y > -1*x -1*R*PI/4.0 + eps && y >= x + R*5.0*PI/4.0 - eps){ capmap.cn = 2; }else if(y <= -1*x -1*R*PI/4.0 + eps && y > x + R*5.0*PI/4.0 + eps){ capmap.cn = 3; }else{ capmap.cn = 0; } }else if(capmap.region == south){ if(y <= x + R*PI/4.0 + eps && y > -1*x - R*5.0*PI/4 + eps){ capmap.cn = 1; }else if(y < x + R*PI/4.0 - eps && y <= -1*x - R*5.0*PI/4.0 + eps){ capmap.cn = 2; }else if(y >= x + R*PI/4.0 - eps && y < -1*x - R*5.0*PI/4.0 - eps){ capmap.cn = 3; }else { capmap.cn = 0; } } return capmap; } } /** * Rearrange point x,y in the HEALPix projection by * combining the polar caps into two polar squares. * Put the north polar square in position npole and * the south polar square in position spole. * @param x coordinate in the HEALPix projection of the sphere. * @param y coordinate in the HEALPix projection of the sphere. * @param R - the Sphere's radius. * @param npole integer between 0 and 3 indicating the position * of the north polar square. * @param spole integer between 0 and 3 indicating the position * of the south polar square. * @param inverse 1 to uncombine the polar caps and 0 to combine. **/ static XY combine_caps(double x, double y, double R, int npole, int spole, int inverse){ XY xy; double v[2]; double a[2]; double vector[2]; double tmpVect[2]; double v_min_c[2]; double ret_dot[2]; double ret_add[2]; CapMap capmap = get_cap(x,y,R,npole,spole,inverse); if(capmap.region == equatorial){ xy.x = capmap.x; xy.y = capmap.y; return xy; } v[0] = x; v[1] = y; if(inverse == 0){ // compute forward function by rotating, translating, and shifting xy. int pole = 0; double (*tmpRot)[2]; double c[2] = {capmap.x,capmap.y}; if(capmap.region == north){ pole = npole; tmpRot = rot[capmap.cn]; a[0] = R*-3.0*PI/4.0; a[1] = PI/2.0; }else { pole = spole; tmpRot = rot[capmap.cn+RFACTOR]; a[0] = R*-3.0*PI/4.0; a[1] = PI/-2.0; } tmpVect[0] = R*pole*PI/2.0; tmpVect[1] = 0; // translate, rotate, then shift vector_sub(v,c,v_min_c); dot_product(tmpRot,v_min_c,ret_dot); vector_add(a,tmpVect,ret_add); vector_add(ret_dot, ret_add, vector); xy.x = vector[0]; xy.y = vector[1]; return xy; }else{ // compute inverse function. // get the current position of rHEALPix polar squares int pole = floor( (capmap.x + R*3.0*PI/4.0) / (R*PI/2.0)); double tmpVect[2] = {R*pole*PI/2.0,0}; double coord[2] = {x,y}; double (*tmpRot)[2]; int cn; // translate polar square to position 0 vector_sub(coord,tmpVect,v); // disassemble if(capmap.region == north){ cn = capmap.cn + RFACTOR; a[0] = R*-3*PI/4.0; a[1] = PI/2.0; }else{ cn = capmap.cn; a[0] = R*-3*PI/4.0; a[1] = PI/-2.0; } tmpVect[0] = R*capmap.cn*PI/2.0; tmpVect[1] = 0; // Math: Rotate Matrix * v-a + a + R*CN*{PI/2,0} vector_sub(v,a,v_min_c); dot_product(rot[cn],v_min_c,ret_dot); vector_add(ret_dot,a,ret_add); vector_add(ret_add,tmpVect,vector); xy.x = vector[0]; xy.y = vector[1]; return xy; } } FORWARD(e_healpix_forward); /* ellipsoidal */ //int r1[][2] = R1; double bet = auth_lat(lp.phi, P->e, 0); lp.phi = bet; P->a = P->ra; return healpix_sphere(lp,P); } FORWARD(s_healpix_forward); /* spheroid */ return healpix_sphere(lp, P); } INVERSE(e_healpix_inverse); /* ellipsoidal */ double bet, x, y; P->a = P->ra; // Scale down to radius 1 sphere before checking x,y x = scale_number(xy.x,P->a,1); y = scale_number(xy.y,P->a,1); // check if the point is in the image if(in_image(x,y,0,0,0) == 0){ lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; pj_ctx_set_errno( P->ctx, -15); return lp; } lp = healpix_sphere_inv(xy, P); lp.phi = auth_lat(lp.phi,P->e,1); return (lp); } INVERSE(s_healpix_inverse); /* spheroid */ double x = xy.x; double y = xy.y; // Scale down to radius 1 sphere before checking x,y x = scale_number(x,P->a,1); y = scale_number(y,P->a,1); // check if the point is in the image if(in_image(x,y,0,0,0) == 0){ lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; pj_ctx_set_errno( P->ctx, -15); return lp; } return healpix_sphere_inv(xy, P); } FORWARD(e_rhealpix_forward); /* ellipsoidal */ double bet = auth_lat(lp.phi,P->e,0); lp.phi = bet; xy = healpix_sphere(lp,P); return combine_caps(xy.x, xy.y, P->a, P->npole, P->spole, 0); } FORWARD(s_rhealpix_forward); /* spheroid */ // Compute forward function. xy = healpix_sphere(lp,P); return combine_caps(xy.x, xy.y, P->a, P->npole, P->spole, 0); } INVERSE(e_rhealpix_inverse); /* ellipsoidal */ double x = scale_number(xy.x,P->a,1); double y = scale_number(xy.y,P->a,1); // check for out of bounds coordinates if(in_image(x,y,1,P->npole,P->spole) == 0){ lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; pj_ctx_set_errno( P->ctx, -15); return lp; } xy = combine_caps(xy.x,xy.y,P->a,P->npole,P->spole,1); lp = healpix_sphere_inv(xy, P); lp.phi = auth_lat(lp.phi,P->e,1); return lp; } INVERSE(s_rhealpix_inverse); /* spheroid */ double x = scale_number(xy.x,P->a,1); double y = scale_number(xy.y,P->a,1); // check for out of bounds coordinates if(in_image(x,y,1,P->npole,P->spole) == 0){ lp.lam = HUGE_VAL; lp.phi = HUGE_VAL; pj_ctx_set_errno( P->ctx, -15); return lp; } xy = combine_caps(xy.x,xy.y,P->a,P->npole,P->spole,1); return healpix_sphere_inv(xy, P); } FREEUP; if (P) { pj_dalloc(P); } } ENTRY0(healpix) if(P->es){ P->inv = e_healpix_inverse; P->fwd = e_healpix_forward; }else{ P->inv = s_healpix_inverse; P->fwd = s_healpix_forward; } ENDENTRY(P) ENTRY0(rhealpix) P->npole = pj_param(P->ctx, P->params,"inpole").i; P->spole = pj_param(P->ctx,P->params,"ispole").i; // check for valid npole and spole inputs if(P->npole < 0 || P->npole > 3){ E_ERROR(-47); } if(P->spole < 0 || P->spole > 3){ E_ERROR(-47); } if(P->es){ P->inv = e_rhealpix_inverse; P->fwd = e_rhealpix_forward; }else{ P->inv = s_rhealpix_inverse; P->fwd = s_rhealpix_forward; } ENDENTRY(P)