Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/ConformalProjection.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -16,19 +16,15 @@ */ package org.apache.sis.referencing.operation.projection; -import java.util.Map; import java.io.IOException; import java.io.ObjectInputStream; -import org.opengis.parameter.ParameterDescriptor; -import org.opengis.referencing.operation.OperationMethod; import org.apache.sis.util.resources.Errors; -import org.apache.sis.parameter.Parameters; import static java.lang.Math.*; /** - * Base class of {@link LambertConformal}, {@link Mercator} and {@link PolarStereographic} projections. + * Base class of {@link LambertConicConformal}, {@link Mercator} and {@link PolarStereographic} projections. * All those projections have in common the property of being <cite>conformal</cite>, i.e. they preserve * angles locally. However we do not put this base class in public API because we do not (yet) guarantee * than all conformal projections will extend this base class. @@ -45,6 +41,12 @@ import static java.lang.Math.*; * appear to be the same with the <var>n</var> factor fixed to 1 or -1, so we leverage the code provided by * this base class. This class hierarchy is only an implementation convenience and not part of public API.</p> * + * <div class="note"><b>Reference:</b> + * “Lambert developed the regular Conformal Conic as the oblique aspect of a family containing the previously + * known polar Stereographic and regular Mercator projections. (…) If the standard parallels are symmetrical + * about the Equator, the regular Mercator results (although formulas must be revised). If the only standard + * parallel is a pole, the polar Stereographic results.” (Snyder, page 105)</div> + * * @author Martin Desruisseaux (Geomatys) * @since 0.6 * @version 0.6 @@ -101,17 +103,12 @@ abstract class ConformalProjection exten private transient boolean useIterations; /** - * Constructs a new map projection from the supplied parameters. + * Creates a new normalized projection from the parameters computed by the given initializer. * - * @param method Description of the map projection parameters. - * @param parameters The parameters of the projection to be created. - * @param roles Parameters to look for <cite>central meridian</cite>, <cite>scale factor</cite>, - * <cite>false easting</cite>, <cite>false northing</cite> and other values. - */ - protected ConformalProjection(final OperationMethod method, final Parameters parameters, - final Map<ParameterRole, ? extends ParameterDescriptor<Double>> roles) - { - super(method, parameters, roles); + * @param initializer The initializer for computing map projection internal parameters. + */ + ConformalProjection(final Initializer initializer) { + super(initializer); initialize(); }
Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/Mercator.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -16,7 +16,6 @@ */ package org.apache.sis.referencing.operation.projection; -import java.util.Map; import java.util.EnumMap; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; @@ -39,6 +38,7 @@ import org.apache.sis.util.Workaround; import static java.lang.Math.*; import static java.lang.Double.*; import static org.apache.sis.math.MathFunctions.isPositive; +import static org.apache.sis.internal.util.DoubleDouble.verbatim; /** @@ -125,13 +125,33 @@ public class Mercator extends ConformalP private final byte variant; /** - * Returns the (<var>role</var> → <var>parameter</var>) associations for a Mercator projection of the given variant. + * Creates a Mercator projection from the given parameters. + * The {@code method} argument can be the description of one of the following: + * + * <ul> + * <li><cite>"Mercator (variant A)"</cite>, also known as <cite>"Mercator (1SP)"</cite>.</li> + * <li><cite>"Mercator (variant B)"</cite>, also known as <cite>"Mercator (2SP)"</cite>.</li> + * <li><cite>"Mercator (variant C)"</cite>.</li> + * <li><cite>"Mercator (Spherical)"</cite>.</li> + * <li><cite>"Popular Visualisation Pseudo Mercator"</cite>.</li> + * <li><cite>"Miller Cylindrical"</cite>.</li> + * </ul> * - * @param variant One of {@link #REGIONAL}, {@link #SPHERICAL}, {@link #PSEUDO} or {@link #MILLER} constants. - * @return The roles map to give to super-class constructor. + * @param method Description of the projection parameters. + * @param parameters The parameter values of the projection to create. + */ + public Mercator(final OperationMethod method, final Parameters parameters) { + this(initializer(method, parameters)); + } + + /** + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). */ @SuppressWarnings("fallthrough") - private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final byte variant) { + @Workaround(library="JDK", version="1.7") + private static Initializer initializer(final OperationMethod method, final Parameters parameters) { + final byte variant = getVariant(method); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); /* * "Longitude of origin" is a parameter of all Mercator projections, but is intentionally omitted from @@ -171,27 +191,7 @@ public class Mercator extends ConformalP break; } } - return roles; - } - - /** - * Creates a Mercator projection from the given parameters. - * The {@code method} argument can be the description of one of the following: - * - * <ul> - * <li><cite>"Mercator (variant A)"</cite>, also known as <cite>"Mercator (1SP)"</cite>.</li> - * <li><cite>"Mercator (variant B)"</cite>, also known as <cite>"Mercator (2SP)"</cite>.</li> - * <li><cite>"Mercator (variant C)"</cite>.</li> - * <li><cite>"Mercator (Spherical)"</cite>.</li> - * <li><cite>"Popular Visualisation Pseudo Mercator"</cite>.</li> - * <li><cite>"Miller Cylindrical"</cite>.</li> - * </ul> - * - * @param method Description of the projection parameters. - * @param parameters The parameter values of the projection to create. - */ - public Mercator(final OperationMethod method, final Parameters parameters) { - this(method, parameters, getVariant(method)); + return new Initializer(method, parameters, roles, variant); } /** @@ -199,15 +199,15 @@ 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 variant) { - super(method, parameters, roles(variant)); - this.variant = variant; + private Mercator(final Initializer initializer) { + super(initializer); + this.variant = initializer.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 * for now; it will be converted to radians later. */ - final double λ0 = getAndStore(parameters, Mercator1SP.LONGITUDE_OF_ORIGIN); + final double λ0 = initializer.getAndStore(Mercator1SP.LONGITUDE_OF_ORIGIN); /* * The "Latitude of natural origin" is not formally a parameter of Mercator projection. But the parameter * is included for completeness in CRS labelling, with the restriction (specified in EPSG documentation) @@ -220,7 +220,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, (variant == REGIONAL) + final double φ0 = toRadians(initializer.getAndStore((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 @@ -228,9 +228,8 @@ public class Mercator extends ConformalP * the later is for projections "1SP" (namely variant A and spherical). However we let users specify both * if they really want, since we sometime see such CRS definitions. */ - final double φ1 = toRadians(getAndStore(parameters, Mercator2SP.STANDARD_PARALLEL)); - final DoubleDouble k0 = new DoubleDouble(cos(φ1), 0); - k0.divide(rν(sin(φ1)), 0); + final double φ1 = toRadians(initializer.getAndStore(Mercator2SP.STANDARD_PARALLEL)); + final Number k0 = verbatim(initializer.scaleAtφ(sin(φ1), cos(φ1))); /* * In principle we should rotate the central meridian (λ0) in the normalization transform, as below: * @@ -247,16 +246,21 @@ public class Mercator extends ConformalP denormalize.convertBefore(0, k0, null); denormalize.convertBefore(1, k0, null); if (λ0 != 0) { + /* + * Use double-double arithmetic here for consistency with the work done in the normalization matrix. + * The intend is to have exact value at 'double' precision when computing Matrix.invert(). Note that + * there is no such goal for other parameters computed from sine or consine functions. + */ final DoubleDouble offset = DoubleDouble.createDegreesToRadians(); offset.multiply(-λ0); denormalize.convertBefore(0, null, offset); } if (φ0 != 0) { - denormalize.convertBefore(1, null, new DoubleDouble(-log(expOfNorthing(φ0, excentricity * sin(φ0))))); + denormalize.convertBefore(1, null, verbatim(-log(expOfNorthing(φ0, excentricity * sin(φ0))))); } if (variant == MILLER) { - normalize.convertBefore(1, new DoubleDouble(0.80), null); - denormalize.convertBefore(1, new DoubleDouble(1.25), null); + normalize .convertBefore(1, 0.80, null); + denormalize.convertBefore(1, 1.25, null); } /* * At this point we are done, but we add here a little bit a maniac precision hunting. @@ -282,9 +286,9 @@ public class Mercator extends ConformalP * those remaning lines of code. */ if (φ0 == 0 && isPositive(φ1 != 0 ? φ1 : φ0)) { - final DoubleDouble revert = new DoubleDouble(-1, 0); - normalize.convertBefore(1, revert, null); - denormalize.convertBefore(1, revert, null); // Must be before false easting/northing. + final Number reverseSign = verbatim(-1); + normalize .convertBefore(1, reverseSign, null); + denormalize.convertBefore(1, reverseSign, null); // Must be before false easting/northing. } } Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/NormalizedProjection.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -16,16 +16,14 @@ */ package org.apache.sis.referencing.operation.projection; -import java.util.List; -import java.util.ArrayList; import java.util.Map; +import java.util.HashMap; import java.io.Serializable; +import java.lang.reflect.Modifier; import org.opengis.metadata.Identifier; -import org.opengis.parameter.ParameterValue; import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; -import org.opengis.parameter.GeneralParameterDescriptor; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.MathTransform2D; @@ -34,22 +32,22 @@ import org.opengis.referencing.operation import org.opengis.referencing.operation.MathTransformFactory; import org.opengis.util.FactoryException; import org.apache.sis.util.Debug; +import org.apache.sis.util.CharSequences; import org.apache.sis.util.ComparisonMode; import org.apache.sis.parameter.Parameters; -import org.apache.sis.parameter.DefaultParameterDescriptorGroup; -import org.apache.sis.referencing.IdentifiedObjects; +import org.apache.sis.parameter.ParameterBuilder; +import org.apache.sis.metadata.iso.citation.Citations; import org.apache.sis.referencing.operation.matrix.Matrices; import org.apache.sis.referencing.operation.matrix.MatrixSIS; import org.apache.sis.referencing.operation.transform.AbstractMathTransform2D; import org.apache.sis.referencing.operation.transform.ContextualParameters; -import org.apache.sis.internal.referencing.provider.MapProjection; import org.apache.sis.internal.referencing.Formulas; -import org.apache.sis.internal.util.DoubleDouble; +import org.apache.sis.internal.util.CollectionsExt; import org.apache.sis.internal.util.Constants; +import org.apache.sis.internal.util.Utilities; import org.apache.sis.internal.util.Numerics; import static java.lang.Math.*; -import static org.apache.sis.util.ArgumentChecks.ensureNonNull; // Branch-dependent imports import java.util.Objects; @@ -132,14 +130,19 @@ public abstract class NormalizedProjecti /** * Maximum difference allowed when comparing longitudes or latitudes in radians. - * The current value take the system-wide angular tolerance value (equivalent to + * The current value takes the system-wide angular tolerance value (equivalent to * about 1 cm on Earth) converted to radians. * * <p>Some formulas use this tolerance value for testing sines or cosines of an angle. * In the sine case, this is justified because sin(θ) ≅ θ when θ is small. * Similar reasoning applies to cosine with cos(θ) ≅ θ + π/2 when θ is small.</p> + * + * <p>Some formulas may use this tolerance value as a <em>linear</em> tolerance on the unit sphere. + * This is okay because the arc length for an angular tolerance θ is r⋅θ, but in this class r=1.</p> */ static final double ANGULAR_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180); + // Note: an alternative way to compute this value could be Formulas.LINEAR_TOLERANCE / AUTHALIC_RADIUS. + // But the later is only 0.07% lower than the current value. /** * Desired accuracy for the result of iterative computations, in radians. @@ -149,7 +152,7 @@ public abstract class NormalizedProjecti * So if the linear tolerance is 1 cm, then the accuracy that we will seek for is 0.25 cm (about * 4E-10 radians). The 0.25 factor is a safety margin for meeting the 1 cm accuracy.</p> */ - static final double ITERATION_TOLERANCE = Formulas.ANGULAR_TOLERANCE * (PI/180) * 0.25; + static final double ITERATION_TOLERANCE = ANGULAR_TOLERANCE * 0.25; /** * Maximum number of iterations for iterative computations. @@ -160,6 +163,17 @@ public abstract class NormalizedProjecti static final int MAXIMUM_ITERATIONS = 15; /** + * The internal parameter descriptors. Keys are implementation classes. Values are parameter descriptor groups + * containing at least a parameter for the {@link #excentricity} value, and optionally other internal parameter + * added by some subclasses. + * + * <p>Entries are created only when first needed. Those descriptors are usually never created since they are + * used only by {@link #getParameterDescriptors()}, which is itself invoked mostly for debugging purpose.</p> + */ + @Debug + private static final Map<Class<?>,ParameterDescriptorGroup> DESCRIPTORS = new HashMap<>(); + + /** * The parameters used for creating this projection. They are used for formatting <cite>Well Known Text</cite> (WKT) * and error messages. Subclasses shall not use the values defined in this object for computation purpose, except at * construction time. @@ -383,60 +397,21 @@ public abstract class NormalizedProjecti * <cite>false easting</cite>, <cite>false northing</cite> and other values. */ protected NormalizedProjection(final OperationMethod method, final Parameters parameters, - final Map<ParameterRole, ? extends ParameterDescriptor<Double>> roles) + final Map<ParameterRole, ? extends ParameterDescriptor<? extends Number>> roles) { - ensureNonNull("method", method); - ensureNonNull("parameters", parameters); - ensureNonNull("roles", roles); - context = new ContextualParameters(method); - /* - * Note: we do not use Map.getOrDefault(K,V) below because the user could have explicitly associated - * a null value to keys (we are paranoiac...) and because it conflicts with the "? extends" part of - * in this constructor signature. - */ - ParameterDescriptor<Double> semiMajor = roles.get(ParameterRole.SEMI_MAJOR); - ParameterDescriptor<Double> semiMinor = roles.get(ParameterRole.SEMI_MINOR); - if (semiMajor == null) semiMajor = MapProjection.SEMI_MAJOR; - if (semiMinor == null) semiMinor = MapProjection.SEMI_MINOR; - - double a = getAndStore(parameters, semiMajor); - final double b = getAndStore(parameters, semiMinor); - final double λ0 = getAndStore(parameters, roles.get(ParameterRole.CENTRAL_MERIDIAN)); - final double fe = getAndStore(parameters, roles.get(ParameterRole.FALSE_EASTING)) - - getAndStore(parameters, roles.get(ParameterRole.FALSE_WESTING)); - final double fn = getAndStore(parameters, roles.get(ParameterRole.FALSE_NORTHING)) - - getAndStore(parameters, roles.get(ParameterRole.FALSE_SOUTHING)); - final double rs = b / a; - excentricitySquared = 1 - (rs * rs); - excentricity = sqrt(excentricitySquared); - if (excentricitySquared != 0) { - final ParameterDescriptor<Double> radius = roles.get(ParameterRole.LATITUDE_OF_CONFORMAL_SPHERE_RADIUS); - if (radius != null) { - /* - * EPSG said: R is the radius of the sphere and will normally be one of the CRS parameters. - * If the figure of the earth used is an ellipsoid rather than a sphere then R should be calculated - * as the radius of the conformal sphere at the projection origin at latitude φ₀ using the formula - * for Rc given in section 1.2, table 3. - * - * Table 3 gives: - * Radius of conformal sphere Rc = a √(1 – ℯ²) / (1 – ℯ²⋅sin²φ) - * - * Using √(1 – ℯ²) = b/a we rewrite as: Rc = b / (1 – ℯ²⋅sin²φ) - */ - final double sinφ = sin(toRadians(parameters.doubleValue(radius))); - a = b / (1 - excentricitySquared * (sinφ*sinφ)); - } - } - context.normalizeGeographicInputs(λ0); - final DoubleDouble k = new DoubleDouble(a); - final ParameterDescriptor<Double> scaleFactor = roles.get(ParameterRole.SCALE_FACTOR); - if (scaleFactor != null) { - k.multiply(getAndStore(parameters, scaleFactor)); - } - final MatrixSIS denormalize = context.getMatrix(false); - denormalize.convertAfter(0, k, new DoubleDouble(fe)); - denormalize.convertAfter(1, k, new DoubleDouble(fn)); - inverse = new Inverse(); + this(new Initializer(method, parameters, roles, (byte) 0)); + } + + /** + * Creates a new normalized projection from the parameters computed by the given initializer. + * + * @param initializer The initializer for computing map projection internal parameters. + */ + NormalizedProjection(final Initializer initializer) { + context = initializer.context; + excentricitySquared = initializer.excentricitySquared.value; + excentricity = sqrt(excentricitySquared); // DoubleDouble.sqrt() does not make any difference here. + inverse = new Inverse(); } /** @@ -480,50 +455,6 @@ public abstract class NormalizedProjecti } /** - * Gets a parameter value identified by the given descriptor and stores it in the {@link #context}. - * A "contextual parameter" is a parameter that apply to the normalize → {@code this} → denormalize - * chain as a whole. It does not really apply to this {@code NormalizedProjection} instance when taken alone. - * - * <p>This method performs the following actions:</p> - * <ul> - * <li>Convert the value to the units specified by the descriptor.</li> - * <li>Ensure that the value is contained in the range specified by the descriptor.</li> - * <li>Store the value only if different than the default value.</li> - * </ul> - * - * This method shall be invoked at construction time only. - */ - final double getAndStore(final Parameters parameters, final ParameterDescriptor<Double> descriptor) { - if (descriptor == null) { - return 0; // Default value for all parameters except scale factor. - } - final double value = parameters.doubleValue(descriptor); // Apply a unit conversion if needed. - final Double defaultValue = descriptor.getDefaultValue(); - if (defaultValue == null || !defaultValue.equals(value)) { - MapProjection.validate(descriptor, value); - context.getOrCreate(descriptor).setValue(value); - } - return value; - } - - /** - * Same as {@link #getAndStore(Parameters, ParameterDescriptor)}, but returns the given default value - * if the parameter is not specified. This method shall be used only for parameters having a default - * value more complex than what we can represent in {@link ParameterDescriptor#getDefaultValue()}. - */ - final double getAndStore(final Parameters parameters, final ParameterDescriptor<Double> descriptor, - final double defaultValue) - { - final Double value = parameters.getValue(descriptor); // Apply a unit conversion if needed. - if (value == null) { - return defaultValue; - } - MapProjection.validate(descriptor, value); - context.parameter(descriptor.getName().getCode()).setValue(value); - return value; - } - - /** * Returns the sequence of <cite>normalization</cite> → {@code this} → <cite>denormalization</cite> transforms * as a whole. The transform returned by this method except (<var>longitude</var>, <var>latitude</var>) * coordinates in <em>degrees</em> and returns (<var>x</var>,<var>y</var>) coordinates in <em>metres</em>. @@ -566,18 +497,11 @@ public abstract class NormalizedProjecti } /** - * Returns a copy of the parameter values for this projection. - * This base class supplies a value only for the following parameters: - * - * <ul> - * <li>Semi-major axis length, which is set to 1.</li> - * <li>Semi-minor axis length, which is set to - * <code>sqrt(1 - {@linkplain #excentricitySquared ℯ²})</code>.</li> - * </ul> - * - * Subclasses must complete if needed. Many projections will not need to complete, - * because most parameters like the scale factor or the false easting/northing can - * be handled by the (de)normalization affine transforms. + * Returns a copy of non-linear internal parameter values of this {@code NormalizedProjection}. + * The returned group contained at least the {@link #excentricity} parameter value. + * Some subclasses add more non-linear parameters, but most of them do not because many parameters + * like the <cite>scale factor</cite> or the <cite>false easting/northing</cite> are handled by the + * {@linkplain ContextualParameters#getMatrix(boolean) (de)normalization affine transforms} instead. * * <div class="note"><b>Note:</b> * This method is mostly for {@linkplain org.apache.sis.io.wkt.Convention#INTERNAL debugging purposes} @@ -585,56 +509,85 @@ public abstract class NormalizedProjecti * Most GIS applications will instead be interested in the {@linkplain #getContextualParameters() * contextual parameters}.</div> * - * @return A copy of the parameter values for this normalized projection. + * @return A copy of the internal parameter values for this normalized projection. */ @Debug @Override public ParameterValueGroup getParameterValues() { - return getParameterValues(new String[] { - Constants.SEMI_MAJOR, - Constants.SEMI_MINOR - }); + final ParameterValueGroup group = getParameterDescriptors().createValue(); + group.parameter("excentricity").setValue(excentricity); + final String[] names = getInternalParameterNames(); + final double[] values = getInternalParameterValues(); + for (int i=0; i<names.length; i++) { + group.parameter(names[i]).setValue(values[i]); + } + return group; } /** - * Filters the parameter descriptor in order to retain only the parameters of the given names, and - * sets the semi-major and semi-minor axis lengths. The specified parameters list should contains at - * least the {@code "semi_major"} and {@code "semi_minor"} strings. + * Returns a description of the non-linear internal parameters of this {@code NormalizedProjection}. + * The returned group contained at least a descriptor for the {@link #excentricity} parameter. + * Subclasses may add more parameters. + * + * <p>This method is for inspecting the parameter values of this non-linear kernel only, + * not for inspecting the {@linkplain #getContextualParameters() contextual parameters}. + * Inspecting the kernel parameter values is usually for debugging purpose only.</p> * - * <p>This filtered descriptor is used for displaying the parameter values of this non-linear kernel only, - * not for displaying the {@linkplain #getContextualParameters() contextual parameters}. Since displaying - * the kernel parameter values is for debugging purpose only, it is not worth to cache this descriptor.</p> + * @return A description of the internal parameters. */ @Debug - final ParameterValueGroup getParameterValues(final String[] nonLinearParameters) { - ParameterDescriptorGroup descriptor = getParameterDescriptors(); - final List<GeneralParameterDescriptor> filtered = new ArrayList<>(nonLinearParameters.length); - for (final GeneralParameterDescriptor p : descriptor.descriptors()) { - for (final String name : nonLinearParameters) { - if (IdentifiedObjects.isHeuristicMatchForName(p, name)) { - filtered.add(p); - break; + @Override + public ParameterDescriptorGroup getParameterDescriptors() { + Class<?> type = getClass(); + while (!Modifier.isPublic(type.getModifiers())) { + type = type.getSuperclass(); + } + ParameterDescriptorGroup group; + synchronized (DESCRIPTORS) { + group = DESCRIPTORS.get(type); + if (group == null) { + final ParameterBuilder builder = new ParameterBuilder().setRequired(true); + if (Utilities.isSIS(type)) { + builder.setCodeSpace(Citations.SIS, "SIS"); } + final String[] names = getInternalParameterNames(); + final ParameterDescriptor<?>[] parameters = new ParameterDescriptor<?>[names.length + 1]; + for (int i=0; i<parameters.length; i++) { + final ParameterDescriptor<?> p; + if (i == 0) { + final ParameterDescriptorGroup existing = CollectionsExt.first(DESCRIPTORS.values()); + if (existing != null) { + p = (ParameterDescriptor<?>) existing.descriptor("excentricity"); + } else { + p = builder.addName(Citations.SIS, "excentricity").createBounded(0, 1, Double.NaN, null); + } + } else { + p = builder.addName(names[i-1]).create(Double.class, null); + } + parameters[i] = p; + } + group = builder.addName(CharSequences.camelCaseToSentence(type.getSimpleName())).createGroup(1, 1, parameters); + DESCRIPTORS.put(type, group); } } - descriptor = new DefaultParameterDescriptorGroup(IdentifiedObjects.getProperties(descriptor), - 1, 1, filtered.toArray(new GeneralParameterDescriptor[filtered.size()])); - /* - * Parameter values for the ellipsoid semi-major and semi-minor axis lengths are 1 and <= 1 - * respectively because the denormalization (e.g. multiplication by a scale factor) will be - * applied by an affine transform after this NormalizedProjection. - */ - final ParameterValueGroup values = descriptor.createValue(); - for (final GeneralParameterDescriptor desc : filtered) { - final String name = desc.getName().getCode(); - final ParameterValue<?> p = values.parameter(name); - switch (name) { - case Constants.SEMI_MAJOR: p.setValue(1.0); break; - case Constants.SEMI_MINOR: p.setValue(sqrt(1 - excentricitySquared)); break; - default: p.setValue(context.parameter(name).getValue()); - } - } - return values; + return group; + } + + /** + * Returns the names of any additional internal parameters (other than {@link #excentricity}) + * that this projection has. The length of this array must be the same than the length of the + * {@link #getInternalParameterValues()} array, if the later is non-null. + */ + String[] getInternalParameterNames() { + return CharSequences.EMPTY_ARRAY; + } + + /** + * Returns the values of any additional internal parameters (other than {@link #excentricity}) that + * this projection has. Those values are also compared by {@link #equals(Object, ComparisonMode)}. + */ + double[] getInternalParameterValues() { + return null; } /** @@ -760,14 +713,20 @@ public abstract class NormalizedProjecti } /** - * Computes a hash code value for this map projection. - * The default implementation computes a value from the parameters given at construction time. + * Computes a hash code value for this {@code NormalizedProjection}. * * @return The hash code value. */ @Override protected int computeHashCode() { - return context.hashCode() + 31 * super.computeHashCode(); + long c = Double.doubleToLongBits(excentricity); + final double[] parameters = getInternalParameterValues(); + if (parameters != null) { + for (int i=0; i<parameters.length; i++) { + c = c*31 + Double.doubleToLongBits(parameters[i]); + } + } + return super.computeHashCode() ^ Numerics.hashCode(c); } /** @@ -792,70 +751,82 @@ public abstract class NormalizedProjecti * @return {@code true} if the given object is equivalent to this map projection. */ @Override + @SuppressWarnings("fallthrough") public boolean equals(final Object object, final ComparisonMode mode) { if (object == this) { return true; } - if (super.equals(object, mode)) { - final double e1, e2; - final NormalizedProjection that = (NormalizedProjection) object; - if (mode.ordinal() < ComparisonMode.IGNORE_METADATA.ordinal()) { + if (!super.equals(object, mode)) { + return false; + } + final NormalizedProjection that = (NormalizedProjection) object; + switch (mode) { + case STRICT: + case BY_CONTRACT: { if (!Objects.equals(context, that.context)) { return false; } - e1 = this.excentricitySquared; - e2 = that.excentricitySquared; - } else { - e1 = this.excentricity; - e2 = that.excentricity; + // Fall through for comparing the excentricity. + } + case IGNORE_METADATA: { + /* + * There is no need to compare both 'excentricity' and 'excentricitySquared' since the former + * is computed from the later. We are better to compare 'excentricitySquared' since it is the + * original value from which the other value is derived. + */ + if (!Numerics.equals(excentricitySquared, that.excentricitySquared)) { + return false; + } + break; + } + default: { + /* + * We want to compare the excentricity with a tolerance threshold corresponding approximatively + * to an error of 1 cm on Earth. The excentricity for an ellipsoid of semi-major axis a=1 is: + * + * ℯ² = 1 - b² + * + * If we add a slight ε error to the semi-minor axis length (where ε will be our linear tolerance + * threshold), we get: + * + * (ℯ + ε′)² = 1 - (b + ε)² ≈ 1 - (b² + 2⋅b⋅ε) assuming ε ≪ b + * + * Replacing 1 - b² by ℯ²: + * + * ℯ² + 2⋅ℯ⋅ε′ ≈ ℯ² - 2⋅b⋅ε + * + * After a few rearrangements: + * + * ε′ ≈ ε⋅(ℯ - 1/ℯ) + * + * Note that ε′ is negative for ℯ < 1 so we actually need to compute ε⋅(1/ℯ - ℯ) instead. + * The result is less than 2E-8 for the excentricity of the Earth. + */ + final double e = max(excentricity, that.excentricity); + if (!Numerics.epsilonEqual(excentricity, that.excentricity, ANGULAR_TOLERANCE * (1/e - e))) { + assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference( + "excentricity", excentricity, that.excentricity); + return false; + } + break; } + } + final double[] parameters = getInternalParameterValues(); + if (parameters != null) { /* - * There is no need to compare both 'excentricity' and 'excentricitySquared' since - * the former is computed from the later. In strict comparison mode, we are better - * to compare the 'excentricitySquared' since it is the original value from which - * the other value is derived. However in approximative comparison mode, we need - * to use the 'excentricity', otherwise we would need to take the square of the - * tolerance factor before comparing 'excentricitySquared'. + * super.equals(…) guarantees that the two objects are of the same class. + * So in SIS implementation, this implies that the arrays have the same length. */ - return Numerics.epsilonEqual(e1, e2, mode); + final double[] others = that.getInternalParameterValues(); + assert others.length == parameters.length; + for (int i=0; i<parameters.length; i++) { + if (!Numerics.epsilonEqual(parameters[i], others[i], mode)) { + assert (mode != ComparisonMode.DEBUG) : Numerics.messageForDifference( + getInternalParameterNames()[i], parameters[i], others[i]); + return false; + } + } } - return false; - } - - - - - ////////////////////////////////////////////////////////////////////////////////////////// - //////// //////// - //////// FORMULAS FROM EPSG or SNYDER //////// - //////// //////// - ////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Computes the reciprocal of the radius of curvature of the ellipsoid perpendicular to the meridian at latitude φ. - * That radius of curvature is: - * - * <blockquote>ν = 1 / √(1 - ℯ²⋅sin²φ)</blockquote> - * - * This method returns 1/ν. - * - * <div class="section">Relationship with Snyder</div> - * This is related to functions (14-15) from Snyder (used for computation of scale factors - * at the true scale latitude) as below: - * - * <blockquote>m = cosφ / rν</blockquote> - * - * Special cases: - * <ul> - * <li>If φ is 0°, then <var>m</var> is 1.</li> - * <li>If φ is ±90°, then <var>m</var> is 0 provided that we are not in the spherical case - * (otherwise we get {@link Double#NaN}).</li> - * </ul> - * - * @param sinφ The sine of the φ latitude in radians. - * @return Reciprocal of the radius of curvature of the ellipsoid perpendicular to the meridian at latitude φ. - */ - final double rν(final double sinφ) { - return sqrt(1 - excentricitySquared * (sinφ*sinφ)); + return true; } } Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/PolarStereographic.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -16,7 +16,6 @@ */ package org.apache.sis.referencing.operation.projection; -import java.util.Map; import java.util.EnumMap; import org.opengis.util.FactoryException; import org.opengis.parameter.ParameterDescriptor; @@ -34,8 +33,10 @@ import org.apache.sis.parameter.Paramete import org.apache.sis.util.resources.Errors; import org.apache.sis.util.Workaround; import org.apache.sis.measure.Latitude; +import org.apache.sis.math.MathFunctions; import static java.lang.Math.*; +import static org.apache.sis.internal.util.DoubleDouble.verbatim; /** @@ -53,7 +54,7 @@ import static java.lang.Math.*; * @see EquatorialStereographic * @see ObliqueStereographic */ -public class PolarStereographic extends ConformalProjection { // Seen as a special case of LambertConformal. +public class PolarStereographic extends ConformalProjection { /** * For cross-version compatibility. */ @@ -85,12 +86,30 @@ public class PolarStereographic extends } /** - * Returns the (<var>role</var> → <var>parameter</var>) associations for a Polar Stereographic projection. + * Creates a Polar Stereographic projection from the given parameters. + * The {@code method} argument can be the description of one of the following: + * + * <ul> + * <li><cite>"Polar Stereographic (Variant A)"</cite>.</li> + * <li><cite>"Polar Stereographic (Variant B)"</cite>.</li> + * <li><cite>"Polar Stereographic (Variant C)"</cite>.</li> + * </ul> * - * @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. + * @param method Description of the projection parameters. + * @param parameters The parameter values of the projection to create. */ - private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final byte variant) { + public PolarStereographic(final OperationMethod method, final Parameters parameters) { + this(initializer(method, parameters)); + } + + /** + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). + */ + @SuppressWarnings("fallthrough") + @Workaround(library="JDK", version="1.7") + private static Initializer initializer(final OperationMethod method, final Parameters parameters) { + final byte variant = getVariant(method); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); ParameterDescriptor<Double> falseEasting = PolarStereographicA.FALSE_EASTING; ParameterDescriptor<Double> falseNorthing = PolarStereographicA.FALSE_NORTHING; @@ -104,24 +123,7 @@ public class PolarStereographic extends roles.put(ParameterRole.CENTRAL_MERIDIAN, (variant == A) ? PolarStereographicA.LONGITUDE_OF_ORIGIN : PolarStereographicB.LONGITUDE_OF_ORIGIN); - return roles; - } - - /** - * Creates a Polar Stereographic projection from the given parameters. - * The {@code method} argument can be the description of one of the following: - * - * <ul> - * <li><cite>"Polar Stereographic (Variant A)"</cite>.</li> - * <li><cite>"Polar Stereographic (Variant B)"</cite>.</li> - * <li><cite>"Polar Stereographic (Variant C)"</cite>.</li> - * </ul> - * - * @param method Description of the projection parameters. - * @param parameters The parameter values of the projection to create. - */ - public PolarStereographic(final OperationMethod method, final Parameters parameters) { - this(method, parameters, getVariant(method)); + return new Initializer(method, parameters, roles, variant); } /** @@ -129,8 +131,9 @@ public class PolarStereographic extends * ("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)); + private PolarStereographic(final Initializer initializer) { + super(initializer); + final byte variant = initializer.variant; /* * "Standard parallel" and "Latitude of origin" should be mutually exclusive, * but this is not a strict requirement for the constructor. @@ -148,9 +151,9 @@ public class PolarStereographic extends */ double φ0; if (variant == A) { - φ0 = getAndStore(parameters, PolarStereographicA.LATITUDE_OF_ORIGIN); // Mandatory + φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN); // Mandatory } else { - φ0 = getAndStore(parameters, PolarStereographicA.LATITUDE_OF_ORIGIN, // Optional (should not be present) + φ0 = initializer.getAndStore(PolarStereographicA.LATITUDE_OF_ORIGIN, // Optional (should not be present) (variant == NORTH) ? Latitude.MAX_VALUE : (variant == SOUTH) ? Latitude.MIN_VALUE : Double.NaN); } @@ -160,9 +163,9 @@ public class PolarStereographic extends } double φ1; if (variant == B || variant == C || Double.isNaN(φ0)) { - φ1 = getAndStore(parameters, PolarStereographicB.STANDARD_PARALLEL); // Mandatory + φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL); // Mandatory } else { - φ1 = getAndStore(parameters, PolarStereographicB.STANDARD_PARALLEL, φ0); // Optional + φ1 = initializer.getAndStore(PolarStereographicB.STANDARD_PARALLEL, φ0); // Optional } /* * At this point we should ensure that the sign of φ0 is the same than the sign of φ1, @@ -171,10 +174,18 @@ 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). */ - final boolean isNorthPole = (φ1 >= 0); - φ1 = -abs(toRadians(φ1)); // May be anything in [-π/2 … 0] range. - final double ρ; - Double ρF = null; // Actually -ρF (compared to EPSG guide). + final boolean isNorth = MathFunctions.isPositive(φ1); + if (isNorth) { + /* + * The South case has the most "natural" formulas. For the North case, we use the same formulas + * with only the sign reversed before and after projection. This sign reversal is done in the + * (de)normalization matrices at the end of this method. But we need to apply the same politic + * on the parameters that we will use below. + */ + φ1 = -φ1; + } + φ1 = toRadians(φ1); // May be anything in [-π/2 … 0] range. + final Number ρ, ρF; // This ρF is actually -ρF in EPSG guide. if (abs(φ1 + PI/2) < ANGULAR_TOLERANCE) { /* * Polar Stereographic (variant A) @@ -188,7 +199,8 @@ public class PolarStereographic extends * * In the spherical case, should give ρ == 2. */ - ρ = 2 / sqrt(pow(1+excentricity, 1+excentricity) * pow(1-excentricity, 1-excentricity)); + ρ = verbatim(2 / sqrt(pow(1+excentricity, 1+excentricity) * pow(1-excentricity, 1-excentricity))); + ρF = null; } else { /* * Polar Stereographic (variant B or C) @@ -208,11 +220,9 @@ public class PolarStereographic extends * In the spherical case, should give ρ = 1 + sinφ1 (Synder 21-7 and 21-11). */ final double sinφ1 = sin(φ1); - final double mF = cos(φ1) / rν(sinφ1); - ρ = mF / expOfNorthing(φ1, excentricity*sinφ1); - if (variant == C) { - ρF = -mF; - } + final double mF = initializer.scaleAtφ(sinφ1, cos(φ1)); + ρ = verbatim(mF / expOfNorthing(φ1, excentricity*sinφ1)); + ρF = (variant == C) ? verbatim(-mF) : null; } /* * At this point, all parameters have been processed. Now process to their @@ -221,9 +231,11 @@ public class PolarStereographic extends final MatrixSIS denormalize = context.getMatrix(false); denormalize.convertBefore(0, ρ, null); denormalize.convertBefore(1, ρ, ρF); - if (isNorthPole) { - context.getMatrix(true).convertAfter(1, -1, null); - denormalize.convertBefore(1, -1, null); + if (isNorth) { + final Number reverseSign = verbatim(-1); + final MatrixSIS normalize = context.getMatrix(true); + normalize .convertAfter (1, reverseSign, null); + denormalize.convertBefore(1, reverseSign, null); } } @@ -269,7 +281,7 @@ public class PolarStereographic extends final boolean derivate) throws ProjectionException { /* - * Note: formulas below are very similar to LambertConformal.transform(…) with n = -1. + * Note: formulas below are very similar to LambertConicConformal.transform(…) with n = -1. */ final double θ = srcPts[srcOff ]; // θ = λ - λ₀ final double φ = srcPts[srcOff+1]; // Sign may be reversed Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/projection/TransverseMercator.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -16,17 +16,16 @@ */ package org.apache.sis.referencing.operation.projection; -import java.util.Map; import java.util.EnumMap; import org.opengis.parameter.ParameterDescriptor; import org.opengis.referencing.operation.Matrix; import org.opengis.referencing.operation.OperationMethod; import org.apache.sis.referencing.operation.matrix.MatrixSIS; -import org.apache.sis.internal.referencing.provider.MapProjection; import org.apache.sis.internal.referencing.provider.TransverseMercatorSouth; import org.apache.sis.internal.util.DoubleDouble; import org.apache.sis.parameter.Parameters; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.Workaround; import static java.lang.Math.*; import static org.apache.sis.math.MathFunctions.asinh; @@ -72,11 +71,29 @@ public class TransverseMercator extends private final double h1, h2, h3, h4, ih1, ih2, ih3, ih4; /** - * Returns the (<var>role</var> → <var>parameter</var>) associations for a Transverse Mercator projection. + * Creates a Transverse Mercator projection from the given parameters. + * The {@code method} argument can be the description of one of the following: * - * @return The roles map to give to super-class constructor. + * <ul> + * <li><cite>"Transverse Mercator"</cite>.</li> + * <li><cite>"Transverse Mercator (South Orientated)"</cite>.</li> + * </ul> + * + * @param method Description of the projection parameters. + * @param parameters The parameter values of the projection to create. */ - private static Map<ParameterRole, ParameterDescriptor<Double>> roles(final boolean isSouth) { + public TransverseMercator(final OperationMethod method, final Parameters parameters) { + this(initializer(method, parameters)); + } + + /** + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). + */ + @SuppressWarnings("fallthrough") + @Workaround(library="JDK", version="1.7") + private static Initializer initializer(final OperationMethod method, final Parameters parameters) { + final boolean isSouth = identMatch(method, "(?i).*\\bSouth\\b.*", TransverseMercatorSouth.IDENTIFIER); final EnumMap<ParameterRole, ParameterDescriptor<Double>> roles = new EnumMap<>(ParameterRole.class); ParameterRole xOffset = ParameterRole.FALSE_EASTING; ParameterRole yOffset = ParameterRole.FALSE_NORTHING; @@ -88,47 +105,45 @@ public class TransverseMercator extends roles.put(ParameterRole.SCALE_FACTOR, org.apache.sis.internal.referencing.provider.TransverseMercator.SCALE_FACTOR); roles.put(xOffset, org.apache.sis.internal.referencing.provider.TransverseMercator.FALSE_EASTING); roles.put(yOffset, org.apache.sis.internal.referencing.provider.TransverseMercator.FALSE_NORTHING); - return roles; + return new Initializer(method, parameters, roles, isSouth ? (byte) 1 : (byte) 0); } /** - * Returns the type of the projection based on the name and identifier of the given operation method. + * Work around for RFE #4093999 in Sun's bug database + * ("Relax constraint on placement of this()/super() call in constructors"). */ - private static boolean isSouth(final OperationMethod method) { - return identMatch(method, "(?i).*\\bSouth\\b.*", TransverseMercatorSouth.IDENTIFIER); - } - - /** - * Creates a Transverse Mercator projection from the given parameters. - * The {@code method} argument can be the description of one of the following: - * - * <ul> - * <li><cite>"Transverse Mercator"</cite>.</li> - * <li><cite>"Transverse Mercator (South Orientated)"</cite>.</li> - * </ul> - * - * @param method Description of the projection parameters. - * @param parameters The parameter values of the projection to create. - */ - public TransverseMercator(final OperationMethod method, final Parameters parameters) { - super(method, parameters, roles(isSouth(method))); - final double φ0 = toRadians(getAndStore(parameters, + @Workaround(library="JDK", version="1.7") + private TransverseMercator(final Initializer initializer) { + super(initializer); + final double φ0 = toRadians(initializer.getAndStore( org.apache.sis.internal.referencing.provider.TransverseMercator.LATITUDE_OF_ORIGIN)); - final double rs = parameters.doubleValue(MapProjection.SEMI_MINOR) - / parameters.doubleValue(MapProjection.SEMI_MAJOR); - - final double n = (1 - rs) / (1 + rs); // Rewrite of n = f / (2-f) + /* + * Opportunistically use double-double arithmetic for computation of B since we will store + * it in the denormalization matrix, and there is no sine/cosine functions involved here. + */ + final double n; + final DoubleDouble B; + { // For keeping the 't' variable locale. + /* + * EPSG gives: n = f / (2-f) + * We rewrite as: n = (1 - b/a) / (1 + b/a) + */ + final DoubleDouble t = initializer.axisLengthRatio(); // t = b/a + t.ratio_1m_1p(); // t = (1 - t) / (1 + t) + n = t.doubleValue(); + /* + * Compute B = (1 + n²/4 + n⁴/64) / (1 + n) + */ + B = new DoubleDouble(t); // B = n + B.square(); + B.series(1, 0.25, 1./64); // B = (1 + n²/4 + n⁴/64) + t.add(1,0); + B.divide(t); // B = (1 + n²/4 + n⁴/64) / (1 + n) + } final double n2 = n * n; final double n3 = n2 * n; final double n4 = n2 * n2; /* - * Compute B = (n4/64 + n2/4 + 1) / (n + 1) - * Opportunistically uses double-double arithmetic since we use it anyway for denormalization matrix. - */ - final DoubleDouble B = new DoubleDouble(n); - B.add(1); - B.inverseDivide(1, n4/64 + n2/4); - /* * Coefficients for direct projection. * Add the smallest values first in order to reduce rounding errors. */ @@ -146,21 +161,22 @@ public class TransverseMercator extends ih4 = (4397. / 161280)*n4; /* * Compute M₀ = B⋅(ξ₁ + ξ₂ + ξ₃ + ξ₄) and negate in anticipation for what will be needed - * in the denormalization matrix. We opportunistically use double-double arithmetic, but - * the precision is actually not better than double (in current SIS version) because of - * the precision of trigonometric functions. We may improve on that in the future if it - * seems useful. + * in the denormalization matrix. We opportunistically use double-double arithmetic but + * only for the final multiplication by B, for consistency with the translation term to + * be stored in the denormalization matrix. It is not worth to use double-double in the + * sum of sine functions because the extra digits would be meaningless. * * NOTE: the EPSG documentation makes special cases for φ₀ = 0 or ±π/2. This is not * needed here; we verified that the code below produces naturally the expected values. */ final double Q = asinh(tan(φ0)) - excentricity * atanh(excentricity * sin(φ0)); final double β = atan(sinh(Q)); - final DoubleDouble M0 = new DoubleDouble(β, 0); - M0.add(h1 * sin(2*β), 0); - M0.add(h2 * sin(4*β), 0); - M0.add(h3 * sin(6*β), 0); - M0.add(h4 * sin(8*β), 0); + final DoubleDouble M0 = new DoubleDouble(); + M0.value = h4 * sin(8*β) + + h3 * sin(6*β) + + h2 * sin(4*β) + + h1 * sin(2*β) + + β; M0.multiply(B); M0.negate(); /* Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractLinearTransform.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -64,6 +64,7 @@ abstract class AbstractLinearTransform e * Returns a copy of the matrix that user can modify. */ @Override + @SuppressWarnings("CloneDoesntCallSuperClone") public final Matrix clone() { return Matrices.copy(this); } @@ -151,10 +152,8 @@ abstract class AbstractLinearTransform e return true; } if (object != null) { - if (getClass() == object.getClass()) { - if (mode.ordinal() < ComparisonMode.APPROXIMATIVE.ordinal()) { - return equalsSameClass(object); - } + if (getClass() == object.getClass() && !mode.isApproximative()) { + return equalsSameClass(object); } if (mode != ComparisonMode.STRICT) { if (object instanceof LinearTransform) { Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/AbstractMathTransform.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -874,14 +874,13 @@ public abstract class AbstractMathTransf * <p>The default implementation returns {@code true} if the following conditions are meet:</p> * <ul> * <li>{@code object} is an instance of the same class than {@code this}. We require the - * same class because there is no interface for the various kinds of transform.</li> - * <li>The {@linkplain #getParameterDescriptors() parameter descriptors} are equal according + * same class because there is no interface for the various kinds of transform.</li> + * <li>If the hash code value has already been {@linkplain #computeHashCode() computed} for both + * instances, their values are the same <i>(opportunist performance enhancement)</i>.</li> + * <li>The {@linkplain #getContextualParameters() contextual parameters} are equal according * the given comparison mode.</li> * </ul> * - * The {@linkplain #getParameterValues() parameter values} are <strong>not</strong> compared because - * subclasses can typically compare those values more efficiently by accessing to their member fields. - * * @param object The object to compare with this transform. * @param mode The strictness level of the comparison. Default to {@link ComparisonMode#STRICT STRICT}. * @return {@code true} if the given object is considered equals to this math transform. @@ -896,7 +895,7 @@ public abstract class AbstractMathTransf * If the classes are the same, then the hash codes should be computed in the same way. Since those * codes are cached, this is an efficient way to quickly check if the two objects are different. */ - if (mode.ordinal() < ComparisonMode.APPROXIMATIVE.ordinal()) { + if (!mode.isApproximative()) { final int tc = hashCode; if (tc != 0) { final int oc = that.hashCode; @@ -906,11 +905,11 @@ public abstract class AbstractMathTransf } } // See the policy documented in the LenientComparable javadoc. - if (mode.ordinal() >= ComparisonMode.IGNORE_METADATA.ordinal()) { + if (mode.isIgnoringMetadata()) { return true; } - return Utilities.deepEquals(this.getParameterDescriptors(), - that.getParameterDescriptors(), mode); + return Utilities.deepEquals(this.getContextualParameters(), + that.getContextualParameters(), mode); } return false; } Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ConcatenatedTransform.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -428,6 +428,35 @@ class ConcatenatedTransform extends Abst i = ((AbstractMathTransform) step).beforeFormat(transforms, i, false); } } + /* + * Merge consecutive affine transforms. The transforms list should never contain consecutive instances + * of LinearTransform because the ConcatenatedTransform.create(…) method already merged them (this is + * verified by assertions in MathTransforms). However the above loop may have created synthetic affine + * transforms for WKT formatting purpose. Those synthetic affine transforms are actually represented by + * Matrix objects (rather than full MathTransform objects), and two matrices may have been generated + * consecutively. + */ + Matrix after = null; + for (int i=transforms.size(); --i >= 0;) { + final Object step = transforms.get(i); + if (step instanceof Matrix) { + if (after != null) { + final Matrix merged = Matrices.multiply(after, (Matrix) step); + if (merged.isIdentity()) { + transforms.subList(i, i+2).clear(); + after = null; + } else { + transforms.set(i, MathTransforms.linear(merged)); + transforms.remove(i+1); + after = merged; + } + } else { + after = (Matrix) step; + } + } else { + after = null; + } + } return transforms; } Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/ContextualParameters.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -37,6 +37,7 @@ import org.opengis.referencing.operation import org.opengis.referencing.operation.MathTransformFactory; import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix; import org.apache.sis.internal.referencing.WKTUtilities; +import org.apache.sis.internal.referencing.Formulas; import org.apache.sis.internal.metadata.WKTKeywords; import org.apache.sis.internal.system.Loggers; import org.apache.sis.internal.util.DoubleDouble; @@ -562,7 +563,7 @@ public class ContextualParameters extend */ @Override public int hashCode() { - return (normalize.hashCode() + 31*denormalize.hashCode()) ^ (int) serialVersionUID; + return (normalize.hashCode() + 31*denormalize.hashCode()) ^ Arrays.hashCode(values) ^ (int) serialVersionUID; } /** @@ -575,9 +576,10 @@ public class ContextualParameters extend public boolean equals(final Object object) { if (object != null && object.getClass() == getClass()) { final ContextualParameters that = (ContextualParameters) object; - return Objects.equals(descriptor, that.descriptor) && - Objects.equals(normalize, that.normalize) && - Objects.equals(denormalize, that.denormalize); + return Objects.equals(descriptor, that.descriptor) && + Objects.equals(normalize, that.normalize) && + Objects.equals(denormalize, that.denormalize) && + Arrays.equals(values, that.values); } return false; } @@ -702,11 +704,31 @@ public class ContextualParameters extend * At this point "userDefined" is the affine transform to show to user instead of the * "before" affine transform. Replaces "before" by "userDefined" locally (but not yet * in the list), or set it to null (meaning that it will be removed from the list) if - * it is identity, which happen quite often. Note that in the former (non-null) case, - * the coefficients are often either 0 or 1 since the transform is often for changing - * axis order, so it is worth to attempt rounding coefficents. + * it is identity, which happen quite often. + * + * Note on rounding error: the coefficients are often either 0 or 1 since the transform + * is often for changing axis order. Thanks to double-double arithmetic in SIS matrices, + * the non-zero values are usually accurate. But the values that should be zero are much + * harder to get right. Sometime we see small values (around 1E-12) in the last column of + * the 'before' matrix below. Since this column contains translation terms, those numbers + * are in the unit of measurement of input values of the MathTransform after the matrix. + * + * - For forward map projections, those values are conceptually in decimal degrees + * (in fact the values are converted to radians but not by this 'before' matrix). + * + * - For inverse map projections, those values are conceptually in metres (in fact + * converted to distances on a unitary ellipsoid but not by this 'before' matrix). + * + * - Geographic/Geocentric transformations behave like map projections in regard to units. + * Molodensky transformations conceptually use always decimal degrees. There is not much + * other cases since this mechanism is internal to SIS (not in public API). + * + * Consequently we set the tolerance threshold to ANGULAR_TOLERANCE. We do not bother (at least + * for now) to identify the cases where we could use LINEAR_TOLERANCE because just checking the + * 'inverse' flag is not sufficient (e.g. the Molodensky case). Since the angular tolerance is + * smaller than the linear one, unconditional usage of ANGULAR_TOLERANCE is more conservative. */ - before = userDefined.isIdentity() ? null : userDefined; + before = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined; /* * Compute the "after" affine transform in a way similar than the "before" affine. * Note that if this operation fails, we will cancel everything we would have done @@ -722,7 +744,23 @@ public class ContextualParameters extend if (hasAfter) { userDefined = Matrices.multiply(after, userDefined); } - after = userDefined.isIdentity() ? null : userDefined; + /* + * Note on rounding error: same discussion than the "note on rounding error" of the 'before' matrix, + * with the following differences: + * + * - For forward map projections, unit of measurements of translation terms are conceptually + * metres (instead than degrees) multiplied by the scale factors in the 'after' matrix. + * + * - For inverse map projections, unit of measurements of translation terms are conceptually + * degrees (instead than metres) multiplied by the scale factors in the 'after' matrix. + * + * - And so on for all cases: swap the units of the forward and inverse cases, then multiply + * by the scale factor. Note that the multiplication step does not exist in the 'before' case. + * + * Since we are seeking for the identity matrix, the scale factor is 1. We do not bother to distinguish + * the ANGULAR_TOLERANCE and LINEAR_TOLERANCE cases for the same reasons than for the 'before' matrix. + */ + after = Matrices.isIdentity(userDefined, Formulas.ANGULAR_TOLERANCE) ? null : userDefined; /* * At this point we have computed all the affine transforms to show to the user. * We can replace the elements in the list. The transform referenced by transforms.get(index) Modified: sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/DefaultMathTransformFactory.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -36,6 +36,7 @@ import org.opengis.parameter.ParameterVa import org.opengis.parameter.ParameterValueGroup; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterDescriptorGroup; +import org.opengis.parameter.ParameterNotFoundException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CoordinateSystem; import org.opengis.referencing.datum.Ellipsoid; @@ -538,12 +539,34 @@ public class DefaultMathTransformFactory */ failure = e; } + final boolean isIvfDefinitive; if (mismatchedParam != null) { final LogRecord record = Messages.getResources((Locale) null).getLogRecord(Level.WARNING, Messages.Keys.MismatchedEllipsoidAxisLength_3, ellipsoid.getName().getCode(), mismatchedParam.getDescriptor().getName().getCode(), mismatchedValue); record.setLoggerName(Loggers.COORDINATE_OPERATION); Logging.log(DefaultMathTransformFactory.class, "createBaseToDerived", record); + isIvfDefinitive = false; + } else { + isIvfDefinitive = ellipsoid.isIvfDefinitive(); + } + /* + * Following is specific to Apache SIS. We use this non-standard API for allowing the + * NormalizedProjection class (our base class for all map projection implementations) + * to known that the ellipsoid definitive parameter is the inverse flattening factor + * instead than the semi-major axis length. It makes a small difference in the accuracy + * of the excentricity parameter. + */ + if (isIvfDefinitive) try { + parameters.parameter(Constants.INVERSE_FLATTENING).setValue(ellipsoid.getInverseFlattening()); + } catch (ParameterNotFoundException e) { + /* + * Should never happen with Apache SIS implementation, but may happen if the given parameters come + * from another implementation. We can safely abandon our attempt to set the inverse flattening value, + * since it was redundant with semi-minor axis length. + */ + Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION), + DefaultMathTransformFactory.class, "createBaseToDerived", e); } } MathTransform baseToDerived; Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/FormulasTest.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -78,4 +78,25 @@ public final strictfp class FormulasTest public void testGetAuthalicRadius() { assertEquals(ReferencingServices.AUTHALIC_RADIUS, Formulas.getAuthalicRadius(6378137, 6356752), 0.5); } + + /** + * Tests {@link Formulas#getSemiMinor(double, double)}. + */ + @Test + public void testGetSemiMinor() { + assertEquals("WGS 84", 6356752.314245179, Formulas.getSemiMinor(6378137, 298.257223563), 1E-9); + assertEquals("International 1924", 6356911.9461279465, Formulas.getSemiMinor(6378388, 297), 1E-9); + assertEquals("Clarke 1858", 20855233, // Unit in feet. Is the definitive parameter for this ellipsoid. + Formulas.getSemiMinor(20926348, 294.26067636926103), 1E-8); + } + + /** + * Tests {@link Formulas#getInverseFlattening(double, double)}. + */ + @Test + public void testGetInverseFlattening() { + assertEquals("WGS 84", 298.2572235629972, Formulas.getInverseFlattening(6378137, 6356752.314245179), 1E-11); + assertEquals("International 1924", 297, Formulas.getInverseFlattening(6378388, 6356911.9461279465), 1E-11); + assertEquals("Clarke 1858", 294.26067636926103, Formulas.getInverseFlattening(20926348, 20855233), 1E-11); + } } Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/internal/referencing/ReferencingUtilitiesTest.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -22,7 +22,6 @@ import org.opengis.referencing.cs.*; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.GeographicCRS; import org.opengis.referencing.datum.PrimeMeridian; -import org.apache.sis.referencing.datum.DefaultPrimeMeridian; import org.apache.sis.referencing.datum.HardCodedDatum; import org.apache.sis.referencing.crs.HardCodedCRS; import org.apache.sis.internal.metadata.WKTKeywords; @@ -31,8 +30,6 @@ import org.apache.sis.test.TestCase; import org.junit.Test; import static org.junit.Assert.*; -import static java.util.Collections.singletonMap; -import static org.opengis.referencing.IdentifiedObject.NAME_KEY; import static org.apache.sis.internal.referencing.ReferencingUtilities.*; @@ -46,26 +43,6 @@ import static org.apache.sis.internal.re */ public final strictfp class ReferencingUtilitiesTest extends TestCase { /** - * Tests {@link ReferencingUtilities#isGreenwichLongitudeEquals(PrimeMeridian, PrimeMeridian)}. - */ - @Test - public void testIsGreenwichLongitudeEquals() { - assertFalse(isGreenwichLongitudeEquals(null, null)); // "null" interpreted as "unknown". - assertFalse(isGreenwichLongitudeEquals(null, HardCodedDatum.GREENWICH)); - assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.GREENWICH, null)); - assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.GREENWICH, HardCodedDatum.PARIS)); - assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.PARIS, HardCodedDatum.PARIS_RGS)); - assertFalse(isGreenwichLongitudeEquals(HardCodedDatum.PARIS_RGS, HardCodedDatum.PARIS)); - assertTrue (isGreenwichLongitudeEquals(HardCodedDatum.PARIS, HardCodedDatum.PARIS)); - /* - * Test two prime meridians using different units (Paris in grade and Paris in degrees). - */ - final PrimeMeridian pd = new DefaultPrimeMeridian(singletonMap(NAME_KEY, "Paris"), 2.33722917, NonSI.DEGREE_ANGLE); - assertTrue(isGreenwichLongitudeEquals(HardCodedDatum.PARIS, pd)); - assertTrue(isGreenwichLongitudeEquals(pd, HardCodedDatum.PARIS)); - } - - /** * Tests {@link ReferencingUtilities#getGreenwichLongitude(PrimeMeridian, Unit)}. */ @Test Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/parameter/MapProjectionParametersTest.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -28,12 +28,13 @@ import org.junit.Test; import static org.junit.Assert.*; import static org.apache.sis.internal.util.Constants.SEMI_MAJOR; import static org.apache.sis.internal.util.Constants.SEMI_MINOR; +import static org.apache.sis.internal.util.Constants.EARTH_RADIUS; +import static org.apache.sis.internal.util.Constants.INVERSE_FLATTENING; +import static org.apache.sis.internal.util.Constants.IS_IVF_DEFINITIVE; import static org.apache.sis.internal.util.Constants.CENTRAL_MERIDIAN; +import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL; import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_1; import static org.apache.sis.internal.util.Constants.STANDARD_PARALLEL_2; -import static org.apache.sis.parameter.MapProjectionDescriptor.EARTH_RADIUS; -import static org.apache.sis.parameter.MapProjectionDescriptor.INVERSE_FLATTENING; -import static org.apache.sis.parameter.MapProjectionDescriptor.STANDARD_PARALLEL; /** @@ -103,17 +104,24 @@ public final strictfp class MapProjectio final MapProjectionDescriptor descriptor = createDescriptor(0); final ParameterValueGroup parameters = descriptor.createValue(); - parameters.parameter(SEMI_MAJOR).setValue(6378206.4); // Clarke 1866 + parameters.parameter(SEMI_MAJOR).setValue(6378206.4); // Clarke 1866 parameters.parameter(SEMI_MINOR).setValue(6356583.8); assertEquals(294.97870, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.00001); assertEquals(6378206.4, parameters.parameter(SEMI_MAJOR) .doubleValue(), 0.5); assertEquals(6356583.8, parameters.parameter(SEMI_MINOR) .doubleValue(), 0.5); + assertFalse("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue()); - parameters.parameter(SEMI_MAJOR).setValue(6378137.000); // WGS84 + parameters.parameter(SEMI_MAJOR).setValue(6378137.0); // WGS84 parameters.parameter(INVERSE_FLATTENING).setValue(298.257223563); assertEquals(298.257, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001); assertEquals(6378137, parameters.parameter(SEMI_MAJOR) .doubleValue(), 0.5); assertEquals(6356752, parameters.parameter(SEMI_MINOR) .doubleValue(), 0.5); + assertTrue("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue()); + + parameters.parameter(SEMI_MAJOR).setValue(6378350.9); // Clarke 1858 (approximative) + parameters.parameter(SEMI_MINOR).setValue(6356675.0); + assertEquals(294.26, parameters.parameter(INVERSE_FLATTENING).doubleValue(), 0.001); + assertFalse("isIvfDefinitive", parameters.parameter(IS_IVF_DEFINITIVE).booleanValue()); } /** Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderMock.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -32,6 +32,19 @@ import org.opengis.util.GenericName; */ final strictfp class BuilderMock extends Builder<BuilderMock> { /** + * Creates a new builder. + */ + BuilderMock() { + } + + /** + * Creates a new builder initialized to the given object. + */ + BuilderMock(final IdentifiedObject object) { + super(object); + } + + /** * Convenience accessor for the property value assigned to {@link IdentifiedObject#NAME_KEY}. */ Object getName() { Modified: sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java?rev=1693908&r1=1693907&r2=1693908&view=diff ============================================================================== --- sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java [UTF-8] (original) +++ sis/branches/JDK7/core/sis-referencing/src/test/java/org/apache/sis/referencing/BuilderTest.java [UTF-8] Mon Aug 3 14:25:01 2015 @@ -17,6 +17,7 @@ package org.apache.sis.referencing; import java.util.Map; +import java.util.HashMap; import org.opengis.util.NameSpace; import org.opengis.util.LocalName; import org.opengis.util.GenericName; @@ -24,6 +25,7 @@ import org.opengis.util.NameFactory; import org.opengis.metadata.citation.Citation; import org.opengis.metadata.Identifier; import org.apache.sis.internal.simple.SimpleCitation; +import org.apache.sis.internal.simple.SimpleIdentifier; import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.metadata.iso.ImmutableIdentifier; import org.apache.sis.metadata.iso.citation.Citations; @@ -49,6 +51,7 @@ public final strictfp class BuilderTest * Tests {@link Builder#verifyParameterizedType(Class)}. */ @Test + @SuppressWarnings("ResultOfObjectAllocationIgnored") public void testVerifyParameterizedType() { final class Invalid extends Builder<BuilderMock> { } @@ -281,4 +284,36 @@ public final strictfp class BuilderTest "GeoTIFF:CT_Mercator" }, builder.getAsStrings(1)); } + + /** + * Tests the {@link Builder#Builder(IdentifiedObject)} constructor. + * + * @since 0.6 + */ + @Test + public void testCreationFromObject() { + final Map<String,Object> properties = new HashMap<>(); + final Identifier id = new SimpleIdentifier(null, "An identifier", false); + assertNull(properties.put(AbstractIdentifiedObject.IDENTIFIERS_KEY, id)); + assertNull(properties.put(AbstractIdentifiedObject.ALIAS_KEY, "An alias")); + assertNull(properties.put(AbstractIdentifiedObject.NAME_KEY, "Dummy object")); + assertNull(properties.put(AbstractIdentifiedObject.REMARKS_KEY, "Some remarks")); + final BuilderMock builder = new BuilderMock(new AbstractIdentifiedObject(properties)); + + assertEquals("Expected only name and remarks.", 2, builder.properties.size()); + builder.onCreate(false); + assertEquals("Expected name, aliases, identifiers and remarks.", 4, builder.properties.size()); + + assertEquals(AbstractIdentifiedObject.NAME_KEY, "Dummy object", + builder.properties.get(AbstractIdentifiedObject.NAME_KEY).toString()); + + assertEquals(AbstractIdentifiedObject.REMARKS_KEY, "Some remarks", + builder.properties.get(AbstractIdentifiedObject.REMARKS_KEY).toString()); + + assertEquals(AbstractIdentifiedObject.ALIAS_KEY, "An alias", + ((Object[]) builder.properties.get(AbstractIdentifiedObject.ALIAS_KEY))[0].toString()); + + assertSame(AbstractIdentifiedObject.IDENTIFIERS_KEY, id, + ((Object[]) builder.properties.get(AbstractIdentifiedObject.IDENTIFIERS_KEY))[0]); + } }
