001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing.factory;
018:
019: // J2SE dependencies and extensions
020: import java.util.Map;
021: import java.util.Arrays;
022: import java.util.HashMap;
023: import java.util.Comparator;
024: import javax.units.Unit;
025: import javax.units.SI;
026: import javax.units.NonSI;
027:
028: // OpenGIS dependencies
029: import org.opengis.referencing.cs.*;
030: import org.opengis.referencing.crs.CRSAuthorityFactory;
031: import org.opengis.referencing.FactoryException;
032:
033: // Geotools dependencies
034: import org.geotools.factory.Hints;
035: import org.geotools.factory.FactoryRegistryException;
036: import org.geotools.referencing.ReferencingFactoryFinder;
037: import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
038: import org.geotools.resources.i18n.Errors;
039: import org.geotools.resources.i18n.ErrorKeys;
040:
041: /**
042: * An authority factory which delegates all the work to an other factory, and reorder the axis in
043: * some pre-determined order. This factory is mostly used by application expecting geographic
044: * coordinates in (<var>longitude</var>, <var>latitude</var>) order, while most geographic CRS
045: * specified in the <A HREF="http://www.epsg.org">EPSG database</A> use the opposite axis order.
046: * <p>
047: * It is better to avoid this class if you can. This class exists primarily for compatibility with
048: * external data or applications that assume (<var>longitude</var>, <var>latitude</var>) axis order
049: * no matter what the EPSG database said, for example Shapefiles.
050: * <p>
051: * The axis order can be specified at construction time as an array of {@linkplain AxisDirection
052: * axis directions}. If no such array is explicitly specified, then the default order is
053: * {@linkplain AxisDirection#EAST East},
054: * {@linkplain AxisDirection#EAST_NORTH_EAST East-North-East},
055: * {@linkplain AxisDirection#NORTH_EAST North-East},
056: * {@linkplain AxisDirection#NORTH_NORTH_EAST North-North-East},
057: * {@linkplain AxisDirection#NORTH North},
058: * {@linkplain AxisDirection#UP Up},
059: * {@linkplain AxisDirection#GEOCENTRIC_X Geocentric X},
060: * {@linkplain AxisDirection#GEOCENTRIC_Y Geocentric Y},
061: * {@linkplain AxisDirection#GEOCENTRIC_Z Geocentric Z},
062: * {@linkplain AxisDirection#COLUMN_POSITIVE Column},
063: * {@linkplain AxisDirection#ROW_POSITIVE Row},
064: * {@linkplain AxisDirection#DISPLAY_RIGHT Display right},
065: * {@linkplain AxisDirection#DISPLAY_UP Display up} and
066: * {@linkplain AxisDirection#FUTURE Future}.
067: * This means that, for example, axis with East or West direction will be placed before any
068: * axis with North or South direction. Axis directions not specified in the table (for example
069: * {@link AxisDirection#OTHER OTHER}) will be ordered last. This is somewhat equivalent to the
070: * ordering of {@link Double#NaN NaN} values in an array of {@code double}.
071: * <p>
072: * <strong>Notes:</strong>
073: * <ul>
074: * <li>This class compares only the "{@linkplain AxisDirection#absolute absolute}" axis
075: * directions, so North and South are considered equivalent.</li>
076: * <li>The default direction order may changes in future Geotools version in order
077: * to fit what appears to be the most common usage on the market.</li>
078: * <li>The actual axis ordering is determined by the {@link #compare compare} method
079: * implementation. Subclasses may override this method if the want to provide a more
080: * sophesticated axis ordering.</li>
081: * </ul>
082: * <p>
083: * For some authority factories, an instance of this class can be obtained by passing a
084: * {@link Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER FORCE_LONGITUDE_FIRST_AXIS_ORDER} hint
085: * to the <code>{@linkplain ReferencingFactoryFinder#getCRSAuthorityFactory
086: * FactoryFinder.getCRSAuthorityFactory}(...)</code> method. Whatever this hint is supported
087: * or not is authority dependent. Example:
088: *
089: * <blockquote><pre>
090: * Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
091: * CRSAuthorityFactory factory = FactoryFinder.getCRSAuthorityFactory("EPSG", hints);
092: * CoordinateReferenceSystem crs = factory.createCoordinateReferenceSystem("EPSG:4326");
093: * </pre></blockquote>
094: *
095: * This class is named <cite>ordered axis authority factory</cite> instead of something like
096: * <cite>longitude first axis order</cite> because the axis order can be user-supplied. The
097: * (<var>longitude</var>, <var>latitude</var>) order just appears to be the default one.
098: *
099: * @since 2.2
100: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/OrderedAxisAuthorityFactory.java $
101: * @version $Id: OrderedAxisAuthorityFactory.java 25406 2007-05-03 18:08:10Z desruisseaux $
102: * @author Martin Desruisseaux
103: *
104: * @see Hints#FORCE_LONGITUDE_FIRST_AXIS_ORDER
105: * @see Hints#FORCE_STANDARD_AXIS_UNITS
106: * @tutorial http://docs.codehaus.org/display/GEOTOOLS/The+axis+order+issue
107: */
108: public class OrderedAxisAuthorityFactory extends
109: TransformedAuthorityFactory implements CSAuthorityFactory,
110: CRSAuthorityFactory, Comparator/*<CoordinateSystemAxis>*/
111: {
112: /**
113: * The default order for axis directions. Note that this array needs to contain only the
114: * "{@linkplain AxisDirection#absolute absolute}" directions.
115: *
116: * REMINDER: If this array is modified, don't forget to update the class javadoc above.
117: */
118: private static final AxisDirection[] DEFAULT_ORDER = {
119: AxisDirection.EAST, AxisDirection.EAST_NORTH_EAST,
120: AxisDirection.NORTH_EAST, AxisDirection.NORTH_NORTH_EAST,
121: AxisDirection.NORTH, AxisDirection.UP,
122: AxisDirection.GEOCENTRIC_X, AxisDirection.GEOCENTRIC_Y,
123: AxisDirection.GEOCENTRIC_Z, AxisDirection.COLUMN_POSITIVE,
124: AxisDirection.ROW_POSITIVE, AxisDirection.DISPLAY_RIGHT,
125: AxisDirection.DISPLAY_UP, AxisDirection.FUTURE };
126:
127: /**
128: * The rank to be given to each axis direction. The rank is stored at the indice
129: * corresponding to the direction {@linkplain AxisDirection#ordinal ordinal} value.
130: */
131: private final int[] directionRanks;
132:
133: /**
134: * {@code true} if this authority factory should also force the axis to their standard
135: * direction. For example if {@code true}, then axis with increasing values toward South
136: * will be converted to axis with increasing values toward North. The default value is
137: * {@code false}.
138: *
139: * @see Hints#FORCE_STANDARD_AXIS_DIRECTIONS
140: * @since 2.3
141: */
142: protected final boolean forceStandardDirections;
143:
144: /**
145: * {@code true} if this authority factory should also force all angular units to
146: * decimal degrees and linear units to meters. The default value is {@code false}.
147: *
148: * @see Hints#FORCE_STANDARD_AXIS_UNITS
149: * @since 2.3
150: */
151: protected final boolean forceStandardUnits;
152:
153: /**
154: * Creates a factory which will reorder the axis of all objects created by the default
155: * authority factories. The factories are fetched using {@link ReferencingFactoryFinder}. This
156: * constructor accepts the following hints:
157: * <p>
158: * <ul>
159: * <li>{@link Hints#FORCE_STANDARD_AXIS_UNITS}</li>
160: * <li>{@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}</li>
161: * <li>All hints understood by {@link ReferencingFactoryFinder}</li>
162: * </ul>
163: *
164: * @param authority The authority to wraps (example: {@code "EPSG"}). If {@code null},
165: * then all authority factories must be explicitly specified in the set of hints.
166: * @param userHints An optional set of hints, or {@code null} if none.
167: * @param axisOrder An array of axis directions that determine the axis order wanted,
168: * or {@code null} for the default axis order.
169: * @throws FactoryRegistryException if at least one factory can not be obtained.
170: * @throws IllegalArgumentException If at least two axis directions are colinear.
171: *
172: * @since 2.3
173: */
174: public OrderedAxisAuthorityFactory(final String authority,
175: final Hints userHints, final AxisDirection[] axisOrder)
176: throws FactoryRegistryException, IllegalArgumentException {
177: super (authority, userHints);
178: forceStandardUnits = booleanValue(userHints,
179: Hints.FORCE_STANDARD_AXIS_UNITS);
180: forceStandardDirections = booleanValue(userHints,
181: Hints.FORCE_STANDARD_AXIS_DIRECTIONS);
182: directionRanks = computeDirectionRanks(axisOrder);
183: completeHints();
184: }
185:
186: /**
187: * Creates a factory which will reorder the axis of all objects created by the supplied
188: * factory. This constructor accepts the following optional hints:
189: * <p>
190: * <ul>
191: * <li>{@link Hints#FORCE_STANDARD_AXIS_UNITS}</li>
192: * <li>{@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS}</li>
193: * </ul>
194: *
195: * @param factory The factory that produces objects using arbitrary axis order.
196: * @param userHints An optional set of hints, or {@code null} if none.
197: * @param axisOrder An array of axis directions that determine the axis order wanted,
198: * or {@code null} for the default axis order.
199: * @throws IllegalArgumentException If at least two axis directions are colinear.
200: *
201: * @since 2.3
202: */
203: public OrderedAxisAuthorityFactory(
204: final AbstractAuthorityFactory factory,
205: final Hints userHints, final AxisDirection[] axisOrder)
206: throws IllegalArgumentException {
207: super (factory);
208: forceStandardUnits = booleanValue(userHints,
209: Hints.FORCE_STANDARD_AXIS_UNITS);
210: forceStandardDirections = booleanValue(userHints,
211: Hints.FORCE_STANDARD_AXIS_DIRECTIONS);
212: directionRanks = computeDirectionRanks(axisOrder);
213: completeHints();
214: }
215:
216: /**
217: * Returns the boolean value for the specified hint.
218: */
219: private static boolean booleanValue(final Hints userHints,
220: final Hints.Key key) {
221: if (userHints != null) {
222: final Boolean value = (Boolean) userHints.get(key);
223: if (value != null) {
224: return value.booleanValue();
225: }
226: }
227: return false;
228: }
229:
230: /**
231: * Completes the set of hints according the value currently set in this object.
232: * This method is invoked by constructors only.
233: */
234: private void completeHints() {
235: hints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean
236: .valueOf(forceStandardUnits));
237: hints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS, Boolean
238: .valueOf(forceStandardDirections));
239: // The following hint has no effect on this class behaviour,
240: // but tells to the user what this factory do about axis order.
241: if (compare(DefaultCoordinateSystemAxis.EASTING,
242: DefaultCoordinateSystemAxis.NORTHING) < 0) {
243: hints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
244: Boolean.TRUE);
245: }
246: }
247:
248: /**
249: * Computes the rank for every direction in the specified. The rank is stored in an array
250: * at the indice corresponding to the direction {@linkplain AxisDirection#ordinal ordinal}
251: * value. This method is used by constructors for computing the {@link #directionRanks} field.
252: *
253: * @throws IllegalArgumentException If at least two axis directions are colinear.
254: */
255: private static int[] computeDirectionRanks(AxisDirection[] axisOrder)
256: throws IllegalArgumentException {
257: if (axisOrder == null) {
258: axisOrder = DEFAULT_ORDER;
259: }
260: int length = 0;
261: for (int i = 0; i < axisOrder.length; i++) {
262: final int ordinal = axisOrder[i].absolute().ordinal() + 1;
263: if (ordinal > length) {
264: length = ordinal;
265: }
266: }
267: final int[] directionRanks = new int[length];
268: Arrays.fill(directionRanks, length);
269: for (int i = 0; i < axisOrder.length; i++) {
270: final int ordinal = axisOrder[i].absolute().ordinal();
271: final int previous = directionRanks[ordinal];
272: if (previous != length) {
273: // TODO: Use the localized version of 'getName' in GeoAPI 2.1
274: throw new IllegalArgumentException(Errors.format(
275: ErrorKeys.COLINEAR_AXIS_$2, axisOrder[previous]
276: .name(), axisOrder[i].name()));
277: }
278: directionRanks[ordinal] = i;
279: }
280: return directionRanks;
281: }
282:
283: /**
284: * Returns the rank for the specified axis. Any axis that were not specified
285: * at construction time will ordered after all known axis.
286: */
287: private final int rank(final CoordinateSystemAxis axis) {
288: int c = axis.getDirection().absolute().ordinal();
289: c = (c >= 0 && c < directionRanks.length) ? directionRanks[c]
290: : directionRanks.length;
291: return c;
292: }
293:
294: /**
295: * Compares two axis for order. This method is invoked automatically by the
296: * {@link #replace(CoordinateSystem) replace} method for ordering the axis in a
297: * coordinate system. The default implementation orders the axis according their
298: * {@linkplain CoordinateSystemAxis#getDirection direction}, using the direction
299: * table given at {@linkplain #OrderedAxisAuthorityFactory(AbstractAuthorityFactory,
300: * Hints, AxisDirection[]) construction time} (see also the class description).
301: * Subclasses may override this method if they want to define a more sophesticated
302: * axis ordering.
303: *
304: * @param axis1 The first axis to compare.
305: * @param axis2 The second axis to compare.
306: * @return A negative integer if {@code axis1} should appears before {@code axis2}, or a
307: * positive number if {@code axis2} should appears before {@code axis1}, or 0 if
308: * the two axis are unordered one relative to the other.
309: *
310: * @todo The argument type will be changed to {@link CoordinateSystemAxis} when we will
311: * be allowed to compile for J2SE 1.5.
312: *
313: * @since 2.3
314: */
315: public int compare(final Object axis1, final Object axis2) {
316: return rank((CoordinateSystemAxis) axis1)
317: - rank((CoordinateSystemAxis) axis2);
318: }
319:
320: /**
321: * Replaces the specified unit, if applicable. This method is invoked automatically by the
322: * {@link #replace(CoordinateSystem)} method. The default implementation replaces the unit
323: * only if the {@link Hints#FORCE_STANDARD_AXIS_UNITS FORCE_STANDARD_AXIS_UNITS} hint was
324: * specified as {@link Boolean#TRUE TRUE} at construction time. In such case, the default
325: * substitution table is:
326: * <p>
327: * <ul>
328: * <li>Any linear units converted to {@linkplain SI#METER meters}</li>
329: * <li>{@linkplain SI#RADIAN Radians} and {@linkplain NonSI#GRADE grades} converted to
330: * {@linkplain NonSI#DEGREE_ANGLE decimal degrees}</li>
331: * </ul>
332: * <p>
333: * This default substitution table may be expanded in future Geotools versions.
334: *
335: * @since 2.3
336: */
337: protected Unit replace(final Unit units) {
338: if (forceStandardUnits) {
339: if (units.isCompatible(SI.METER)) {
340: return SI.METER;
341: }
342: if (units.equals(SI.RADIAN) || units.equals(NonSI.GRADE)) {
343: return NonSI.DEGREE_ANGLE;
344: }
345: }
346: return units;
347: }
348:
349: /**
350: * Replaces the specified direction, if applicable. This method is invoked automatically by the
351: * {@link #replace(CoordinateSystem)} method. The default implementation replaces the direction
352: * only if the {@link Hints#FORCE_STANDARD_AXIS_DIRECTIONS FORCE_STANDARD_AXIS_DIRECTIONS} hint
353: * was specified as {@link Boolean#TRUE TRUE} at construction time. In such case, the default
354: * substitution table is as specified in the {@link AxisDirection#absolute} method.
355: * Subclasses may override this method if they want to use a different substitution table.
356: *
357: * @since 2.3
358: */
359: protected AxisDirection replace(final AxisDirection direction) {
360: return (forceStandardDirections) ? direction.absolute()
361: : direction;
362: }
363:
364: /**
365: * Returns the error message for the specified coordinate system.
366: * Used when throwing {@link FactoryException}.
367: */
368: private static final String getErrorMessage(
369: final CoordinateSystem cs) {
370: return Errors.format(
371: ErrorKeys.UNSUPPORTED_COORDINATE_SYSTEM_$1, cs
372: .getName().getCode());
373: }
374: }
|