Return-Path: X-Original-To: apmail-sis-commits-archive@www.apache.org Delivered-To: apmail-sis-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3EAED18E59 for ; Sat, 18 Jul 2015 16:35:56 +0000 (UTC) Received: (qmail 45309 invoked by uid 500); 18 Jul 2015 16:35:56 -0000 Delivered-To: apmail-sis-commits-archive@sis.apache.org Received: (qmail 45279 invoked by uid 500); 18 Jul 2015 16:35:56 -0000 Mailing-List: contact commits-help@sis.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sis-dev@sis.apache.org Delivered-To: mailing list commits@sis.apache.org Received: (qmail 45270 invoked by uid 99); 18 Jul 2015 16:35:56 -0000 Received: from eris.apache.org (HELO hades.apache.org) (140.211.11.105) by apache.org (qpsmtpd/0.29) with ESMTP; Sat, 18 Jul 2015 16:35:56 +0000 Received: from hades.apache.org (localhost [127.0.0.1]) by hades.apache.org (ASF Mail Server at hades.apache.org) with ESMTP id CCBF6AC0337 for ; Sat, 18 Jul 2015 16:35:55 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1691751 - in /sis/branches/JDK8/core/sis-referencing/src: main/java/org/apache/sis/internal/referencing/provider/ main/java/org/apache/sis/referencing/operation/projection/ test/java/org/apache/sis/referencing/operation/projection/ test/ja... Date: Sat, 18 Jul 2015 16:35:55 -0000 To: commits@sis.apache.org From: desruisseaux@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20150718163555.CCBF6AC0337@hades.apache.org> Author: desruisseaux Date: Sat Jul 18 16:35:54 2015 New Revision: 1691751 URL: http://svn.apache.org/r1691751 Log: Fix the PolarStereographic projection (tests pass). Rearrange Mercator and LambertConformal code in order to have a consistent pattern accross all our projection implementations. Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicB.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -45,6 +45,12 @@ public final class PolarStereographicB e public static final String IDENTIFIER = "9829"; /** + * The operation parameter descriptor for the Longitude of origin (λ₀) parameter value. + * Valid values range is [-180 … 180]° and default value is 0°. + */ + public static final ParameterDescriptor LONGITUDE_OF_ORIGIN; + + /** * The operation parameter descriptor for the Latitude of standard parallel (φ₁) parameter value. * Valid values are -90° or 90°. */ @@ -67,6 +73,10 @@ public final class PolarStereographicB e static final ParameterDescriptorGroup PARAMETERS; static { final ParameterBuilder builder = builder(); + LONGITUDE_OF_ORIGIN = createLongitude( + exceptEPSG(PolarStereographicA.LONGITUDE_OF_ORIGIN, + builder.addIdentifier("8833").addName("Longitude of origin").setDeprecated(false))); + STANDARD_PARALLEL = createMandatoryLatitude(builder .addIdentifier("8832").addName("Latitude of standard parallel") .addName(sameNameAs(Citations.OGC, Mercator2SP.STANDARD_PARALLEL))); @@ -75,10 +85,6 @@ public final class PolarStereographicB e .addNamesAndIdentifiers(Mercator2SP.SCALE_FACTOR) .setRemarks(notFormalParameter("Polar Stereographic (variant A)")).setDeprecated(true)); - final ParameterDescriptor longitudeOfOrigin = createLongitude( - exceptEPSG(PolarStereographicA.LONGITUDE_OF_ORIGIN, - builder.addIdentifier("8833").addName("Longitude of origin").setDeprecated(false))); - PARAMETERS = builder .addIdentifier(IDENTIFIER) .addName("Polar Stereographic (variant B)") @@ -89,7 +95,7 @@ public final class PolarStereographicB e .addIdentifier(Citations.S57, "11") .createGroupForMapProjection( STANDARD_PARALLEL, - longitudeOfOrigin, + LONGITUDE_OF_ORIGIN, SCALE_FACTOR, // Not formally a parameter of this projection. FALSE_EASTING, FALSE_NORTHING); Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -185,10 +185,14 @@ abstract class ConformalProjection exten */ final double φ(final double expOfSouthing) throws ProjectionException { /* - * Get a first approximation of φ. The result below is exact if the ellipsoid is actually a sphere. - * But if the excentricity is different than 0, then we will need to add a correction. + * Get a first approximation of φ from Snyder (7-11). The result below would be exact if the + * ellipsoid was actually a sphere. But if the excentricity is different than 0, then we will + * need to add a correction. + * + * Note that the φ value computed by the line below is called χ in EPSG guide. + * We name it φ in our code because we will modify that value in-place in order to get φ. */ - double φ = (PI/2) - 2*atan(expOfSouthing); // Snyder (7-11) + double φ = (PI/2) - 2*atan(expOfSouthing); // at this point == χ /* * Add a correction for the flattened shape of the Earth. The correction can be represented by an * infinite series. Here, we apply only the first 4 terms. Those terms are given by §1.3.3 in the Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/LambertConformal.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -18,9 +18,12 @@ package org.apache.sis.referencing.opera import java.util.Map; import java.util.EnumMap; +import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.OperationMethod; import org.opengis.referencing.operation.Matrix; import org.apache.sis.measure.Latitude; @@ -71,16 +74,32 @@ public class LambertConformal extends Co private static final long serialVersionUID = 2067358524298002016L; /** - * Codes for special kinds of Lambert projection. We do not provide such codes in public API because - * they duplicate the functionality of {@link OperationMethod} instances. We use them only for convenience. + * Codes for variants of Lambert Conical Conformal projection. Those variants modify the way the projections are + * constructed (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. + * Those variants are not exactly the same than variants 1SP and 2SP used by EPSG, but they are closely related. * - *

