diff --git a/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Robinson.java b/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Robinson.java new file mode 100644 index 0000000..e7be2e7 --- /dev/null +++ b/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Robinson.java @@ -0,0 +1,374 @@ +/* + * GeoTools - The Open Source Java GIS Toolkit + * http://geotools.org + * + * (C) 1999-2008, Open Source Geospatial Foundation (OSGeo) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * This package contains formulas from the PROJ package of USGS. + * USGS's work is fully acknowledged here. This derived work has + * been relicensed under LGPL with Frank Warmerdam's permission. + */ +package org.geotools.referencing.operation.projection; + +import static java.lang.Math.*; + +import java.awt.geom.Point2D; + +import org.geotools.metadata.iso.citation.Citations; +import org.geotools.referencing.NamedIdentifier; +import org.opengis.parameter.ParameterDescriptor; +import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; +import org.opengis.parameter.ParameterValueGroup; +import org.opengis.referencing.operation.MathTransform; + + +/** + * Robinson projection + * + * @see Robinson projection on Wikipedia + * @see "Robinson" on RemoteSensing.org + * + * @since 2.6.3 + * @source $URL: http://svn.osgeo.org/geotools/branches/2.6.x/modules/library/referencing/src/main/java/org/geotools/referencing/operation/projection/Mercator.java $ + * @author Andrea Aime + */ +public class Robinson extends MapProjection { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 8428056162968814860L; + + static final class Coeff { + double c0, c1, c2, c3; + + public Coeff(double c0, double c1, double c2, double c3) { + super(); + this.c0 = c0; + this.c1 = c1; + this.c2 = c2; + this.c3 = c3; + } + + public Coeff(Coeff other) { + super(); + this.c0 = other.c0; + this.c1 = other.c1; + this.c2 = other.c2; + this.c3 = other.c3; + } + + public final double forward(double z) { + return c0 + z * (c1 + z * (c2 + z * c3)); + } + + public final double inverse(double z) { + return c1 + z * (c2 + c2 + z * 3d * c3); + } + } + + /** + * X translation table + */ + private final static Coeff X[] = { + new Coeff(1, -5.67239e-12, -7.15511e-05, 3.11028e-06), + new Coeff(0.9986, -0.000482241, -2.4897e-05, -1.33094e-06), + new Coeff(0.9954, -0.000831031, -4.4861e-05, -9.86588e-07), + new Coeff(0.99, -0.00135363, -5.96598e-05, 3.67749e-06), + new Coeff(0.9822, -0.00167442, -4.4975e-06, -5.72394e-06), + new Coeff(0.973, -0.00214869, -9.03565e-05, 1.88767e-08), + new Coeff(0.96, -0.00305084, -9.00732e-05, 1.64869e-06), + new Coeff(0.9427, -0.00382792, -6.53428e-05, -2.61493e-06), + new Coeff(0.9216, -0.00467747, -0.000104566, 4.8122e-06), + new Coeff(0.8962, -0.00536222, -3.23834e-05, -5.43445e-06), + new Coeff(0.8679, -0.00609364, -0.0001139, 3.32521e-06), + new Coeff(0.835, -0.00698325, -6.40219e-05, 9.34582e-07), + new Coeff(0.7986, -0.00755337, -5.00038e-05, 9.35532e-07), + new Coeff(0.7597, -0.00798325, -3.59716e-05, -2.27604e-06), + new Coeff(0.7186, -0.00851366, -7.0112e-05, -8.63072e-06), + new Coeff(0.6732, -0.00986209, -0.000199572, 1.91978e-05), + new Coeff(0.6213, -0.010418, 8.83948e-05, 6.24031e-06), + new Coeff(0.5722, -0.00906601, 0.000181999, 6.24033e-06), + new Coeff(0.5322, 0., 0., 0.) + }; + + /** + * Y translation table + */ + private final static Coeff Y[] = { + new Coeff(0, 0.0124, 3.72529e-10, 1.15484e-09), + new Coeff(0.062, 0.0124001, 1.76951e-08, -5.92321e-09), + new Coeff(0.124, 0.0123998, -7.09668e-08, 2.25753e-08), + new Coeff(0.186, 0.0124008, 2.66917e-07, -8.44523e-08), + new Coeff(0.248, 0.0123971, -9.99682e-07, 3.15569e-07), + new Coeff(0.31, 0.0124108, 3.73349e-06, -1.1779e-06), + new Coeff(0.372, 0.0123598, -1.3935e-05, 4.39588e-06), + new Coeff(0.434, 0.0125501, 5.20034e-05, -1.00051e-05), + new Coeff(0.4968, 0.0123198, -9.80735e-05, 9.22397e-06), + new Coeff(0.5571, 0.0120308, 4.02857e-05, -5.2901e-06), + new Coeff(0.6176, 0.0120369, -3.90662e-05, 7.36117e-07), + new Coeff(0.6769, 0.0117015, -2.80246e-05, -8.54283e-07), + new Coeff(0.7346, 0.0113572, -4.08389e-05, -5.18524e-07), + new Coeff(0.7903, 0.0109099, -4.86169e-05, -1.0718e-06), + new Coeff(0.8435, 0.0103433, -6.46934e-05, 5.36384e-09), + new Coeff(0.8936, 0.00969679, -6.46129e-05, -8.54894e-06), + new Coeff(0.9394, 0.00840949, -0.000192847, -4.21023e-06), + new Coeff(0.9761, 0.00616525, -0.000256001, -4.21021e-06), + new Coeff(1., 0., 0., 0) + }; + + private final static double FXC = 0.8487; + private final static double FYC = 1.3523; + private final static double C1 = 11.45915590261646417544; + private final static double RC1 = 0.08726646259971647884; + private final int NODES = 18; + private final static double ONEEPS = 1.000001; + private final static double EPS = 1e-12; + + + /** + * Constructs a new map projection from the supplied parameters. + * + * @param parameters The parameter values in standard units. + * @throws ParameterNotFoundException if a mandatory parameter is missing. + */ + protected Robinson(final ParameterValueGroup parameters) throws ParameterNotFoundException { + super(parameters); + } + + /** + * {@inheritDoc} + */ + public ParameterDescriptorGroup getParameterDescriptors() { + return Provider.PARAMETERS; + } + + /** + * Transforms the specified (λ,φ) coordinates + * (units in radians) and stores the result in {@code ptDst} (linear distance + * on a unit sphere). + */ + protected Point2D transformNormalized(double lam, double phi, final Point2D ptDst) + throws ProjectionException { + + double dphi = abs(phi); + int i = (int) floor(dphi * C1); + if (i >= NODES) { + i = NODES - 1; + } + dphi = toDegrees(dphi - RC1 * i); + double x = X[i].forward(dphi) * FXC * lam; + double y = Y[i].forward(dphi) * FYC; + if (phi < 0d) { + y = -y; + } + + if(ptDst != null) { + ptDst.setLocation(x, y); + return ptDst; + } else { + return new Point2D.Double(x, y); + } + } + + /** + * Transforms the specified (x,y) coordinates + * and stores the result in {@code ptDst}. + */ + protected Point2D inverseTransformNormalized(double x, double y, final Point2D ptDst) + throws ProjectionException { + double lam = x / FXC; + double phi = abs(y / FYC); + if (phi >= 1d) { /* simple pathologic cases */ + if (phi > ONEEPS) { + phi = y < 0. ? -PI / 2 : PI / 2; + lam /= X[NODES].c0; + } + } else { /* general problem */ + /* in Y space, reduce to table interval */ + int i; + for (i = (int) floor(phi * NODES);;) { + if (Y[i].c0 > phi) { + --i; + } else if (Y[i+1].c0 <= phi) { + ++i; + } else { + break; + } + } + Coeff T = new Coeff(Y[i]); + /* first guess, linear interp */ + double t = 5d * (phi - T.c0) / (Y[i+1].c0 - T.c0); + /* make into root */ + T.c0 -= phi; + for (;;) { /* Newton-Raphson reduction */ + double t1 = T.forward(t) / T.inverse(t); + t -= t1; + if (abs(t1) < EPS) { + break; + } + } + phi = toRadians(5 * i + t); + if (y < 0d) { + phi = -phi; + } + lam /= X[i].forward(t); + } + + if(ptDst != null) { + ptDst.setLocation(lam, phi); + return ptDst; + } else { + return new Point2D.Double(lam, phi); + } + } + + /** + * Compares the specified object with this map projection for equality. + */ + @Override + public boolean equals(final Object object) { + if (object == this) { + // Slight optimization + return true; + } + return super.equals(object); + } + + @Override + protected double getToleranceForAssertions(double longitude, double latitude) { + // the Robinson projection is meant for world-wide displays, don't be picky + return 2; + } + + + ////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// + //////// //////// + //////// PROVIDERS //////// + //////// //////// + ////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////// + + /** + * The {@linkplain org.geotools.referencing.operation.MathTransformProvider math transform + * provider} for the Robinson projection (not part of the EPSG database). + * + * @since 2.6.3 + * @author Andrea Aime + * + * @see org.geotools.referencing.operation.DefaultMathTransformFactory + */ + public static class Provider extends AbstractProvider { + /** + * For cross-version compatibility. + */ + private static final long serialVersionUID = 3586488124601927036L; + + /** + * The parameters group. + */ + static final ParameterDescriptorGroup PARAMETERS = createDescriptorGroup(new NamedIdentifier[] { + new NamedIdentifier(Citations.GEOTOOLS, "Robinson"), + new NamedIdentifier(Citations.ESRI, "Robinson") + }, new ParameterDescriptor[] { + SEMI_MAJOR, SEMI_MINOR, CENTRAL_MERIDIAN + }); + + /** + * Constructs a new provider. + */ + public Provider() { + super(PARAMETERS); + } + + /** + * Creates a transform from the specified group of parameter values. + * + * @param parameters The group of parameter values. + * @return The created math transform. + * @throws ParameterNotFoundException if a required parameter was not found. + */ + protected MathTransform createMathTransform(final ParameterValueGroup parameters) + throws ParameterNotFoundException + { + return new Robinson(parameters); + } + } + +// public static void main(String[] args) { +// double px[] = { +// 1, -5.67239e-12, -7.15511e-05, 3.11028e-06, +// 0.9986, -0.000482241, -2.4897e-05, -1.33094e-06, +// 0.9954, -0.000831031, -4.4861e-05, -9.86588e-07, +// 0.99, -0.00135363, -5.96598e-05, 3.67749e-06, +// 0.9822, -0.00167442, -4.4975e-06, -5.72394e-06, +// 0.973, -0.00214869, -9.03565e-05, 1.88767e-08, +// 0.96, -0.00305084, -9.00732e-05, 1.64869e-06, +// 0.9427, -0.00382792, -6.53428e-05, -2.61493e-06, +// 0.9216, -0.00467747, -0.000104566, 4.8122e-06, +// 0.8962, -0.00536222, -3.23834e-05, -5.43445e-06, +// 0.8679, -0.00609364, -0.0001139, 3.32521e-06, +// 0.835, -0.00698325, -6.40219e-05, 9.34582e-07, +// 0.7986, -0.00755337, -5.00038e-05, 9.35532e-07, +// 0.7597, -0.00798325, -3.59716e-05, -2.27604e-06, +// 0.7186, -0.00851366, -7.0112e-05, -8.63072e-06, +// 0.6732, -0.00986209, -0.000199572, 1.91978e-05, +// 0.6213, -0.010418, 8.83948e-05, 6.24031e-06, +// 0.5722, -0.00906601, 0.000181999, 6.24033e-06, +// 0.5322, 0.,0.,0. }; +// +// +// +// +// +// double py[] = { +// 0, 0.0124, 3.72529e-10, 1.15484e-09, +// 0.062, 0.0124001, 1.76951e-08, -5.92321e-09, +// 0.124, 0.0123998, -7.09668e-08, 2.25753e-08, +// 0.186, 0.0124008, 2.66917e-07, -8.44523e-08, +// 0.248, 0.0123971, -9.99682e-07, 3.15569e-07, +// 0.31, 0.0124108, 3.73349e-06, -1.1779e-06, +// 0.372, 0.0123598, -1.3935e-05, 4.39588e-06, +// 0.434, 0.0125501, 5.20034e-05, -1.00051e-05, +// 0.4968, 0.0123198, -9.80735e-05, 9.22397e-06, +// 0.5571, 0.0120308, 4.02857e-05, -5.2901e-06, +// 0.6176, 0.0120369, -3.90662e-05, 7.36117e-07, +// 0.6769, 0.0117015, -2.80246e-05, -8.54283e-07, +// 0.7346, 0.0113572, -4.08389e-05, -5.18524e-07, +// 0.7903, 0.0109099, -4.86169e-05, -1.0718e-06, +// 0.8435, 0.0103433, -6.46934e-05, 5.36384e-09, +// 0.8936, 0.00969679, -6.46129e-05, -8.54894e-06, +// 0.9394, 0.00840949, -0.000192847, -4.21023e-06, +// 0.9761, 0.00616525, -0.000256001, -4.21021e-06, +// 1., 0.,0.,0 }; +// +// for (int i = 0; i < X.length; i++) { +// System.out.println(X[i].c0 == px[i * 4]); +// System.out.println(X[i].c1 == px[i * 4 + 1]); +// System.out.println(X[i].c2 == px[i * 4 + 2]); +// System.out.println(X[i].c3 == px[i * 4 + 3]); +// } +// +// for (int i = 0; i < Y.length; i++) { +// System.out.println(Y[i].c0 == py[i * 4]); +// System.out.println(Y[i].c1 == py[i * 4 + 1]); +// System.out.println(Y[i].c2 == py[i * 4 + 2]); +// System.out.println(Y[i].c3 == py[i * 4 + 3]); +// } +// } + + public static void main(String[] args) { + System.out.println(PI / 2d); + } +} diff --git a/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.operation.MathTransformProvider b/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.operation.MathTransformProvider index 0098e8a..e4a0b11 100644 --- a/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.operation.MathTransformProvider +++ b/modules/library/referencing/src/main/resources/META-INF/services/org.geotools.referencing.operation.MathTransformProvider @@ -40,3 +40,4 @@ org.geotools.referencing.operation.projection.Krovak$Provider org.geotools.referencing.operation.projection.CassiniSoldner$Provider org.geotools.referencing.operation.projection.EquidistantConic$Provider org.geotools.referencing.operation.projection.Polyconic$Provider +org.geotools.referencing.operation.projection.Robinson$Provider diff --git a/modules/library/referencing/src/test/java/org/geotools/referencing/ScriptTest.java b/modules/library/referencing/src/test/java/org/geotools/referencing/ScriptTest.java index 5a4018d..7b10116 100644 --- a/modules/library/referencing/src/test/java/org/geotools/referencing/ScriptTest.java +++ b/modules/library/referencing/src/test/java/org/geotools/referencing/ScriptTest.java @@ -16,11 +16,14 @@ */ package org.geotools.referencing; +import java.io.IOException; import java.io.LineNumberReader; -import org.geotools.test.TestData; -import org.junit.*; -import static org.junit.Assert.*; +import org.geotools.test.TestData; +import org.junit.Ignore; +import org.junit.Test; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.operation.TransformException; /** @@ -209,6 +212,15 @@ public final class ScriptTest { public void testPolyconic() throws Exception { runScript("scripts/Polyconic.txt"); } + + /** + * Run "Robinson.txt" + * @throws Exception + */ + @Test + public void testRobinson() throws Exception { + runScript("scripts/Robinson.txt"); + } /** * Run "OpenGIS.txt". diff --git a/modules/library/referencing/src/test/resources/org/geotools/referencing/test-data/scripts/Robinson.txt b/modules/library/referencing/src/test/resources/org/geotools/referencing/test-data/scripts/Robinson.txt new file mode 100644 index 0000000..e94d53c --- /dev/null +++ b/modules/library/referencing/src/test/resources/org/geotools/referencing/test-data/scripts/Robinson.txt @@ -0,0 +1,51 @@ +// Test points for the Robinson projection +// +// (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) +// +// The following test points were calculated with libproj4 utility "cs2cs" +// cs2cs -v +datum=WGS84 +proj=lonlat +to +datum=WGS84 +proj=robin +// +// Andrea Aime - March 20 2010 +// +// -------------------------------------------------------------------------- +// How to run this script: +// +// chdir org/geotools/referencing/test-data/scripts +// java -ea org.geotools.referencing.ScriptRunner Robinson.txt +// +// A test is performed every time a "target pt" statement occurs. If the target point computed +// by Geotools is different from the target point declared in this script by an amount greater +// than the value specified in the last "test tolerance" statement, then a failure is reported. +// Inverse transforms are tested if java assertions are enabled. +// If some test fails, "print" statements can be added in this script for debugging purpose: +// +// print crs (prints the source and target CRS, and the transform between them) +// print pts (prints the source and target points, and their transformed points) + + +// CRS used for the test +set _WGS84_ = GEOGCS["WGS84", DATUM["WGS84", SPHEROID["WGS84", 6378137.0, 298.257223563]], PRIMEM["Greenwich", 0.0], UNIT["degree",0.017453292519943295], AXIS["Longitude",EAST], AXIS["Latitude",NORTH]] + +//////////////////////////////////////////// +// Robinson + +set _robinson_ = PROJCS["Robinson", _WGS84_, PROJECTION["Robinson"], UNIT["m", 1.0], AXIS["Easting", EAST], AXIS["Northing", NORTH]] + +source crs = _WGS84_ +target crs = _robinson_ +// hmm... looked everywhere and cannot find a significant difference between GeoTools code +// and the proj one. Simply assuming the difference in double handling +// shows up quite badly in this projection +test tolerance = (1, 1) + +source pt = (0, 0) +target pt = (0, 0) + +source pt = (10, 10) +target pt = (940422.59, 1069519.16) + +source pt = (30, 30) +target pt = (2720933.27, 3208557.61) + +source pt = (-180, 90) +target pt = (-9050503.94, 8625154.47) \ No newline at end of file