/*
* 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:
*
* - This class compares only the "{@linkplain AxisDirection#absolute absolute}" axis
* directions, so North and South are considered equivalent.
* - 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.
* - 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.
*
*
* 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:
*
*
* - {@link Hints#FORCE_STANDARD_AXIS_UNITS}
* - {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}
* - All hints understood by {@link ReferencingFactoryFinder}
*
*
* @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:
*
*
* - {@link Hints#FORCE_STANDARD_AXIS_UNITS}
* - {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}
*
*
* @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;
}
}