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.Set;
022: import java.util.List;
023: import java.util.Arrays;
024: import java.util.Iterator;
025: import java.util.Comparator;
026: import java.util.LinkedHashSet;
027: import javax.units.Unit;
028:
029: // OpenGIS dependencies
030: import org.opengis.metadata.citation.Citation;
031: import org.opengis.referencing.IdentifiedObject;
032: import org.opengis.referencing.AuthorityFactory;
033: import org.opengis.referencing.FactoryException;
034: import org.opengis.referencing.cs.*;
035: import org.opengis.referencing.crs.*;
036: import org.opengis.referencing.datum.*;
037: import org.opengis.referencing.operation.*;
038:
039: // Geotools dependencies
040: import org.geotools.util.CanonicalSet;
041: import org.geotools.factory.Hints;
042: import org.geotools.factory.Factory;
043: import org.geotools.factory.FactoryRegistryException;
044: import org.geotools.referencing.ReferencingFactoryFinder;
045: import org.geotools.referencing.AbstractIdentifiedObject;
046: import org.geotools.referencing.operation.DefiningConversion;
047: import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
048: import org.geotools.resources.i18n.ErrorKeys;
049: import org.geotools.resources.i18n.Errors;
050: import org.geotools.resources.Utilities;
051:
052: /**
053: * An authority factory which returns modified {@linkplain CoordinateReferenceSystem CRS},
054: * {@linkplain CoordinateSystem CS} or {@linkplain Datum datum} objects from other factory
055: * implementations. This class provides a set of {@code replace(...)} methods to be overridden
056: * by subclasses in order to replace some {@linkplain CoordinateReferenceSystem CRS},
057: * {@linkplain CoordinateSystem CS} or {@linkplain Datum datum} objects by other ones.
058: * The replacement rules are determined by the subclass being used. For example the
059: * {@link OrderedAxisAuthorityFactory} subclass can replace
060: * {@linkplain CoordinateSystem coordinate systems} using (<var>latitude</var>,
061: * <var>longitude</var>) axis order by coordinate systems using (<var>longitude</var>,
062: * <var>latitude</var>) axis order.
063: * <p>
064: * All constructors are protected because this class must be subclassed in order to
065: * determine which of the {@link DatumAuthorityFactory}, {@link CSAuthorityFactory}
066: * and {@link CRSAuthorityFactory} interfaces to implement.
067: *
068: * @since 2.3
069: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/TransformedAuthorityFactory.java $
070: * @version $Id: TransformedAuthorityFactory.java 25972 2007-06-21 13:38:35Z desruisseaux $
071: * @author Martin Desruisseaux
072: *
073: * @todo Use generic types for all {@code replace(...)} methods when we will be
074: * allowed to compile for J2SE 1.5, and remove casts in all
075: * {@code createXXX(...)} methods.
076: */
077: public class TransformedAuthorityFactory extends
078: AuthorityFactoryAdapter {
079: /**
080: * Axis that need to be renamed if their direction changes.
081: */
082: private static final DefaultCoordinateSystemAxis[] RENAMEABLE = {
083: DefaultCoordinateSystemAxis.NORTHING,
084: DefaultCoordinateSystemAxis.SOUTHING,
085: DefaultCoordinateSystemAxis.EASTING,
086: DefaultCoordinateSystemAxis.WESTING };
087:
088: /**
089: * The coordinate operation factory. Will be created only when first needed.
090: */
091: private transient CoordinateOperationFactory opFactory;
092:
093: /**
094: * A pool of modified objects created up to date.
095: */
096: private final CanonicalSet pool = new CanonicalSet();
097:
098: /**
099: * Creates a wrapper around the specified factory.
100: *
101: * @param factory The factory to wrap.
102: */
103: protected TransformedAuthorityFactory(final AuthorityFactory factory) {
104: super (factory);
105: }
106:
107: /**
108: * Creates a wrapper around the specified factories.
109: *
110: * @param crsFactory The {@linkplain CoordinateReferenceSystem coordinate reference system}
111: * authority factory, or {@code null}.
112: * @param csFactory The {@linkplain CoordinateSystem coordinate system} authority
113: * factory, or {@code null}.
114: * @param datumFactory The {@linkplain Datum datum} authority factory, or {@code null}.
115: * @param opFactory The {@linkplain CoordinateOperation coordinate operation}
116: * authority factory, or {@code null}.
117: */
118: protected TransformedAuthorityFactory(
119: final CRSAuthorityFactory crsFactory,
120: final CSAuthorityFactory csFactory,
121: final DatumAuthorityFactory datumFactory,
122: final CoordinateOperationAuthorityFactory opFactory) {
123: super (crsFactory, csFactory, datumFactory, opFactory);
124: }
125:
126: /**
127: * Creates a wrappers around the default factories for the specified
128: * authority. The factories are fetched using {@link ReferencingFactoryFinder}.
129: *
130: * @param authority The authority to wraps (example: {@code "EPSG"}). If {@code null},
131: * then all authority factories must be explicitly specified in the
132: * set of hints.
133: * @param userHints An optional set of hints, or {@code null} if none.
134: * @throws FactoryRegistryException if at least one factory can not be obtained.
135: *
136: * @since 2.4
137: */
138: protected TransformedAuthorityFactory(final String authority,
139: final Hints userHints) throws FactoryRegistryException {
140: super (authority, userHints);
141: }
142:
143: /**
144: * Returns the priority for this factory. Priorities are used by
145: * {@link ReferencingFactoryFinder} for selecting a preferred factory when many are
146: * found for the same service. The default implementation returns
147: * <code>{@linkplain #priority priority} + 1</code>, which implies that
148: * this adapter has precedence over the wrapped factories. Subclasses should
149: * override this method if they want a different priority order for this
150: * instance.
151: */
152: public int getPriority() {
153: return priority + 1;
154: }
155:
156: /**
157: * Replaces the specified unit, if applicable. This method is invoked
158: * automatically by the {@link #replace(CoordinateSystem)} method. The
159: * default implementation returns the unit unchanged.
160: *
161: * @param units The units to replace.
162: * @return The new units, or {@code units} if no change were needed.
163: * @throws FactoryException if an error occured while creating the new units.
164: */
165: // @Override
166: protected Unit replace(final Unit units) throws FactoryException {
167: return units;
168: }
169:
170: /**
171: * Replaces the specified direction, if applicable. This method is invoked
172: * automatically by the {@link #replace(CoordinateSystem)} method. The
173: * default implementation returns the axis direction unchanged.
174: *
175: * @param direction The axis direction to replace.
176: * @return The new direction, or {@code direction} if no change were needed.
177: * @throws FactoryException if an error occured while creating the new axis direction.
178: */
179: protected AxisDirection replace(final AxisDirection direction)
180: throws FactoryException {
181: return direction;
182: }
183:
184: /**
185: * Replaces (if needed) the specified axis by a new one. The default
186: * implementation invokes {@link #replace(Unit)} and
187: * {@link #replace(AxisDirection)}.
188: *
189: * @param axis The coordinate system axis to replace.
190: * @return The new coordinate system axis, or {@code axis} if no change were needed.
191: * @throws FactoryException if an error occured while creating the new coordinate system axis.
192: */
193: // @Override
194: protected CoordinateSystemAxis replace(CoordinateSystemAxis axis)
195: throws FactoryException {
196: final AxisDirection oldDirection = axis.getDirection();
197: final AxisDirection newDirection = replace(oldDirection);
198: Unit oldUnits = axis.getUnit();
199: final Unit newUnits = replace(oldUnits);
200: boolean directionChanged = !oldDirection.equals(newDirection);
201: if (directionChanged) {
202: /*
203: * Check if the direction change implies an axis renaming. For example if the axis
204: * name was "Southing" and the direction has been changed from SOUTH to NORTH, then
205: * the axis should be renamed as "Northing".
206: */
207: final String name = axis.getName().getCode();
208: for (int i = 0; i < RENAMEABLE.length; i++) {
209: if (RENAMEABLE[i].nameMatches(name)) {
210: for (i = 0; i < RENAMEABLE.length; i++) {
211: final CoordinateSystemAxis candidate = RENAMEABLE[i];
212: if (newDirection.equals(candidate
213: .getDirection())) {
214: axis = candidate; // The new axis, but may change again later.
215: oldUnits = axis.getUnit(); // For detecting change relative to new axis.
216: directionChanged = false; // The new axis has the requested direction.
217: break;
218: }
219: }
220: break;
221: }
222: }
223: }
224: if (directionChanged || !oldUnits.equals(newUnits)) {
225: final ReferencingFactoryContainer factories = getFactoryContainer(false);
226: final CSFactory csFactory = factories.getCSFactory();
227: final Map properties = getProperties(axis);
228: axis = csFactory.createCoordinateSystemAxis(properties,
229: axis.getAbbreviation(), newDirection, newUnits);
230: axis = (CoordinateSystemAxis) pool.unique(axis);
231: }
232: return axis;
233: }
234:
235: /**
236: * Replaces (if needed) the specified coordinate system by a new one. The
237: * default implementation invokes {@link #replace(CoordinateSystemAxis) replace}
238: * for each axis. In addition, axis are sorted if this factory implements the
239: * {@link Comparator} interface.
240: *
241: * @param cs The coordinate system to replace.
242: * @return The new coordinate system, or {@code cs} if no change were needed.
243: * @throws FactoryException if an error occured while creating the new coordinate system.
244: */
245: // @Override
246: protected CoordinateSystem replace(final CoordinateSystem cs)
247: throws FactoryException {
248: final int dimension = cs.getDimension();
249: final CoordinateSystemAxis[] orderedAxis = new CoordinateSystemAxis[dimension];
250: for (int i = 0; i < dimension; i++) {
251: orderedAxis[i] = replace(cs.getAxis(i));
252: }
253: if (this instanceof Comparator) {
254: Arrays.sort(orderedAxis, (Comparator) this );
255: }
256: for (int i = 0; i < dimension; i++) {
257: if (!orderedAxis[i].equals(cs.getAxis(i))) {
258: CoordinateSystem modified = createCS(cs.getClass(),
259: getProperties(cs), orderedAxis);
260: assert Utilities.sameInterfaces(cs.getClass(), modified
261: .getClass(), CoordinateSystem.class);
262: modified = (CoordinateSystem) pool.unique(modified);
263: return modified;
264: }
265: }
266: // All axis are identical - the CS was actually not changed.
267: return cs;
268: }
269:
270: /**
271: * Replaces (if needed) the specified datum by a new one. The default
272: * implementation returns the datum unchanged. Subclasses should override
273: * this method if some datum replacements are desired.
274: *
275: * @param datum The datum to replace.
276: * @return The new datum, or {@code datum} if no change were needed.
277: * @throws FactoryException if an error occured while creating the new datum.
278: */
279: // @Override
280: protected Datum replace(final Datum datum) throws FactoryException {
281: return super .replace(datum);
282: }
283:
284: /**
285: * Replaces (if needed) the specified coordinate reference system. The default
286: * implementation checks if there is a {@linkplain #replace(Datum) datum replacement}
287: * or a {@linkplain #replace(CoordinateSystem) coordinate system replacement}.
288: * If there is at least one of those, then this method returns a new
289: * coordinate reference system using the new datum and coordinate system.
290: *
291: * @param crs The coordinate reference system to replace.
292: * @return A new CRS, or {@code crs} if no change were needed.
293: * @throws FactoryException if an error occured while creating the new CRS object.
294: */
295: // @Override
296: protected CoordinateReferenceSystem replace(
297: final CoordinateReferenceSystem crs)
298: throws FactoryException {
299: /*
300: * Gets the replaced coordinate system and datum, and checks if there is any change.
301: */
302: final CoordinateSystem oldCS = crs.getCoordinateSystem();
303: final CoordinateSystem cs = replace(oldCS);
304: final Datum oldDatum, datum;
305: if (crs instanceof SingleCRS) {
306: oldDatum = ((SingleCRS) crs).getDatum();
307: datum = replace(oldDatum);
308: } else {
309: datum = oldDatum = null;
310: }
311: final boolean sameCS = Utilities.equals(cs, oldCS)
312: && Utilities.equals(datum, oldDatum);
313: /*
314: * Creates a new coordinate reference system using the same properties
315: * than the original CRS, except for the coordinate system, datum and
316: * authority code.
317: */
318: CoordinateReferenceSystem modified;
319: if (crs instanceof GeneralDerivedCRS) {
320: final GeneralDerivedCRS derivedCRS = (GeneralDerivedCRS) crs;
321: final CoordinateReferenceSystem oldBaseCRS = derivedCRS
322: .getBaseCRS();
323: final CoordinateReferenceSystem baseCRS = replace(oldBaseCRS);
324: if (sameCS && Utilities.equals(baseCRS, oldBaseCRS)) {
325: return crs;
326: }
327: final Map properties = getProperties(crs);
328: final ReferencingFactoryContainer factories = getFactoryContainer(true);
329: final CRSFactory crsFactory = factories.getCRSFactory();
330: Conversion fromBase = derivedCRS.getConversionFromBase();
331: fromBase = new DefiningConversion(getProperties(fromBase),
332: fromBase.getMethod(), fromBase.getParameterValues());
333: if (crs instanceof ProjectedCRS) {
334: modified = factories.createProjectedCRS(properties,
335: (GeographicCRS) baseCRS, fromBase,
336: (CartesianCS) cs);
337: } else {
338: // TODO: Need a createDerivedCRS method.
339: throw new FactoryException(Errors.format(
340: ErrorKeys.UNSUPPORTED_CRS_$1, crs.getName()
341: .getCode()));
342: }
343: } else if (sameCS) {
344: return crs;
345: } else {
346: final Map properties = getProperties(crs);
347: final ReferencingFactoryContainer factories = getFactoryContainer(true);
348: final CRSFactory crsFactory = factories.getCRSFactory();
349: if (crs instanceof GeographicCRS) {
350: modified = crsFactory.createGeographicCRS(properties,
351: (GeodeticDatum) datum, (EllipsoidalCS) cs);
352: } else if (crs instanceof GeocentricCRS) {
353: final GeodeticDatum gd = (GeodeticDatum) datum;
354: if (cs instanceof CartesianCS) {
355: modified = crsFactory.createGeocentricCRS(
356: properties, gd, (CartesianCS) cs);
357: } else {
358: modified = crsFactory.createGeocentricCRS(
359: properties, gd, (SphericalCS) cs);
360: }
361: } else if (crs instanceof VerticalCRS) {
362: modified = crsFactory.createVerticalCRS(properties,
363: (VerticalDatum) datum, (VerticalCS) cs);
364: } else if (crs instanceof TemporalCRS) {
365: modified = crsFactory.createTemporalCRS(properties,
366: (TemporalDatum) datum, (TimeCS) cs);
367: } else if (crs instanceof ImageCRS) {
368: modified = crsFactory.createImageCRS(properties,
369: (ImageDatum) datum, (AffineCS) cs);
370: } else if (crs instanceof EngineeringCRS) {
371: modified = crsFactory.createEngineeringCRS(properties,
372: (EngineeringDatum) datum, cs);
373: } else if (crs instanceof CompoundCRS) {
374: final List/* <CoordinateReferenceSystem> */elements = ((CompoundCRS) crs)
375: .getCoordinateReferenceSystems();
376: final CoordinateReferenceSystem[] m = new CoordinateReferenceSystem[elements
377: .size()];
378: for (int i = 0; i < m.length; i++) {
379: m[i] = replace((CoordinateReferenceSystem) elements
380: .get(i));
381: }
382: modified = crsFactory.createCompoundCRS(properties, m);
383: } else {
384: throw new FactoryException(Errors.format(
385: ErrorKeys.UNSUPPORTED_CRS_$1, crs.getName()
386: .getCode()));
387: }
388: }
389: modified = (CoordinateReferenceSystem) pool.unique(modified);
390: return modified;
391: }
392:
393: /**
394: * Replaces (if needed) the specified coordinate operation. The default
395: * implementation checks if there is a source or target
396: * {@linkplain #replace(CoordinateReferenceSystem) CRS replacement}. If
397: * there is at least one of those, then this method returns a new coordinate
398: * operation using the new CRS.
399: *
400: * @param operation The coordinate operation to replace.
401: * @return A new operation, or {@code operation} if no change were needed.
402: * @throws FactoryException if an error occured while creating the new operation object.
403: */
404: // @Override
405: protected CoordinateOperation replace(
406: final CoordinateOperation operation)
407: throws FactoryException {
408: final CoordinateReferenceSystem oldSrcCRS = operation
409: .getSourceCRS();
410: final CoordinateReferenceSystem oldTgtCRS = operation
411: .getTargetCRS();
412: final CoordinateReferenceSystem sourceCRS = (oldSrcCRS != null) ? replace(oldSrcCRS)
413: : null;
414: final CoordinateReferenceSystem targetCRS = (oldTgtCRS != null) ? replace(oldTgtCRS)
415: : null;
416: if (Utilities.equals(oldSrcCRS, sourceCRS)
417: && Utilities.equals(oldTgtCRS, targetCRS)) {
418: return operation;
419: }
420: if (opFactory == null) {
421: opFactory = getCoordinateOperationFactory();
422: }
423: CoordinateOperation modified;
424: modified = opFactory.createOperation(sourceCRS, targetCRS);
425: modified = (CoordinateOperation) pool.unique(modified);
426: return modified;
427: }
428:
429: /**
430: * Creates a new coordinate system of the specified kind. This method is
431: * invoked automatically by {@link #replace(CoordinateSystem)} after it
432: * determined that the axis need to be changed.
433: *
434: * @param type The coordinate system type to create.
435: * @param properties The properties to gives to the new coordinate system.
436: * @param axis The axis to give to the new coordinate system. Subclasses are
437: * allowed to write directly in this array (no need to copy it).
438: * @return A new coordinate system of the specified kind with the specified axis.
439: * @throws FactoryException if the coordinate system can't be created.
440: */
441: private CoordinateSystem createCS(
442: final Class/* <CoordinateSystem> */type,
443: final Map properties, final CoordinateSystemAxis[] axis)
444: throws FactoryException {
445: final int dimension = axis.length;
446: final ReferencingFactoryContainer factories = getFactoryContainer(false);
447: final CSFactory csFactory = factories.getCSFactory();
448: if (CartesianCS.class.isAssignableFrom(type)) {
449: switch (dimension) {
450: case 2:
451: return csFactory.createCartesianCS(properties, axis[0],
452: axis[1]);
453: case 3:
454: return csFactory.createCartesianCS(properties, axis[0],
455: axis[1], axis[2]);
456: }
457: } else if (EllipsoidalCS.class.isAssignableFrom(type)) {
458: switch (dimension) {
459: case 2:
460: return csFactory.createEllipsoidalCS(properties,
461: axis[0], axis[1]);
462: case 3:
463: return csFactory.createEllipsoidalCS(properties,
464: axis[0], axis[1], axis[2]);
465: }
466: } else if (SphericalCS.class.isAssignableFrom(type)) {
467: switch (dimension) {
468: case 3:
469: return csFactory.createSphericalCS(properties, axis[0],
470: axis[1], axis[2]);
471: }
472: } else if (CylindricalCS.class.isAssignableFrom(type)) {
473: switch (dimension) {
474: case 3:
475: return csFactory.createCylindricalCS(properties,
476: axis[0], axis[1], axis[2]);
477: }
478: } else if (PolarCS.class.isAssignableFrom(type)) {
479: switch (dimension) {
480: case 2:
481: return csFactory.createPolarCS(properties, axis[0],
482: axis[1]);
483: }
484: } else if (VerticalCS.class.isAssignableFrom(type)) {
485: switch (dimension) {
486: case 1:
487: return csFactory.createVerticalCS(properties, axis[0]);
488: }
489: } else if (TimeCS.class.isAssignableFrom(type)) {
490: switch (dimension) {
491: case 1:
492: return csFactory.createTimeCS(properties, axis[0]);
493: }
494: } else if (LinearCS.class.isAssignableFrom(type)) {
495: switch (dimension) {
496: case 1:
497: return csFactory.createLinearCS(properties, axis[0]);
498: }
499: } else if (UserDefinedCS.class.isAssignableFrom(type)) {
500: switch (dimension) {
501: case 2:
502: return csFactory.createUserDefinedCS(properties,
503: axis[0], axis[1]);
504: case 3:
505: return csFactory.createUserDefinedCS(properties,
506: axis[0], axis[1], axis[2]);
507: }
508: }
509: throw new FactoryException(Errors.format(
510: ErrorKeys.UNSUPPORTED_COORDINATE_SYSTEM_$1, Utilities
511: .getShortName(type)));
512: }
513:
514: /**
515: * Returns the properties to be given to an object replacing an original
516: * one. If the new object keep the same authority, then all metadata are
517: * preserved. Otherwise (i.e. if a new authority is given to the new
518: * object), then the old identifiers will be removed from the new object
519: * metadata.
520: *
521: * @param object The original object.
522: * @return The properties to be given to the object created as a substitute
523: * of {@code object}.
524: */
525: private Map getProperties(final IdentifiedObject object) {
526: final Citation authority = getAuthority();
527: if (!Utilities.equals(authority, object.getName()
528: .getAuthority())) {
529: return AbstractIdentifiedObject.getProperties(object,
530: authority);
531: } else {
532: return AbstractIdentifiedObject.getProperties(object);
533: }
534: }
535:
536: /**
537: * Creates an operation from coordinate reference system codes. The default
538: * implementation first invokes the same method from the
539: * {@linkplain #operationFactory underlying operation factory}, and next
540: * invokes {@link #replace(CoordinateOperation) replace} for each
541: * operations.
542: */
543: public Set/* <CoordinateOperation> */createFromCoordinateReferenceSystemCodes(
544: final String sourceCode, final String targetCode)
545: throws FactoryException {
546: final Set/* <CoordinateOperation> */operations, modified;
547: operations = super .createFromCoordinateReferenceSystemCodes(
548: sourceCode, targetCode);
549: modified = new LinkedHashSet(
550: (int) (operations.size() / 0.75f) + 1);
551: for (final Iterator it = operations.iterator(); it.hasNext();) {
552: final CoordinateOperation operation;
553: try {
554: operation = (CoordinateOperation) it.next();
555: } catch (BackingStoreException exception) {
556: final Throwable cause = exception.getCause();
557: if (cause instanceof FactoryException) {
558: throw (FactoryException) cause;
559: } else {
560: throw exception;
561: }
562: }
563: modified.add(replace(operation));
564: }
565: return modified;
566: }
567:
568: /**
569: * Releases resources immediately instead of waiting for the garbage
570: * collector. This method do <strong>not</strong> dispose the resources of
571: * wrapped factories (e.g. {@link #crsFactory crsFactory}), because they may
572: * still in use by other classes.
573: */
574: public synchronized void dispose() throws FactoryException {
575: pool.clear();
576: super.dispose();
577: }
578: }
|