/* * 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 (<var>longitude</var>, <var>latitude</var>) order, while most geographic CRS * specified in the <A HREF="http://www.epsg.org">EPSG database</A> use the opposite axis order. * <p> * It is better to avoid this class if you can. This class exists primarily for compatibility with * external data or applications that assume (<var>longitude</var>, <var>latitude</var>) axis order * no matter what the EPSG database said, for example Shapefiles. * <p> * 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}. * <p> * <strong>Notes:</strong> * <ul> * <li>This class compares only the "{@linkplain AxisDirection#absolute absolute}" axis * directions, so North and South are considered equivalent.</li> * <li>The default direction order may changes in future Geotools version in order * to fit what appears to be the most common usage on the market.</li> * <li>The actual axis ordering is determined by the {@link #compare compare} method * implementation. Subclasses may override this method if the want to provide a more * sophesticated axis ordering.</li> * </ul> * <p> * 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 <code>{@linkplain ReferencingFactoryFinder#getCRSAuthorityFactory * FactoryFinder.getCRSAuthorityFactory}(...)</code> method. Whatever this hint is supported * or not is authority dependent. Example: * * <blockquote><pre> * Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); * CRSAuthorityFactory factory = FactoryFinder.getCRSAuthorityFactory("EPSG", hints); * CoordinateReferenceSystem crs = factory.createCoordinateReferenceSystem("EPSG:4326"); * </pre></blockquote> * * This class is named <cite>ordered axis authority factory</cite> instead of something like * <cite>longitude first axis order</cite> because the axis order can be user-supplied. The * (<var>longitude</var>, <var>latitude</var>) 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/*<CoordinateSystemAxis>*/ { /** * 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: * <p> * <ul> * <li>{@link Hints#FORCE_STANDARD_AXIS_UNITS}</li> * <li>{@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}</li> * <li>All hints understood by {@link ReferencingFactoryFinder}</li> * </ul> * * @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: * <p> * <ul> * <li>{@link Hints#FORCE_STANDARD_AXIS_UNITS}</li> * <li>{@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}</li> * </ul> * * @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<axisOrder.length; i++) { final int ordinal = axisOrder[i].absolute().ordinal() + 1; if (ordinal > length) { length = ordinal; } } final int[] directionRanks = new int[length]; Arrays.fill(directionRanks, length); for (int i=0; i<axisOrder.length; i++) { final int ordinal = axisOrder[i].absolute().ordinal(); final int previous = directionRanks[ordinal]; if (previous != length) { // TODO: Use the localized version of 'getName' in GeoAPI 2.1 throw new IllegalArgumentException(Errors.format(ErrorKeys.COLINEAR_AXIS_$2, axisOrder[previous].name(), axisOrder[i].name())); } directionRanks[ordinal] = i; } return directionRanks; } /** * Returns the rank for the specified axis. Any axis that were not specified * at construction time will ordered after all known axis. */ private final int rank(final CoordinateSystemAxis axis) { int c = axis.getDirection().absolute().ordinal(); c = (c>=0 && c<directionRanks.length) ? directionRanks[c] : directionRanks.length; return c; } /** * Compares two axis for order. This method is invoked automatically by the * {@link #replace(CoordinateSystem) replace} method for ordering the axis in a * coordinate system. The default implementation orders the axis according their * {@linkplain CoordinateSystemAxis#getDirection direction}, using the direction * table given at {@linkplain #OrderedAxisAuthorityFactory(AbstractAuthorityFactory, * Hints, AxisDirection[]) construction time} (see also the class description). * Subclasses may override this method if they want to define a more sophesticated * axis ordering. * * @param axis1 The first axis to compare. * @param axis2 The second axis to compare. * @return A negative integer if {@code axis1} should appears before {@code axis2}, or a * positive number if {@code axis2} should appears before {@code axis1}, or 0 if * the two axis are unordered one relative to the other. * * @todo The argument type will be changed to {@link CoordinateSystemAxis} when we will * be allowed to compile for J2SE 1.5. * * @since 2.3 */ public int compare(final Object axis1, final Object axis2) { return rank((CoordinateSystemAxis) axis1) - rank((CoordinateSystemAxis) axis2); } /** * Replaces the specified unit, if applicable. This method is invoked automatically by the * {@link #replace(CoordinateSystem)} method. The default implementation replaces the unit * only if the {@link Hints#FORCE_STANDARD_AXIS_UNITS FORCE_STANDARD_AXIS_UNITS} hint was * specified as {@link Boolean#TRUE TRUE} at construction time. In such case, the default * substitution table is: * <p> * <ul> * <li>Any linear units converted to {@linkplain SI#METER meters}</li> * <li>{@linkplain SI#RADIAN Radians} and {@linkplain NonSI#GRADE grades} converted to * {@linkplain NonSI#DEGREE_ANGLE decimal degrees}</li> * </ul> * <p> * 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; } }