0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2003-2006, GeoTools Project Managment Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation;
0010: * version 2.1 of the License.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: *
0017: * This package contains documentation from OpenGIS specifications.
0018: * OpenGIS consortium's work is fully acknowledged here.
0019: */
0020: package org.geotools.referencing.cs;
0021:
0022: // J2SE dependencies and extensions
0023: import java.util.Collections;
0024: import java.util.HashMap;
0025: import java.util.Locale;
0026: import java.util.Map;
0027: import java.util.NoSuchElementException;
0028: import javax.units.Converter;
0029: import javax.units.NonSI;
0030: import javax.units.SI;
0031: import javax.units.Unit;
0032:
0033: // OpenGIS dependencies
0034: import org.opengis.referencing.cs.AxisDirection;
0035: import org.opengis.referencing.cs.CoordinateSystemAxis;
0036: import org.opengis.referencing.cs.RangeMeaning;
0037: import org.opengis.util.InternationalString;
0038:
0039: // Geotools dependencies
0040: import org.geotools.referencing.AbstractIdentifiedObject;
0041: import org.geotools.referencing.wkt.Formatter;
0042: import org.geotools.resources.Utilities;
0043: import org.geotools.resources.i18n.Errors;
0044: import org.geotools.resources.i18n.ErrorKeys;
0045: import org.geotools.resources.i18n.Vocabulary;
0046: import org.geotools.resources.i18n.VocabularyKeys;
0047: import org.geotools.util.SimpleInternationalString;
0048: import org.geotools.util.NameFactory;
0049:
0050: /**
0051: * Definition of a coordinate system axis. This is used to label axes, and indicate the orientation.
0052: * See {@linkplain org.opengis.referencing.cs#AxisNames axis name constraints}.
0053: * <p>
0054: * In some case, the axis name is constrained by ISO 19111 depending on the
0055: * {@linkplain org.opengis.referencing.crs.CoordinateReferenceSystem coordinate reference system}
0056: * type. These constraints are identified in the javadoc by "<cite>ISO 19111 name is...</cite>"
0057: * sentences. This constraint works in two directions; for example the names
0058: * "<cite>geodetic latitude</cite>" and "<cite>geodetic longitude</cite>" shall be used to
0059: * designate the coordinate axis names associated with a
0060: * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic coordinate reference system}.
0061: * Conversely, these names shall not be used in any other context.
0062: *
0063: * @since 2.1
0064: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/cs/DefaultCoordinateSystemAxis.java $
0065: * @version $Id: DefaultCoordinateSystemAxis.java 25778 2007-06-08 08:46:34Z desruisseaux $
0066: * @author Martin Desruisseaux
0067: *
0068: * @see AbstractCS
0069: * @see Unit
0070: */
0071: public class DefaultCoordinateSystemAxis extends
0072: AbstractIdentifiedObject implements CoordinateSystemAxis {
0073: /**
0074: * Serial number for interoperability with different versions.
0075: */
0076: private static final long serialVersionUID = -7883614853277827689L;
0077:
0078: /**
0079: * Number of directions from "North", "North-North-East", "North-East", etc.
0080: * This is verified by {@code DefaultCoordinateSystemAxisTest.testCompass}.
0081: */
0082: static final int COMPASS_DIRECTION_COUNT = 16;
0083:
0084: /**
0085: * Number of items in {@link #PREDEFINED}. Should be considered
0086: * as a constant after the class initialisation is completed.
0087: */
0088: private static int PREDEFINED_COUNT = 0;
0089:
0090: /**
0091: * The list of predefined constants declared in this class,
0092: * in declaration order. This order matter.
0093: */
0094: private static final DefaultCoordinateSystemAxis[] PREDEFINED = new DefaultCoordinateSystemAxis[26];
0095:
0096: /**
0097: * Default axis info for geodetic longitudes in a
0098: * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}.
0099: *
0100: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0101: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0102: *
0103: * The ISO 19111 name is "<cite>geodetic longitude</cite>" and the abbreviation is "λ"
0104: * (lambda).
0105: *
0106: * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
0107: * {@link #ELLIPSOIDAL_HEIGHT} set.
0108: *
0109: * @see #LONGITUDE
0110: * @see #SPHERICAL_LONGITUDE
0111: * @see #GEODETIC_LATITUDE
0112: */
0113: public static final DefaultCoordinateSystemAxis GEODETIC_LONGITUDE = new DefaultCoordinateSystemAxis(
0114: VocabularyKeys.GEODETIC_LONGITUDE, "\u03BB",
0115: AxisDirection.EAST, NonSI.DEGREE_ANGLE);
0116:
0117: /**
0118: * Default axis info for geodetic latitudes in a
0119: * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}.
0120: *
0121: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0122: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0123: *
0124: * The ISO 19111 name is "<cite>geodetic latitude</cite>" and the abbreviation is "φ" (phi).
0125: *
0126: * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
0127: * {@link #ELLIPSOIDAL_HEIGHT} set.
0128: *
0129: * @see #LATITUDE
0130: * @see #SPHERICAL_LATITUDE
0131: * @see #GEODETIC_LONGITUDE
0132: */
0133: public static final DefaultCoordinateSystemAxis GEODETIC_LATITUDE = new DefaultCoordinateSystemAxis(
0134: VocabularyKeys.GEODETIC_LATITUDE, "\u03C6",
0135: AxisDirection.NORTH, NonSI.DEGREE_ANGLE);
0136:
0137: /**
0138: * Default axis info for longitudes.
0139: *
0140: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0141: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0142: *
0143: * The abbreviation is "λ" (lambda).
0144: *
0145: * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set.
0146: *
0147: * @see #GEODETIC_LONGITUDE
0148: * @see #SPHERICAL_LONGITUDE
0149: * @see #LATITUDE
0150: */
0151: public static final DefaultCoordinateSystemAxis LONGITUDE = new DefaultCoordinateSystemAxis(
0152: VocabularyKeys.LONGITUDE, "\u03BB", AxisDirection.EAST,
0153: NonSI.DEGREE_ANGLE);
0154:
0155: /**
0156: * Default axis info for latitudes.
0157: *
0158: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0159: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0160: *
0161: * The abbreviation is "φ" (phi).
0162: *
0163: * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set.
0164: *
0165: * @see #GEODETIC_LATITUDE
0166: * @see #SPHERICAL_LATITUDE
0167: * @see #LONGITUDE
0168: */
0169: public static final DefaultCoordinateSystemAxis LATITUDE = new DefaultCoordinateSystemAxis(
0170: VocabularyKeys.LATITUDE, "\u03C6", AxisDirection.NORTH,
0171: NonSI.DEGREE_ANGLE);
0172:
0173: /**
0174: * The default axis for height values above the ellipsoid in a
0175: * {@linkplain org.opengis.referencing.crs.GeographicCRS geographic CRS}.
0176: *
0177: * Increasing ordinates values go {@linkplain AxisDirection#UP up}
0178: * and units are {@linkplain SI#METER metres}.
0179: *
0180: * The ISO 19111 name is "<cite>ellipsoidal heigt</cite>" and the abbreviation is lower case
0181: * "<var>h</var>".
0182: *
0183: * This axis is usually part of a {@link #GEODETIC_LONGITUDE}, {@link #GEODETIC_LATITUDE},
0184: * {@link #ELLIPSOIDAL_HEIGHT} set.
0185: *
0186: * @see #ALTITUDE
0187: * @see #GEOCENTRIC_RADIUS
0188: * @see #GRAVITY_RELATED_HEIGHT
0189: * @see #DEPTH
0190: */
0191: public static final DefaultCoordinateSystemAxis ELLIPSOIDAL_HEIGHT = new DefaultCoordinateSystemAxis(
0192: VocabularyKeys.ELLIPSOIDAL_HEIGHT, "h", AxisDirection.UP,
0193: SI.METER);
0194:
0195: /**
0196: * The default axis for height values measured from gravity.
0197: *
0198: * Increasing ordinates values go {@linkplain AxisDirection#UP up}
0199: * and units are {@linkplain SI#METER metres}.
0200: *
0201: * The ISO 19111 name is "<cite>gravity-related height</cite>" and the abbreviation is lower
0202: * case "<var>h</var>".
0203: *
0204: * @see #ALTITUDE
0205: * @see #ELLIPSOIDAL_HEIGHT
0206: * @see #GEOCENTRIC_RADIUS
0207: * @see #DEPTH
0208: */
0209: public static final DefaultCoordinateSystemAxis GRAVITY_RELATED_HEIGHT = new DefaultCoordinateSystemAxis(
0210: VocabularyKeys.GRAVITY_RELATED_HEIGHT, "h",
0211: AxisDirection.UP, SI.METER);
0212:
0213: /**
0214: * The default axis for altitude values.
0215: *
0216: * Increasing ordinates values go {@linkplain AxisDirection#UP up}
0217: * and units are {@linkplain SI#METER metres}.
0218: *
0219: * The abbreviation is lower case "<var>h</var>".
0220: *
0221: * This axis is usually part of a {@link #LONGITUDE}, {@link #LATITUDE}, {@link #ALTITUDE} set.
0222: *
0223: * @see #ELLIPSOIDAL_HEIGHT
0224: * @see #GEOCENTRIC_RADIUS
0225: * @see #GRAVITY_RELATED_HEIGHT
0226: * @see #DEPTH
0227: */
0228: public static final DefaultCoordinateSystemAxis ALTITUDE = new DefaultCoordinateSystemAxis(
0229: VocabularyKeys.ALTITUDE, "h", AxisDirection.UP, SI.METER);
0230:
0231: /**
0232: * The default axis for depth.
0233: *
0234: * Increasing ordinates values go {@linkplain AxisDirection#DOWN down}
0235: * and units are {@linkplain SI#METER metres}.
0236: *
0237: * The ISO 19111 name is "<cite>depth</cite>".
0238: *
0239: * @see #ALTITUDE
0240: * @see #ELLIPSOIDAL_HEIGHT
0241: * @see #GEOCENTRIC_RADIUS
0242: * @see #GRAVITY_RELATED_HEIGHT
0243: */
0244: public static final DefaultCoordinateSystemAxis DEPTH = new DefaultCoordinateSystemAxis(
0245: VocabularyKeys.DEPTH, "d", AxisDirection.DOWN, SI.METER);
0246: static {
0247: ALTITUDE.opposite = DEPTH;
0248: DEPTH.opposite = ALTITUDE;
0249: }
0250:
0251: /**
0252: * Default axis info for radius in a
0253: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0254: * {@linkplain org.opengis.referencing.cs.SphericalCS spherical CS}.
0255: *
0256: * Increasing ordinates values go {@linkplain AxisDirection#UP up}
0257: * and units are {@linkplain SI#METER metres}.
0258: *
0259: * The ISO 19111 name is "<cite>geocentric radius</cite>" and the abbreviation is lower case
0260: * "<var>r</var>".
0261: *
0262: * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE},
0263: * {@link #GEOCENTRIC_RADIUS} set.
0264: *
0265: * @see #ALTITUDE
0266: * @see #ELLIPSOIDAL_HEIGHT
0267: * @see #GRAVITY_RELATED_HEIGHT
0268: * @see #DEPTH
0269: */
0270: public static final DefaultCoordinateSystemAxis GEOCENTRIC_RADIUS = new DefaultCoordinateSystemAxis(
0271: VocabularyKeys.GEOCENTRIC_RADIUS, "r", AxisDirection.UP,
0272: SI.METER);
0273:
0274: /**
0275: * Default axis info for longitudes in a
0276: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0277: * {@linkplain org.opengis.referencing.crs.SphericalCS spherical CS}.
0278: *
0279: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0280: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0281: *
0282: * The ISO 19111 name is "<cite>spherical longitude</cite>" and the abbreviation is "Ω"
0283: * (omega).
0284: *
0285: * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE},
0286: * {@link #GEOCENTRIC_RADIUS} set.
0287: *
0288: * @see #LONGITUDE
0289: * @see #GEODETIC_LONGITUDE
0290: * @see #SPHERICAL_LATITUDE
0291: */
0292: public static final DefaultCoordinateSystemAxis SPHERICAL_LONGITUDE = new DefaultCoordinateSystemAxis(
0293: VocabularyKeys.SPHERICAL_LONGITUDE, "\u03A9",
0294: AxisDirection.EAST, NonSI.DEGREE_ANGLE);
0295:
0296: /**
0297: * Default axis info for latitudes in a
0298: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0299: * {@linkplain org.opengis.referencing.cs.SphericalCS spherical CS}.
0300: *
0301: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0302: * and units are {@linkplain NonSI#DEGREE_ANGLE decimal degrees}.
0303: *
0304: * The ISO 19111 name is "<cite>spherical latitude</cite>" and the abbreviation is "Θ"
0305: * (theta).
0306: *
0307: * This axis is usually part of a {@link #SPHERICAL_LONGITUDE}, {@link #SPHERICAL_LATITUDE},
0308: * {@link #GEOCENTRIC_RADIUS} set.
0309: *
0310: * @see #LATITUDE
0311: * @see #GEODETIC_LATITUDE
0312: * @see #SPHERICAL_LONGITUDE
0313: */
0314: public static final DefaultCoordinateSystemAxis SPHERICAL_LATITUDE = new DefaultCoordinateSystemAxis(
0315: VocabularyKeys.SPHERICAL_LATITUDE, "\u03B8",
0316: AxisDirection.NORTH, NonSI.DEGREE_ANGLE);
0317:
0318: /**
0319: * Default axis info for <var>x</var> values in a
0320: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0321: *
0322: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0323: * and units are {@linkplain SI#METER metres}.
0324: *
0325: * The abbreviation is lower case "<var>x</var>".
0326: *
0327: * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set.
0328: *
0329: * @see #EASTING
0330: * @see #WESTING
0331: * @see #GEOCENTRIC_X
0332: * @see #DISPLAY_X
0333: * @see #COLUMN
0334: */
0335: public static final DefaultCoordinateSystemAxis X = new DefaultCoordinateSystemAxis(
0336: -1, "x", AxisDirection.EAST, SI.METER);
0337:
0338: /**
0339: * Default axis info for <var>y</var> values in a
0340: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0341: *
0342: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0343: * and units are {@linkplain SI#METER metres}.
0344: *
0345: * The abbreviation is lower case "<var>y</var>".
0346: *
0347: * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set.
0348: *
0349: * @see #NORTHING
0350: * @see #SOUTHING
0351: * @see #GEOCENTRIC_Y
0352: * @see #DISPLAY_Y
0353: * @see #ROW
0354: */
0355: public static final DefaultCoordinateSystemAxis Y = new DefaultCoordinateSystemAxis(
0356: -1, "y", AxisDirection.NORTH, SI.METER);
0357:
0358: /**
0359: * Default axis info for <var>z</var> values in a
0360: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0361: *
0362: * Increasing ordinates values go {@linkplain AxisDirection#UP up}
0363: * and units are {@linkplain SI#METER metres}.
0364: *
0365: * The abbreviation is lower case "<var>z</var>".
0366: *
0367: * This axis is usually part of a {@link #X}, {@link #Y}, {@link #Z} set.
0368: */
0369: public static final DefaultCoordinateSystemAxis Z = new DefaultCoordinateSystemAxis(
0370: -1, "z", AxisDirection.UP, SI.METER);
0371:
0372: /**
0373: * Default axis info for <var>x</var> values in a
0374: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0375: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0376: *
0377: * Increasing ordinates values go toward prime meridian
0378: * and units are {@linkplain SI#METER metres}.
0379: *
0380: * The ISO 19111 name is "<cite>geocentric X</cite>" and the abbreviation is upper case
0381: * "<var>X</var>".
0382: *
0383: * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y},
0384: * {@link #GEOCENTRIC_Z} set.
0385: */
0386: public static final DefaultCoordinateSystemAxis GEOCENTRIC_X = new DefaultCoordinateSystemAxis(
0387: VocabularyKeys.GEOCENTRIC_X, "X", AxisDirection.OTHER,
0388: SI.METER);
0389:
0390: /**
0391: * Default axis info for <var>y</var> values in a
0392: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0393: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0394: *
0395: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0396: * and units are {@linkplain SI#METER metres}.
0397: *
0398: * The ISO 19111 name is "<cite>geocentric Y</cite>" and the abbreviation is upper case
0399: * "<var>Y</var>".
0400: *
0401: * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y},
0402: * {@link #GEOCENTRIC_Z} set.
0403: */
0404: public static final DefaultCoordinateSystemAxis GEOCENTRIC_Y = new DefaultCoordinateSystemAxis(
0405: VocabularyKeys.GEOCENTRIC_Y, "Y", AxisDirection.EAST,
0406: SI.METER);
0407:
0408: /**
0409: * Default axis info for <var>z</var> values in a
0410: * {@linkplain org.opengis.referencing.crs.GeocentricCRS geocentric CRS} using
0411: * {@linkplain org.opengis.referencing.cs.CartesianCS cartesian CS}.
0412: *
0413: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0414: * and units are {@linkplain SI#METER metres}.
0415: *
0416: * The ISO 19111 name is "<cite>geocentric Z</cite>" and the abbreviation is upper case
0417: * "<var>Z</var>".
0418: *
0419: * This axis is usually part of a {@link #GEOCENTRIC_X}, {@link #GEOCENTRIC_Y},
0420: * {@link #GEOCENTRIC_Z} set.
0421: */
0422: public static final DefaultCoordinateSystemAxis GEOCENTRIC_Z = new DefaultCoordinateSystemAxis(
0423: VocabularyKeys.GEOCENTRIC_Z, "Z", AxisDirection.NORTH,
0424: SI.METER);
0425:
0426: /**
0427: * Default axis info for Easting values in a
0428: * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}.
0429: *
0430: * Increasing ordinates values go {@linkplain AxisDirection#EAST East}
0431: * and units are {@linkplain SI#METER metres}.
0432: *
0433: * The ISO 19111 name is "<cite>easting</cite>" and the abbreviation is upper case
0434: * "<var>E</var>".
0435: *
0436: * This axis is usually part of a {@link #EASTING}, {@link #NORTHING} set.
0437: *
0438: * @see #X
0439: * @see #EASTING
0440: * @see #WESTING
0441: */
0442: public static final DefaultCoordinateSystemAxis EASTING = new DefaultCoordinateSystemAxis(
0443: VocabularyKeys.EASTING, "E", AxisDirection.EAST, SI.METER);
0444:
0445: /**
0446: * Default axis info for Westing values in a
0447: * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}.
0448: *
0449: * Increasing ordinates values go {@linkplain AxisDirection#WEST West}
0450: * and units are {@linkplain SI#METER metres}.
0451: *
0452: * The ISO 19111 name is "<cite>westing</cite>" and the abbreviation is upper case
0453: * "<var>W</var>".
0454: *
0455: * @see #X
0456: * @see #EASTING
0457: * @see #WESTING
0458: */
0459: public static final DefaultCoordinateSystemAxis WESTING = new DefaultCoordinateSystemAxis(
0460: VocabularyKeys.WESTING, "W", AxisDirection.WEST, SI.METER);
0461: static {
0462: EASTING.opposite = WESTING;
0463: WESTING.opposite = EASTING;
0464: }
0465:
0466: /**
0467: * Default axis info for Northing values in a
0468: * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}.
0469: *
0470: * Increasing ordinates values go {@linkplain AxisDirection#NORTH North}
0471: * and units are {@linkplain SI#METER metres}.
0472: *
0473: * The ISO 19111 name is "<cite>northing</cite>" and the abbreviation is upper case
0474: * "<var>N</var>".
0475: *
0476: * This axis is usually part of a {@link #EASTING}, {@link #NORTHING} set.
0477: *
0478: * @see #Y
0479: * @see #NORTHING
0480: * @see #SOUTHING
0481: */
0482: public static final DefaultCoordinateSystemAxis NORTHING = new DefaultCoordinateSystemAxis(
0483: VocabularyKeys.NORTHING, "N", AxisDirection.NORTH, SI.METER);
0484:
0485: /**
0486: * Default axis info for Southing values in a
0487: * {@linkplain org.opengis.referencing.crs.ProjectedCRS projected CRS}.
0488: *
0489: * Increasing ordinates values go {@linkplain AxisDirection#SOUTH South}
0490: * and units are {@linkplain SI#METER metres}.
0491: *
0492: * The ISO 19111 name is "<cite>southing</cite>" and the abbreviation is upper case
0493: * "<var>S</var>".
0494: *
0495: * @see #Y
0496: * @see #NORTHING
0497: * @see #SOUTHING
0498: */
0499: public static final DefaultCoordinateSystemAxis SOUTHING = new DefaultCoordinateSystemAxis(
0500: VocabularyKeys.SOUTHING, "S", AxisDirection.SOUTH, SI.METER);
0501: static {
0502: NORTHING.opposite = SOUTHING;
0503: SOUTHING.opposite = NORTHING;
0504: }
0505:
0506: /**
0507: * A default axis for time values in a {@linkplain org.opengis.referencing.cs.TimeCS time CS}.
0508: *
0509: * Increasing time go toward {@linkplain AxisDirection#FUTURE future}
0510: * and units are {@linkplain NonSI#DAY days}.
0511: *
0512: * The abbreviation is lower case "<var>t</var>".
0513: */
0514: public static final DefaultCoordinateSystemAxis TIME = new DefaultCoordinateSystemAxis(
0515: VocabularyKeys.TIME, "t", AxisDirection.FUTURE, NonSI.DAY);
0516:
0517: /**
0518: * A default axis for column indices in a {@linkplain org.opengis.coverage.grid.GridCoverage
0519: * grid coverage}. Increasing values go toward {@linkplain AxisDirection#COLUMN_POSITIVE
0520: * positive column number}.
0521: *
0522: * The abbreviation is lower case "<var>i</var>".
0523: */
0524: public static final DefaultCoordinateSystemAxis COLUMN = new DefaultCoordinateSystemAxis(
0525: VocabularyKeys.COLUMN, "i", AxisDirection.COLUMN_POSITIVE,
0526: Unit.ONE);
0527:
0528: /**
0529: * A default axis for row indices in a {@linkplain org.opengis.coverage.grid.GridCoverage grid
0530: * coverage}. Increasing values go toward {@linkplain AxisDirection#ROW_POSITIVE positive row
0531: * number}.
0532: *
0533: * The abbreviation is lower case "<var>j</var>".
0534: */
0535: public static final DefaultCoordinateSystemAxis ROW = new DefaultCoordinateSystemAxis(
0536: VocabularyKeys.ROW, "j", AxisDirection.ROW_POSITIVE,
0537: Unit.ONE);
0538:
0539: /**
0540: * A default axis for <var>x</var> values in a display device. Increasing values go toward
0541: * {@linkplain AxisDirection#DISPLAY_RIGHT display right}.
0542: *
0543: * The abbreviation is lower case "<var>x</var>".
0544: *
0545: * @since 2.2
0546: */
0547: public static final DefaultCoordinateSystemAxis DISPLAY_X = new DefaultCoordinateSystemAxis(
0548: -1, "x", AxisDirection.DISPLAY_RIGHT, Unit.ONE);
0549:
0550: /**
0551: * A default axis for <var>y</var> values in a display device. Increasing values go toward
0552: * {@linkplain AxisDirection#DISPLAY_DOWN display down}.
0553: *
0554: * The abbreviation is lower case "<var>y</var>".
0555: *
0556: * @since 2.2
0557: */
0558: public static final DefaultCoordinateSystemAxis DISPLAY_Y = new DefaultCoordinateSystemAxis(
0559: -1, "y", AxisDirection.DISPLAY_DOWN, Unit.ONE);
0560:
0561: /**
0562: * Some names to be treated as equivalent. This is needed because axis names are the primary
0563: * way to distinguish between {@link CoordinateSystemAxis} instances. Those names are strictly
0564: * defined by ISO 19111 as "Geodetic latitude" and "Geodetic longitude" among others, but the
0565: * legacy WKT specifications from OGC 01-009 defined the names as "Lon" and "Lat" for the same
0566: * axis.
0567: * <p>
0568: * Keys in this map are names <strong>in lower cases</strong>. Values are the axis that the
0569: * name is for. The actual axis instance doesn't matter (the algorithm using this map should
0570: * work for any axis instance); it is just a way to differentiate latitude and longitude.
0571: */
0572: private static final Map/*<String,CoordinateSystemAxis>*/ALIASES = new HashMap(
0573: 8);
0574: static {
0575: ALIASES.put("lat", GEODETIC_LATITUDE);
0576: ALIASES.put("latitude", GEODETIC_LATITUDE);
0577: ALIASES.put("geodetic latitude", GEODETIC_LATITUDE);
0578: ALIASES.put("lon", GEODETIC_LONGITUDE);
0579: ALIASES.put("longitude", GEODETIC_LONGITUDE);
0580: ALIASES.put("geodetic longitude", GEODETIC_LONGITUDE);
0581: /*
0582: * "x" and "y" are sometime used in WKT for meaning "Easting" and "Northing".
0583: * We could be tempted to add them as alias in this map, but experience shows
0584: * that such alias have a lot of indesirable side effet. "x" and "y" are used
0585: * for too many things ("Easting", "Westing", "Geocentric X", "Display right",
0586: * "Display left", etc.) and declaring them as alias introduces confusion in
0587: * AbstractCS constructor (during the check of axis directions), in
0588: * PredefinedCS.standard(CoordinateSystem), etc.
0589: */
0590: }
0591:
0592: /**
0593: * Special cases for "x" and "y" names. "x" is considered equivalent to "Easting"
0594: * or "Westing", but the converse is not true. Note: by avoiding to put "x" in the
0595: * {@link #ALIASES} map, we avoid undesirable side effects like considering "Easting"
0596: * as equivalent to "Westing".
0597: *
0598: * @param xy The name which may be "x" or "y".
0599: * @param name The second name to compare with.
0600: * @return {@code true} if the second name is equivalent to "x" or "y" (depending on
0601: * the {@code xy} value), or {@code false} otherwise.
0602: */
0603: private static boolean nameMatchesXY(String xy, final String name) {
0604: xy = xy.trim();
0605: if (xy.length() == 0) {
0606: final DefaultCoordinateSystemAxis axis;
0607: switch (Character.toLowerCase(xy.charAt(0))) {
0608: case 'x':
0609: axis = EASTING;
0610: break;
0611: case 'y':
0612: axis = NORTHING;
0613: break;
0614: default:
0615: return false;
0616: }
0617: return axis.nameMatches(name)
0618: || axis.getOpposite().nameMatches(name);
0619: }
0620: return false;
0621: }
0622:
0623: /**
0624: * The abbreviation used for this coordinate system axes. This abbreviation is also
0625: * used to identify the ordinates in coordinate tuple. Examples are "<var>X</var>"
0626: * and "<var>Y</var>".
0627: */
0628: private final String abbreviation;
0629:
0630: /**
0631: * Direction of this coordinate system axis. In the case of Cartesian projected
0632: * coordinates, this is the direction of this coordinate system axis locally.
0633: */
0634: private final AxisDirection direction;
0635:
0636: /**
0637: * The unit of measure used for this coordinate system axis.
0638: */
0639: private final Unit unit;
0640:
0641: /**
0642: * Minimal and maximal value for this axis.
0643: */
0644: private final double minimum, maximum;
0645:
0646: /**
0647: * The range meaning for this axis.
0648: */
0649: private final RangeMeaning rangeMeaning;
0650:
0651: /**
0652: * The axis with opposite direction, or {@code null} if unknow.
0653: * Not serialized because only used for the predefined constants.
0654: */
0655: private transient DefaultCoordinateSystemAxis opposite;
0656:
0657: /**
0658: * Constructs a new coordinate system axis with the same values than the specified one.
0659: * This copy constructor provides a way to wrap an arbitrary implementation into a
0660: * Geotools one or a user-defined one (as a subclass), usually in order to leverage
0661: * some implementation-specific API. This constructor performs a shallow copy,
0662: * i.e. the properties are not cloned.
0663: *
0664: * @since 2.2
0665: */
0666: public DefaultCoordinateSystemAxis(final CoordinateSystemAxis axis) {
0667: super (axis);
0668: abbreviation = axis.getAbbreviation();
0669: direction = axis.getDirection();
0670: unit = axis.getUnit();
0671: minimum = axis.getMinimumValue();
0672: maximum = axis.getMaximumValue();
0673: rangeMeaning = axis.getRangeMeaning();
0674: }
0675:
0676: /**
0677: * Constructs an axis from a set of properties. The properties map is given unchanged to the
0678: * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
0679: *
0680: * @param properties Set of properties. Should contains at least <code>"name"</code>.
0681: * @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
0682: * coordinate system axes.
0683: * @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
0684: * @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
0685: * system axis.
0686: * @param minimum The minimum value normally allowed for this axis.
0687: * @param maximum The maximum value normally allowed for this axis.
0688: * @param rangeMeaning The meaning of axis value range specified by the minimum and
0689: * maximum values.
0690: *
0691: * @since 2.3
0692: */
0693: public DefaultCoordinateSystemAxis(final Map properties,
0694: final String abbreviation, final AxisDirection direction,
0695: final Unit unit, final double minimum,
0696: final double maximum, final RangeMeaning rangeMeaning) {
0697: super (properties);
0698: this .abbreviation = abbreviation;
0699: this .direction = direction;
0700: this .unit = unit;
0701: this .minimum = minimum;
0702: this .maximum = maximum;
0703: this .rangeMeaning = rangeMeaning;
0704: ensureNonNull("abbreviation", abbreviation);
0705: ensureNonNull("direction", direction);
0706: ensureNonNull("unit", unit);
0707: ensureNonNull("rangeMeaning", rangeMeaning);
0708: if (!(minimum < maximum)) { // Use '!' for catching NaN
0709: throw new IllegalArgumentException(Errors.format(
0710: ErrorKeys.BAD_RANGE_$2, new Double(minimum),
0711: new Double(maximum)));
0712: }
0713: }
0714:
0715: /**
0716: * Constructs an unbounded axis from a set of properties. The properties map is given
0717: * unchanged to the {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map)
0718: * super-class constructor}. The {@linkplain #getMinimumValue minimum} and
0719: * {@linkplain #getMaximumValue maximum} values are inferred from the axis unit and
0720: * direction.
0721: *
0722: * @param properties Set of properties. Should contains at least <code>"name"</code>.
0723: * @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
0724: * coordinate system axes.
0725: * @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
0726: * @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
0727: * system axis.
0728: */
0729: public DefaultCoordinateSystemAxis(final Map properties,
0730: final String abbreviation, final AxisDirection direction,
0731: final Unit unit) {
0732: // NOTE: we would invoke this(properties, abbreviation, ...) instead if Sun fixed
0733: // RFE #4093999 ("Relax constraint on placement of this()/super() call in constructors").
0734: super (properties);
0735: this .abbreviation = abbreviation;
0736: this .direction = direction;
0737: this .unit = unit;
0738: ensureNonNull("abbreviation", abbreviation);
0739: ensureNonNull("direction", direction);
0740: ensureNonNull("unit", unit);
0741: if (unit.isCompatible(NonSI.DEGREE_ANGLE)) {
0742: final Converter fromDegrees = NonSI.DEGREE_ANGLE
0743: .getConverterTo(unit);
0744: final AxisDirection dir = direction.absolute();
0745: if (dir.equals(AxisDirection.NORTH)) {
0746: final double range = Math.abs(fromDegrees.convert(90));
0747: minimum = -range;
0748: maximum = +range;
0749: rangeMeaning = RangeMeaning.EXACT; // 90°N do not wraps to 90°S
0750: return;
0751: }
0752: if (dir.equals(AxisDirection.EAST)) {
0753: final double range = Math.abs(fromDegrees.convert(180));
0754: minimum = -range;
0755: maximum = +range;
0756: rangeMeaning = RangeMeaning.WRAPAROUND; // 180°E wraps to 180°W
0757: return;
0758: }
0759: }
0760: minimum = Double.NEGATIVE_INFINITY;
0761: maximum = Double.POSITIVE_INFINITY;
0762: rangeMeaning = RangeMeaning.EXACT;
0763: }
0764:
0765: /**
0766: * Constructs an axis with the same {@linkplain #getName name} as the abbreviation.
0767: *
0768: * @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
0769: * coordinate system axes.
0770: * @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
0771: * @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
0772: * system axis.
0773: */
0774: public DefaultCoordinateSystemAxis(final String abbreviation,
0775: final AxisDirection direction, final Unit unit) {
0776: this (Collections.singletonMap(NAME_KEY, abbreviation),
0777: abbreviation, direction, unit);
0778: }
0779:
0780: /**
0781: * Constructs an axis with a name as an {@linkplain InternationalString international string}
0782: * and an abbreviation. The {@linkplain #getName name of this identified object} is set to the
0783: * unlocalized version of the {@code name} argument, as given by
0784: * <code>name.{@linkplain InternationalString#toString(Locale) toString}(null)</code>. The
0785: * same {@code name} argument is also stored as an {@linkplain #getAlias alias}, which
0786: * allows fetching localized versions of the name.
0787: *
0788: * @param name The name of this axis. Also stored as an alias for localization purpose.
0789: * @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
0790: * coordinate system axis.
0791: * @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
0792: * @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
0793: * system axis.
0794: */
0795: public DefaultCoordinateSystemAxis(final InternationalString name,
0796: final String abbreviation, final AxisDirection direction,
0797: final Unit unit) {
0798: this (toMap(name), abbreviation, direction, unit);
0799: }
0800:
0801: /**
0802: * Work around for RFE #4093999 in Sun's bug database
0803: * ("Relax constraint on placement of this()/super() call in constructors").
0804: */
0805: private static Map toMap(final InternationalString name) {
0806: final Map properties = new HashMap(4);
0807: if (name != null) {
0808: properties.put(NAME_KEY, name.toString(Locale.US));
0809: properties.put(ALIAS_KEY, NameFactory
0810: .create(new InternationalString[] { name }));
0811: }
0812: return properties;
0813: }
0814:
0815: /**
0816: * Constructs an axis with a name and an abbreviation as a resource bundle key.
0817: * To be used for construction of pre-defined constants only.
0818: *
0819: * @param name The resource bundle key for the name.
0820: * @param abbreviation The {@linkplain #getAbbreviation abbreviation} used for this
0821: * coordinate system axes.
0822: * @param direction The {@linkplain #getDirection direction} of this coordinate system axis.
0823: * @param unit The {@linkplain #getUnit unit of measure} used for this coordinate
0824: * system axis.
0825: */
0826: private DefaultCoordinateSystemAxis(final int name,
0827: final String abbreviation, final AxisDirection direction,
0828: final Unit unit) {
0829: this (name >= 0 ? Vocabulary.formatInternational(name)
0830: : new SimpleInternationalString(abbreviation),
0831: abbreviation, direction, unit);
0832: PREDEFINED[PREDEFINED_COUNT++] = this ;
0833: }
0834:
0835: /**
0836: * Returns one of the predefined axis for the given name and direction, or {@code null} if
0837: * none. This method searchs only in predefined constants like {@link #GEODETIC_LATITUDE},
0838: * not in any custom axis instantiated by a public constructor. The name of those constants
0839: * match ISO 19111 names or some names commonly found in <cite>Well Known Text</cite> (WKT)
0840: * formats.
0841: * <p>
0842: * This method first checks if the specified name matches the {@linkplain #getAbbreviation
0843: * abbreviation} of a predefined axis. The comparaison is case-sensitive (for example the
0844: * {@link #GEOCENTRIC_X} abbreviation is uppercase {@code "X"}, while the abbreviation for
0845: * the generic {@link #X} axis is lowercase {@code "x"}).
0846: * <p>
0847: * If the specified name doesn't match any abbreviation, then this method compares the name
0848: * against predefined axis {@linkplain #getName name} in a case-insensitive manner. Examples
0849: * of valid names are "<cite>Geodetic latitude</cite>" and "<cite>Northing</cite>".
0850: * <p>
0851: * The direction argument is optional and can be used in order to resolve ambiguity like
0852: * {@link #X} and {@link #DISPLAY_X} axis. If this argument is {@code null}, then the first
0853: * axis with a matching name or abbreviation will be returned.
0854: *
0855: * @param name The axis name or abbreviation.
0856: * @param direction An optional direction, or {@code null}.
0857: * @return One of the constants declared in this class, or {@code null}.
0858: *
0859: * @since 2.4
0860: */
0861: public static DefaultCoordinateSystemAxis getPredefined(
0862: String name, AxisDirection direction) {
0863: ensureNonNull("name", name);
0864: name = name.trim();
0865: DefaultCoordinateSystemAxis found = null;
0866: for (int i = 0; i < PREDEFINED_COUNT; i++) {
0867: final DefaultCoordinateSystemAxis candidate = PREDEFINED[i];
0868: if (direction != null
0869: && !direction.equals(candidate.getDirection())) {
0870: continue;
0871: }
0872: // Reminder: case matter for abbreviation, so 'equalsIgnoreCase' is not allowed.
0873: if (candidate.abbreviation.equals(name)) {
0874: return candidate;
0875: }
0876: if (found == null && candidate.nameMatches(name)) {
0877: /*
0878: * We need to perform a special check for Geodetic longitude and latitude.
0879: * Because of the ALIAS map, the "Geodetic latitude" and "Latitude" names
0880: * are considered equivalent, while they are two distinct predefined axis
0881: * constants in Geotools. Because Geodetic longitude & latitude constants
0882: * are declared first, they have precedence. So we prevent the selection
0883: * of GEODETIC_LATITUDE if the user is likely to ask for LATITUDE.
0884: */
0885: if (candidate == GEODETIC_LONGITUDE
0886: || candidate == GEODETIC_LATITUDE) {
0887: if (!name.toLowerCase().startsWith("geodetic")) {
0888: continue;
0889: }
0890: }
0891: found = candidate;
0892: }
0893: }
0894: return found;
0895: }
0896:
0897: /**
0898: * Returns a predefined axis similar to the specified one except for units.
0899: * Returns {@code null} if no predefined axis match.
0900: */
0901: static DefaultCoordinateSystemAxis getPredefined(
0902: final CoordinateSystemAxis axis) {
0903: return getPredefined(axis.getName().getCode(), axis
0904: .getDirection());
0905: }
0906:
0907: /**
0908: * Returns the list of all predefined constants.
0909: * Currently used for testing purpose only.
0910: */
0911: static DefaultCoordinateSystemAxis[] values() {
0912: return (DefaultCoordinateSystemAxis[]) PREDEFINED.clone();
0913: }
0914:
0915: /**
0916: * Returns an axis direction constants from its name.
0917: *
0918: * @param direction The direction name (e.g. "north", "east", etc.).
0919: * @return The axis direction for the given name.
0920: * @throws NoSuchElementException if the given name is not a know axis direction.
0921: */
0922: public static AxisDirection getDirection(String direction)
0923: throws NoSuchElementException {
0924: ensureNonNull("direction", direction);
0925: direction = direction.trim();
0926: AxisDirection candidate = DirectionAlongMeridian
0927: .findDirection(direction);
0928: if (candidate != null) {
0929: return candidate;
0930: }
0931: /*
0932: * Some EPSG direction names are of the form "South along 180 deg". We check that the
0933: * direction before "along" is valid and create a new axis direction if it is. We can
0934: * not just replace "South along 180 deg" by "South" because the same CRS may use two
0935: * of those directions. For example EPSG:32661 has the following axis direction:
0936: *
0937: * South along 180 deg
0938: * South along 90 deg East
0939: */
0940: final DirectionAlongMeridian meridian = DirectionAlongMeridian
0941: .parse(direction);
0942: if (meridian != null) {
0943: candidate = meridian.getDirection();
0944: assert candidate == DirectionAlongMeridian
0945: .findDirection(meridian.toString());
0946: return candidate;
0947: }
0948: throw new NoSuchElementException(Errors.format(
0949: ErrorKeys.UNKNOW_AXIS_DIRECTION_$1, direction));
0950: }
0951:
0952: /**
0953: * Direction of this coordinate system axis. In the case of Cartesian projected
0954: * coordinates, this is the direction of this coordinate system axis locally.
0955: * Examples:
0956: * {@linkplain AxisDirection#NORTH north} or {@linkplain AxisDirection#SOUTH south},
0957: * {@linkplain AxisDirection#EAST east} or {@linkplain AxisDirection#WEST west},
0958: * {@linkplain AxisDirection#UP up} or {@linkplain AxisDirection#DOWN down}.
0959: *
0960: * <P>Within any set of coordinate system axes, only one of each pair of terms
0961: * can be used. For earth-fixed coordinate reference systems, this direction is often
0962: * approximate and intended to provide a human interpretable meaning to the axis. When a
0963: * geodetic datum is used, the precise directions of the axes may therefore vary slightly
0964: * from this approximate direction.</P>
0965: *
0966: * <P>Note that an {@link org.geotools.referencing.crs.DefaultEngineeringCRS} often requires
0967: * specific descriptions of the directions of its coordinate system axes.</P>
0968: */
0969: public AxisDirection getDirection() {
0970: return direction;
0971: }
0972:
0973: /**
0974: * The abbreviation used for this coordinate system axes. This abbreviation is also
0975: * used to identify the ordinates in coordinate tuple. Examples are "<var>X</var>"
0976: * and "<var>Y</var>".
0977: *
0978: * @return The coordinate system axis abbreviation.
0979: */
0980: public String getAbbreviation() {
0981: return abbreviation;
0982: }
0983:
0984: /**
0985: * The unit of measure used for this coordinate system axis. The value of this
0986: * coordinate in a coordinate tuple shall be recorded using this unit of measure,
0987: * whenever those coordinates use a coordinate reference system that uses a
0988: * coordinate system that uses this axis.
0989: */
0990: public Unit getUnit() {
0991: return unit;
0992: }
0993:
0994: /**
0995: * Returns the minimum value normally allowed for this axis, in the
0996: * {@linkplain #getUnit unit of measure for the axis}. If there is no minimum value, then
0997: * this method returns {@linkplain Double#NEGATIVE_INFINITY negative infinity}.
0998: *
0999: * @since 2.3
1000: */
1001: public double getMinimumValue() {
1002: return minimum;
1003: }
1004:
1005: /**
1006: * Returns the maximum value normally allowed for this axis, in the
1007: * {@linkplain #getUnit unit of measure for the axis}. If there is no maximum value, then
1008: * this method returns {@linkplain Double#POSITIVE_INFINITY negative infinity}.
1009: *
1010: * @since 2.3
1011: */
1012: public double getMaximumValue() {
1013: return maximum;
1014: }
1015:
1016: /**
1017: * Returns the meaning of axis value range specified by the {@linkplain #getMinimumValue
1018: * minimum} and {@linkplain #getMaximumValue maximum} values. This element shall be omitted
1019: * when both minimum and maximum values are omitted. It may be included when minimum and/or
1020: * maximum values are included. If this element is omitted when minimum or maximum values are
1021: * included, the meaning is unspecified.
1022: *
1023: * @since 2.3
1024: */
1025: public RangeMeaning getRangeMeaning() {
1026: return rangeMeaning;
1027: }
1028:
1029: /**
1030: * Returns an axis with the opposite direction of this one, or {@code null} if unknown.
1031: * This method is not public because only a few predefined constants have this information.
1032: */
1033: final DefaultCoordinateSystemAxis getOpposite() {
1034: return opposite;
1035: }
1036:
1037: /**
1038: * Returns {@code true} if the specified direction is a compass direction.
1039: * Compass directions include "<cite>North</cite>", "<cite>North-North-East</cite>",
1040: * "<cite>North-East</cite>", <cite>etc.</cite>
1041: *
1042: * @since 2.4
1043: */
1044: public static boolean isCompassDirection(
1045: final AxisDirection direction) {
1046: ensureNonNull("direction", direction);
1047: final int n = direction.ordinal()
1048: - AxisDirection.NORTH.ordinal();
1049: return n >= 0 && n < COMPASS_DIRECTION_COUNT;
1050: }
1051:
1052: /*
1053: * Returns {@code true} if the specified direction is a direction along a meridian.
1054: * Those directions are used in coordinate systems for polar area. Examples:
1055: * "<cite>North along 90 deg East</cite>", "<cite>North along 0 deg</cite>".
1056: *
1057: * We do not provide such method yet. If we want this functionality, maybe we should
1058: * consider making DirectionAlongMeridian a public class extending AxisDirection code
1059: * list instead.
1060: */
1061: // public static boolean isDirectionAlongMeridian(final AxisDirection direction);
1062: /**
1063: * Returns the arithmetic (counterclockwise) angle from the first direction to the second
1064: * direction, in decimal <strong>degrees</strong>. This method returns a value between
1065: * -180° and +180°, or {@link Double#NaN NaN} if no angle can be computed.
1066: * <p>
1067: * A positive angle denotes a right-handed system, while a negative angle denotes
1068: * a left-handed system. Example:
1069: * <p>
1070: * <ul>
1071: * <li>The angle from {@linkplain AxisDirection#EAST EAST} to
1072: * {@linkplain AxisDirection#NORTH NORTH} is 90°</li>
1073: * <li>The angle from {@linkplain AxisDirection#SOUTH SOUTH} to
1074: * {@linkplain AxisDirection#WEST WEST} is -90°</li>
1075: * <li>The angle from "<cite>North along 90 deg East</cite>" to
1076: * "<cite>North along 0 deg</cite>" is 90°.</li>
1077: * </ul>
1078: *
1079: * @since 2.4
1080: */
1081: public static double getAngle(final AxisDirection source,
1082: final AxisDirection target) {
1083: ensureNonNull("source", source);
1084: ensureNonNull("target", target);
1085: // Tests for NORTH, SOUTH, EAST, EAST-NORTH-EAST, etc. directions.
1086: final int compass = getCompassAngle(source, target);
1087: if (compass != Integer.MIN_VALUE) {
1088: return compass * (360.0 / COMPASS_DIRECTION_COUNT);
1089: }
1090: // Tests for "South along 90 deg East", etc. directions.
1091: final DirectionAlongMeridian src = DirectionAlongMeridian
1092: .parse(source);
1093: if (src != null) {
1094: final DirectionAlongMeridian tgt = DirectionAlongMeridian
1095: .parse(target);
1096: if (tgt != null) {
1097: return src.getAngle(tgt);
1098: }
1099: }
1100: return Double.NaN;
1101: }
1102:
1103: /**
1104: * Tests for angle on compass only (do not tests angle between direction along meridians).
1105: * Returns {@link Integer#MIN_VALUE} if the angle can't be computed.
1106: */
1107: static int getCompassAngle(final AxisDirection source,
1108: final AxisDirection target) {
1109: final int base = AxisDirection.NORTH.ordinal();
1110: final int src = source.ordinal() - base;
1111: if (src >= 0 && src < COMPASS_DIRECTION_COUNT) {
1112: int tgt = target.ordinal() - base;
1113: if (tgt >= 0 && tgt < COMPASS_DIRECTION_COUNT) {
1114: tgt = src - tgt;
1115: if (tgt < -COMPASS_DIRECTION_COUNT / 2) {
1116: tgt += COMPASS_DIRECTION_COUNT;
1117: } else if (tgt > COMPASS_DIRECTION_COUNT / 2) {
1118: tgt -= COMPASS_DIRECTION_COUNT;
1119: }
1120: return tgt;
1121: }
1122: }
1123: return Integer.MIN_VALUE;
1124: }
1125:
1126: /**
1127: * Returns {@code true} if the specified directions are perpendicular.
1128: *
1129: * @since 2.4
1130: */
1131: public static boolean perpendicular(final AxisDirection first,
1132: final AxisDirection second) {
1133: return Math.abs(Math.abs(getAngle(first, second)) - 90) <= DirectionAlongMeridian.EPS;
1134: }
1135:
1136: /**
1137: * Returns a new axis with the same properties than current axis except for the units.
1138: *
1139: * @param newUnit The unit for the new axis.
1140: * @return An axis using the specified unit.
1141: * @throws IllegalArgumentException If the specified unit is incompatible with the expected one.
1142: */
1143: final DefaultCoordinateSystemAxis usingUnit(final Unit newUnit)
1144: throws IllegalArgumentException {
1145: if (unit.equals(newUnit)) {
1146: return this ;
1147: }
1148: if (unit.isCompatible(newUnit)) {
1149: return new DefaultCoordinateSystemAxis(getProperties(this ,
1150: null), abbreviation, direction, newUnit, minimum,
1151: maximum, rangeMeaning);
1152: }
1153: throw new IllegalArgumentException(Errors.format(
1154: ErrorKeys.INCOMPATIBLE_UNIT_$1, newUnit));
1155: }
1156:
1157: /**
1158: * Returns {@code true} if either the {@linkplain #getName() primary name} or at least
1159: * one {@linkplain #getAlias alias} matches the specified string. This method performs
1160: * all the searh done by the {@linkplain AbstractIdentifiedObject#nameMatches(String)
1161: * super-class}, with the addition of special processing for latitudes and longitudes:
1162: * <p>
1163: * <ul>
1164: * <li>{@code "Lat"}, {@code "Latitude"} and {@code "Geodetic latitude"} are considered
1165: * equivalent.</li>
1166: * <li>{@code "Lon"}, {@code "Longitude"} and {@code "Geodetic longitude"} are considered
1167: * equivalent.</li>
1168: * </ul>
1169: * <p>
1170: * The above special cases are needed in order to workaround a conflict in specifications:
1171: * ISO 19111 explicitly state that the latitude and longitude axis names shall be
1172: * "Geodetic latitude" and "Geodetic longitude", will legacy OGC 01-009 (where WKT is defined)
1173: * said that the default values shall be "Lat" and "Lon".
1174: *
1175: * @param name The name to compare.
1176: * @return {@code true} if the primary name of at least one alias
1177: * matches the specified {@code name}.
1178: */
1179: //@Override
1180: public boolean nameMatches(final String name) {
1181: if (super .nameMatches(name)) {
1182: return true;
1183: }
1184: /*
1185: * The standard comparaisons didn't worked. Check for the aliases. Note: we don't
1186: * test for 'nameMatchesXY(...)' here because the "x" and "y" axis names are too
1187: * generic. We test them only in the 'equals' method, which has the extra-safety
1188: * of units comparaison (so less risk to treat incompatible axis as equivalent).
1189: *
1190: * TODO: replace Object by CoordinateSystemAxis when we will be allowed
1191: * to compile for J2SE 1.5.
1192: */
1193: final Object type = ALIASES.get(name.trim().toLowerCase());
1194: return (type != null)
1195: && (type == ALIASES.get(getName().getCode().trim()
1196: .toLowerCase()));
1197: }
1198:
1199: /**
1200: * Compares the specified object with this axis for equality.
1201: *
1202: * @param object The object to compare to {@code this}.
1203: * @param compareMetadata {@code true} for performing a strict comparaison, or
1204: * {@code false} for comparing only properties relevant to transformations.
1205: * @return {@code true} if both objects are equal.
1206: */
1207: public boolean equals(final AbstractIdentifiedObject object,
1208: final boolean compareMetadata) {
1209: if (object == this ) {
1210: return true; // Slight optimization.
1211: }
1212: if (super .equals(object, compareMetadata)) {
1213: return equals((DefaultCoordinateSystemAxis) object,
1214: compareMetadata, true);
1215: }
1216: return false;
1217: }
1218:
1219: /**
1220: * Compares the specified object with this axis for equality, with optional comparaison
1221: * of units. Units should always be compared (they are not just metadata), except in the
1222: * particular case of {@link AbstractCS#axisColinearWith}, which is used as a first step
1223: * toward units conversions through {@link AbstractCS#swapAndScaleAxis}.
1224: */
1225: final boolean equals(final DefaultCoordinateSystemAxis that,
1226: final boolean compareMetadata, final boolean compareUnit) {
1227: if (compareMetadata) {
1228: if (!Utilities.equals(this .abbreviation, that.abbreviation)
1229: || !Utilities.equals(this .rangeMeaning,
1230: that.rangeMeaning)
1231: || Double.doubleToLongBits(minimum) != Double
1232: .doubleToLongBits(that.minimum)
1233: || Double.doubleToLongBits(maximum) != Double
1234: .doubleToLongBits(that.maximum)) {
1235: return false;
1236: }
1237: } else {
1238: /*
1239: * Checking the abbreviation is not suffisient. For example the polar angle and the
1240: * spherical latitude have the same abbreviation (theta). Geotools extensions like
1241: * "Longitude" (in addition of ISO 19111 "Geodetic longitude") bring more potential
1242: * confusion. Furthermore, not all implementors will use the greek letters (even if
1243: * they are part of ISO 19111). For example most CRS in WKT format use the "Lat"
1244: * abbreviation instead of the greek letter phi. For comparaisons without metadata,
1245: * we ignore the unreliable abbreviation and check the axis name instead. These
1246: * names are constrained by ISO 19111 specification (see class javadoc), so they
1247: * should be reliable enough.
1248: *
1249: * Note: there is no need to execute this block if 'compareMetadata' is true,
1250: * because in this case a stricter check has already been performed by
1251: * the 'equals' method in the superclass.
1252: */
1253: final String thatName = that.getName().getCode();
1254: if (!nameMatches(thatName)) {
1255: // The above test checked for special cases ("Lat" / "Lon" aliases, etc.).
1256: // The next line may not, but is tested anyway in case the user overrided
1257: // the 'that.nameMatches(...)' method.
1258: final String this Name = getName().getCode();
1259: if (!nameMatches(that, this Name)) {
1260: // For the needs of AbstractCS.axisColinearWith(...), we must stop here.
1261: // In addition it may be safer to not test 'nameMatchesXY' when we don't
1262: // have the extra-safety of units comparaison, because "x" and "y" names
1263: // are too generic.
1264: if (!compareUnit) {
1265: return false;
1266: }
1267: // Last chance: check for the special case of "x" and "y" axis names.
1268: if (!nameMatchesXY(thatName, this Name)
1269: && !nameMatchesXY(this Name, thatName)) {
1270: return false;
1271: }
1272: }
1273: }
1274: }
1275: return Utilities.equals(this .direction, that.direction)
1276: && (!compareUnit || Utilities.equals(this .unit,
1277: that.unit));
1278: }
1279:
1280: /**
1281: * Returns a hash value for this axis. This value doesn't need to be the same
1282: * in past or future versions of this class.
1283: */
1284: public int hashCode() {
1285: int code = (int) serialVersionUID;
1286: code = code * 37 + abbreviation.hashCode();
1287: code = code * 37 + direction.hashCode();
1288: code = code * 37 + unit.hashCode();
1289: return code;
1290: }
1291:
1292: /**
1293: * Format the inner part of a
1294: * <A HREF="http://geoapi.sourceforge.net/snapshot/javadoc/org/opengis/referencing/doc-files/WKT.html"><cite>Well
1295: * Known Text</cite> (WKT)</A> element. WKT is returned by the {@link #toString toString} method
1296: * and looks like <code>AXIS["name",NORTH]</code>.
1297: *
1298: * @param formatter The formatter to use.
1299: * @return The WKT element name, which is "AXIS".
1300: */
1301: protected String formatWKT(final Formatter formatter) {
1302: formatter.append(direction);
1303: return "AXIS";
1304: }
1305: }
|