You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2017/03/31 18:49:18 UTC
svn commit: r1789729 [3/9] - in /sis/branches/JDK9: ./
application/sis-console/
application/sis-console/src/main/java/org/apache/sis/console/
application/sis-console/src/test/java/org/apache/sis/console/ core/
core/sis-feature/src/main/java/org/apache/...
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridCompressed.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -27,9 +27,9 @@ import org.apache.sis.math.DecimalFuncti
* increase the precision in the common case where the shifts are specified with no more than
* 5 digits in base 10 in ASCII files.
*
- * @param <C> Dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
- * @param <T> Dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
- * or {@link javax.measure.quantity.Length}).
+ * @param <C> dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
+ * @param <T> dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
+ * or {@link javax.measure.quantity.Length}).
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.7
@@ -123,7 +123,7 @@ final class DatumShiftGridCompressed<C e
* Returns a new grid with the same geometry than this grid but different data arrays.
*/
@Override
- final DatumShiftGridFile<C,T> setData(final Object[] other) {
+ protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
return new DatumShiftGridCompressed<>(this, averages, (short[][]) other, scale);
}
@@ -140,11 +140,13 @@ final class DatumShiftGridCompressed<C e
}
/**
- * Returns direct references (not cloned) to the data arrays.
+ * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+ * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+ * in other context.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField")
- final Object[] getData() {
+ protected final Object[] getData() {
return data;
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/DatumShiftGridFile.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -24,28 +24,35 @@ import javax.measure.Quantity;
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.NoninvertibleTransformException;
import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.util.collection.Cache;
import org.apache.sis.util.Debug;
import org.apache.sis.parameter.Parameters;
import org.apache.sis.referencing.datum.DatumShiftGrid;
+import org.apache.sis.referencing.operation.transform.LinearTransform;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
+import org.apache.sis.internal.util.Utilities;
/**
* A datum shift grid loaded from a file.
* The filename is usually a parameter defined in the EPSG database.
+ * This class should not be in public API because it requires implementation to expose internal mechanic:
*
- * <p>This class is in internal package (not public API) because it makes the following assumptions:</p>
* <ul>
- * <li>Values <var>x\u2080</var>, <var>y\u2080</var>, <var>\u0394x</var> and <var>\u0394y</var>
- * given to the constructor are in degrees and needs to be converted to radians.</li>
- * <li>Single floating-point precision ({@code float)} is sufficient.</li>
- * <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
- * from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+ * <li>Subclasses need to give an access to their internal data (not a copy) through the {@link #getData()}
+ * and {@link #setData(Object[])} methods. We use that for managing the cache, reducing memory usage by
+ * sharing data and for {@link #equals(Object)} and {@link #hashCode()} implementations.</li>
+ * <li>{@link #descriptor}, {@link #gridToTarget()} and {@link #setFileParameters(Parameters)} are convenience
+ * members for {@link org.apache.sis.referencing.operation.transform.InterpolatedTransform} constructor.
+ * What they do are closely related to how {@code InterpolatedTransform} works, and trying to document that
+ * in a public API would probably be too distracting for the users.</li>
* </ul>
*
+ * The main concrete subclass is {@link DatumShiftGridFile.Float}.
+ *
* @param <C> dimension of the coordinate unit (usually {@link javax.measure.quantity.Angle}).
* @param <T> dimension of the translation unit (usually {@link javax.measure.quantity.Angle}
* or {@link javax.measure.quantity.Length}).
@@ -54,6 +61,8 @@ import org.apache.sis.internal.referenci
* @since 0.7
* @version 0.8
* @module
+ *
+ * @see org.apache.sis.referencing.operation.transform.InterpolatedTransform
*/
public abstract class DatumShiftGridFile<C extends Quantity<C>, T extends Quantity<T>> extends DatumShiftGrid<C,T> {
/**
@@ -91,7 +100,7 @@ public abstract class DatumShiftGridFile
/**
* Number of grid cells along the <var>x</var> axis.
*/
- final int nx;
+ protected final int nx;
/**
* The best translation accuracy that we can expect from this file.
@@ -101,7 +110,37 @@ public abstract class DatumShiftGridFile
*
* @see #getCellPrecision()
*/
- double accuracy;
+ protected double accuracy;
+
+ /**
+ * Creates a new datum shift grid for the given grid geometry.
+ * The actual offset values need to be provided by subclasses.
+ *
+ * @param coordinateUnit the unit of measurement of input values, before conversion to grid indices by {@code coordinateToGrid}.
+ * @param translationUnit the unit of measurement of output values.
+ * @param isCellValueRatio {@code true} if results of {@link #interpolateInCell interpolateInCell(\u2026)} are divided by grid cell size.
+ * @param coordinateToGrid conversion from the "real world" coordinates to grid indices including fractional parts.
+ * @param nx number of cells along the <var>x</var> axis in the grid.
+ * @param ny number of cells along the <var>y</var> axis in the grid.
+ * @param descriptor the parameter descriptor of the provider that created this grid.
+ * @param files the file(s) from which the grid has been loaded.
+ *
+ * @since 0.8
+ */
+ protected DatumShiftGridFile(final Unit<C> coordinateUnit,
+ final Unit<T> translationUnit,
+ final boolean isCellValueRatio,
+ final LinearTransform coordinateToGrid,
+ final int nx, final int ny,
+ final ParameterDescriptorGroup descriptor,
+ final Path... files)
+ {
+ super(coordinateUnit, coordinateToGrid, new int[] {nx, ny}, isCellValueRatio, translationUnit);
+ this.descriptor = descriptor;
+ this.files = files;
+ this.nx = nx;
+ this.accuracy = Double.NaN;
+ }
/**
* Creates a new datum shift grid for the given grid geometry.
@@ -123,15 +162,8 @@ public abstract class DatumShiftGridFile
final ParameterDescriptorGroup descriptor,
final Path... files) throws NoninvertibleTransformException
{
- super(coordinateUnit, new AffineTransform2D(\u0394x, 0, 0, \u0394y, x0, y0).inverse(),
- new int[] {nx, ny}, isCellValueRatio, translationUnit);
- this.descriptor = descriptor;
- this.files = files;
- this.nx = nx;
- this.accuracy = Double.NaN;
- if (files.length == 0) {
- throw new IllegalArgumentException();
- }
+ this(coordinateUnit, translationUnit, isCellValueRatio,
+ new AffineTransform2D(\u0394x, 0, 0, \u0394y, x0, y0).inverse(), nx, ny, descriptor, files);
}
/**
@@ -139,7 +171,7 @@ public abstract class DatumShiftGridFile
*
* @param other the other datum shift grid from which to copy the grid geometry.
*/
- DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
+ protected DatumShiftGridFile(final DatumShiftGridFile<C,T> other) {
super(other);
descriptor = other.descriptor;
files = other.files;
@@ -148,21 +180,29 @@ public abstract class DatumShiftGridFile
}
/**
- * Suggests a precision for the translation values in this grid.
- * The default implementation returns a value smaller than the accuracy.
- *
- * @return a precision for the translation values in this grid.
+ * Returns {@code this} casted to the given type, after verification that those types are valid.
+ * This method is invoked after {@link NADCON}, {@link NTv2} or other providers got an existing
+ * {@code DatumShiftGridFile} instance from the {@link #CACHE}.
*/
- @Override
- public double getCellPrecision() {
- return accuracy / 10; // Division by 10 is arbitrary.
+ @SuppressWarnings("unchecked")
+ final <NC extends Quantity<NC>, NT extends Quantity<NT>> DatumShiftGridFile<NC,NT> castTo(
+ final Class<NC> coordinateType, final Class<NT> translationType)
+ {
+ super.getCoordinateUnit() .asType(coordinateType);
+ super.getTranslationUnit().asType(translationType);
+ return (DatumShiftGridFile<NC,NT>) this;
}
/**
* If a grid exists in the cache for the same data, returns a new grid sharing the same data arrays.
* Otherwise returns {@code this}.
+ *
+ * @return a grid using the same data than this grid, or {@code this}.
+ *
+ * @see #getData()
+ * @see #setData(Object[])
*/
- final DatumShiftGridFile<C,T> useSharedData() {
+ protected final DatumShiftGridFile<C,T> useSharedData() {
final Object[] data = getData();
for (final DatumShiftGridFile<?,?> grid : CACHE.values()) {
final Object[] other = grid.getData();
@@ -179,13 +219,44 @@ public abstract class DatumShiftGridFile
* the same data than an existing grid. The typical use case is when a filename is different but still
* reference the same grid (e.g. symbolic link, lower case versus upper case in a case-insensitive file
* system).
+ *
+ * @param other data from another {@code DatumShiftGridFile} that we can share.
+ * @return a new {@code DatumShiftGridFile} using the given data reference.
*/
- abstract DatumShiftGridFile<C,T> setData(Object[] other);
+ protected abstract DatumShiftGridFile<C,T> setData(Object[] other);
/**
- * Returns the data for each shift dimensions.
+ * Returns the data for each shift dimensions. This method is for cache management, {@link #equals(Object)}
+ * and {@link #hashCode()} implementations only and should not be invoked in other context.
+ *
+ * @return a direct (not cloned) reference to the internal data array.
*/
- abstract Object[] getData();
+ protected abstract Object[] getData();
+
+ /**
+ * Suggests a precision for the translation values in this grid.
+ * The default implementation returns a value smaller than the accuracy.
+ *
+ * @return a precision for the translation values in this grid.
+ */
+ @Override
+ public double getCellPrecision() {
+ return accuracy / 10; // Division by 10 is arbitrary.
+ }
+
+ /**
+ * Returns the transform from grid coordinates to "real world" coordinates after the datum shift has been applied,
+ * or {@code null} for the default. This is usually the inverse of the transform from "real world" coordinates to
+ * grid coordinates before datum shift, since NADCON and NTv2 transformations have source and target coordinates
+ * in the same coordinate system (with axis units in degrees). But this method may be overridden by subclasses that
+ * use {@code DatumShiftGridFile} for other kind of transformations.
+ *
+ * @return the transformation from grid coordinates to "real world" coordinates after datum shift,
+ * or {@code null} for the default (namely the inverse of the "source to grid" transformation).
+ */
+ public Matrix gridToTarget() {
+ return null;
+ }
/**
* Sets all parameters for a value of type {@link Path} to the values given to th constructor.
@@ -193,31 +264,21 @@ public abstract class DatumShiftGridFile
* @param parameters the parameter group where to set the values.
*/
public final void setFileParameters(final Parameters parameters) {
- int i = 0; // The 'files' array should always contains at least one element.
- for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
- if (gd instanceof ParameterDescriptor<?>) {
- final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
- if (Path.class.isAssignableFrom(d.getValueClass())) {
- parameters.getOrCreate(d).setValue(files[i]);
- if (++i == files.length) break;
+ if (files.length != 0) {
+ int i = 0;
+ for (final GeneralParameterDescriptor gd : descriptor.descriptors()) {
+ if (gd instanceof ParameterDescriptor<?>) {
+ final ParameterDescriptor<?> d = (ParameterDescriptor<?>) gd;
+ if (Path.class.isAssignableFrom(d.getValueClass())) {
+ parameters.getOrCreate(d).setValue(files[i]);
+ if (++i == files.length) break;
+ }
}
}
}
}
/**
- * Returns {@code this} casted to the given type, after verification that those types are valid.
- */
- @SuppressWarnings("unchecked")
- final <NC extends Quantity<NC>, NT extends Quantity<NT>> DatumShiftGridFile<NC,NT> castTo(
- final Class<NC> coordinateType, final Class<NT> translationType)
- {
- super.getCoordinateUnit() .asType(coordinateType);
- super.getTranslationUnit().asType(translationType);
- return (DatumShiftGridFile<NC,NT>) this;
- }
-
- /**
* Returns {@code true} if the given object is a grid containing the same data than this grid.
*
* @param other the other object to compare with this datum shift grid.
@@ -254,7 +315,7 @@ public abstract class DatumShiftGridFile
@Debug
@Override
public String toString() {
- return "DatumShiftGrid[\"" + files[0].getFileName() + "\"]";
+ return Utilities.toString(getClass(), "file", (files.length != 0) ? files[0] : null);
}
@@ -262,6 +323,14 @@ public abstract class DatumShiftGridFile
/**
* An implementation of {@link DatumShiftGridFile} which stores the offset values in {@code float[]} arrays.
+ * This class is in internal package (not public API) because it makes the following assumptions:
+ * <ul>
+ * <li>Values <var>x\u2080</var>, <var>y\u2080</var>, <var>\u0394x</var> and <var>\u0394y</var>
+ * given to the constructor are in degrees and needs to be converted to radians.</li>
+ * <li>Single floating-point precision ({@code float)} is sufficient.</li>
+ * <li>Values were defined in base 10, usually in ASCII files. This assumption has an impact on conversions
+ * from {@code float} to {@code double} performed by the {@link #getCellValue(int, int, int)} method.</li>
+ * </ul>
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.7
@@ -313,16 +382,18 @@ public abstract class DatumShiftGridFile
* Returns a new grid with the same geometry than this grid but different data arrays.
*/
@Override
- final DatumShiftGridFile<C,T> setData(final Object[] other) {
+ protected final DatumShiftGridFile<C,T> setData(final Object[] other) {
return new Float<>(this, (float[][]) other);
}
/**
- * Returns direct references (not cloned) to the data arrays.
+ * Returns direct references (not cloned) to the data arrays. This method is for cache management,
+ * {@link #equals(Object)} and {@link #hashCode()} implementations only and should not be invoked
+ * in other context.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField")
- final Object[] getData() {
+ protected final Object[] getData() {
return offsets;
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/GeocentricAffine.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -132,7 +132,7 @@ public abstract class GeocentricAffine e
RX = createRotation(builder.addIdentifier("8608"), "X-axis rotation", "ex");
RY = createRotation(builder.addIdentifier("8609"), "Y-axis rotation", "ey");
RZ = createRotation(builder.addIdentifier("8610"), "Z-axis rotation", "ez");
- DS = builder.addIdentifier("8611").addName("Scale difference").addName(Citations.OGC, "ppm").create(1, Units.PPM);
+ DS = builder.addIdentifier("8611").addName("Scale difference").addName(Citations.OGC, "ppm").create(0, Units.PPM);
}
/**
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/PolarStereographicA.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -17,14 +17,21 @@
package org.apache.sis.internal.referencing.provider;
import javax.xml.bind.annotation.XmlTransient;
+import org.opengis.parameter.ParameterValueGroup;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.parameter.ParameterBuilder;
+import org.apache.sis.internal.referencing.Formulas;
+import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.measure.Latitude;
+import org.apache.sis.measure.Units;
/**
* The provider for <cite>"Polar Stereographic (Variant A)"</cite> projection (EPSG:9810).
+ * Also used for the definition of Universal Polar Stereographic (UPS) projection.
*
* @author Rueben Schulz (UBC)
* @author Martin Desruisseaux (Geomatys)
@@ -101,4 +108,64 @@ public final class PolarStereographicA e
public PolarStereographicA() {
super(PARAMETERS);
}
+
+ /**
+ * False Easting and false Northing value used in Universal Polar Stereographic (UPS) projections.
+ * Represented as an integer for the convenience of Military Reference Grid System (MGRS) or other
+ * grid systems.
+ */
+ public static final int UPS_SHIFT = 2000000;
+
+ /**
+ * Sets the parameter values for a Universal Polar Stereographic projection
+ * and returns a suggested conversion name.
+ *
+ * <blockquote><table class="sis">
+ * <caption>Universal Polar Stereographic parameters</caption>
+ * <tr><th>Parameter name</th> <th>Value</th></tr>
+ * <tr><td>Latitude of natural origin</td> <td>90�N or 90�S</td></tr>
+ * <tr><td>Longitude of natural origin</td> <td>0�</td></tr>
+ * <tr><td>Scale factor at natural origin</td> <td>0.994</td></tr>
+ * <tr><td>False easting</td> <td>2000000 metres</td></tr>
+ * <tr><td>False northing</td> <td>2000000 metres</td></tr>
+ * </table></blockquote>
+ *
+ * @param group the parameters for which to set the values.
+ * @param north {@code true} for North pole, or {@code false} for South pole.
+ * @return a name like <cite>"Universal Polar Stereographic North"</cite>,
+ * depending on the arguments given to this method.
+ *
+ * @since 0.8
+ */
+ public static String setParameters(final ParameterValueGroup group, final boolean north) {
+ group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(north ? Latitude.MAX_VALUE : Latitude.MIN_VALUE, Units.DEGREE);
+ group.parameter(Constants.CENTRAL_MERIDIAN) .setValue(0, Units.DEGREE);
+ group.parameter(Constants.SCALE_FACTOR) .setValue(0.994, Units.UNITY);
+ group.parameter(Constants.FALSE_EASTING) .setValue(UPS_SHIFT, Units.METRE);
+ group.parameter(Constants.FALSE_NORTHING) .setValue(UPS_SHIFT, Units.METRE);
+ return "Universal Polar Stereographic " + (north ? "North" : "South");
+ }
+
+ /**
+ * If the given parameter values are those of a Universal Polar Stereographic projection,
+ * returns -1 for South pole or +1 for North pole. Otherwise returns 0. It is caller's
+ * responsibility to verify that the operation method is {@value #NAME}.
+ *
+ * @param group the Transverse Mercator projection parameters.
+ * @return +1 if UPS north, -1 if UPS south, or 0 if the given parameters are not for a UPS projection.
+ *
+ * @since 0.8
+ */
+ public static int isUPS(final ParameterValueGroup group) {
+ if (Numerics.epsilonEqual(group.parameter(Constants.SCALE_FACTOR) .doubleValue(Units.UNITY), 0.994, Numerics.COMPARISON_THRESHOLD) &&
+ Numerics.epsilonEqual(group.parameter(Constants.FALSE_EASTING) .doubleValue(Units.METRE), UPS_SHIFT, Formulas.LINEAR_TOLERANCE) &&
+ Numerics.epsilonEqual(group.parameter(Constants.FALSE_NORTHING) .doubleValue(Units.METRE), UPS_SHIFT, Formulas.LINEAR_TOLERANCE) &&
+ Numerics.epsilonEqual(group.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(Units.DEGREE), 0, Formulas.ANGULAR_TOLERANCE))
+ {
+ final double \u03c6 = group.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(Units.DEGREE);
+ if (Numerics.epsilonEqual(\u03c6, Latitude.MAX_VALUE, Formulas.ANGULAR_TOLERANCE)) return +1;
+ if (Numerics.epsilonEqual(\u03c6, Latitude.MIN_VALUE, Formulas.ANGULAR_TOLERANCE)) return -1;
+ }
+ return 0;
+ }
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/internal/referencing/provider/TransverseMercator.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -25,7 +25,9 @@ import org.apache.sis.parameter.Paramete
import org.apache.sis.parameter.ParameterBuilder;
import org.apache.sis.metadata.iso.citation.Citations;
import org.apache.sis.referencing.operation.projection.NormalizedProjection;
+import org.apache.sis.internal.referencing.Formulas;
import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.util.Numerics;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Units;
@@ -49,14 +51,6 @@ public final class TransverseMercator ex
private static final long serialVersionUID = -3386587506686432398L;
/**
- * Width of a Universal Transverse Mercator (UTM) zone, in degrees.
- *
- * @see #zone(double)
- * @see #centralMeridian(int)
- */
- private static final double ZONE_WIDTH = 6;
-
- /**
* The {@value} string, which is also the EPSG name for this projection.
*/
public static final String NAME = "Transverse Mercator";
@@ -138,79 +132,312 @@ public final class TransverseMercator ex
}
/**
- * Sets the parameter values for a Transverse Mercator projection and returns a suggested conversion name.
- *
- * <blockquote><table class="sis">
- * <caption>Transverse Mercator parameters</caption>
- * <tr><th>Parameter name</th> <th>Value</th></tr>
- * <tr><td>Latitude of natural origin</td> <td>Given latitude, or 0� if UTM projection</td></tr>
- * <tr><td>Longitude of natural origin</td> <td>Given longitude, optionally snapped to a UTM central meridian</td></tr>
- * <tr><td>Scale factor at natural origin</td> <td>0.9996</td></tr>
- * <tr><td>False easting</td> <td>500000 metres</td></tr>
- * <tr><td>False northing</td> <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
- * </table></blockquote>
+ * Computes zone numbers and central meridian.
*
- * @param group the parameters for which to set the values.
- * @param isUTM {@code true} for Universal Transverse Mercator (UTM) projection.
- * @param latitude the latitude in the center of the desired projection.
- * @param longitude the longitude in the center of the desired projection.
- * @return a name like <cite>"Transverse Mercator"</cite> or <cite>"UTM zone 10N"</cite>,
- * depending on the arguments given to this method.
- *
- * @since 0.7
- */
- public static String setParameters(final ParameterValueGroup group,
- final boolean isUTM, double latitude, double longitude)
- {
- final boolean isSouth = MathFunctions.isNegative(latitude);
- int zone = zone(longitude);
- if (isUTM) {
- latitude = 0;
- longitude = centralMeridian(zone);
- } else if (longitude != centralMeridian(zone)) {
- zone = 0;
- }
- String name = NAME;
- if (zone != 0) {
- name = "UTM zone " + zone + (isSouth ? 'S' : 'N');
- }
- group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(latitude, Units.DEGREE);
- group.parameter(Constants.CENTRAL_MERIDIAN) .setValue(longitude, Units.DEGREE);
- group.parameter(Constants.SCALE_FACTOR) .setValue(0.9996, Units.UNITY);
- group.parameter(Constants.FALSE_EASTING) .setValue(500000, Units.METRE);
- group.parameter(Constants.FALSE_NORTHING) .setValue(isSouth ? 10000000 : 0, Units.METRE);
- return name;
- }
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.8
+ * @version 0.8
+ * @module
+ */
+ public static enum Zoner {
+ /**
+ * Universal Transverse Mercator (UTM) projection zones.
+ * The zone computation includes special cases for Norway and Svalbard.
+ *
+ * <blockquote><table class="sis">
+ * <caption>Universal Transverse Mercator parameters</caption>
+ * <tr><th>Parameter name</th> <th>Value</th></tr>
+ * <tr><td>Latitude of natural origin</td> <td>0�</td></tr>
+ * <tr><td>Longitude of natural origin</td> <td>Given longitude snapped to a UTM central meridian</td></tr>
+ * <tr><td>Scale factor at natural origin</td> <td>0.9996</td></tr>
+ * <tr><td>False easting</td> <td>500000 metres</td></tr>
+ * <tr><td>False northing</td> <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
+ * </table></blockquote>
+ */
+ UTM(Longitude.MIN_VALUE, 6, 0.9996, 500000, 10000000) {
+ /** Computes the zone from a meridian in the zone. */
+ @Override public int zone(final double \u03c6, final double \u03bb) {
+ int zone = super.zone(\u03c6, \u03bb);
+ switch (zone) {
+ /*
+ * Between 56� and 64�, zone 32 is widened to 9� at the expense of zone 31 to accommodate Norway.
+ * Between 72� and 84�, zones 33 and 35 are widened to 12� to accommodate Svalbard. To compensate,
+ * zones 31 and 37 are widened to 9� and zones 32, 34, and 36 are eliminated.
+ * In this switch statement, only the zones that are reduced or eliminated needs to appear.
+ */
+ case 31: if (isNorway (\u03c6)) {if (\u03bb >= 3) zone++; } break; // 3� is zone 31 central meridian.
+ case 32: if (isSvalbard(\u03c6)) {if (\u03bb >= 9) zone++; else zone--;} break; // 9� is zone 32 central meridian.
+ case 34: if (isSvalbard(\u03c6)) {if (\u03bb >= 21) zone++; else zone--;} break; // 21� is zone 34 central meridian.
+ case 36: if (isSvalbard(\u03c6)) {if (\u03bb >= 33) zone++; else zone--;} break; // 33� is zone 36 central meridian.
+ }
+ return zone;
+ }
+
+ /** Indicates whether the given zone needs to be handled in a special way for the given latitude. */
+ @Override public boolean isSpecialCase(final int zone, final double \u03c6) {
+ if (zone >= 31 && zone <= 37) {
+ return isSvalbard(\u03c6) || (zone <= 32 && isNorway(\u03c6));
+ }
+ return false;
+ }
+
+ /** Indicates whether the given geographic area intersects the regions that need to be handled in a special way. */
+ @Override public boolean isSpecialCase(final double \u03c6min, final double \u03c6max, final double \u03bbmin, final double \u03bbmax) {
+ if (\u03c6max >= NORWAY_BOUNDS && \u03c6min < NORTH_BOUNDS) {
+ return super.zone(0, \u03bbmax) >= 31 && super.zone(0, \u03bbmin) <= 37;
+ }
+ return false;
+ }
+ },
+
+ /**
+ * Modified Transverse Mercator (MTM) projection zones.
+ * This projection is used in Canada only.
+ *
+ * <blockquote><table class="sis">
+ * <caption>Modified Transverse Mercator parameters</caption>
+ * <tr><th>Parameter name</th> <th>Value</th></tr>
+ * <tr><td>Latitude of natural origin</td> <td>0�</td></tr>
+ * <tr><td>Longitude of natural origin</td> <td>Given longitude snapped to a MTM central meridian</td></tr>
+ * <tr><td>Scale factor at natural origin</td> <td>0.9999</td></tr>
+ * <tr><td>False easting</td> <td>304800 metres</td></tr>
+ * <tr><td>False northing</td> <td>0 metres</td></tr>
+ * </table></blockquote>
+ */
+ MTM(-51.5, -3, 0.9999, 304800, Double.NaN),
- /**
- * Computes the UTM zone from a meridian in the zone.
- *
- * @param longitude a meridian inside the desired zone, in degrees relative to Greenwich.
- * Positive longitudes are toward east, and negative longitudes toward west.
- * @return the UTM zone number numbered from 1 to 60 inclusive, or 0 if the given central meridian was NaN.
- *
- * @since 0.7
- */
- public static int zone(double longitude) {
- /*
- * Casts to int are equivalent to Math.floor(double) for positive values, which is guaranteed
- * to be the case here since we normalize the central meridian to the [MIN_VALUE \u2026 MAX_VALUE] range.
- */
- double z = (longitude - Longitude.MIN_VALUE) / ZONE_WIDTH; // Zone number with fractional part.
- z -= Math.floor(z / ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / ZONE_WIDTH)) // Roll in the [0 \u2026 60) range.
- * ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / ZONE_WIDTH);
- return (int) (z + 1); // Cast only after addition in order to handle NaN as documented.
- }
+ /**
+ * Like UTM, but allows <cite>latitude of origin</cite> and <cite>central meridian</cite> to be anywhere.
+ * The given central meridian is not snapped to the UTM zone center and no special case is applied for
+ * Norway or Svalbard.
+ *
+ * <p>This zoner matches the behavior of {@code AUTO(2):42002} authority code specified in the
+ * OGC <cite>Web Map Service</cite> (WMS) specification.</p>
+ */
+ ANY(Longitude.MIN_VALUE, 6, 0.9996, 500000, 10000000);
- /**
- * Computes the central meridian of a given UTM zone.
- *
- * @param zone the UTM zone as a number in the [1 \u2026 60] range.
- * @return the central meridian of the given UTM zone.
- *
- * @since 0.7
- */
- public static double centralMeridian(final int zone) {
- return (zone - 0.5) * ZONE_WIDTH + Longitude.MIN_VALUE;
+ /**
+ * Longitude of the beginning of zone 1. This is the westmost longitude if {@link #width}�is positive,
+ * or the eastmost longitude if {@code width} is negative.
+ */
+ public final double origin;
+
+ /**
+ * Width of a zone, in degrees of longitude.
+ * Positive if zone numbers are increasing eastward, or negative if increasing westwards.
+ *
+ * @see #zone(double)
+ * @see #centralMeridian(int)
+ */
+ public final double width;
+
+ /**
+ * The scale factor of zoned projections.
+ */
+ public final double scale;
+
+ /**
+ * The false easting of zoned projections, in metres.
+ */
+ public final double easting;
+
+ /**
+ * The false northing in South hemisphere of zoned projection, in metres.
+ */
+ public final double northing;
+
+ /**
+ * Creates a new instance for computing zones using the given parameters.
+ */
+ private Zoner(final double origin, final double width, final double scale, final double easting, final double northing) {
+ this.origin = origin;
+ this.width = width;
+ this.scale = scale;
+ this.easting = easting;
+ this.northing = northing;
+ }
+
+ /**
+ * Sets the parameter values for a Transverse Mercator projection and returns a suggested conversion name.
+ *
+ * <blockquote><table class="sis">
+ * <caption>Transverse Mercator parameters</caption>
+ * <tr><th>Parameter name</th> <th>Value</th></tr>
+ * <tr><td>Latitude of natural origin</td> <td>Given latitude, or 0� if zoned projection</td></tr>
+ * <tr><td>Longitude of natural origin</td> <td>Given longitude, optionally snapped to a zone central meridian</td></tr>
+ * <tr><td>Scale factor at natural origin</td> <td>0.9996 for UTM or 0.9999 for MTM</td></tr>
+ * <tr><td>False easting</td> <td>500000 metres for UTM or 304800 metres for MTM</td></tr>
+ * <tr><td>False northing</td> <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
+ * </table></blockquote>
+ *
+ * @param group the parameters for which to set the values.
+ * @param latitude the latitude in the center of the desired projection.
+ * @param longitude the longitude in the center of the desired projection.
+ * @return a name like <cite>"Transverse Mercator"</cite> or <cite>"UTM zone 10N"</cite>,
+ * depending on the arguments given to this method.
+ */
+ public final String setParameters(final ParameterValueGroup group, double latitude, double longitude) {
+ final boolean isSouth = MathFunctions.isNegative(latitude);
+ int zone = zone(latitude, longitude);
+ String name;
+ if (this == ANY) {
+ name = "UTM";
+ if (latitude != 0 || longitude != centralMeridian(zone)) {
+ name = NAME;
+ zone = 0;
+ }
+ } else {
+ name = name();
+ latitude = 0;
+ longitude = centralMeridian(zone);
+ }
+ if (zone != 0) {
+ name = name + " zone " + zone + (isSouth ? 'S' : 'N');
+ }
+ group.parameter(Constants.LATITUDE_OF_ORIGIN).setValue(latitude, Units.DEGREE);
+ group.parameter(Constants.CENTRAL_MERIDIAN) .setValue(longitude, Units.DEGREE);
+ group.parameter(Constants.SCALE_FACTOR) .setValue(scale, Units.UNITY);
+ group.parameter(Constants.FALSE_EASTING) .setValue(easting, Units.METRE);
+ group.parameter(Constants.FALSE_NORTHING) .setValue(isSouth ? northing : 0, Units.METRE);
+ return name;
+ }
+
+ /**
+ * If the given parameter values are those of a zoned projection, returns the zone number (negative if South).
+ * Otherwise returns 0. It is caller's responsibility to verify that the operation method is {@value #NAME}.
+ *
+ * @param group the Transverse Mercator projection parameters.
+ * @return zone number (positive if North, negative if South),
+ * or 0 if the given parameters are not for a zoned projection.
+ */
+ public final int zone(final ParameterValueGroup group) {
+ if (Numerics.epsilonEqual(group.parameter(Constants.SCALE_FACTOR) .doubleValue(Units.UNITY), scale, Numerics.COMPARISON_THRESHOLD) &&
+ Numerics.epsilonEqual(group.parameter(Constants.FALSE_EASTING) .doubleValue(Units.METRE), easting, Formulas.LINEAR_TOLERANCE) &&
+ Numerics.epsilonEqual(group.parameter(Constants.LATITUDE_OF_ORIGIN).doubleValue(Units.DEGREE), 0, Formulas.ANGULAR_TOLERANCE))
+ {
+ double v = group.parameter(Constants.FALSE_NORTHING).doubleValue(Units.METRE);
+ final boolean isNorth = Numerics.epsilonEqual(v, 0, Formulas.LINEAR_TOLERANCE);
+ if (isNorth || Numerics.epsilonEqual(v, northing, Formulas.LINEAR_TOLERANCE)) {
+ v = group.parameter(Constants.CENTRAL_MERIDIAN).doubleValue(Units.DEGREE);
+ int zone = zone(0, v);
+ if (Numerics.epsilonEqual(centralMeridian(zone), v, Formulas.ANGULAR_TOLERANCE)) {
+ if (!isNorth) zone = -zone;
+ return zone;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Computes the zone from a meridian in the zone.
+ *
+ * @param \u03c6 a latitude for which to get the zone. Used for taking in account the special cases.
+ * @param \u03bb a meridian inside the desired zone, in degrees relative to Greenwich.
+ * Positive longitudes are toward east, and negative longitudes toward west.
+ * @return the zone number numbered from 1 inclusive, or 0 if the given central meridian was NaN.
+ */
+ public int zone(final double \u03c6, final double \u03bb) {
+ double z = (\u03bb - origin) / width; // Zone number with fractional part.
+ final double count = (Longitude.MAX_VALUE - Longitude.MIN_VALUE) / width;
+ z -= Math.floor(z / count) * count; // Roll in the [0 \u2026 60) range.
+ /*
+ * Casts to int are equivalent to Math.floor(double) for positive values, which is guaranteed
+ * to be the case here since we normalize the central meridian to the [MIN_VALUE \u2026 MAX_VALUE]
+ * range. We cast only after addition in order to handle NaN as documented.
+ */
+ return (int) (z + 1);
+ }
+
+ /**
+ * Returns the number of zones.
+ *
+ * @return number of zones.
+ */
+ public final int zoneCount() {
+ return (int) ((Longitude.MAX_VALUE - Longitude.MIN_VALUE) / width);
+ }
+
+ /**
+ * Computes the central meridian of a given zone.
+ *
+ * @param zone the zone as a number starting with 1.
+ * @return the central meridian of the given zone.
+ */
+ public final double centralMeridian(final int zone) {
+ return (zone - 0.5) * width + origin;
+ }
+
+ /**
+ * Indicates whether the given zone needs to be handled in a special way for the given latitude.
+ *
+ * @param zone the zone to test if it is a special case.
+ * @param \u03c6 the latitude for which to test if there is a special case.
+ * @return whether the given zone at the given latitude is a special case.
+ */
+ public boolean isSpecialCase(final int zone, final double \u03c6) {
+ return false;
+ }
+
+ /**
+ * Indicates whether the given geographic area intersects the regions that need to be handled in a special way.
+ *
+ * @param \u03c6min southernmost latitude in degrees.
+ * @param \u03c6max northernmost latitude in degrees.
+ * @param \u03bbmin westernmost longitude in degrees.
+ * @param \u03bbmax easternmost longitude in degrees.
+ * @return whether the given area intersects a region that needs to be handled as a special case.
+ */
+ public boolean isSpecialCase(final double \u03c6min, final double \u03c6max, final double \u03bbmin, final double \u03bbmax) {
+ return false;
+ }
+
+ /**
+ * First exception in UTM projection, corresponding to latitude band V.
+ * This method is public for {@code MilitaryGridReferenceSystemTest.verifyZonerConsistency()} purpose only.
+ *
+ * @param \u03c6 the latitude in degrees to test.
+ * @return whether the given latitude is in the Norway latitude band.
+ */
+ public static boolean isNorway(final double \u03c6) {
+ return (\u03c6 >= NORWAY_BOUNDS) && (\u03c6 < 64);
+ }
+
+ /**
+ * Second exception in UTM projection, corresponding to latitude band X.
+ * This method is public for {@code MilitaryGridReferenceSystemTest.verifyZonerConsistency()} purpose only.
+ *
+ * @param \u03c6 the latitude in degrees to test.
+ * @return whether the given latitude is in the Svalbard latitude band.
+ */
+ public static boolean isSvalbard(final double \u03c6) {
+ return (\u03c6 >= SVALBARD_BOUNDS) && (\u03c6 < NORTH_BOUNDS);
+ }
+
+ /**
+ * Southernmost bound of the first latitude band ({@code 'C'}), inclusive.
+ *
+ * @see #NORTH_BOUNDS
+ */
+ public static final double SOUTH_BOUNDS = -80;
+
+ /**
+ * Southernmost bounds (inclusive) of the latitude band that contains Norway ({@code 'V'}).
+ * This is the first latitude band where we may need to handle special cases (Norway and Svalbard).
+ */
+ private static final double NORWAY_BOUNDS = 56;
+
+ /**
+ * Southernmost bounds (inclusive) of the last latitude band, which contains Svalbard.
+ * This latitude band is 12� height instead of 8�.
+ */
+ public static final double SVALBARD_BOUNDS = 72;
+
+ /**
+ * Northernmost bound of the last latitude band ({@code 'X'}), exclusive.
+ *
+ * @see #SOUTH_BOUNDS
+ */
+ public static final double NORTH_BOUNDS = 84;
}
}
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -28,6 +28,7 @@ import org.opengis.parameter.ParameterVa
import org.opengis.parameter.ParameterDescriptor;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Numbers;
+import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.measure.Range;
@@ -67,7 +68,7 @@ import static org.apache.sis.util.Argume
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Johann Sorel (Geomatys)
* @since 0.4
- * @version 0.6
+ * @version 0.8
* @module
*
* @see DefaultParameterValue
@@ -468,7 +469,7 @@ public class DefaultParameterDescriptor<
final ParameterDescriptor<?> that = (ParameterDescriptor<?>) object;
return getValueClass() == that.getValueClass() &&
Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
- Objects.equals(getUnit(), that.getUnit()) &&
+ Utilities.deepEquals(getUnit(), that.getUnit(), mode) &&
(isHeuristicMatchForName(that.getName().getCode()) ||
IdentifiedObjects.isHeuristicMatchForName(that, getName().getCode()));
}
@@ -481,7 +482,7 @@ public class DefaultParameterDescriptor<
Objects. equals(getMinimumValue(), that.getMinimumValue()) &&
Objects. equals(getMaximumValue(), that.getMaximumValue()) &&
Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
- Objects. equals(getUnit(), that.getUnit());
+ Utilities.deepEquals(getUnit(), that.getUnit(), mode);
}
case STRICT: {
final DefaultParameterDescriptor<?> that = (DefaultParameterDescriptor<?>) object;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -118,7 +118,7 @@ import static org.apache.sis.util.Utilit
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @since 0.4
- * @version 0.7
+ * @version 0.8
* @module
*
* @see DefaultParameterDescriptor
@@ -787,8 +787,8 @@ public class DefaultParameterValue<T> ex
} else if (object instanceof ParameterValue<?>) {
final ParameterValue<?> that = (ParameterValue<?>) object;
return deepEquals(getDescriptor(), that.getDescriptor(), mode) &&
- deepEquals(getValue(), that.getValue(), mode) &&
- Objects.equals(getUnit(), that.getUnit());
+ deepEquals(getValue(), that.getValue(), mode) &&
+ deepEquals(getUnit(), that.getUnit(), mode);
}
}
return false;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/parameter/ParameterFormat.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -97,9 +97,11 @@ import static org.apache.sis.util.collec
* <tr><td><code>{@linkplain IdentifiedObject}[]</code></td><td>Accepted only for {@link ContentLevel#NAME_SUMMARY}.</td></tr>
* </table>
*
- * <div class="warning"><b>Limitation:</b>
- * Current implementation supports only formatting, not parsing.
- * </div>
+ * <p><b>Limitations:</b></p>
+ * <ul>
+ * <li>The current implementation can only format features \u2014 parsing is not yet implemented.</li>
+ * <li>{@code ParameterFormat}, like most {@code java.text.Format} subclasses, is not thread-safe.</li>
+ * </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @since 0.4
@@ -1012,7 +1014,7 @@ public class ParameterFormat extends Tab
@Override
public Object parse(final CharSequence text, final ParsePosition pos) throws ParseException {
throw new ParseException(Errors.getResources(displayLocale)
- .getString(Errors.Keys.UnsupportedOperation_1, "parse"), 0);
+ .getString(Errors.Keys.UnsupportedOperation_1, "parse"), pos.getIndex());
}
/**
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractReferenceSystem.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -17,6 +17,7 @@
package org.apache.sis.referencing;
import java.util.Map;
+import java.util.Objects;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
@@ -34,9 +35,6 @@ import org.apache.sis.internal.metadata.
import static org.apache.sis.util.Utilities.deepEquals;
import static org.apache.sis.util.collection.Containers.property;
-// Branch-dependent imports
-import java.util.Objects;
-
/**
* Description of a spatial and temporal reference system used by a dataset.
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CRS.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -145,23 +145,29 @@ public final class CRS extends Static {
*
* <blockquote><table class="sis">
* <caption>Minimal set of supported authority codes</caption>
- * <tr><th>Code</th> <th>Enum</th> <th>CRS Type</th> <th>Description</th></tr>
- * <tr><td>CRS:27</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>CRS:83</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>CRS:84</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
- * <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td> <td>GRS 1980 Authalic Sphere</td></tr>
- * <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50 ED50}</td> <td>Geographic</td> <td>European Datum 1950</td></tr>
- * <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>North American Datum 1927</td></tr>
- * <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>North American Datum 1983</td></tr>
- * <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic</td> <td>World Geodetic System 1972</td></tr>
- * <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
- * <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geocentric</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
- * <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geocentric</td> <td>World Geodetic System 1972</td></tr>
- * <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><th>Code</th> <th>Enum</th> <th>CRS Type</th> <th>Description</th></tr>
+ * <tr><td>CRS:27</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>Like EPSG:4267 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>CRS:83</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>Like EPSG:4269 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>CRS:84</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>Like EPSG:4326 except for (<var>longitude</var>, <var>latitude</var>) axis order</td></tr>
+ * <tr><td>EPSG:4047</td> <td>{@link CommonCRS#SPHERE SPHERE}</td> <td>Geographic</td> <td>GRS 1980 Authalic Sphere</td></tr>
+ * <tr><td>EPSG:4230</td> <td>{@link CommonCRS#ED50 ED50}</td> <td>Geographic</td> <td>European Datum 1950</td></tr>
+ * <tr><td>EPSG:4258</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4267</td> <td>{@link CommonCRS#NAD27 NAD27}</td> <td>Geographic</td> <td>North American Datum 1927</td></tr>
+ * <tr><td>EPSG:4269</td> <td>{@link CommonCRS#NAD83 NAD83}</td> <td>Geographic</td> <td>North American Datum 1983</td></tr>
+ * <tr><td>EPSG:4322</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:4326</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4936</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geocentric</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4937</td> <td>{@link CommonCRS#ETRS89 ETRS89}</td> <td>Geographic 3D</td> <td>European Terrestrial Reference Frame 1989</td></tr>
+ * <tr><td>EPSG:4978</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geocentric</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4979</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Geographic 3D</td> <td>World Geodetic System 1984</td></tr>
+ * <tr><td>EPSG:4984</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geocentric</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:4985</td> <td>{@link CommonCRS#WGS72 WGS72}</td> <td>Geographic 3D</td> <td>World Geodetic System 1972</td></tr>
+ * <tr><td>EPSG:5041</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS North (E,N)</td></tr>
+ * <tr><td>EPSG:5042</td> <td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UPS South (E,N)</td></tr>
+ * <tr><td>EPSG:322##</td><td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##N</td></tr>
+ * <tr><td>EPSG:323##</td><td>{@link CommonCRS#WGS72 WGS72}</td> <td>Projected</td> <td>WGS 72 / UTM zone ##S</td></tr>
+ * <tr><td>EPSG:326##</td><td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##N</td></tr>
+ * <tr><td>EPSG:327##</td><td>{@link CommonCRS#WGS84 WGS84}</td> <td>Projected</td> <td>WGS 84 / UTM zone ##S</td></tr>
* <tr><td>EPSG:5715</td> <td>{@link CommonCRS.Vertical#DEPTH DEPTH}</td> <td>Vertical</td> <td>Mean Sea Level depth</td></tr>
* <tr><td>EPSG:5714</td> <td>{@link CommonCRS.Vertical#MEAN_SEA_LEVEL MEAN_SEA_LEVEL}</td> <td>Vertical</td> <td>Mean Sea Level height</td></tr>
* </table></blockquote>
@@ -960,9 +966,11 @@ check: while (lower != 0 || upper != di
crs = components.get(i);
dimension = crs.getCoordinateSystem().getDimension();
if (lower < dimension) {
- // The requested dimensions may intersect the dimension of this CRS.
- // The outer loop will perform the verification, and eventually go
- // down again in the tree of sub-components.
+ /*
+ * The requested dimensions may intersect the dimension of this CRS.
+ * The outer loop will perform the verification, and eventually go
+ * down again in the tree of sub-components.
+ */
continue check;
}
lower -= dimension;
Modified: sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java?rev=1789729&r1=1789728&r2=1789729&view=diff
==============================================================================
--- sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] (original)
+++ sis/branches/JDK9/core/sis-referencing/src/main/java/org/apache/sis/referencing/CommonCRS.java [UTF-8] Fri Mar 31 18:49:16 2017
@@ -19,26 +19,29 @@ package org.apache.sis.referencing;
import java.util.Map;
import java.util.Date;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import javax.measure.Unit;
import javax.measure.quantity.Time;
+import org.opengis.metadata.Identifier;
import org.opengis.util.FactoryException;
import org.opengis.util.InternationalString;
import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.GeodeticCRS;
import org.opengis.referencing.crs.VerticalCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.crs.GeographicCRS;
import org.opengis.referencing.crs.GeocentricCRS;
import org.opengis.referencing.crs.ProjectedCRS;
+import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.cs.TimeCS;
import org.opengis.referencing.cs.VerticalCS;
import org.opengis.referencing.cs.CartesianCS;
import org.opengis.referencing.cs.SphericalCS;
import org.opengis.referencing.cs.EllipsoidalCS;
import org.opengis.referencing.cs.AxisDirection;
+import org.opengis.referencing.datum.Datum;
import org.opengis.referencing.datum.Ellipsoid;
import org.opengis.referencing.datum.GeodeticDatum;
import org.opengis.referencing.datum.PrimeMeridian;
@@ -66,9 +69,11 @@ import org.apache.sis.internal.system.Mo
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.internal.util.Constants;
import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.Utilities;
import org.apache.sis.math.MathFunctions;
import org.apache.sis.measure.Latitude;
import org.apache.sis.measure.Units;
@@ -144,8 +149,8 @@ public enum CommonCRS {
* <tr><th>UTM zones:</th> <td>1 to 60 in North and South hemispheres</td></tr>
* </table></blockquote>
*/
- WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030, // Geodetic info
- (short) 32600, (short) 32700, (byte) 1, (byte) 60), // UTM info
+ WGS84((short) 4326, (short) 4979, (short) 4978, (short) 6326, (short) 7030, // Geodetic info
+ (short) 5041, (short) 5042, (short) 32600, (short) 32700, (byte) 1, (byte) 60), // UPS and UTM info
/**
* World Geodetic System 1972.
@@ -162,8 +167,8 @@ public enum CommonCRS {
* <tr><th>UTM zones:</th> <td>1 to 60 in North and South hemispheres</td></tr>
* </table></blockquote>
*/
- WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043, // Geodetic info
- (short) 32200, (short) 32300, (byte) 1, (byte) 60), // UTM info
+ WGS72((short) 4322, (short) 4985, (short) 4984, (short) 6322, (short) 7043, // Geodetic info
+ (short) 0, (short) 0, (short) 32200, (short) 32300, (byte) 1, (byte) 60), // UPS and UTM info
/**
* North American Datum 1983.
@@ -188,8 +193,8 @@ public enum CommonCRS {
* The <cite>Web Map Server</cite> {@code "CRS:83"} authority code uses the NAD83 datum,
* while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.</div>
*/
- NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019, // Geodetic info
- (short) 26900, (short) 0, (byte) 1, (byte) 23), // UTM info
+ NAD83((short) 4269, (short) 0, (short) 0, (short) 6269, (short) 7019, // Geodetic info
+ (short) 0, (short) 0, (short) 26900, (short) 0, (byte) 1, (byte) 23), // UPS and UTM info
/**
* North American Datum 1927.
@@ -206,8 +211,8 @@ public enum CommonCRS {
* <tr><th>UTM zones:</th> <td>1 to 22 in the North hemisphere</td></tr>
* </table></blockquote>
*/
- NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008, // Geodetic info
- (short) 26700, (short) 0, (byte) 1, (byte) 22), // UTM info
+ NAD27((short) 4267, (short) 0, (short) 0, (short) 6267, (short) 7008, // Geodetic info
+ (short) 0, (short) 0, (short) 26700, (short) 0, (byte) 1, (byte) 22), // UPS and UTM info
/**
* European Terrestrial Reference System 1989.
@@ -231,8 +236,8 @@ public enum CommonCRS {
* The <cite>Web Map Server</cite> {@code "CRS:83"} authority code uses the NAD83 datum,
* while the {@code "IGNF:MILLER"} authority code uses the GRS80 datum.</div>
*/
- ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019, // Geodetic info
- (short) 25800, (short) 0, (byte) 28, (byte) 37), // UTM info
+ ETRS89((short) 4258, (short) 4937, (short) 4936, (short) 6258, (short) 7019, // Geodetic info
+ (short) 0, (short) 0, (short) 25800, (short) 0, (byte) 28, (byte) 37), // UPS and UTM info
/**
* European Datum 1950.
@@ -249,8 +254,8 @@ public enum CommonCRS {
* <tr><th>UTM zones:</th> <td>28 to 38 in the North hemisphere</td></tr>
* </table></blockquote>
*/
- ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022, // Geodetic info
- (short) 23000, (short) 0, (byte) 28, (byte) 38), // UTM info
+ ED50((short) 4230, (short) 0, (short) 0, (short) 6230, (short) 7022, // Geodetic info
+ (short) 0, (short) 0, (short) 23000, (short) 0, (byte) 28, (byte) 38), // UPS and UTM info
/**
* Unspecified datum based upon the GRS 1980 Authalic Sphere. Spheres use a simpler algorithm for
@@ -268,8 +273,8 @@ public enum CommonCRS {
*
* @see org.apache.sis.referencing.datum.DefaultEllipsoid#getAuthalicRadius()
*/
- SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048, // Geodetic info
- (short) 0, (short) 0, (byte) 0, (byte) 0); // UTM info
+ SPHERE((short) 4047, (short) 0, (short) 0, (short) 6047, (short) 7048, // Geodetic info
+ (short) 0, (short) 0, (short) 0, (short) 0, (byte) 0, (byte) 0); // UPS and UTM info
/**
* The enum for the default CRS.
@@ -311,6 +316,11 @@ public enum CommonCRS {
final short ellipsoid;
/**
+ * EPSG codes of Universal Polar Stereographic projections, North and South cases.
+ */
+ final short northUPS, southUPS;
+
+ /**
* EPSG codes of pseudo "UTM zone zero" (North case and South case), or 0 if none.
*/
final short northUTM, southUTM;
@@ -356,12 +366,21 @@ public enum CommonCRS {
private transient volatile GeocentricCRS cachedSpherical;
/**
- * The Universal Transverse Mercator projections, created when first needed.
- * All accesses to this map shall be synchronized on {@code cachedUTM}.
+ * The Universal Transverse Mercator (UTM) or Universal Polar Stereographic (UPS) projections,
+ * created when first needed. The UPS projections are arbitrarily given zone numbers
+ * {@value #POLAR} and -{@value #POLAR} for North and South poles respectively.
+ *
+ * <p>All accesses to this map shall be synchronized on {@code cachedProjections}.</p>
*
- * @see #UTM(double, double)
+ * @see #universal(double, double)
*/
- private final Map<Integer,ProjectedCRS> cachedUTM;
+ private final Map<Integer,ProjectedCRS> cachedProjections;
+
+ /**
+ * The special zone number used as key in {@link #cachedProjections} for polar stereographic projections.
+ * Must be outside the range of UTM zone numbers.
+ */
+ private static final int POLAR = 90;
/**
* Creates a new constant for the given EPSG or SIS codes.
@@ -373,18 +392,20 @@ public enum CommonCRS {
* @param ellipsoid the EPSG code for the ellipsoid.
*/
private CommonCRS(final short geographic, final short geo3D, final short geocentric, final short datum, final short ellipsoid,
- final short northUTM, final short southUTM, final byte firstZone, final byte lastZone)
+ final short northUPS, final short southUPS, final short northUTM, final short southUTM, final byte firstZone, final byte lastZone)
{
this.geographic = geographic;
this.geocentric = geocentric;
this.geo3D = geo3D;
this.datum = datum;
this.ellipsoid = ellipsoid;
+ this.northUPS = northUPS;
+ this.southUPS = southUPS;
this.northUTM = northUTM;
this.southUTM = southUTM;
this.firstZone = firstZone;
this.lastZone = lastZone;
- cachedUTM = new HashMap<>();
+ cachedProjections = new HashMap<>();
}
/**
@@ -404,15 +425,81 @@ public enum CommonCRS {
/**
* Invoked by when the cache needs to be cleared after a classpath change.
*/
- @SuppressWarnings("NestedSynchronizedStatement") // Safe because cachedUTM never call any method of 'this'.
+ @SuppressWarnings("NestedSynchronizedStatement") // Safe because cachedProjections never call any method of 'this'.
synchronized void clear() {
cached = null;
cachedGeo3D = null;
cachedNormalized = null;
cachedGeocentric = null;
- synchronized (cachedUTM) {
- cachedUTM.clear();
+ synchronized (cachedProjections) {
+ cachedProjections.clear();
+ }
+ }
+
+ /**
+ * Returns the {@code CommonCRS} enumeration value for the datum of the given CRS.
+ * The given CRS shall comply to the following conditions
+ * (otherwise an {@link IllegalArgumentException} is thrown):
+ *
+ * <ul>
+ * <li>The {@code crs} is either an instance of {@link SingleCRS},
+ * or an instance of {@link org.opengis.referencing.crs.CompoundCRS}
+ * with an {@linkplain CRS#getHorizontalComponent horizontal component}.</li>
+ * <li>The {@code crs} or the horizontal component of {@code crs} is associated to a {@link GeodeticDatum}.</li>
+ * <li>The geodetic datum either<ul>
+ * <li>has the same EPSG code than one of the {@code CommonCRS}�enumeration values, or</li>
+ * <li>has no EPSG code but is {@linkplain Utilities#equalsIgnoreMetadata equal, ignoring metadata},
+ * to the {@link #datum()} value of one of the {@code CommonCRS}�enumeration values.</li>
+ * </ul></li>
+ * </ul>
+ *
+ * This method is useful for easier creation of various coordinate reference systems through the
+ * {@link #geographic()}, {@link #geocentric()} or other convenience methods when the set of datums
+ * supported by {@code CommonCRS} is known to be sufficient.
+ *
+ * @param crs the coordinate reference system for which to get a {@code CommonCRS} value.
+ * @return the {@code CommonCRS} value for the geodetic datum of the given CRS.
+ * @throws IllegalArgumentException if no {@code CommonCRS} value can be found for the given CRS.
+ *
+ * @see #datum()
+ * @since 0.8
+ */
+ public static CommonCRS forDatum(final CoordinateReferenceSystem crs) {
+ final SingleCRS single;
+ if (crs instanceof SingleCRS) {
+ single = (SingleCRS) crs;
+ } else {
+ single = CRS.getHorizontalComponent(crs);
+ if (single == null) {
+ throw new IllegalArgumentException(Resources.format(
+ Resources.Keys.NonHorizontalCRS_1, IdentifiedObjects.getName(crs, null)));
+ }
}
+ final Datum datum = single.getDatum();
+ if (datum instanceof GeodeticDatum) {
+ /*
+ * First, try to search using only the EPSG code. This approach avoid initializing unneeded
+ * geodetic objects (such initializations are costly if they require connection to the EPSG
+ * database).
+ */
+ int epsg = 0;
+ final Identifier identifier = IdentifiedObjects.getIdentifier(datum, Citations.EPSG);
+ if (identifier != null) {
+ final String code = identifier.getCode();
+ if (code != null) try {
+ epsg = Integer.parseInt(code);
+ } catch (NumberFormatException e) {
+ Logging.recoverableException(Logging.getLogger(Modules.REFERENCING), CommonCRS.class, "forDatum", e);
+ }
+ }
+ for (final CommonCRS c : values()) {
+ if ((epsg != 0) ? c.datum == epsg : Utilities.equalsIgnoreMetadata(c.datum(), datum)) {
+ return c;
+ }
+ }
+ }
+ throw new IllegalArgumentException(Errors.format(
+ Errors.Keys.UnsupportedDatum_1, IdentifiedObjects.getName(datum, null)));
}
/**
@@ -724,6 +811,7 @@ public enum CommonCRS {
*
* @return the geodetic datum associated to this enum.
*
+ * @see #forDatum(CoordinateReferenceSystem)
* @see org.apache.sis.referencing.datum.DefaultGeodeticDatum
*/
public GeodeticDatum datum() {
@@ -886,90 +974,175 @@ public enum CommonCRS {
/**
* Returns a Universal Transverse Mercator (UTM) projection for the zone containing the given point.
* There is a total of 120 UTM zones, with 60 zones in the North hemisphere and 60 zones in the South hemisphere.
+ *
+ * @param latitude a latitude in the desired UTM projection zone.
+ * @param longitude a longitude in the desired UTM projection zone.
+ * @return a Universal Transverse Mercator projection for the zone containing the given point.
+ *
+ * @since 0.7
+ *
+ * @deprecated Generalized by {@link #universal(double, double)},
+ * which can also return a UPS projection when appropriate.
+ */
+ @Deprecated
+ public ProjectedCRS UTM(final double latitude, final double longitude) {
+ return universal(Math.signum(latitude), longitude);
+ }
+
+ /**
+ * Returns a Universal Transverse Mercator (UTM) or a Universal Polar Stereographic (UPS) projection
+ * for the zone containing the given point.
+ * There is a total of 120 UTM zones, with 60 zones in the North hemisphere and 60 zones in the South hemisphere.
* The projection zone is determined from the arguments as below:
*
- * <ul>
- * <li>The sign of the <var>latitude</var> argument determines the hemisphere:
- * North for positive latitudes (including positive zero) or
- * South for negative latitudes (including negative zero).
- * The latitude magnitude is ignored, except for ensuring that the latitude is inside the [-90 \u2026 90]� range.</li>
- * <li>The value of the <var>longitude</var> argument determines the 6�-width zone,
- * numbered from 1 for the zone starting at 180�W up to 60 for the zone finishing at 180�E.
- * Longitudes outside the [-180 \u2026 180]� range will be rolled as needed before to compute the zone.</li>
+ * <ul class="verbose">
+ * <li>If the <var>latitude</var> argument is less than 80�S or equal or greater than 84�N,
+ * then a <cite>Universal Polar Stereographic</cite> projection is created.</li>
+ * <li>Otherwise a <cite>Universal Transverse Mercator</cite> projection is created as below:
+ * <ul class="verbose">
+ * <li>The sign of the <var>latitude</var> argument determines the hemisphere:
+ * North for positive latitudes (including positive zero) or
+ * South for negative latitudes (including negative zero).
+ * The latitude magnitude is ignored, except for the special cases documented below
+ * and for ensuring that the latitude is inside the [-90 \u2026 90]� range.</li>
+ * <li>The value of the <var>longitude</var> argument determines the 6�-width zone,
+ * numbered from 1 for the zone starting at 180�W up to 60 for the zone finishing at 180�E.
+ * Longitudes outside the [-180 \u2026 180]� range will be rolled as needed before to compute the zone.</li>
+ * <li>Calculation of UTM zone involves two special cases (if those special cases are not desired,
+ * they can be avoided by making sure that the given latitude is below 56�N):
+ * <ul>
+ * <li>Between 56�N and 64�N, zone 32 is widened to 9� (at the expense of zone 31)
+ * to accommodate southwest Norway.</li>
+ * <li>Between 72�N and 84�N, zones 33 and 35 are widened to 12� to accommodate Svalbard.
+ * To compensate for these 12� wide zones, zones 31 and 37 are widened to 9� and
+ * zones 32, 34, and 36 are eliminated.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * </li>
* </ul>
*
- * <div class="note"><b>Warning:</b>
- * be aware of parameter order! For this method, latitude is first.
- * This order is for consistency with the non-normalized {@linkplain #geographic() geographic} CRS
- * of all items in this {@code CommonCRS} enumeration.</div>
+ * <div class="note"><b>Tip:</b>
+ * for "straight" UTM zone calculation without any special case (neither Norway, Svalbard or Universal Polar
+ * Stereographic projection), one can replace the {@code latitude} argument by {@code Math.signum(latitude)}.
+ * For using a specific zone number, one can additionally replace the {@code longitude} argument by
+ * {@code zone * 6 - 183}.</div>
*
* The map projection uses the following parameters:
*
- * <blockquote><table class="sis">
- * <caption>Universal Transverse Mercator (UTM) parameters</caption>
- * <tr><th>Parameter name</th> <th>Value</th></tr>
- * <tr><td>Latitude of natural origin</td> <td>0�</td></tr>
- * <tr><td>Longitude of natural origin</td> <td>Central meridian of the UTM zone containing the given longitude</td></tr>
- * <tr><td>Scale factor at natural origin</td> <td>0.9996</td></tr>
- * <tr><td>False easting</td> <td>500000 metres</td></tr>
- * <tr><td>False northing</td> <td>0 (North hemisphere) or 10000000 (South hemisphere) metres</td></tr>
- * </table></blockquote>
+ * <table class="sis">
+ * <caption>Universal Transverse Mercator (UTM) and Universal Polar Stereographic (UPS) projection parameters</caption>
+ * <tr>
+ * <th>Parameter name</th>
+ * <th>UTM parameter value</th>
+ * <th>UPS parameter value</th>
+ * </tr><tr>
+ * <td>Latitude of natural origin</td>
+ * <td>0�</td>
+ * <td>90�N or 90�S depending on the sign of given latitude</td>
+ * </tr><tr>
+ * <td>Longitude of natural origin</td>
+ * <td>Central meridian of the UTM zone containing the given longitude</td>
+ * <td>0�</td>
+ * </tr><tr>
+ * <td>Scale factor at natural origin</td>
+ * <td>0.9996</td>
+ * <td>0.994</td>
+ * </tr><tr>
+ * <td>False easting</td>
+ * <td>500\u2009000 metres</td>
+ * <td>2\u2009000\u2009000 metres</td>
+ * </tr><tr>
+ * <td>False northing</td>
+ * <td>0 (North hemisphere) or 10\u2009000\u2009000 (South hemisphere) metres</td>
+ * <td>2\u2009000\u2009000 metres</td>
+ * </tr>
+ * </table>
*
* The coordinate system axes are (Easting, Northing) in metres.
*
- * @param latitude a latitude in the desired UTM projection zone.
- * @param longitude a longitude in the desired UTM projection zone.
- * @return a Universal Transverse Mercator projection for the zone containing the given point.
+ * <div class="note"><b>Warning:</b>
+ * be aware of parameter order! For this method, latitude is first.
+ * This order is for consistency with the non-normalized {@linkplain #geographic() geographic} CRS
+ * of all items in this {@code CommonCRS} enumeration.</div>
*
- * @since 0.7
+ * @param latitude a latitude in the desired UTM or UPS projection zone.
+ * @param longitude a longitude in the desired UTM or UPS projection zone.
+ * @return a Universal Transverse Mercator or Polar Stereographic projection for the zone containing the given point.
+ *
+ * @since 0.8
*/
- public ProjectedCRS UTM(final double latitude, final double longitude) {
+ public ProjectedCRS universal(final double latitude, final double longitude) {
ArgumentChecks.ensureBetween("latitude", Latitude.MIN_VALUE, Latitude.MAX_VALUE, latitude);
ArgumentChecks.ensureBetween("longitude", -Formulas.LONGITUDE_MAX, Formulas.LONGITUDE_MAX, longitude);
final boolean isSouth = MathFunctions.isNegative(latitude);
- final int zone = TransverseMercator.zone(longitude);
+ final boolean isUTM = latitude >= TransverseMercator.Zoner.SOUTH_BOUNDS
+ && latitude < TransverseMercator.Zoner.NORTH_BOUNDS;
+ final int zone = isUTM ? TransverseMercator.Zoner.UTM.zone(latitude, longitude) : POLAR;
final Integer key = isSouth ? -zone : zone;
ProjectedCRS crs;
- synchronized (cachedUTM) {
- crs = cachedUTM.get(key);
+ synchronized (cachedProjections) {
+ crs = cachedProjections.get(key);
}
if (crs == null) {
+ /*
+ * Requested CRS has not been previously created, or the cache has been cleared.
+ * Before to create the CRS explicitely, try to get it from the EPSG database.
+ * Using the EPSG geodetic dataset when possible gives us more information,
+ * like the aliases and area of validity.
+ */
int code = 0;
- if (zone >= firstZone && zone <= lastZone) {
+ if (!isUTM) {
+ code = Short.toUnsignedInt(isSouth ? southUPS : northUPS);
+ } else if (zone >= firstZone && zone <= lastZone) {
code = Short.toUnsignedInt(isSouth ? southUTM : northUTM);
- if (code != 0) {
- code += zone;
- final GeodeticAuthorityFactory factory = factory();
- if (factory != null) try {
- return factory.createProjectedCRS(String.valueOf(code));
- } catch (FactoryException e) {
- failure(this, "UTM", e, code);
- }
+ }
+ if (code != 0) {
+ if (isUTM) code += zone;
+ final GeodeticAuthorityFactory factory = factory();
+ if (factory != null) try {
+ return factory.createProjectedCRS(String.valueOf(code));
+ } catch (FactoryException e) {
+ failure(this, "universal", e, code);
}
}
/*
- * All constants defined in this enumeration use the same coordinate system, EPSG:4400.
- * We will arbitrarily create this CS only for a frequently created CRS, and share that
- * CS instance for all other constants.
+ * At this point we couldn't use the EPSG dataset; we have to create the CRS ourselves.
+ * All constants defined in this enumeration use the same coordinate system (EPSG:4400)
+ * except for the polar regions. We will arbitrarily create the CS only for a frequently
+ * used datum, then share that CS instance for all other constants.
*/
CartesianCS cs = null;
- synchronized (DEFAULT.cachedUTM) {
- final Iterator<ProjectedCRS> it = DEFAULT.cachedUTM.values().iterator();
- if (it.hasNext()) {
- cs = it.next().getCoordinateSystem();
+ if (isUTM) {
+ synchronized (DEFAULT.cachedProjections) {
+ for (final Map.Entry<Integer,ProjectedCRS> entry : DEFAULT.cachedProjections.entrySet()) {
+ if (Math.abs(entry.getKey()) != POLAR) {
+ cs = entry.getValue().getCoordinateSystem();
+ break;
+ }
+ }
}
}
+ /*
+ * If we didn't found a Coordinate System for EPSG:4400, or if the CS that we needed was
+ * for another EPSG code (polar cases), delegate to the WGS84 datum or create the CS now.
+ *
+ * EPSG:4400 \u2014 Cartesian 2D CS. Axes: easting, northing (E,N). Orientations: east, north. UoM: m.
+ * EPSG:1026 \u2014 Cartesian 2D CS for UPS north. Axes: E,N. Orientations: E along 90�E meridian, N along 180�E meridian. UoM: m.
+ * EPSG:1027 \u2014 Cartesian 2D CS for UPS south. Axes: E,N. Orientations: E along 90�E, N along 0�E meridians. UoM: m.
+ */
if (cs == null) {
if (this != DEFAULT) {
- cs = DEFAULT.UTM(latitude, longitude).getCoordinateSystem();
+ cs = DEFAULT.universal(latitude, longitude).getCoordinateSystem();
} else {
- cs = (CartesianCS) StandardDefinitions.createCoordinateSystem(Constants.EPSG_PROJECTED_CS);
+ cs = (CartesianCS) StandardDefinitions.createCoordinateSystem(
+ isUTM ? Constants.EPSG_PROJECTED_CS : isSouth ? (short) 1027 : (short) 1026);
}
}
- crs = StandardDefinitions.createUTM(code, geographic(), latitude, longitude, cs);
+ crs = StandardDefinitions.createUniversal(code, geographic(), isUTM, latitude, longitude, cs);
final ProjectedCRS other;
- synchronized (cachedUTM) {
- other = cachedUTM.putIfAbsent(key, crs);
+ synchronized (cachedProjections) {
+ other = cachedProjections.putIfAbsent(key, crs);
}
if (other != null) {
return other;