Codes for SP1 case must be odd, and codes for SP2 case must be even.

+ *

We do not provide such codes in public API because they duplicate the functionality of + * {@link OperationMethod} instances. We use them only for constructors convenience.

* - * @see #getType(ParameterDescriptorGroup) + *

CONVENTION: Codes for SP1 case must be odd, and codes for SP2 case must be even. + * + * @see #getVariant(ParameterDescriptorGroup) */ - private static final byte SP1 = 1, SP2 = 2, - WEST = 3, BELGIUM = 4, - MICHIGAN = 6; + private static final byte SP1 = 1, WEST = 3, // Must be odd + SP2 = 2, BELGIUM = 4, MICHIGAN = 6; // Must be even + + /** + * Returns the type of the projection based on the name and identifier of the given parameter group. + * If this method can not identify the type, then the parameters should be considered as a 2SP case. + */ + private static byte getVariant(final ParameterDescriptorGroup parameters) { + if (identMatch(parameters, "(?i).*\\bBelgium\\b.*", LambertConformalBelgium .IDENTIFIER)) return BELGIUM; + if (identMatch(parameters, "(?i).*\\bMichigan\\b.*", LambertConformalMichigan.IDENTIFIER)) return MICHIGAN; + if (identMatch(parameters, "(?i).*\\bWest\\b.*", LambertConformalWest .IDENTIFIER)) return WEST; + if (identMatch(parameters, "(?i).*\\b2SP\\b.*", LambertConformal2SP .IDENTIFIER)) return SP2; + if (identMatch(parameters, "(?i).*\\b1SP\\b.*", LambertConformal1SP .IDENTIFIER)) return SP1; + return 0; // Unidentified case, to be considered as 2SP. + } /** * Constant for the Belgium 2SP case. This is 29.2985 seconds, given here in radians. @@ -95,13 +114,13 @@ public class LambertConformal extends Co final double n; /** - * Returns the (roleparameter) associations for a Lambert projection of the given type. + * Returns the (roleparameter) associations for a Lambert projection of the given variant. * - * @param type One of {@link #SP1}, {@link #SP2}, {@link #WEST}, {@link #BELGIUM} and {@link #MICHIGAN} constants. + * @param variant One of {@link #SP1}, {@link #SP2}, {@link #WEST}, {@link #BELGIUM} and {@link #MICHIGAN} constants. * @return The roles map to give to super-class constructor. */ @SuppressWarnings("fallthrough") - private static Map> roles(final byte type) { + private static Map> roles(final byte variant) { final EnumMap> roles = new EnumMap<>(ParameterRole.class); /* * "Scale factor" is not formally a "Lambert Conformal (2SP)" argument, but we accept it @@ -109,7 +128,7 @@ public class LambertConformal extends Co */ ParameterDescriptor scaleFactor = LambertConformal1SP.SCALE_FACTOR; ParameterRole eastingDirection = ParameterRole.FALSE_EASTING; - switch (type) { + switch (variant) { case WEST: { /* * For "Lambert Conic Conformal (West Orientated)" projection, the "false easting" parameter is @@ -135,7 +154,7 @@ public class LambertConformal extends Co roles.put(ParameterRole.CENTRAL_MERIDIAN, LambertConformal2SP.LONGITUDE_OF_FALSE_ORIGIN); break; } - default: throw new AssertionError(type); + default: throw new AssertionError(variant); } roles.put(ParameterRole.SCALE_FACTOR, scaleFactor); return roles; @@ -157,7 +176,7 @@ public class LambertConformal extends Co * @param parameters The parameter values of the projection to create. */ public LambertConformal(final OperationMethod method, final Parameters parameters) { - this(method, parameters, getType(parameters.getDescriptor())); + this(method, parameters, getVariant(parameters.getDescriptor())); } /** @@ -246,13 +265,17 @@ public class LambertConformal extends Co F.negate(); } /* - * Compute r = a⋅F⋅tⁿ from EPSG notes where (in our case) a=1 and t is our 'expOfNorthing' function. - * Note that Snyder calls this term "ρ0". + * Compute the radius of the parallel of latitude of the false origin. + * This is related to the "ρ0" term in Snyder. From EPG guide: + * + * r = a⋅F⋅tⁿ where (in our case) a=1 and t is our 'expOfNorthing' function. + * + * EPSG uses this term in the computation of y = FN + rF – r⋅cos(θ). */ - final DoubleDouble r0 = new DoubleDouble(); // Initialized to zero. + final DoubleDouble rF = new DoubleDouble(); // Initialized to zero. if (φ0 != copySign(PI/2, -n)) { // For avoiding the rounding error documented in expOfNorthing(+π/2). - r0.value = pow(expOfNorthing(φ0, excentricity*sin(φ0)), n); - r0.multiply(F); + rF.value = pow(expOfNorthing(φ0, excentricity*sin(φ0)), n); + rF.multiply(F); } /* * At this point, all parameters have been processed. Now store @@ -280,7 +303,7 @@ public class LambertConformal extends Co final MatrixSIS denormalize = context.getMatrix(false); denormalize.convertBefore(0, F, null); F.negate(); - denormalize.convertBefore(1, F, r0); + denormalize.convertBefore(1, F, rF); } /** @@ -292,16 +315,24 @@ public class LambertConformal extends Co } /** - * Returns the type of the projection based on the name and identifier of the given parameter group. - * If this method can not identify the type, then the parameters should be considered as a 2SP case. + * Returns the sequence of normalization → {@code this} → denormalization transforms + * as a whole. The transform returned by this method except (longitude, latitude) + * coordinates in degrees and returns (x,y) coordinates in metres. + * + *

