/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.referencing.cs; import java.util.Collections; import java.util.HashMap; import java.util.Locale; // For javadoc import java.util.Map; import java.util.NoSuchElementException; import javax.measure.converter.UnitConverter; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; import org.opengis.referencing.cs.AxisDirection; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.opengis.referencing.cs.RangeMeaning; import org.opengis.util.InternationalString; import org.geotools.referencing.AbstractIdentifiedObject; import org.geotools.referencing.wkt.Formatter; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.util.SimpleInternationalString; import org.geotools.util.NameFactory; import org.geotools.util.Utilities; /** * Definition of a coordinate system axis. This is used to label axes, and indicate the orientation. * See {@linkplain org.opengis.referencing.cs#AxisNames axis name constraints}. *
* In some case, the axis name is constrained by ISO 19111 depending on the * {@linkplain org.opengis.referencing.crs.CoordinateReferenceSystem coordinate reference system} * type. These constraints are identified in the javadoc by "ISO 19111 name is..." * sentences. This constraint works in two directions; for example the names * "geodetic latitude" and "geodetic longitude" shall be used to * designate the coordinate axis names associated with a * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic coordinate reference system}. * Conversely, these names shall not be used in any other context. * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see AbstractCS * @see Unit */ public class DefaultCoordinateSystemAxis extends AbstractIdentifiedObject implements CoordinateSystemAxis { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -7883614853277827689L; /** * Number of directions from "North", "North-North-East", "North-East", etc. * This is verified by {@code DefaultCoordinateSystemAxisTest.testCompass}. */ static final int COMPASS_DIRECTION_COUNT = 16; /** * Number of items in {@link #PREDEFINED}. Should be considered * as a constant after the class initialisation is completed. */ private static int PREDEFINED_COUNT = 0; /** * The list of predefined constants declared in this class, * in declaration order. This order matter. */ private static final DefaultCoordinateSystemAxis[] PREDEFINED = new DefaultCoordinateSystemAxis[26]; /** * Default axis info for geodetic longitudes in a * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The ISO 19111 name is "geodetic longitude" and the abbreviation is "λ" * (lambda). * * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE}, * {@link #ELLIPSOIDAL_HEIGHT} set. * * @see #LONGITUDE * @see #SPHERICAL_LONGITUDE * @see #GEODETIC_LATITUDE */ public static final DefaultCoordinateSystemAxis GEODETIC_LONGITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.GEODETIC_LONGITUDE, "\u03BB", AxisDirection.EAST, NonSI.DEGREE_ANGLE); /** * Default axis info for geodetic latitudes in a * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The ISO 19111 name is "geodetic latitude" and the abbreviation is "φ" (phi). * * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE}, * {@link #ELLIPSOIDAL_HEIGHT} set. * * @see #LATITUDE * @see #SPHERICAL_LATITUDE * @see #GEODETIC_LONGITUDE */ public static final DefaultCoordinateSystemAxis GEODETIC_LATITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.GEODETIC_LATITUDE, "\u03C6", AxisDirection.NORTH, NonSI.DEGREE_ANGLE); /** * Default axis info for longitudes. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The abbreviation is "λ" (lambda). * * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set. * * @see #GEODETIC_LONGITUDE * @see #SPHERICAL_LONGITUDE * @see #LATITUDE */ public static final DefaultCoordinateSystemAxis LONGITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.LONGITUDE, "\u03BB", AxisDirection.EAST, NonSI.DEGREE_ANGLE); /** * Default axis info for latitudes. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The abbreviation is "φ" (phi). * * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set. * * @see #GEODETIC_LATITUDE * @see #SPHERICAL_LATITUDE * @see #LONGITUDE */ public static final DefaultCoordinateSystemAxis LATITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.LATITUDE, "\u03C6", AxisDirection.NORTH, NonSI.DEGREE_ANGLE); /** * The default axis for height values above the ellipsoid in a * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#UP up} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "ellipsoidal heigt" and the abbreviation is lower case * "h". * * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE}, * {@link #ELLIPSOIDAL_HEIGHT} set. * * @see #ALTITUDE * @see #GEOCENTRIC_RADIUS * @see #GRAVITY_RELATED_HEIGHT * @see #DEPTH */ public static final DefaultCoordinateSystemAxis ELLIPSOIDAL_HEIGHT = new DefaultCoordinateSystemAxis( VocabularyKeys.ELLIPSOIDAL_HEIGHT, "h", AxisDirection.UP, SI.METER); /** * The default axis for height values measured from gravity. * * Increasing ordinates values go {@linkplain AxisDirection#UP up} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "gravity-related height" and the abbreviation is lower * case "H". * * @see #ALTITUDE * @see #ELLIPSOIDAL_HEIGHT * @see #GEOCENTRIC_RADIUS * @see #DEPTH */ public static final DefaultCoordinateSystemAxis GRAVITY_RELATED_HEIGHT = new DefaultCoordinateSystemAxis( VocabularyKeys.GRAVITY_RELATED_HEIGHT, "H", AxisDirection.UP, SI.METER); /** * The default axis for altitude values. * * Increasing ordinates values go {@linkplain AxisDirection#UP up} * and units are {@linkplain SI#METER metres}. * * The abbreviation is lower case "h". * * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set. * * @see #ELLIPSOIDAL_HEIGHT * @see #GEOCENTRIC_RADIUS * @see #GRAVITY_RELATED_HEIGHT * @see #DEPTH */ public static final DefaultCoordinateSystemAxis ALTITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.ALTITUDE, "h", AxisDirection.UP, SI.METER); /** * The default axis for depth. * * Increasing ordinates values go {@linkplain AxisDirection#DOWN down} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "depth". * * @see #ALTITUDE * @see #ELLIPSOIDAL_HEIGHT * @see #GEOCENTRIC_RADIUS * @see #GRAVITY_RELATED_HEIGHT */ public static final DefaultCoordinateSystemAxis DEPTH = new DefaultCoordinateSystemAxis( VocabularyKeys.DEPTH, "d", AxisDirection.DOWN, SI.METER); static { ALTITUDE.opposite = DEPTH; DEPTH.opposite = ALTITUDE; } /** * Default axis info for radius in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.cs.SphericalCS spherical CS}. * * Increasing ordinates values go {@linkplain AxisDirection#UP up} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "geocentric radius" and the abbreviation is lower case * "r". * * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE}, * {@link #GEOCENTRIC_RADIUS} set. * * @see #ALTITUDE * @see #ELLIPSOIDAL_HEIGHT * @see #GRAVITY_RELATED_HEIGHT * @see #DEPTH */ public static final DefaultCoordinateSystemAxis GEOCENTRIC_RADIUS = new DefaultCoordinateSystemAxis( VocabularyKeys.GEOCENTRIC_RADIUS, "r", AxisDirection.UP, SI.METER); /** * Default axis info for longitudes in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.crs.SphericalCS spherical CS}. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The ISO 19111 name is "spherical longitude" and the abbreviation is "Ω" * (omega). * * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE}, * {@link #GEOCENTRIC_RADIUS} set. * * @see #LONGITUDE * @see #GEODETIC_LONGITUDE * @see #SPHERICAL_LATITUDE */ public static final DefaultCoordinateSystemAxis SPHERICAL_LONGITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.SPHERICAL_LONGITUDE, "\u03A9", AxisDirection.EAST, NonSI.DEGREE_ANGLE); /** * Default axis info for latitudes in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.cs.SphericalCS spherical CS}. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}. * * The ISO 19111 name is "spherical latitude" and the abbreviation is "Θ" * (theta). * * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE}, * {@link #GEOCENTRIC_RADIUS} set. * * @see #LATITUDE * @see #GEODETIC_LATITUDE * @see #SPHERICAL_LONGITUDE */ public static final DefaultCoordinateSystemAxis SPHERICAL_LATITUDE = new DefaultCoordinateSystemAxis( VocabularyKeys.SPHERICAL_LATITUDE, "\u03B8", AxisDirection.NORTH, NonSI.DEGREE_ANGLE); /** * Default axis info for x values in a * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain SI#METER metres}. * * The abbreviation is lower case "x". * * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set. * * @see #EASTING * @see #WESTING * @see #GEOCENTRIC_X * @see #DISPLAY_X * @see #COLUMN */ public static final DefaultCoordinateSystemAxis X = new DefaultCoordinateSystemAxis( -1, "x", AxisDirection.EAST, SI.METER); /** * Default axis info for y values in a * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain SI#METER metres}. * * The abbreviation is lower case "y". * * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set. * * @see #NORTHING * @see #SOUTHING * @see #GEOCENTRIC_Y * @see #DISPLAY_Y * @see #ROW */ public static final DefaultCoordinateSystemAxis Y = new DefaultCoordinateSystemAxis( -1, "y", AxisDirection.NORTH, SI.METER); /** * Default axis info for z values in a * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go {@linkplain AxisDirection#UP up} * and units are {@linkplain SI#METER metres}. * * The abbreviation is lower case "z". * * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set. */ public static final DefaultCoordinateSystemAxis Z = new DefaultCoordinateSystemAxis( -1, "z", AxisDirection.UP, SI.METER); /** * Default axis info for x values in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go toward prime meridian * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "geocentric X" and the abbreviation is upper case * "X". * * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y}, * {@link #GEOCENTRIC_Z} set. */ public static final DefaultCoordinateSystemAxis GEOCENTRIC_X = new DefaultCoordinateSystemAxis( VocabularyKeys.GEOCENTRIC_X, "X", AxisDirection.OTHER, SI.METER); /** * Default axis info for y values in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "geocentric Y" and the abbreviation is upper case * "Y". * * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y}, * {@link #GEOCENTRIC_Z} set. */ public static final DefaultCoordinateSystemAxis GEOCENTRIC_Y = new DefaultCoordinateSystemAxis( VocabularyKeys.GEOCENTRIC_Y, "Y", AxisDirection.EAST, SI.METER); /** * Default axis info for z values in a * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "geocentric Z" and the abbreviation is upper case * "Z". * * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y}, * {@link #GEOCENTRIC_Z} set. */ public static final DefaultCoordinateSystemAxis GEOCENTRIC_Z = new DefaultCoordinateSystemAxis( VocabularyKeys.GEOCENTRIC_Z, "Z", AxisDirection.NORTH, SI.METER); /** * Default axis info for Easting values in a * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#EAST East} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "easting" and the abbreviation is upper case * "E". * * This axis is usually part of a {@link #EASTING}, {@link #NORTHING} set. * * @see #X * @see #EASTING * @see #WESTING */ public static final DefaultCoordinateSystemAxis EASTING = new DefaultCoordinateSystemAxis( VocabularyKeys.EASTING, "E", AxisDirection.EAST, SI.METER); /** * Default axis info for Westing values in a * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#WEST West} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "westing" and the abbreviation is upper case * "W". * * @see #X * @see #EASTING * @see #WESTING */ public static final DefaultCoordinateSystemAxis WESTING = new DefaultCoordinateSystemAxis( VocabularyKeys.WESTING, "W", AxisDirection.WEST, SI.METER); static { EASTING.opposite = WESTING; WESTING.opposite = EASTING; } /** * Default axis info for Northing values in a * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#NORTH North} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "northing" and the abbreviation is upper case * "N". * * This axis is usually part of a {@link #EASTING}, {@link #NORTHING} set. * * @see #Y * @see #NORTHING * @see #SOUTHING */ public static final DefaultCoordinateSystemAxis NORTHING = new DefaultCoordinateSystemAxis( VocabularyKeys.NORTHING, "N", AxisDirection.NORTH, SI.METER); /** * Default axis info for Southing values in a * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}. * * Increasing ordinates values go {@linkplain AxisDirection#SOUTH South} * and units are {@linkplain SI#METER metres}. * * The ISO 19111 name is "southing" and the abbreviation is upper case * "S". * * @see #Y * @see #NORTHING * @see #SOUTHING */ public static final DefaultCoordinateSystemAxis SOUTHING = new DefaultCoordinateSystemAxis( VocabularyKeys.SOUTHING, "S", AxisDirection.SOUTH, SI.METER); static { NORTHING.opposite = SOUTHING; SOUTHING.opposite = NORTHING; } /** * A default axis for time values in a {@linkplain org.opengis.referencing.cs.TimeCS time CS}. * * Increasing time go toward {@linkplain AxisDirection#FUTURE future} * and units are {@linkplain NonSI#DAY days}. * * The abbreviation is lower case "t". */ public static final DefaultCoordinateSystemAxis TIME = new DefaultCoordinateSystemAxis( VocabularyKeys.TIME, "t", AxisDirection.FUTURE, NonSI.DAY); /** * A default axis for column indices in a {@linkplain org.opengis.coverage.grid.GridCoverage * grid coverage}. Increasing values go toward {@linkplain AxisDirection#COLUMN_POSITIVE * positive column number}. * * The abbreviation is lower case "i". */ public static final DefaultCoordinateSystemAxis COLUMN = new DefaultCoordinateSystemAxis( VocabularyKeys.COLUMN, "i", AxisDirection.COLUMN_POSITIVE, Unit.ONE); /** * A default axis for row indices in a {@linkplain org.opengis.coverage.grid.GridCoverage grid * coverage}. Increasing values go toward {@linkplain AxisDirection#ROW_POSITIVE positive row * number}. * * The abbreviation is lower case "j". */ public static final DefaultCoordinateSystemAxis ROW = new DefaultCoordinateSystemAxis( VocabularyKeys.ROW, "j", AxisDirection.ROW_POSITIVE, Unit.ONE); /** * A default axis for x values in a display device. Increasing values go toward * {@linkplain AxisDirection#DISPLAY_RIGHT display right}. * * The abbreviation is lower case "x". * * @since 2.2 */ public static final DefaultCoordinateSystemAxis DISPLAY_X = new DefaultCoordinateSystemAxis( -1, "x", AxisDirection.DISPLAY_RIGHT, Unit.ONE); /** * A default axis for y values in a display device. Increasing values go toward * {@linkplain AxisDirection#DISPLAY_DOWN display down}. * * The abbreviation is lower case "y". * * @since 2.2 */ public static final DefaultCoordinateSystemAxis DISPLAY_Y = new DefaultCoordinateSystemAxis( -1, "y", AxisDirection.DISPLAY_DOWN, Unit.ONE); /** * Some names to be treated as equivalent. This is needed because axis names are the primary * way to distinguish between {@link CoordinateSystemAxis} instances. Those names are strictly * defined by ISO 19111 as "Geodetic latitude" and "Geodetic longitude" among others, but the * legacy WKT specifications from OGC 01-009 defined the names as "Lon" and "Lat" for the same * axis. *
* Keys in this map are names in lower cases. Values are the axis that the
* name is for. The actual axis instance doesn't matter (the algorithm using this map should
* work for any axis instance); it is just a way to differentiate latitude and longitude.
*/
private static final Map
* This method first checks if the specified name matches the {@linkplain #getAbbreviation
* abbreviation} of a predefined axis. The comparaison is case-sensitive (for example the
* {@link #GEOCENTRIC_X} abbreviation is uppercase {@code "X"}, while the abbreviation for
* the generic {@link #X} axis is lowercase {@code "x"}).
*
* If the specified name doesn't match any abbreviation, then this method compares the name
* against predefined axis {@linkplain #getName name} in a case-insensitive manner. Examples
* of valid names are "Geodetic latitude" and "Northing".
*
* The direction argument is optional and can be used in order to resolve ambiguity like
* {@link #X} and {@link #DISPLAY_X} axis. If this argument is {@code null}, then the first
* axis with a matching name or abbreviation will be returned.
*
* @param name The axis name or abbreviation.
* @param direction An optional direction, or {@code null}.
* @return One of the constants declared in this class, or {@code null}.
*
* @since 2.4
*/
public static DefaultCoordinateSystemAxis getPredefined(String name, AxisDirection direction) {
ensureNonNull("name", name);
name = name.trim();
DefaultCoordinateSystemAxis found = null;
for (int i=0; i Note that an {@link org.geotools.referencing.crs.DefaultEngineeringCRS} often requires
* specific descriptions of the directions of its coordinate system axes.
* A positive angle denotes a right-handed system, while a negative angle denotes
* a left-handed system. Example:
*
*
*
* The above special cases are needed in order to workaround a conflict in specifications:
* ISO 19111 explicitly state that the latitude and longitude axis names shall be
* "Geodetic latitude" and "Geodetic longitude", will legacy OGC 01-009 (where WKT is defined)
* said that the default values shall be "Lat" and "Lon".
*
* @param name The name to compare.
* @return {@code true} if the primary name of at least one alias
* matches the specified {@code name}.
*/
@Override
public boolean nameMatches(final String name) {
if (super.nameMatches(name)) {
return true;
}
/*
* The standard comparaisons didn't worked. Check for the aliases. Note: we don't
* test for 'nameMatchesXY(...)' here because the "x" and "y" axis names are too
* generic. We test them only in the 'equals' method, which has the extra-safety
* of units comparaison (so less risk to treat incompatible axis as equivalent).
*
* TODO: replace Object by CoordinateSystemAxis when we will be allowed
* to compile for J2SE 1.5.
*/
final Object type = ALIASES.get(name.trim().toLowerCase());
return (type != null) && (type == ALIASES.get(getName().getCode().trim().toLowerCase()));
}
/**
* Compares the specified object with this axis for equality.
*
* @param object The object to compare to {@code this}.
* @param compareMetadata {@code true} for performing a strict comparaison, or
* {@code false} for comparing only properties relevant to transformations.
* @return {@code true} if both objects are equal.
*/
@Override
public boolean equals(final AbstractIdentifiedObject object, final boolean compareMetadata) {
if (object == this) {
return true; // Slight optimization.
}
if (super.equals(object, compareMetadata)) {
return equals((DefaultCoordinateSystemAxis) object, compareMetadata, true);
}
return false;
}
/**
* Compares the specified object with this axis for equality, with optional comparaison
* of units. Units should always be compared (they are not just metadata), except in the
* particular case of {@link AbstractCS#axisColinearWith}, which is used as a first step
* toward units conversions through {@link AbstractCS#swapAndScaleAxis}.
*/
final boolean equals(final DefaultCoordinateSystemAxis that,
final boolean compareMetadata, final boolean compareUnit)
{
if (compareMetadata) {
if (!Utilities.equals(this.abbreviation, that.abbreviation) ||
!Utilities.equals(this.rangeMeaning, that.rangeMeaning) ||
Double.doubleToLongBits(minimum) != Double.doubleToLongBits(that.minimum) ||
Double.doubleToLongBits(maximum) != Double.doubleToLongBits(that.maximum))
{
return false;
}
} else {
/*
* Checking the abbreviation is not suffisient. For example the polar angle and the
* spherical latitude have the same abbreviation (theta). Geotools extensions like
* "Longitude" (in addition of ISO 19111 "Geodetic longitude") bring more potential
* confusion. Furthermore, not all implementors will use the greek letters (even if
* they are part of ISO 19111). For example most CRS in WKT format use the "Lat"
* abbreviation instead of the greek letter phi. For comparaisons without metadata,
* we ignore the unreliable abbreviation and check the axis name instead. These
* names are constrained by ISO 19111 specification (see class javadoc), so they
* should be reliable enough.
*
* Note: there is no need to execute this block if 'compareMetadata' is true,
* because in this case a stricter check has already been performed by
* the 'equals' method in the superclass.
*/
final String thatName = that.getName().getCode();
if (!nameMatches(thatName)) {
// The above test checked for special cases ("Lat" / "Lon" aliases, etc.).
// The next line may not, but is tested anyway in case the user overrided
// the 'that.nameMatches(...)' method.
final String thisName = getName().getCode();
if (!nameMatches(that, thisName)) {
// For the needs of AbstractCS.axisColinearWith(...), we must stop here.
// In addition it may be safer to not test 'nameMatchesXY' when we don't
// have the extra-safety of units comparaison, because "x" and "y" names
// are too generic.
if (!compareUnit) {
return false;
}
// Last chance: check for the special case of "x" and "y" axis names.
if (!nameMatchesXY(thatName, thisName) && !nameMatchesXY(thisName, thatName)) {
return false;
}
}
}
}
return Utilities.equals(this.direction, that.direction) &&
(!compareUnit || Utilities.equals(this.unit, that.unit));
}
/**
* Returns a hash value for this axis. This value doesn't need to be the same
* in past or future versions of this class.
*/
@Override
public int hashCode() {
int code = (int)serialVersionUID;
code = code*37 + abbreviation.hashCode();
code = code*37 + direction .hashCode();
code = code*37 + unit .hashCode();
return code;
}
/**
* Format the inner part of a
* Well
* Known Text (WKT) element. WKT is returned by the {@link #toString toString} method
* and looks like name.{@linkplain InternationalString#toString(Locale) toString}(null)
. The
* same {@code name} argument is also stored as an {@linkplain #getAlias alias}, which
* allows fetching localized versions of the name.
*
* @param name The name of this axis. Also stored as an alias for localization purpose.
* @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
* coordinate system axis.
* @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
* @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
* system axis.
*/
public DefaultCoordinateSystemAxis(final InternationalString name,
final String abbreviation,
final AxisDirection direction,
final Unit> unit)
{
this(toMap(name), abbreviation, direction, unit);
}
/**
* Work around for RFE #4093999 in Sun's bug database
* ("Relax constraint on placement of this()/super() call in constructors").
*/
private static Map
*
*
* @param source The source axis direction.
* @param target The target axis direction.
* @return The arithmetic angle (in degrees) of the rotation to apply on a line pointing toward
* the source direction in order to make it point toward the target direction, or
* {@link Double#NaN} if this value can't be computed.
*
* @since 2.4
*/
public static double getAngle(final AxisDirection source, final AxisDirection target) {
ensureNonNull("source", source);
ensureNonNull("target", target);
// Tests for NORTH, SOUTH, EAST, EAST-NORTH-EAST, etc. directions.
final int compass = getCompassAngle(source, target);
if (compass != Integer.MIN_VALUE) {
return compass * (360.0 / COMPASS_DIRECTION_COUNT);
}
// Tests for "South along 90 deg East", etc. directions.
final DirectionAlongMeridian src = DirectionAlongMeridian.parse(source);
if (src != null) {
final DirectionAlongMeridian tgt = DirectionAlongMeridian.parse(target);
if (tgt != null) {
return src.getAngle(tgt);
}
}
return Double.NaN;
}
/**
* Tests for angle on compass only (do not tests angle between direction along meridians).
* Returns {@link Integer#MIN_VALUE} if the angle can't be computed.
*/
static int getCompassAngle(final AxisDirection source, final AxisDirection target) {
final int base = AxisDirection.NORTH.ordinal();
final int src = source.ordinal() - base;
if (src >= 0 && src < COMPASS_DIRECTION_COUNT) {
int tgt = target.ordinal() - base;
if (tgt >= 0 && tgt < COMPASS_DIRECTION_COUNT) {
tgt = src - tgt;
if (tgt < -COMPASS_DIRECTION_COUNT/2) {
tgt += COMPASS_DIRECTION_COUNT;
} else if (tgt > COMPASS_DIRECTION_COUNT/2) {
tgt -= COMPASS_DIRECTION_COUNT;
}
return tgt;
}
}
return Integer.MIN_VALUE;
}
/**
* Returns {@code true} if the specified directions are perpendicular.
*
* @param first The first axis direction to test.
* @param second The second axis direction to test.
* @return {@code true} if the given axis direction are perpendicular.
*
* @since 2.4
*/
public static boolean perpendicular(final AxisDirection first, final AxisDirection second) {
return Math.abs(Math.abs(getAngle(first, second)) - 90) <= DirectionAlongMeridian.EPS;
}
/**
* Returns a new axis with the same properties than current axis except for the units.
*
* @param newUnit The unit for the new axis.
* @return An axis using the specified unit.
* @throws IllegalArgumentException If the specified unit is incompatible with the expected one.
*/
final DefaultCoordinateSystemAxis usingUnit(final Unit> newUnit)
throws IllegalArgumentException
{
if (unit.equals(newUnit)) {
return this;
}
if (unit.isCompatible(newUnit)) {
return new DefaultCoordinateSystemAxis(getProperties(this, null),
abbreviation, direction, newUnit, minimum, maximum, rangeMeaning);
}
throw new IllegalArgumentException(Errors.format(ErrorKeys.INCOMPATIBLE_UNIT_$1, newUnit));
}
/**
* Returns {@code true} if either the {@linkplain #getName() primary name} or at least
* one {@linkplain #getAlias alias} matches the specified string. This method performs
* all the searh done by the {@linkplain AbstractIdentifiedObject#nameMatches(String)
* super-class}, with the addition of special processing for latitudes and longitudes:
*
*
* AXIS["name",NORTH]
.
*
* @param formatter The formatter to use.
* @return The WKT element name, which is "AXIS".
*/
@Override
protected String formatWKT(final Formatter formatter) {
formatter.append(direction);
return "AXIS";
}
}