/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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. */ package org.geotools.referencing.factory; import java.util.Arrays; import java.util.Comparator; import javax.measure.unit.SI; import javax.measure.unit.NonSI; import javax.measure.unit.Unit; import org.opengis.referencing.cs.*; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.geotools.factory.Hints; import org.geotools.factory.FactoryRegistryException; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.cs.DefaultCoordinateSystemAxis; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; /** * An authority factory which delegates all the work to an other factory, and reorder the axis in * some pre-determined order. This factory is mostly used by application expecting geographic * coordinates in (longitude, latitude) order, while most geographic CRS * specified in the EPSG database use the opposite axis order. *

* It is better to avoid this class if you can. This class exists primarily for compatibility with * external data or applications that assume (longitude, latitude) axis order * no matter what the EPSG database said, for example Shapefiles. *

* The axis order can be specified at construction time as an array of {@linkplain AxisDirection * axis directions}. If no such array is explicitly specified, then the default order is * {@linkplain AxisDirection#EAST East}, * {@linkplain AxisDirection#EAST_NORTH_EAST East-North-East}, * {@linkplain AxisDirection#NORTH_EAST North-East}, * {@linkplain AxisDirection#NORTH_NORTH_EAST North-North-East}, * {@linkplain AxisDirection#NORTH North}, * {@linkplain AxisDirection#UP Up}, * {@linkplain AxisDirection#GEOCENTRIC_X Geocentric X}, * {@linkplain AxisDirection#GEOCENTRIC_Y Geocentric Y}, * {@linkplain AxisDirection#GEOCENTRIC_Z Geocentric Z}, * {@linkplain AxisDirection#COLUMN_POSITIVE Column}, * {@linkplain AxisDirection#ROW_POSITIVE Row}, * {@linkplain AxisDirection#DISPLAY_RIGHT Display right}, * {@linkplain AxisDirection#DISPLAY_UP Display up} and * {@linkplain AxisDirection#FUTURE Future}. * This means that, for example, axis with East or West direction will be placed before any * axis with North or South direction. Axis directions not specified in the table (for example * {@link AxisDirection#OTHER OTHER}) will be ordered last. This is somewhat equivalent to the * ordering of {@link Double#NaN NaN} values in an array of {@code double}. *

* Notes: *

*

* For some authority factories, an instance of this class can be obtained by passing a * {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint * to the {@linkplain ReferencingFactoryFinder#getCRSAuthorityFactory * FactoryFinder.getCRSAuthorityFactory}(...) method. Whatever this hint is supported * or not is authority dependent. Example: * *

 * Hints                   hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
 * CRSAuthorityFactory   factory = FactoryFinder.getCRSAuthorityFactory("EPSG", hints);
 * CoordinateReferenceSystem crs = factory.createCoordinateReferenceSystem("EPSG:4326");
 * 
* * This class is named ordered axis authority factory instead of something like * longitude first axis order because the axis order can be user-supplied. The * (longitude, latitude) order just appears to be the default one. * * @since 2.2 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER * @see Hints#FORCE_STANDARD_AXIS_UNITS * @tutorial http://docs.codehaus.org/display/GEOTOOLS/The+axis+order+issue */ public class OrderedAxisAuthorityFactory extends TransformedAuthorityFactory implements CSAuthorityFactory, CRSAuthorityFactory, Comparator/**/ { /** * The default order for axis directions. Note that this array needs to contain only the * "{@linkplain AxisDirection#absolute absolute}" directions. * * REMINDER: If this array is modified, don't forget to update the class javadoc above. */ private static final AxisDirection[] DEFAULT_ORDER = { AxisDirection.EAST, AxisDirection.EAST_NORTH_EAST, AxisDirection.NORTH_EAST, AxisDirection.NORTH_NORTH_EAST, AxisDirection.NORTH, AxisDirection.UP, AxisDirection.GEOCENTRIC_X, AxisDirection.GEOCENTRIC_Y, AxisDirection.GEOCENTRIC_Z, AxisDirection.COLUMN_POSITIVE, AxisDirection.ROW_POSITIVE, AxisDirection.DISPLAY_RIGHT, AxisDirection.DISPLAY_UP, AxisDirection.FUTURE }; /** * The rank to be given to each axis direction. The rank is stored at the indice * corresponding to the direction {@linkplain AxisDirection#ordinal ordinal} value. */ private final int[] directionRanks; /** * {@code true} if this authority factory should also force the axis to their standard * direction. For example if {@code true}, then axis with increasing values toward South * will be converted to axis with increasing values toward North. The default value is * {@code false}. * * @see Hints#FORCE_STANDARD_AXIS_DIRECTIONS * @since 2.3 */ protected final boolean forceStandardDirections; /** * {@code true} if this authority factory should also force all angular units to * decimal degrees and linear units to meters. The default value is {@code false}. * * @see Hints#FORCE_STANDARD_AXIS_UNITS * @since 2.3 */ protected final boolean forceStandardUnits; /** * Creates a factory which will reorder the axis of all objects created by the default * authority factories. The factories are fetched using {@link ReferencingFactoryFinder}. This * constructor accepts the following hints: *

*

* * @param authority The authority to wraps (example: {@code "EPSG"}). If {@code null}, * then all authority factories must be explicitly specified in the set of hints. * @param userHints An optional set of hints, or {@code null} if none. * @param axisOrder An array of axis directions that determine the axis order wanted, * or {@code null} for the default axis order. * @throws FactoryRegistryException if at least one factory can not be obtained. * @throws IllegalArgumentException If at least two axis directions are colinear. * * @since 2.3 */ public OrderedAxisAuthorityFactory(final String authority, final Hints userHints, final AxisDirection[] axisOrder) throws FactoryRegistryException, IllegalArgumentException { super(authority, userHints); forceStandardUnits = booleanValue(userHints, Hints.FORCE_STANDARD_AXIS_UNITS); forceStandardDirections = booleanValue(userHints, Hints.FORCE_STANDARD_AXIS_DIRECTIONS); directionRanks = computeDirectionRanks(axisOrder); completeHints(); } /** * Creates a factory which will reorder the axis of all objects created by the supplied * factory. This constructor accepts the following optional hints: *

*

* * @param factory The factory that produces objects using arbitrary axis order. * @param userHints An optional set of hints, or {@code null} if none. * @param axisOrder An array of axis directions that determine the axis order wanted, * or {@code null} for the default axis order. * @throws IllegalArgumentException If at least two axis directions are colinear. * * @since 2.3 */ public OrderedAxisAuthorityFactory(final AbstractAuthorityFactory factory, final Hints userHints, final AxisDirection[] axisOrder) throws IllegalArgumentException { super(factory); forceStandardUnits = booleanValue(userHints, Hints.FORCE_STANDARD_AXIS_UNITS); forceStandardDirections = booleanValue(userHints, Hints.FORCE_STANDARD_AXIS_DIRECTIONS); directionRanks = computeDirectionRanks(axisOrder); completeHints(); } /** * Returns the boolean value for the specified hint. */ private static boolean booleanValue(final Hints userHints, final Hints.Key key) { if (userHints != null) { final Boolean value = (Boolean) userHints.get(key); if (value != null) { return value.booleanValue(); } } return false; } /** * Completes the set of hints according the value currently set in this object. * This method is invoked by constructors only. */ private void completeHints() { hints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean.valueOf(forceStandardUnits)); hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS, Boolean.valueOf(forceStandardDirections)); // The following hint has no effect on this class behaviour, // but tells to the user what this factory do about axis order. if (compare(DefaultCoordinateSystemAxis.EASTING, DefaultCoordinateSystemAxis.NORTHING) < 0) { hints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); } } /** * Computes the rank for every direction in the specified. The rank is stored in an array * at the indice corresponding to the direction {@linkplain AxisDirection#ordinal ordinal} * value. This method is used by constructors for computing the {@link #directionRanks} field. * * @throws IllegalArgumentException If at least two axis directions are colinear. */ private static int[] computeDirectionRanks(AxisDirection[] axisOrder) throws IllegalArgumentException { if (axisOrder == null) { axisOrder = DEFAULT_ORDER; } int length = 0; for (int i=0; i length) { length = ordinal; } } final int[] directionRanks = new int[length]; Arrays.fill(directionRanks, length); for (int i=0; i=0 && c *
    *
  • Any linear units converted to {@linkplain SI#METER meters}
  • *
  • {@linkplain SI#RADIAN Radians} and {@linkplain NonSI#GRADE grades} converted to * {@linkplain NonSI#DEGREE_ANGLE decimal degrees}
  • *
*

* This default substitution table may be expanded in future Geotools versions. * * @since 2.3 */ @Override protected Unit replace(final Unit units) { if (forceStandardUnits) { if (units.isCompatible(SI.METER)) { return SI.METER; } if (units.equals(SI.RADIAN) || units.equals(NonSI.GRADE)) { return NonSI.DEGREE_ANGLE; } } return units; } /** * Replaces the specified direction, if applicable. This method is invoked automatically by the * {@link #replace(CoordinateSystem)} method. The default implementation replaces the direction * only if the {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS FORCE_STANDARD_AXIS_DIRECTIONS} hint * was specified as {@link Boolean#TRUE TRUE} at construction time. In such case, the default * substitution table is as specified in the {@link AxisDirection#absolute} method. * Subclasses may override this method if they want to use a different substitution table. * * @since 2.3 */ @Override protected AxisDirection replace(final AxisDirection direction) { return (forceStandardDirections) ? direction.absolute() : direction; } }