The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid + * is spherical. In the later case, {@code this} transform will be replaced by a simplified implementation.

+ * + * @param factory The factory to use for creating the transform. + * @return The map projection from (λ,φ) to (x,y) coordinates. + * @throws FactoryException if an error occurred while creating a transform. */ - private static byte getType(final ParameterDescriptorGroup parameters) { - if (identMatch(parameters, "(?i).*\\bBelgium\\b.*", LambertConformalBelgium .IDENTIFIER)) return BELGIUM; - if (identMatch(parameters, "(?i).*\\bMichigan\\b.*", LambertConformalMichigan.IDENTIFIER)) return MICHIGAN; - if (identMatch(parameters, "(?i).*\\bWest\\b.*", LambertConformalWest .IDENTIFIER)) return WEST; - if (identMatch(parameters, "(?i).*\\b2SP\\b.*", LambertConformal2SP .IDENTIFIER)) return SP2; - if (identMatch(parameters, "(?i).*\\b1SP\\b.*", LambertConformal1SP .IDENTIFIER)) return SP1; - return 0; // Unidentified case, to be considered as 2SP. + @Override + public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { + LambertConformal kernel = this; + if (excentricity == 0) { + kernel = new Spherical(this); + } + return context.completeTransform(factory, kernel); } /** @@ -383,8 +414,8 @@ public class LambertConformal extends Co final double x = ρ * sinθ; final double y = ρ * cosθ; if (dstPts != null) { - dstPts[dstOff ] = x; - dstPts[dstOff + 1] = y; + dstPts[dstOff ] = x; + dstPts[dstOff+1] = y; } if (!derivate) { return null; @@ -412,15 +443,15 @@ public class LambertConformal extends Co final double[] dstPts, final int dstOff) throws ProjectionException { - final double x = srcPts[srcOff ]; - final double y = srcPts[srcOff + 1]; + final double x = srcPts[srcOff ]; + final double y = srcPts[srcOff+1]; /* * NOTE: If some equation terms seem missing (e.g. "y = ρ0 - y"), this is because the linear operations * applied before the first non-linear one moved to the inverse of the "denormalize" transform, and the * linear operations applied after the last non-linear one moved to the inverse of the "normalize" transform. */ - dstPts[dstOff ] = atan2(x, y); // Really (x,y), not (y,x) - dstPts[dstOff+1] = φ(pow(hypot(x, y), -1/n)); + dstPts[dstOff ] = atan2(x, y); // Really (x,y), not (y,x) + dstPts[dstOff+1] = -φ(pow(hypot(x, y), 1/n)); // Equivalent to φ(pow(hypot(x,y), -1/n)) but more accurate for n>0. } @@ -475,8 +506,8 @@ public class LambertConformal extends Co final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { - final double θ = srcPts[srcOff]; // θ = λ⋅n - final double φ = srcPts[srcOff + 1]; // Sign may be reversed + final double θ = srcPts[srcOff ]; // θ = λ⋅n + final double φ = srcPts[srcOff+1]; // Sign may be reversed final double absφ = abs(φ); final double sinθ = sin(θ); final double cosθ = cos(θ); @@ -505,8 +536,8 @@ public class LambertConformal extends Co assert Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) && Assertions.checkTransform(dstPts, dstOff, x, y); // dstPts = result from ellipsoidal formulas. if (dstPts != null) { - dstPts[dstOff ] = x; - dstPts[dstOff + 1] = y; + dstPts[dstOff ] = x; + dstPts[dstOff+1] = y; } return derivative; } @@ -523,10 +554,10 @@ public class LambertConformal extends Co double y = srcPts[srcOff+1]; final double ρ = hypot(x, y); x = atan2(x, y); // Really (x,y), not (y,x) - y = 2*atan(pow(1/ρ, -1/n)) - PI/2; + y = PI/2 - 2*atan(pow(1/ρ, 1/n)); assert checkInverseTransform(srcPts, srcOff, dstPts, dstOff, x, y); - dstPts[dstOff ] = x; - dstPts[dstOff + 1] = y; + dstPts[dstOff ] = x; + dstPts[dstOff+1] = y; } /** Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -83,16 +83,31 @@ public class Mercator extends ConformalP private static final long serialVersionUID = 2564172914329253286L; /** - * Codes for special kinds of Mercator projection. We do not provide such codes in public API because - * they duplicate the functionality of {@link OperationMethod} instances. We use them only for convenience. + * Codes for variants of Mercator projection. Those variants modify the way the projections are constructed + * (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. + * Those variants are not exactly the same than variants A, B and C used by EPSG, but they are related. * - *

CONVENTION: Spherical cases must be odd, all other cases must be even. This allow us to perform - * quick checks for all spherical cases using {@code if ((type & SPHERICAL) != 0)}.

+ *

We do not provide such codes in public API because they duplicate the functionality of + * {@link OperationMethod} instances. We use them only for constructors convenience.

* - * @see #getType(ParameterDescriptorGroup) + *

CONVENTION: Spherical cases must be odd, all other cases must be even. + * This allow us to perform quick checks for all spherical cases using {@code if ((type & SPHERICAL) != 0)}.

+ * + * @see #getVariant(ParameterDescriptorGroup) */ - static final byte SPHERICAL = 1, PSEUDO = 3, // Must be odd and SPHERICAL must be 1. - REGIONAL = 2, MILLER = 4; // Must be even. + private static final byte SPHERICAL = 1, PSEUDO = 3, // Must be odd and SPHERICAL must be 1. + REGIONAL = 2, MILLER = 4; // Must be even. + + /** + * Returns the variant of the projection based on the name and identifier of the given parameter group. + */ + private static byte getVariant(final ParameterDescriptorGroup parameters) { + if (identMatch(parameters, "(?i).*\\bvariant\\s*C\\b.*", RegionalMercator .IDENTIFIER)) return REGIONAL; + if (identMatch(parameters, "(?i).*\\bSpherical\\b.*", MercatorSpherical.IDENTIFIER)) return SPHERICAL; + if (identMatch(parameters, "(?i).*\\bPseudo.*", PseudoMercator .IDENTIFIER)) return PSEUDO; + if (identMatch(parameters, "(?i).*\\bMiller.*", null)) return MILLER; + return 0; + } /** * The type of Mercator projection. Possible values are: @@ -106,18 +121,18 @@ public class Mercator extends ConformalP * * Other cases may be added in the future. * - * @see #getType(ParameterDescriptorGroup) + * @see #getVariant(ParameterDescriptorGroup) */ - final byte type; + private final byte variant; /** - * Returns the (roleparameter) associations for a Mercator projection of the given type. + * Returns the (roleparameter) associations for a Mercator projection of the given variant. * - * @param type One of {@link #REGIONAL}, {@link #SPHERICAL}, {@link #PSEUDO} or {@link #MILLER} constants. + * @param variant One of {@link #REGIONAL}, {@link #SPHERICAL}, {@link #PSEUDO} or {@link #MILLER} constants. * @return The roles map to give to super-class constructor. */ @SuppressWarnings("fallthrough") - private static Map> roles(final byte type) { + private static Map> roles(final byte variant) { final EnumMap> roles = new EnumMap<>(ParameterRole.class); /* * "Longitude of origin" is a parameter of all Mercator projections, but is intentionally omitted from @@ -126,7 +141,7 @@ public class Mercator extends ConformalP * since it may be used in some Well Known Text (WKT). */ roles.put(ParameterRole.SCALE_FACTOR, Mercator1SP.SCALE_FACTOR); - switch (type) { + switch (variant) { case REGIONAL: { roles.put(ParameterRole.FALSE_EASTING, RegionalMercator.EASTING_AT_FALSE_ORIGIN); roles.put(ParameterRole.FALSE_NORTHING, RegionalMercator.NORTHING_AT_FALSE_ORIGIN); @@ -177,7 +192,7 @@ public class Mercator extends ConformalP * @param parameters The parameter values of the projection to create. */ public Mercator(final OperationMethod method, final Parameters parameters) { - this(method, parameters, getType(parameters.getDescriptor())); + this(method, parameters, getVariant(parameters.getDescriptor())); } /** @@ -185,9 +200,9 @@ public class Mercator extends ConformalP * ("Relax constraint on placement of this()/super() call in constructors"). */ @Workaround(library="JDK", version="1.7") - private Mercator(final OperationMethod method, final Parameters parameters, final byte type) { - super(method, parameters, roles(type)); - this.type = type; + private Mercator(final OperationMethod method, final Parameters parameters, final byte variant) { + super(method, parameters, roles(variant)); + this.variant = variant; /* * The "Longitude of natural origin" parameter is found in all Mercator projections and is mandatory. * Since this is usually the Greenwich meridian, the default value is 0°. We keep the value in degrees @@ -206,7 +221,7 @@ public class Mercator extends ConformalP * "Latitude of origin" can not have a non-zero value, if it still have non-zero value we will process as * for "Latitude of false origin". */ - final double φ0 = toRadians(getAndStore(parameters, (type == REGIONAL) + final double φ0 = toRadians(getAndStore(parameters, (variant == REGIONAL) ? RegionalMercator.LATITUDE_OF_FALSE_ORIGIN : Mercator1SP.LATITUDE_OF_ORIGIN)); /* * In theory, the "Latitude of 1st standard parallel" and the "Scale factor at natural origin" parameters @@ -240,7 +255,7 @@ public class Mercator extends ConformalP if (φ0 != 0) { denormalize.convertBefore(1, null, new DoubleDouble(-log(expOfNorthing(φ0, excentricity * sin(φ0))))); } - if (type == MILLER) { + if (variant == MILLER) { normalize.convertBefore(1, new DoubleDouble(0.80), null); denormalize.convertBefore(1, new DoubleDouble(1.25), null); } @@ -279,18 +294,7 @@ public class Mercator extends ConformalP */ Mercator(final Mercator other) { super(other); - type = other.type; - } - - /** - * Returns the type of the projection based on the name and identifier of the given parameter group. - */ - private static byte getType(final ParameterDescriptorGroup parameters) { - if (identMatch(parameters, "(?i).*\\bvariant\\s*C\\b.*", RegionalMercator .IDENTIFIER)) return REGIONAL; - if (identMatch(parameters, "(?i).*\\bSpherical\\b.*", MercatorSpherical.IDENTIFIER)) return SPHERICAL; - if (identMatch(parameters, "(?i).*\\bPseudo.*", PseudoMercator .IDENTIFIER)) return PSEUDO; - if (identMatch(parameters, "(?i).*\\bMiller.*", null)) return MILLER; - return 0; + variant = other.variant; } /** @@ -308,7 +312,7 @@ public class Mercator extends ConformalP @Override public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { Mercator kernel = this; - if ((type & SPHERICAL) != 0 || excentricity == 0) { + if ((variant & SPHERICAL) != 0 || excentricity == 0) { kernel = new Spherical(this); } return context.completeTransform(factory, kernel); @@ -328,8 +332,8 @@ public class Mercator extends ConformalP final double[] dstPts, final int dstOff, final boolean derivate) throws ProjectionException { - final double λ = srcPts[srcOff]; - final double φ = srcPts[srcOff + 1]; + final double λ = srcPts[srcOff ]; + final double φ = srcPts[srcOff+1]; final double sinφ = sin(φ); if (dstPts != null) { /* @@ -350,7 +354,7 @@ public class Mercator extends ConformalP y = copySign(a <= (PI/2 + ANGULAR_TOLERANCE) ? POSITIVE_INFINITY : NaN, φ); } } - dstPts[dstOff] = λ; + dstPts[dstOff ] = λ; // Scale will be applied by the denormalization matrix. dstPts[dstOff+1] = y; } /* Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -18,10 +18,13 @@ package org.apache.sis.referencing.opera import java.util.Map; import java.util.EnumMap; +import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; -import org.opengis.referencing.operation.Matrix; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.referencing.operation.OperationMethod; +import org.opengis.referencing.operation.Matrix; import org.apache.sis.referencing.operation.matrix.Matrix2; import org.apache.sis.referencing.operation.matrix.MatrixSIS; import org.apache.sis.internal.referencing.provider.PolarStereographicA; @@ -29,6 +32,7 @@ import org.apache.sis.internal.referenci import org.apache.sis.internal.referencing.Formulas; import org.apache.sis.parameter.Parameters; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.Workaround; import org.apache.sis.measure.Latitude; import static java.lang.Math.*; @@ -56,40 +60,48 @@ public class PolarStereographic extends private static final long serialVersionUID = -6635298308431138524L; /** - * Codes for variants. + * Codes for variants of Polar Stereographic projection. Those variants modify the way the projections are + * constructed (e.g. in the way parameters are interpreted), but formulas are basically the same after construction. + * Those variants are not exactly the same than variants A, B and C used by EPSG, but they are closely related. + * + *

We do not provide such codes in public API because they duplicate the functionality of + * {@link OperationMethod} instances. We use them only for constructors convenience.

* * @see #getVariant(ParameterDescriptorGroup) */ private static final byte A = 1, B = 2, C = 3, NORTH = 4, SOUTH = 5; /** + * Returns the type of the projection based on the name and identifier of the given parameter group. + * If this method can not identify the type, then the parameters should be considered as a 2SP case. + */ + private static byte getVariant(final ParameterDescriptorGroup parameters) { + if (identMatch(parameters, "(?i).*\\bvariant\\s*A\\b.*", PolarStereographicA.IDENTIFIER)) return A; + if (identMatch(parameters, "(?i).*\\bvariant\\s*B\\b.*", PolarStereographicB.IDENTIFIER)) return B; +// if (identMatch(parameters, "(?i).*\\bvariant\\s*C\\b.*", PolarStereographicC.IDENTIFIER)) return C; + if (identMatch(parameters, "(?i).*\\bNorth\\b.*", null)) return NORTH; + if (identMatch(parameters, "(?i).*\\bSouth\\b.*", null)) return SOUTH; + return 0; // Unidentified case, to be considered as variant A. + } + + /** * Returns the (roleparameter) associations for a Polar Stereographic projection. * + * @param variant One of {@link #A}, {@link #B}, {@link #C}, {@link #NORTH} or {@link #SOUTH} constants. * @return The roles map to give to super-class constructor. */ - private static Map> roles() { + private static Map> roles(final byte variant) { final EnumMap> roles = new EnumMap<>(ParameterRole.class); - roles.put(ParameterRole.CENTRAL_MERIDIAN, PolarStereographicA.LONGITUDE_OF_ORIGIN); roles.put(ParameterRole.SCALE_FACTOR, PolarStereographicA.SCALE_FACTOR); roles.put(ParameterRole.FALSE_EASTING, PolarStereographicA.FALSE_EASTING); roles.put(ParameterRole.FALSE_NORTHING, PolarStereographicA.FALSE_NORTHING); + roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == B) + ? PolarStereographicB.LONGITUDE_OF_ORIGIN + : PolarStereographicA.LONGITUDE_OF_ORIGIN); return roles; } /** - * Returns the type of the projection based on the name and identifier of the given parameter group. - * If this method can not identify the type, then the parameters should be considered as a 2SP case. - */ - private static byte getVariant(final ParameterDescriptorGroup parameters) { - if (identMatch(parameters, "(?i).*\\bA\\b.*", PolarStereographicA.IDENTIFIER)) return A; - if (identMatch(parameters, "(?i).*\\bB\\b.*", PolarStereographicB.IDENTIFIER)) return B; -// if (identMatch(parameters, "(?i).*\\bC\\b.*", PolarStereographicC.IDENTIFIER)) return C; - if (identMatch(parameters, "(?i).*\\bNorth\\b.*", null)) return NORTH; - if (identMatch(parameters, "(?i).*\\bSouth\\b.*", null)) return SOUTH; - return 0; // Unidentified case, to be considered as variant A. - } - - /** * Creates a Polar Stereographic projection from the given parameters. * The {@code method} argument can be the description of one of the following: * @@ -103,8 +115,16 @@ public class PolarStereographic extends * @param parameters The parameter values of the projection to create. */ public PolarStereographic(final OperationMethod method, final Parameters parameters) { - super(method, parameters, roles()); - final byte variant = getVariant(parameters.getDescriptor()); + this(method, parameters, getVariant(parameters.getDescriptor())); + } + + /** + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). + */ + @Workaround(library="JDK", version="1.7") + private PolarStereographic(final OperationMethod method, final Parameters parameters, final byte variant) { + super(method, parameters, roles(variant)); /* * "Standard parallel" and "Latitude of origin" should be mutually exclusive, * but this is not a strict requirement for the constructor. @@ -145,9 +165,10 @@ public class PolarStereographic extends * It may be possible to specify φ0 and φ1 if the caller used his own parameter descriptor, * in which case maybe he really wanted different sign (e.g. for testing purpose). */ - φ1 = toRadians(abs(φ1)); // May be anything in [0 … π/2] range. + φ1 = toRadians(φ1); // May be anything in [-π/2 … π/2] range. final double ρ; - if (abs(φ1 - PI/2) < ANGULAR_TOLERANCE) { + Double ρF = null; // Actually -ρF (compared to EPSG guide). + if (abs(abs(φ1) - PI/2) < ANGULAR_TOLERANCE) { /* * Polar Stereographic (variant A) * True scale at pole (part of Synder 21-33). From EPSG guide (April 2015) §1.3.7.2: @@ -163,11 +184,28 @@ public class PolarStereographic extends ρ = 2 / sqrt(pow(1+excentricity, 1+excentricity) * pow(1-excentricity, 1-excentricity)); } else { /* - * Derived from Synder 21-32 and 21-33. + * Polar Stereographic (variant B or C) + * Derived from Synder 21-32 and 21-33. From EPSG guide (April 2015) §1.3.7.2: + * + * tF = tan(π/4 + φ1/2) / {[(1 + ℯ⋅sinφ1) / (1 – ℯ⋅sinφ1)]^(ℯ/2)} + * mF = cosφ1 / √[1 – ℯ²⋅sin²φ1] + * k₀ = mF⋅√[(1+ℯ)^(1+ℯ) ⋅ (1–ℯ)^(1–ℯ)] / (2⋅tF) + * + * In our case: + * + * tF = expOfNorthing(φ1, ℯ⋅sinφ1) + * mF = cos(φ1) / rν(sinφ1) + * ρ = mF / tF + * k₀ = ρ⋅√[…]/2 but we do not need that value. + * * In the spherical case, should give ρ = 1 + sinφ1 (Synder 21-7 and 21-11). */ final double sinφ1 = sin(φ1); - ρ = cos(φ1) * expOfNorthing(φ1, sinφ1) / rν(sinφ1); + final double mF = cos(φ1) / rν(sinφ1); + ρ = mF / expOfNorthing(φ1, excentricity*sinφ1); + if (variant == C) { + ρF = -mF; + } } /* * At this point, all parameters have been processed. Now process to their @@ -175,14 +213,42 @@ public class PolarStereographic extends */ final MatrixSIS denormalize = context.getMatrix(false); denormalize.convertBefore(0, ρ, null); - denormalize.convertBefore(1, ρ, null); - if (φ0 >= 0) { // North pole. + denormalize.convertBefore(1, ρ, ρF); + if (φ1 >= 0) { // North pole. context.getMatrix(true).convertAfter(1, -1, null); denormalize.convertBefore(1, -1, null); } } /** + * Creates a new projection initialized to the same parameters than the given one. + */ + PolarStereographic(final PolarStereographic other) { + super(other); + } + + /** + * Returns the sequence of normalization → {@code this} → denormalization transforms + * as a whole. The transform returned by this method except (longitude, latitude) + * coordinates in degrees and returns (x,y) coordinates in metres. + * + *

The non-linear part of the returned transform will be {@code this} transform, except if the ellipsoid + * is spherical. In the later case, {@code this} transform will be replaced by a simplified implementation.

+ * + * @param factory The factory to use for creating the transform. + * @return The map projection from (λ,φ) to (x,y) coordinates. + * @throws FactoryException if an error occurred while creating a transform. + */ + @Override + public MathTransform createMapProjection(final MathTransformFactory factory) throws FactoryException { + PolarStereographic kernel = this; + if (excentricity == 0) { + kernel = new Spherical(this); + } + return context.completeTransform(factory, kernel); + } + + /** * Converts the specified (θ,φ) coordinate (units in radians) and stores the result in {@code dstPts}. * In addition, opportunistically computes the projection derivative if {@code derivate} is {@code true}. * @@ -206,7 +272,7 @@ public class PolarStereographic extends /* * From EPSG guide: * - * t = tan(π/4 + φ/2) / {[(1+esinφ) / (1–esinφ)]^(e/2)} + * t = tan(π/4 + φ/2) / {[(1 + ℯ⋅sinφ) / (1 – ℯ⋅sinφ)]^(ℯ/2)} * * The next step is to compute ρ = 2⋅a⋅k₀⋅t / …, but those steps are * applied by the denormalization matrix and shall not be done here. @@ -242,6 +308,96 @@ public class PolarStereographic extends final double x = srcPts[srcOff ]; final double y = srcPts[srcOff+1]; dstPts[dstOff ] = atan2(x, y); // Really (x,y), not (y,x) - dstPts[dstOff+1] = φ(hypot(x, y)); + dstPts[dstOff+1] = -φ(hypot(x, y)); + } + + + /** + * Provides the transform equations for the spherical case of the polar stereographic projection. + * + * @author Gerald Evenden (USGS) + * @author André Gosselin (MPO) + * @author Martin Desruisseaux (MPO, IRD, Geomatys) + * @author Rueben Schulz (UBC) + * @since 0.6 + * @version 0.6 + * @module + */ + static final class Spherical extends PolarStereographic { + /** + * For compatibility with different versions during deserialization. + */ + private static final long serialVersionUID = 1655096575897215547L; + + /** + * Constructs a new map projection from the parameters of the given projection. + * + * @param other The other projection (usually ellipsoidal) from which to copy the parameters. + */ + protected Spherical(final PolarStereographic other) { + super(other); + } + + /** + * {@inheritDoc} + */ + @Override + public Matrix transform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff, + final boolean derivate) throws ProjectionException + { + final double θ = srcPts[srcOff ]; // θ = λ - λ₀ + final double φ = srcPts[srcOff+1]; + final double sinθ = sin(θ); + final double cosθ = cos(θ); + final double t = tan(PI/4 + 0.5*φ); + final double x = t * sinθ; // Synder 21-5 + final double y = t * cosθ; // Synder 21-6 + Matrix derivative = null; + if (derivate) { + final double dt = t / cos(φ); + derivative = new Matrix2(y, dt*sinθ, // ∂x/∂λ , ∂x/∂φ + -x, dt*cosθ); // ∂y/∂λ , ∂y/∂φ + } + // Following part is common to all spherical projections: verify, store and return. + assert Assertions.checkDerivative(derivative, super.transform(srcPts, srcOff, dstPts, dstOff, derivate)) + && Assertions.checkTransform(dstPts, dstOff, x, y); // dstPts = result from ellipsoidal formulas. + if (dstPts != null) { + dstPts[dstOff ] = x; + dstPts[dstOff+1] = y; + } + return derivative; + } + + /** + * {@inheritDoc} + */ + @Override + protected void inverseTransform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff) + throws ProjectionException + { + double x = srcPts[srcOff ]; + double y = srcPts[srcOff+1]; + final double ρ = hypot(x, y); + x = atan2(x, y); // Really (x,y), not (y,x) + y = 2*atan(ρ) - PI/2; // (20-14) with φ1=90° and cos(y) = sin(π/2 + y). + assert checkInverseTransform(srcPts, srcOff, dstPts, dstOff, x, y); + dstPts[dstOff ] = x; + dstPts[dstOff+1] = y; + } + + /** + * Computes using ellipsoidal formulas and compare with the + * result from spherical formulas. Used in assertions only. + */ + private boolean checkInverseTransform(final double[] srcPts, final int srcOff, + final double[] dstPts, final int dstOff, + final double λ, final double φ) + throws ProjectionException + { + super.inverseTransform(srcPts, srcOff, dstPts, dstOff); + return Assertions.checkInverseTransform(dstPts, dstOff, λ, φ); + } } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/projection/PolarStereographicTest.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -19,9 +19,15 @@ package org.apache.sis.referencing.opera import org.opengis.util.FactoryException; import org.opengis.referencing.operation.TransformException; import org.apache.sis.internal.referencing.provider.PolarStereographicA; +import org.apache.sis.internal.referencing.provider.PolarStereographicB; +import org.apache.sis.referencing.operation.transform.CoordinateDomain; +import org.apache.sis.parameter.Parameters; +import org.apache.sis.test.DependsOnMethod; import org.apache.sis.test.DependsOn; import org.junit.Test; +import static java.lang.StrictMath.*; + /** * Tests the {@link PolarStereographic} class. @@ -34,6 +40,54 @@ import org.junit.Test; @DependsOn(NormalizedProjectionTest.class) public final strictfp class PolarStereographicTest extends MapProjectionTestCase { /** + * Creates a new instance of {@link PolarStereographic}. + * + * @param ellipse {@code false} for a sphere, or {@code true} for WGS84 ellipsoid. + * @param latitudeOfOrigin The latitude of origin, in decimal degrees. + */ + private void initialize(final boolean ellipse, final double latitudeOfOrigin) { + final PolarStereographicA method = new PolarStereographicA(); + final Parameters parameters = parameters(method, ellipse); + parameters.getOrCreate(PolarStereographicA.LATITUDE_OF_ORIGIN).setValue(latitudeOfOrigin); + transform = new PolarStereographic(method, parameters); + if (!ellipse) { + transform = new PolarStereographic.Spherical((PolarStereographic) transform); + } + tolerance = NORMALIZED_TOLERANCE; + validate(); + } + + /** + * Verifies the consistency between spherical and elliptical formulas in the South pole. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + public void testSphericalCaseSouth() throws FactoryException, TransformException { + initialize(false, -90); + final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. + derivativeDeltas = new double[] {delta, delta}; + verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_SOUTH, 56763886); + } + + /** + * Verifies the consistency between spherical and elliptical formulas in the North pole. + * This is the same formulas than the South case, but with the sign of some coefficients negated. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + */ + @Test + @DependsOnMethod("testSphericalCaseSouth") + public void testSphericalCaseNorth() throws FactoryException, TransformException { + initialize(false, 90); + final double delta = toRadians(100.0 / 60) / 1852; // Approximatively 100 metres. + derivativeDeltas = new double[] {delta, delta}; + verifyInDomain(CoordinateDomain.GEOGRAPHIC_RADIANS_NORTH, 56763886); + } + + /** * Tests the Polar Stereographic (variant A) case (EPSG:9810). * This test is defined in GeoAPI conformance test suite. * @@ -43,8 +97,21 @@ public final strictfp class PolarStereog * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicA() */ @Test - @org.junit.Ignore("To debug") - public void testTransverseMercator() throws FactoryException, TransformException { + public void testPolarStereographicA() throws FactoryException, TransformException { createGeoApiTest(new PolarStereographicA()).testPolarStereographicA(); } + + /** + * Tests the Polar Stereographic (variant B) case (EPSG:9829). + * This test is defined in GeoAPI conformance test suite. + * + * @throws FactoryException if an error occurred while creating the map projection. + * @throws TransformException if an error occurred while projecting a coordinate. + * + * @see org.opengis.test.referencing.ParameterizedTransformTest#testPolarStereographicB() + */ + @Test + public void testPolarStereographicB() throws FactoryException, TransformException { + createGeoApiTest(new PolarStereographicB()).testPolarStereographicB(); + } } Modified: sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java?rev=1691751&r1=1691750&r2=1691751&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/CoordinateDomain.java [UTF-8] Sat Jul 18 16:35:54 2015 @@ -19,6 +19,7 @@ package org.apache.sis.referencing.opera import java.util.Random; import org.apache.sis.measure.Latitude; import org.apache.sis.measure.Longitude; + import static java.lang.StrictMath.*; import static org.apache.sis.internal.metadata.ReferencingServices.AUTHALIC_RADIUS;