diff options
| author | Frank Warmerdam <warmerdam@pobox.com> | 2013-12-06 03:12:39 +0000 |
|---|---|---|
| committer | Frank Warmerdam <warmerdam@pobox.com> | 2013-12-06 03:12:39 +0000 |
| commit | 2ed14f848d4297f28ee750383ec7c4b6d69b76ec (patch) | |
| tree | f8b1c2f511fd81a81a4da0461e07d656d8a9dea8 | |
| parent | dbe116267d341737e9abc7a20fa16b51fdc1982a (diff) | |
| download | PROJ-2ed14f848d4297f28ee750383ec7c4b6d69b76ec.tar.gz PROJ-2ed14f848d4297f28ee750383ec7c4b6d69b76ec.zip | |
add qsc projection (#179)
git-svn-id: http://svn.osgeo.org/metacrs/proj/trunk@2409 4e78687f-474d-0410-85f9-8d5e500ac6b2
| -rw-r--r-- | ChangeLog | 4 | ||||
| -rwxr-xr-x | nad/testvarious | 15 | ||||
| -rw-r--r-- | nad/tv_out.dist | 4 | ||||
| -rw-r--r-- | src/Makefile.am | 2 | ||||
| -rw-r--r-- | src/PJ_qsc.c | 375 | ||||
| -rw-r--r-- | src/makefile.vc | 2 | ||||
| -rw-r--r-- | src/pj_list.h | 1 |
7 files changed, 401 insertions, 2 deletions
@@ -1,3 +1,7 @@ +2013-12-05 Frank Warmerdam <warmerdam@pobox.com> + + * src/PJ_qsc.c: Add QSC projection (#179) + 2013-10-27 Frank Warmerdam <warmerdam@gdal-c> * Prepare 4.9.0beta2 release. diff --git a/nad/testvarious b/nad/testvarious index c20190ea..335fc6de 100755 --- a/nad/testvarious +++ b/nad/testvarious @@ -565,6 +565,21 @@ $EXE -f '%.3f' \ 163 -89.9 163 -80 EOF +echo "##############################################################" >> ${OUT} +echo "Test qsc" >> ${OUT} +# +$EXE -f '%.13f' \ + +proj=latlong +datum=WGS84 \ + +to +proj=qsc +datum=WGS84 +no_defs \ + -E >>${OUT} <<EOF +13 -10 +EOF +$EXE -f '%.13f' \ + +proj=qsc +datum=WGS84 +no_defs \ + +to +proj=latlong +datum=WGS84 \ + -E >>${OUT} <<EOF +2073986.94908809568733 -1680858.27222427958623 +EOF ############################################################################## # Done! # do 'diff' with distribution results diff --git a/nad/tv_out.dist b/nad/tv_out.dist index d8137311..c8607372 100644 --- a/nad/tv_out.dist +++ b/nad/tv_out.dist @@ -267,3 +267,7 @@ Test omerc differences between poles (#190) -27 -89.9 -10033520.737 402158.063 0.000 163 -89.9 -10055728.173 404099.799 0.000 163 -80 -11163496.121 397796.828 0.000 +############################################################## +Test qsc +13 -10 2073986.9490880956873 -1680858.2722242795862 0.0000000000000 +2073986.94908809568733 -1680858.27222427958623 13.0000000000000 -10.0000000000000 0.0000000000000 diff --git a/src/Makefile.am b/src/Makefile.am index 22c2902c..a47d832f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -49,7 +49,7 @@ libproj_la_SOURCES = \ PJ_gn_sinu.c PJ_goode.c PJ_igh.c PJ_hatano.c PJ_loxim.c \ PJ_mbt_fps.c PJ_mbtfpp.c PJ_mbtfpq.c PJ_moll.c \ PJ_nell.c PJ_nell_h.c PJ_putp2.c PJ_putp3.c \ - PJ_putp4p.c PJ_putp5.c PJ_putp6.c PJ_robin.c \ + PJ_putp4p.c PJ_putp5.c PJ_putp6.c PJ_qsc.c PJ_robin.c \ PJ_sts.c PJ_urm5.c PJ_urmfps.c PJ_wag2.c \ PJ_wag3.c PJ_wink1.c PJ_wink2.c pj_latlong.c pj_geocent.c \ aasincos.c adjlon.c bch2bps.c bchgen.c \ diff --git a/src/PJ_qsc.c b/src/PJ_qsc.c new file mode 100644 index 00000000..1c7a4435 --- /dev/null +++ b/src/PJ_qsc.c @@ -0,0 +1,375 @@ +/* + * This implements the Quadrilateralized Spherical Cube (QSC) projection. + * + * Copyright (c) 2011, 2012 Martin Lambers <marlam@marlam.de> + * + * The QSC projection was introduced in: + * [OL76] + * E.M. O'Neill and R.E. Laubscher, "Extended Studies of a Quadrilateralized + * Spherical Cube Earth Data Base", Naval Environmental Prediction Research + * Facility Tech. Report NEPRF 3-76 (CSC), May 1976. + * + * The preceding shift from an ellipsoid to a sphere, which allows to apply + * this projection to ellipsoids as used in the Ellipsoidal Cube Map model, + * is described in + * [LK12] + * M. Lambers and A. Kolb, "Ellipsoidal Cube Maps for Accurate Rendering of + * Planetary-Scale Terrain Data", Proc. Pacfic Graphics (Short Papers), Sep. + * 2012 + * + * You have to choose one of the following projection centers, + * corresponding to the centers of the six cube faces: + * phi0 = 0.0, lam0 = 0.0 ("front" face) + * phi0 = 0.0, lam0 = 90.0 ("right" face) + * phi0 = 0.0, lam0 = 180.0 ("back" face) + * phi0 = 0.0, lam0 = -90.0 ("left" face) + * phi0 = 90.0 ("top" face) + * phi0 = -90.0 ("bottom" face) + * Other projection centers will not work! + * + * In the projection code below, each cube face is handled differently. + * See the computation of the face parameter in the ENTRY0(qsc) function + * and the handling of different face values (FACE_*) in the forward and + * inverse projections. + * + * Furthermore, the projection is originally only defined for theta angles + * between (-1/4 * PI) and (+1/4 * PI) on the current cube face. This area + * of definition is named AREA_0 in the projection code below. The other + * three areas of a cube face are handled by rotation of AREA_0. + */ + +#define PROJ_PARMS__ \ + int face; \ + double a_squared; \ + double b; \ + double one_minus_f; \ + double one_minus_f_squared; +#define PJ_LIB__ +#include <projects.h> +PROJ_HEAD(qsc, "Quadrilateralized Spherical Cube") "\n\tAzi, Sph."; +#define EPS10 1.e-10 + +/* The six cube faces. */ +#define FACE_FRONT 0 +#define FACE_RIGHT 1 +#define FACE_BACK 2 +#define FACE_LEFT 3 +#define FACE_TOP 4 +#define FACE_BOTTOM 5 + +/* The four areas on a cube face. AREA_0 is the area of definition, + * the other three areas are counted counterclockwise. */ +#define AREA_0 0 +#define AREA_1 1 +#define AREA_2 2 +#define AREA_3 3 + +/* Helper function for forward projection: compute the theta angle + * and determine the area number. */ +static double +qsc_fwd_equat_face_theta(double phi, double y, double x, int *area) { + double theta; + if (phi < EPS10) { + *area = AREA_0; + theta = 0.0; + } else { + theta = atan2(y, x); + if (fabs(theta) <= FORTPI) { + *area = AREA_0; + } else if (theta > FORTPI && theta <= HALFPI + FORTPI) { + *area = AREA_1; + theta -= HALFPI; + } else if (theta > HALFPI + FORTPI || theta <= -(HALFPI + FORTPI)) { + *area = AREA_2; + theta = (theta >= 0.0 ? theta - PI : theta + PI); + } else { + *area = AREA_3; + theta += HALFPI; + } + } + return (theta); +} + +/* Helper function: shift the longitude. */ +static double +qsc_shift_lon_origin(double lon, double offset) { + double slon = lon + offset; + if (slon < -PI) { + slon += TWOPI; + } else if (slon > +PI) { + slon -= TWOPI; + } + return slon; +} + +/* Forward projection, ellipsoid */ +FORWARD(e_forward); + double lat, lon; + double sinlat, coslat; + double sinlon, coslon; + double q, r, s; + double theta, phi; + double t, mu, nu; + int area; + + /* Convert the geodetic latitude to a geocentric latitude. + * This corresponds to the shift from the ellipsoid to the sphere + * described in [LK12]. */ + if (P->es) { + lat = atan(P->one_minus_f_squared * tan(lp.phi)); + } else { + lat = lp.phi; + } + + /* Convert the input lat, lon into theta, phi as used by QSC. + * This depends on the cube face and the area on it. + * For the top and bottom face, we can compute theta and phi + * directly from phi, lam. For the other faces, we must use + * unit sphere cartesian coordinates as an intermediate step. */ + lon = lp.lam; + if (P->face != FACE_TOP && P->face != FACE_BOTTOM) { + if (P->face == FACE_RIGHT) { + lon = qsc_shift_lon_origin(lon, +HALFPI); + } else if (P->face == FACE_BACK) { + lon = qsc_shift_lon_origin(lon, +PI); + } else if (P->face == FACE_LEFT) { + lon = qsc_shift_lon_origin(lon, -HALFPI); + } + sinlat = sin(lat); + coslat = cos(lat); + sinlon = sin(lon); + coslon = cos(lon); + q = coslat * coslon; + r = coslat * sinlon; + s = sinlat; + } + if (P->face == FACE_FRONT) { + phi = acos(q); + theta = qsc_fwd_equat_face_theta(phi, s, r, &area); + } else if (P->face == FACE_RIGHT) { + phi = acos(r); + theta = qsc_fwd_equat_face_theta(phi, s, -q, &area); + } else if (P->face == FACE_BACK) { + phi = acos(-q); + theta = qsc_fwd_equat_face_theta(phi, s, -r, &area); + } else if (P->face == FACE_LEFT) { + phi = acos(-r); + theta = qsc_fwd_equat_face_theta(phi, s, q, &area); + } else if (P->face == FACE_TOP) { + phi = HALFPI - lat; + if (lon >= FORTPI && lon <= HALFPI + FORTPI) { + area = AREA_0; + theta = lon - HALFPI; + } else if (lon > HALFPI + FORTPI || lon <= -(HALFPI + FORTPI)) { + area = AREA_1; + theta = (lon > 0.0 ? lon - PI : lon + PI); + } else if (lon > -(HALFPI + FORTPI) && lon <= -FORTPI) { + area = AREA_2; + theta = lon + HALFPI; + } else { + area = AREA_3; + theta = lon; + } + } else /* P->face == FACE_BOTTOM */ { + phi = HALFPI + lat; + if (lon >= FORTPI && lon <= HALFPI + FORTPI) { + area = AREA_0; + theta = -lon + HALFPI; + } else if (lon < FORTPI && lon >= -FORTPI) { + area = AREA_1; + theta = -lon; + } else if (lon < -FORTPI && lon >= -(HALFPI + FORTPI)) { + area = AREA_2; + theta = -lon - HALFPI; + } else { + area = AREA_3; + theta = (lon > 0.0 ? -lon + PI : -lon - PI); + } + } + + /* Compute mu and nu for the area of definition. + * For mu, see Eq. (3-21) in [OL76], but note the typos: + * compare with Eq. (3-14). For nu, see Eq. (3-38). */ + mu = atan((12.0 / PI) * (theta + acos(sin(theta) * cos(FORTPI)) - HALFPI)); + t = sqrt((1.0 - cos(phi)) / (cos(mu) * cos(mu)) / (1.0 - cos(atan(1.0 / cos(theta))))); + /* nu = atan(t); We don't really need nu, just t, see below. */ + + /* Apply the result to the real area. */ + if (area == AREA_1) { + mu += HALFPI; + } else if (area == AREA_2) { + mu += PI; + } else if (area == AREA_3) { + mu += HALFPI + PI; + } + + /* Now compute x, y from mu and nu */ + /* t = tan(nu); */ + xy.x = t * cos(mu); + xy.y = t * sin(mu); + return (xy); +} + +/* Inverse projection, ellipsoid */ +INVERSE(e_inverse); + double mu, nu, cosmu, tannu; + double tantheta, theta, cosphi, phi; + double t; + int area; + + /* Convert the input x, y to the mu and nu angles as used by QSC. + * This depends on the area of the cube face. */ + nu = atan(sqrt(xy.x * xy.x + xy.y * xy.y)); + mu = atan2(xy.y, xy.x); + if (xy.x >= 0.0 && xy.x >= fabs(xy.y)) { + area = AREA_0; + } else if (xy.y >= 0.0 && xy.y >= fabs(xy.x)) { + area = AREA_1; + mu -= HALFPI; + } else if (xy.x < 0.0 && -xy.x >= fabs(xy.y)) { + area = AREA_2; + mu = (mu < 0.0 ? mu + PI : mu - PI); + } else { + area = AREA_3; + mu += HALFPI; + } + + /* Compute phi and theta for the area of definition. + * The inverse projection is not described in the original paper, but some + * good hints can be found here (as of 2011-12-14): + * http://fits.gsfc.nasa.gov/fitsbits/saf.93/saf.9302 + * (search for "Message-Id: <9302181759.AA25477 at fits.cv.nrao.edu>") */ + t = (PI / 12.0) * tan(mu); + tantheta = sin(t) / (cos(t) - (1.0 / sqrt(2.0))); + theta = atan(tantheta); + cosmu = cos(mu); + tannu = tan(nu); + cosphi = 1.0 - cosmu * cosmu * tannu * tannu * (1.0 - cos(atan(1.0 / cos(theta)))); + if (cosphi < -1.0) { + cosphi = -1.0; + } else if (cosphi > +1.0) { + cosphi = +1.0; + } + + /* Apply the result to the real area on the cube face. + * For the top and bottom face, we can compute phi and lam directly. + * For the other faces, we must use unit sphere cartesian coordinates + * as an intermediate step. */ + if (P->face == FACE_TOP) { + phi = acos(cosphi); + lp.phi = HALFPI - phi; + if (area == AREA_0) { + lp.lam = theta + HALFPI; + } else if (area == AREA_1) { + lp.lam = (theta < 0.0 ? theta + PI : theta - PI); + } else if (area == AREA_2) { + lp.lam = theta - HALFPI; + } else /* area == AREA_3 */ { + lp.lam = theta; + } + } else if (P->face == FACE_BOTTOM) { + phi = acos(cosphi); + lp.phi = phi - HALFPI; + if (area == AREA_0) { + lp.lam = -theta + HALFPI; + } else if (area == AREA_1) { + lp.lam = -theta; + } else if (area == AREA_2) { + lp.lam = -theta - HALFPI; + } else /* area == AREA_3 */ { + lp.lam = (theta < 0.0 ? -theta - PI : -theta + PI); + } + } else { + /* Compute phi and lam via cartesian unit sphere coordinates. */ + double q, r, s, t; + q = cosphi; + t = q * q; + if (t >= 1.0) { + s = 0.0; + } else { + s = sqrt(1.0 - t) * sin(theta); + } + t += s * s; + if (t >= 1.0) { + r = 0.0; + } else { + r = sqrt(1.0 - t); + } + /* Rotate q,r,s into the correct area. */ + if (area == AREA_1) { + t = r; + r = -s; + s = t; + } else if (area == AREA_2) { + r = -r; + s = -s; + } else if (area == AREA_3) { + t = r; + r = s; + s = -t; + } + /* Rotate q,r,s into the correct cube face. */ + if (P->face == FACE_RIGHT) { + t = q; + q = -r; + r = t; + } else if (P->face == FACE_BACK) { + q = -q; + r = -r; + } else if (P->face == FACE_LEFT) { + t = q; + q = r; + r = -t; + } + /* Now compute phi and lam from the unit sphere coordinates. */ + lp.phi = acos(-s) - HALFPI; + lp.lam = atan2(r, q); + if (P->face == FACE_RIGHT) { + lp.lam = qsc_shift_lon_origin(lp.lam, -HALFPI); + } else if (P->face == FACE_BACK) { + lp.lam = qsc_shift_lon_origin(lp.lam, -PI); + } else if (P->face == FACE_LEFT) { + lp.lam = qsc_shift_lon_origin(lp.lam, +HALFPI); + } + } + + /* Apply the shift from the sphere to the ellipsoid as described + * in [LK12]. */ + if (P->es) { + int invert_sign; + double tanphi, xa; + invert_sign = (lp.phi < 0.0 ? 1 : 0); + tanphi = tan(lp.phi); + xa = P->b / sqrt(tanphi * tanphi + P->one_minus_f_squared); + lp.phi = atan(sqrt(P->a * P->a - xa * xa) / (P->one_minus_f * xa)); + if (invert_sign) { + lp.phi = -lp.phi; + } + } + return (lp); +} +FREEUP; if (P) pj_dalloc(P); } +ENTRY0(qsc) + P->inv = e_inverse; + P->fwd = e_forward; + /* Determine the cube face from the center of projection. */ + if (P->phi0 >= HALFPI - FORTPI / 2.0) { + P->face = FACE_TOP; + } else if (P->phi0 <= -(HALFPI - FORTPI / 2.0)) { + P->face = FACE_BOTTOM; + } else if (fabs(P->lam0) <= FORTPI) { + P->face = FACE_FRONT; + } else if (fabs(P->lam0) <= HALFPI + FORTPI) { + P->face = (P->lam0 > 0.0 ? FACE_RIGHT : FACE_LEFT); + } else { + P->face = FACE_BACK; + } + /* Fill in useful values for the ellipsoid <-> sphere shift + * described in [LK12]. */ + if (P->es) { + P->a_squared = P->a * P->a; + P->b = P->a * sqrt(1.0 - P->es); + P->one_minus_f = 1.0 - (P->a - P->b) / P->a; + P->one_minus_f_squared = P->one_minus_f * P->one_minus_f; + } +ENDENTRY(P) diff --git a/src/makefile.vc b/src/makefile.vc index bcd47f58..00559656 100644 --- a/src/makefile.vc +++ b/src/makefile.vc @@ -27,7 +27,7 @@ misc = \ PJ_lask.obj PJ_nocol.obj PJ_ob_tran.obj PJ_oea.obj \ PJ_tpeqd.obj PJ_vandg.obj PJ_vandg2.obj PJ_vandg4.obj \ PJ_wag7.obj pj_latlong.obj PJ_krovak.obj pj_geocent.obj \ - PJ_healpix.obj PJ_natearth.obj + PJ_healpix.obj PJ_natearth.obj PJ_qsc.obj pseudo = \ PJ_boggs.obj PJ_collg.obj PJ_crast.obj PJ_denoy.obj \ diff --git a/src/pj_list.h b/src/pj_list.h index 95953758..194bcc68 100644 --- a/src/pj_list.h +++ b/src/pj_list.h @@ -107,6 +107,7 @@ PROJ_HEAD(putp5p, "Putnins P5'") PROJ_HEAD(putp6, "Putnins P6") PROJ_HEAD(putp6p, "Putnins P6'") PROJ_HEAD(qua_aut, "Quartic Authalic") +PROJ_HEAD(qsc, "Quadrilateralized Spherical Cube") PROJ_HEAD(robin, "Robinson") PROJ_HEAD(rouss, "Roussilhe Stereographic") PROJ_HEAD(rpoly, "Rectangular Polyconic") |
