diff options
Diffstat (limited to 'src/projections/adams.cpp')
| -rw-r--r-- | src/projections/adams.cpp | 150 |
1 files changed, 148 insertions, 2 deletions
diff --git a/src/projections/adams.cpp b/src/projections/adams.cpp index 389ca054..4150fff2 100644 --- a/src/projections/adams.cpp +++ b/src/projections/adams.cpp @@ -6,9 +6,21 @@ * PROJ by Kristian Evers. Original code found in file src/proj_guyou.c, see * https://github.com/rouault/libproj4/blob/master/libproject-1.01/src/proj_guyou.c * for reference. +* Fix for Peirce Quincuncial projection to diamond or square by Toby C. Wilkinson to +* correctly flip out southern hemisphere into the four triangles of Peirce's +* quincunx. The fix inspired by a similar rotate and translate solution applied +* by Jonathan Feinberg for cartopy, see +* https://github.com/jonathf/cartopy/blob/8172cac7fc45cafc86573d408ce85b74258a9c28/lib/cartopy/peircequincuncial.py +* Added original code for horizontal and vertical arrangement of hemispheres by Toby +* C. Wilkinson to allow creation of lateral quincuncial projections, such as Grieger's +* Triptychial, see, e.g.: +* - Grieger, B. (2020). “Optimized global map projections for specific applications: +* the triptychial projection and the Spilhaus projection”. EGU2020-9885. +* https://doi.org/10.5194/egusphere-egu2020-9885 * * Copyright (c) 2005, 2006, 2009 Gerald I. Evenden * Copyright (c) 2020 Kristian Evers +* Copyright (c) 2021 Toby C Wilkinson * * Related material * ---------------- @@ -48,8 +60,20 @@ enum projection_type { ADAMS_WS2, }; +enum peirce_type { + PEIRCE_Q_SQUARE, + PEIRCE_Q_DIAMOND, + PEIRCE_Q_NHEMISPHERE, + PEIRCE_Q_SHEMISPHERE, + PEIRCE_Q_HORIZONTAL, + PEIRCE_Q_VERTICAL, +}; + struct pj_opaque { projection_type mode; + peirce_type pqtype; + double scrollx = 0.0; + double scrolly = 0.0; }; } // anonymous namespace @@ -118,9 +142,18 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) { } break; case PEIRCE_Q: { - if( lp.phi < -TOL ) { + /* lam0 - note that the original Peirce model used a central meridian of around -70deg, but the default within proj is +lon0=0 */ + if (Q->pqtype == PEIRCE_Q_NHEMISPHERE) { + if( lp.phi < -TOL ) { + proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); + return proj_coord_error().xy; + } + } + if (Q->pqtype == PEIRCE_Q_SHEMISPHERE) { + if( lp.phi > -TOL ) { proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN); return proj_coord_error().xy; + } } const double sl = sin(lp.lam); const double cl = cos(lp.lam); @@ -173,7 +206,71 @@ static PJ_XY adams_forward(PJ_LP lp, PJ *P) { xy.x = ell_int_5(m); xy.y = ell_int_5(n); - if (Q->mode == ADAMS_HEMI || Q->mode == ADAMS_WS2) { /* rotate by 45deg. */ + if (Q->mode == PEIRCE_Q) { + /* Constant complete elliptic integral of the first kind with m=0.5, calculated using https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.ellipk.html . Used as basic as scaled shift distance */ + constexpr double shd = 1.8540746773013719 * 2; + + /* For square and diamond Quincuncial projections, spin out southern hemisphere to triangular segments of quincunx (before rotation for square)*/ + if( Q->pqtype == PEIRCE_Q_SQUARE || ( Q->pqtype == PEIRCE_Q_DIAMOND )) { + if (lp.phi < 0.) { /* fold out segments */ + if (lp.lam < ( -0.75 * M_PI )) xy.y = shd - xy.y; /* top left segment, shift up and reflect y */ + if ( (lp.lam < (-0.25 * M_PI)) && (lp.lam >= ( -0.75 * M_PI ))) xy.x = - shd - xy.x; /* left segment, shift left and reflect x */ + if ( (lp.lam < (0.25 * M_PI)) && (lp.lam >= ( -0.25 * M_PI ))) xy.y = - shd - xy.y; /* bottom segment, shift down and reflect y */ + if ( (lp.lam < (0.75 * M_PI)) && (lp.lam >= ( 0.25 * M_PI ))) xy.x = shd - xy.x; /* right segment, shift right and reflect x */ + if (lp.lam >= (0.75 * M_PI)) xy.y = shd - xy.y; /* top right segment, shift up and reflect y */ + } + } + + /* For square types rotate xy by 45 deg */ + if( Q->pqtype == PEIRCE_Q_SQUARE ) { + const double temp = xy.x; + xy.x = RSQRT2 * (xy.x - xy.y); + xy.y = RSQRT2 * (temp + xy.y); + } + + /* For rectangle Quincuncial projs, spin out southern hemisphere to east (horizontal) or north (vertical) after rotation */ + if( Q->pqtype == PEIRCE_Q_HORIZONTAL ) { + if (lp.phi < 0.) { + xy.x = shd - xy.x; /* reflect x to east */ + } + xy.x = xy.x - (shd / 2); /* shift everything so origin is in middle of two hemispheres */ + } + if( Q->pqtype == PEIRCE_Q_VERTICAL ) { + if (lp.phi < 0.) { + xy.y = shd - xy.y; /* reflect y to north */ + } + xy.y = xy.y - (shd / 2); /* shift everything so origin is in middle of two hemispheres */ + } + + //if o_scrollx param present, scroll x + if (!(Q->scrollx == 0.0) && (Q->pqtype == PEIRCE_Q_HORIZONTAL) ) { + double xscale = 2.0; + double xthresh = shd / 2; + xy.x = xy.x + (Q->scrollx * (xthresh * 2 * xscale)); /*shift relative to proj width*/ + if(xy.x >= (xthresh * xscale)) { + xy.x = xy.x - (shd * xscale); + } + else if (xy.x < -(xthresh * xscale)) { + xy.x = xy.x + (shd * xscale); + } + } + + //if o_scrolly param present, scroll y + if (!(Q->scrolly == 0.0) && (Q->pqtype == PEIRCE_Q_VERTICAL)) { + double yscale = 2.0; + double ythresh = shd / 2; + xy.y = xy.y + (Q->scrolly * (ythresh * 2 * yscale)); /*shift relative to proj height*/ + if(xy.y >= (ythresh * yscale)) { + xy.y = xy.y - (shd * yscale); + } + else if (xy.y < -(ythresh * yscale)) { + xy.y = xy.y + (shd * yscale); + } + } + + } + + if (Q->mode == ADAMS_HEMI || Q->mode == ADAMS_WS2 ) { /* rotate by 45deg. */ const double temp = xy.x; xy.x = RSQRT2 * (xy.x - xy.y); xy.y = RSQRT2 * (temp + xy.y); @@ -218,6 +315,55 @@ static PJ *setup(PJ *P, projection_type mode) { if( mode == ADAMS_WS2 ) P->inv = adams_inverse; + if( mode == PEIRCE_Q) { + // Quincuncial projections type options: square, diamond, hemisphere, horizontal (rectangle) or vertical (rectangle) + const char* pqtype = pj_param (P->ctx, P->params, "stype").s; + + if (!pqtype) pqtype = "diamond"; /* default if type value not supplied */ + + if (strcmp(pqtype, "square") == 0) { + Q->pqtype = PEIRCE_Q_SQUARE; + } + else if (strcmp(pqtype, "diamond") == 0) { + Q->pqtype = PEIRCE_Q_DIAMOND; + } + else if (strcmp(pqtype, "nhemisphere") == 0) { + Q->pqtype = PEIRCE_Q_NHEMISPHERE; + } + else if (strcmp(pqtype, "shemisphere") == 0) { + Q->pqtype = PEIRCE_Q_SHEMISPHERE; + } + else if (strcmp(pqtype, "horizontal") == 0) { + Q->pqtype = PEIRCE_Q_HORIZONTAL; + if (pj_param(P->ctx, P->params, "tscrollx").i) { + double scrollx; + scrollx = pj_param(P->ctx, P->params, "dscrollx").f; + if (scrollx > 1 || scrollx < -1) { + proj_log_error(P, _("Invalid value for scrollx: |scrollx| should between -1 and 1")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + Q->scrollx = scrollx; + } + } + else if (strcmp(pqtype, "vertical") == 0) { + Q->pqtype = PEIRCE_Q_VERTICAL; + if (pj_param(P->ctx, P->params, "tscrolly").i) { + double scrolly; + scrolly = pj_param(P->ctx, P->params, "dscrolly").f; + if (scrolly > 1 || scrolly < -1) { + proj_log_error(P, _("Invalid value for scrolly: |scrolly| should between -1 and 1")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + Q->scrolly = scrolly; + } + } + else { + proj_log_error (P, _("peirce_q: invalid value for 'type' parameter")); + return pj_default_destructor (P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); + } + + } + return P; } |
