/*
* 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.Map;
import java.util.Set;
import java.util.List;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Comparator;
import java.util.LinkedHashSet;
import javax.measure.unit.Unit;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.geotools.util.CanonicalSet;
import org.geotools.factory.Hints;
import org.geotools.factory.FactoryRegistryException;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.operation.DefiningConversion;
import org.geotools.referencing.cs.DefaultCoordinateSystemAxis;
import org.geotools.resources.Classes;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
/**
* An authority factory which returns modified {@linkplain CoordinateReferenceSystem CRS},
* {@linkplain CoordinateSystem CS} or {@linkplain Datum datum} objects from other factory
* implementations. This class provides a set of {@code replace(...)} methods to be overridden
* by subclasses in order to replace some {@linkplain CoordinateReferenceSystem CRS},
* {@linkplain CoordinateSystem CS} or {@linkplain Datum datum} objects by other ones.
* The replacement rules are determined by the subclass being used. For example the
* {@link OrderedAxisAuthorityFactory} subclass can replace
* {@linkplain CoordinateSystem coordinate systems} using (<var>latitude</var>,
* <var>longitude</var>) axis order by coordinate systems using (<var>longitude</var>,
* <var>latitude</var>) axis order.
* <p>
* All constructors are protected because this class must be subclassed in order to
* determine which of the {@link DatumAuthorityFactory}, {@link CSAuthorityFactory}
* and {@link CRSAuthorityFactory} interfaces to implement.
*
* @since 2.3
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @todo Use generic types for all {@code replace(...)} methods when we will be
* allowed to compile for J2SE 1.5, and remove casts in all
* {@code createXXX(...)} methods.
*/
public class TransformedAuthorityFactory extends AuthorityFactoryAdapter {
/**
* Axis that need to be renamed if their direction changes.
*/
private static final DefaultCoordinateSystemAxis[] RENAMEABLE = {
DefaultCoordinateSystemAxis.NORTHING, DefaultCoordinateSystemAxis.SOUTHING,
DefaultCoordinateSystemAxis.EASTING, DefaultCoordinateSystemAxis.WESTING
};
/**
* The coordinate operation factory. Will be created only when first needed.
*/
private transient CoordinateOperationFactory opFactory;
/**
* A pool of modified objects created up to date.
*/
private final CanonicalSet pool = new CanonicalSet();
/**
* Creates a wrapper around the specified factory.
*
* @param factory The factory to wrap.
*/
protected TransformedAuthorityFactory(final AuthorityFactory factory) {
super(factory);
}
/**
* Creates a wrapper around the specified factories.
*
* @param crsFactory The {@linkplain CoordinateReferenceSystem coordinate reference system}
* authority factory, or {@code null}.
* @param csFactory The {@linkplain CoordinateSystem coordinate system} authority
* factory, or {@code null}.
* @param datumFactory The {@linkplain Datum datum} authority factory, or {@code null}.
* @param opFactory The {@linkplain CoordinateOperation coordinate operation}
* authority factory, or {@code null}.
*/
protected TransformedAuthorityFactory(final CRSAuthorityFactory crsFactory,
final CSAuthorityFactory csFactory,
final DatumAuthorityFactory datumFactory,
final CoordinateOperationAuthorityFactory opFactory)
{
super(crsFactory, csFactory, datumFactory, opFactory);
}
/**
* Creates a wrappers around the default factories for the specified
* authority. The factories are fetched using {@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.
* @throws FactoryRegistryException if at least one factory can not be obtained.
*
* @since 2.4
*/
protected TransformedAuthorityFactory(final String authority, final Hints userHints)
throws FactoryRegistryException
{
super(authority, userHints);
}
/**
* Returns the priority for this factory. Priorities are used by
* {@link ReferencingFactoryFinder} for selecting a preferred factory when many are
* found for the same service. The default implementation returns
* <code>{@linkplain #priority priority} + 1</code>, which implies that
* this adapter has precedence over the wrapped factories. Subclasses should
* override this method if they want a different priority order for this
* instance.
*/
public int getPriority() {
return priority + 1;
}
/**
* Replaces the specified unit, if applicable. This method is invoked
* automatically by the {@link #replace(CoordinateSystem)} method. The
* default implementation returns the unit unchanged.
*
* @param units The units to replace.
* @return The new units, or {@code units} if no change were needed.
* @throws FactoryException if an error occured while creating the new units.
*/
@Override
protected Unit<?> replace(final Unit<?> units) throws FactoryException {
return units;
}
/**
* Replaces the specified direction, if applicable. This method is invoked
* automatically by the {@link #replace(CoordinateSystem)} method. The
* default implementation returns the axis direction unchanged.
*
* @param direction The axis direction to replace.
* @return The new direction, or {@code direction} if no change were needed.
* @throws FactoryException if an error occured while creating the new axis direction.
*/
protected AxisDirection replace(final AxisDirection direction) throws FactoryException {
return direction;
}
/**
* Replaces (if needed) the specified axis by a new one. The default
* implementation invokes {@link #replace(Unit)} and
* {@link #replace(AxisDirection)}.
*
* @param axis The coordinate system axis to replace.
* @return The new coordinate system axis, or {@code axis} if no change were needed.
* @throws FactoryException if an error occured while creating the new coordinate system axis.
*/
@Override
protected CoordinateSystemAxis replace(CoordinateSystemAxis axis) throws FactoryException {
final AxisDirection oldDirection = axis.getDirection();
final AxisDirection newDirection = replace(oldDirection);
Unit<?> oldUnits = axis.getUnit();
final Unit<?> newUnits = replace(oldUnits);
boolean directionChanged = !oldDirection.equals(newDirection);
if (directionChanged) {
/*
* Check if the direction change implies an axis renaming. For example if the axis
* name was "Southing" and the direction has been changed from SOUTH to NORTH, then
* the axis should be renamed as "Northing".
*/
final String name = axis.getName().getCode();
for (int i=0; i<RENAMEABLE.length; i++) {
if (RENAMEABLE[i].nameMatches(name)) {
for (i=0; i<RENAMEABLE.length; i++) {
final CoordinateSystemAxis candidate = RENAMEABLE[i];
if (newDirection.equals(candidate.getDirection())) {
axis = candidate; // The new axis, but may change again later.
oldUnits = axis.getUnit(); // For detecting change relative to new axis.
directionChanged = false; // The new axis has the requested direction.
break;
}
}
break;
}
}
}
if (directionChanged || !oldUnits.equals(newUnits)) {
final ReferencingFactoryContainer factories = getFactoryContainer(false);
final CSFactory csFactory = factories.getCSFactory();
final Map properties = getProperties(axis);
axis = csFactory.createCoordinateSystemAxis(properties,
axis.getAbbreviation(), newDirection, newUnits);
axis = (CoordinateSystemAxis) pool.unique(axis);
}
return axis;
}
/**
* Replaces (if needed) the specified coordinate system by a new one. The
* default implementation invokes {@link #replace(CoordinateSystemAxis) replace}
* for each axis. In addition, axis are sorted if this factory implements the
* {@link Comparator} interface.
*
* @param cs The coordinate system to replace.
* @return The new coordinate system, or {@code cs} if no change were needed.
* @throws FactoryException if an error occured while creating the new coordinate system.
*/
// @Override
protected CoordinateSystem replace(final CoordinateSystem cs) throws FactoryException {
final int dimension = cs.getDimension();
final CoordinateSystemAxis[] orderedAxis = new CoordinateSystemAxis[dimension];
for (int i=0; i<dimension; i++) {
orderedAxis[i] = replace(cs.getAxis(i));
}
if (this instanceof Comparator) {
Arrays.sort(orderedAxis, (Comparator) this);
}
for (int i=0; i<dimension; i++) {
if (!orderedAxis[i].equals(cs.getAxis(i))) {
CoordinateSystem modified = createCS(cs.getClass(), getProperties(cs), orderedAxis);
assert Classes.sameInterfaces(cs.getClass(), modified.getClass(), CoordinateSystem.class);
modified = (CoordinateSystem) pool.unique(modified);
return modified;
}
}
// All axis are identical - the CS was actually not changed.
return cs;
}
/**
* Replaces (if needed) the specified datum by a new one. The default
* implementation returns the datum unchanged. Subclasses should override
* this method if some datum replacements are desired.
*
* @param datum The datum to replace.
* @return The new datum, or {@code datum} if no change were needed.
* @throws FactoryException if an error occured while creating the new datum.
*/
// @Override
protected Datum replace(final Datum datum) throws FactoryException {
return super.replace(datum);
}
/**
* Replaces (if needed) the specified coordinate reference system. The default
* implementation checks if there is a {@linkplain #replace(Datum) datum replacement}
* or a {@linkplain #replace(CoordinateSystem) coordinate system replacement}.
* If there is at least one of those, then this method returns a new
* coordinate reference system using the new datum and coordinate system.
*
* @param crs The coordinate reference system to replace.
* @return A new CRS, or {@code crs} if no change were needed.
* @throws FactoryException if an error occured while creating the new CRS object.
*/
// @Override
protected CoordinateReferenceSystem replace(final CoordinateReferenceSystem crs)
throws FactoryException
{
/*
* Gets the replaced coordinate system and datum, and checks if there is any change.
*/
final CoordinateSystem oldCS = crs.getCoordinateSystem();
final CoordinateSystem cs = replace(oldCS);
final Datum oldDatum, datum;
if (crs instanceof SingleCRS) {
oldDatum = ((SingleCRS) crs).getDatum();
datum = replace(oldDatum);
} else {
datum = oldDatum = null;
}
final boolean sameCS = Utilities.equals(cs, oldCS) && Utilities.equals(datum, oldDatum);
/*
* Creates a new coordinate reference system using the same properties
* than the original CRS, except for the coordinate system, datum and
* authority code.
*/
CoordinateReferenceSystem modified;
if (crs instanceof GeneralDerivedCRS) {
final GeneralDerivedCRS derivedCRS = (GeneralDerivedCRS) crs;
final CoordinateReferenceSystem oldBaseCRS = derivedCRS.getBaseCRS();
final CoordinateReferenceSystem baseCRS = replace(oldBaseCRS);
if (sameCS && Utilities.equals(baseCRS, oldBaseCRS)) {
return crs;
}
final Map<String,?> properties = getProperties(crs);
final ReferencingFactoryContainer factories = getFactoryContainer(true);
final CRSFactory crsFactory = factories.getCRSFactory();
Conversion fromBase = derivedCRS.getConversionFromBase();
fromBase = new DefiningConversion(getProperties(fromBase),
fromBase.getMethod(), fromBase.getParameterValues());
if (crs instanceof ProjectedCRS) {
modified = crsFactory.createProjectedCRS(properties,
(GeographicCRS) baseCRS, fromBase, (CartesianCS) cs);
} else {
// TODO: Need a createDerivedCRS method.
throw new FactoryException(Errors.format(
ErrorKeys.UNSUPPORTED_CRS_$1, crs.getName().getCode()));
}
} else if (sameCS) {
return crs;
} else {
final Map properties = getProperties(crs);
final ReferencingFactoryContainer factories = getFactoryContainer(true);
final CRSFactory crsFactory = factories.getCRSFactory();
if (crs instanceof GeographicCRS) {
modified = crsFactory.createGeographicCRS(properties,
(GeodeticDatum) datum, (EllipsoidalCS) cs);
} else if (crs instanceof GeocentricCRS) {
final GeodeticDatum gd = (GeodeticDatum) datum;
if (cs instanceof CartesianCS) {
modified = crsFactory.createGeocentricCRS(properties, gd, (CartesianCS) cs);
} else {
modified = crsFactory.createGeocentricCRS(properties, gd, (SphericalCS) cs);
}
} else if (crs instanceof VerticalCRS) {
modified = crsFactory.createVerticalCRS(properties,
(VerticalDatum) datum, (VerticalCS) cs);
} else if (crs instanceof TemporalCRS) {
modified = crsFactory.createTemporalCRS(properties,
(TemporalDatum) datum, (TimeCS) cs);
} else if (crs instanceof ImageCRS) {
modified = crsFactory.createImageCRS(properties,
(ImageDatum) datum, (AffineCS) cs);
} else if (crs instanceof EngineeringCRS) {
modified = crsFactory.createEngineeringCRS(properties,
(EngineeringDatum) datum, cs);
} else if (crs instanceof CompoundCRS) {
final List/* <CoordinateReferenceSystem> */elements =
((CompoundCRS) crs).getCoordinateReferenceSystems();
final CoordinateReferenceSystem[] m = new CoordinateReferenceSystem[elements.size()];
for (int i=0; i<m.length; i++) {
m[i] = replace((CoordinateReferenceSystem) elements.get(i));
}
modified = crsFactory.createCompoundCRS(properties, m);
} else {
throw new FactoryException(Errors.format(
ErrorKeys.UNSUPPORTED_CRS_$1, crs.getName().getCode()));
}
}
modified = (CoordinateReferenceSystem) pool.unique(modified);
return modified;
}
/**
* Replaces (if needed) the specified coordinate operation. The default
* implementation checks if there is a source or target
* {@linkplain #replace(CoordinateReferenceSystem) CRS replacement}. If
* there is at least one of those, then this method returns a new coordinate
* operation using the new CRS.
*
* @param operation The coordinate operation to replace.
* @return A new operation, or {@code operation} if no change were needed.
* @throws FactoryException if an error occured while creating the new operation object.
*/
// @Override
protected CoordinateOperation replace(final CoordinateOperation operation)
throws FactoryException
{
final CoordinateReferenceSystem oldSrcCRS = operation.getSourceCRS();
final CoordinateReferenceSystem oldTgtCRS = operation.getTargetCRS();
final CoordinateReferenceSystem sourceCRS = (oldSrcCRS != null) ? replace(oldSrcCRS) : null;
final CoordinateReferenceSystem targetCRS = (oldTgtCRS != null) ? replace(oldTgtCRS) : null;
if (Utilities.equals(oldSrcCRS, sourceCRS) && Utilities.equals(oldTgtCRS, targetCRS)) {
return operation;
}
if (opFactory == null) {
opFactory = getCoordinateOperationFactory();
}
CoordinateOperation modified;
modified = opFactory.createOperation(sourceCRS, targetCRS);
modified = (CoordinateOperation) pool.unique(modified);
return modified;
}
/**
* Creates a new coordinate system of the specified kind. This method is
* invoked automatically by {@link #replace(CoordinateSystem)} after it
* determined that the axis need to be changed.
*
* @param type The coordinate system type to create.
* @param properties The properties to gives to the new coordinate system.
* @param axis The axis to give to the new coordinate system. Subclasses are
* allowed to write directly in this array (no need to copy it).
* @return A new coordinate system of the specified kind with the specified axis.
* @throws FactoryException if the coordinate system can't be created.
*/
private CoordinateSystem createCS(final Class/* <CoordinateSystem> */type,
final Map properties,
final CoordinateSystemAxis[] axis)
throws FactoryException
{
final int dimension = axis.length;
final ReferencingFactoryContainer factories = getFactoryContainer(false);
final CSFactory csFactory = factories.getCSFactory();
if (CartesianCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 2: return csFactory.createCartesianCS(properties, axis[0], axis[1]);
case 3: return csFactory.createCartesianCS(properties, axis[0], axis[1], axis[2]);
}
} else if (EllipsoidalCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 2: return csFactory.createEllipsoidalCS(properties, axis[0], axis[1]);
case 3: return csFactory.createEllipsoidalCS(properties, axis[0], axis[1], axis[2]);
}
} else if (SphericalCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 3: return csFactory.createSphericalCS(properties, axis[0], axis[1], axis[2]);
}
} else if (CylindricalCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 3: return csFactory.createCylindricalCS(properties, axis[0], axis[1], axis[2]);
}
} else if (PolarCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 2: return csFactory.createPolarCS(properties, axis[0], axis[1]);
}
} else if (VerticalCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 1: return csFactory.createVerticalCS(properties, axis[0]);
}
} else if (TimeCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 1: return csFactory.createTimeCS(properties, axis[0]);
}
} else if (LinearCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 1: return csFactory.createLinearCS(properties, axis[0]);
}
} else if (UserDefinedCS.class.isAssignableFrom(type)) {
switch (dimension) {
case 2: return csFactory.createUserDefinedCS(properties, axis[0], axis[1]);
case 3: return csFactory.createUserDefinedCS(properties, axis[0], axis[1], axis[2]);
}
}
throw new FactoryException(Errors.format(ErrorKeys.UNSUPPORTED_COORDINATE_SYSTEM_$1, type));
}
/**
* Returns the properties to be given to an object replacing an original
* one. If the new object keep the same authority, then all metadata are
* preserved. Otherwise (i.e. if a new authority is given to the new
* object), then the old identifiers will be removed from the new object
* metadata.
*
* @param object The original object.
* @return The properties to be given to the object created as a substitute
* of {@code object}.
*/
private Map<String,?> getProperties(final IdentifiedObject object) {
final Citation authority = getAuthority();
if (!Utilities.equals(authority, object.getName().getAuthority())) {
return AbstractIdentifiedObject.getProperties(object, authority);
} else {
return AbstractIdentifiedObject.getProperties(object);
}
}
/**
* Creates an operation from coordinate reference system codes. The default
* implementation first invokes the same method from the
* {@linkplain #operationFactory underlying operation factory}, and next
* invokes {@link #replace(CoordinateOperation) replace} for each
* operations.
*/
public Set<CoordinateOperation> createFromCoordinateReferenceSystemCodes(
final String sourceCode, final String targetCode)
throws FactoryException
{
final Set/* <CoordinateOperation> */operations, modified;
operations = super.createFromCoordinateReferenceSystemCodes(sourceCode, targetCode);
modified = new LinkedHashSet((int) (operations.size() / 0.75f) + 1);
for (final Iterator it = operations.iterator(); it.hasNext();) {
final CoordinateOperation operation;
try {
operation = (CoordinateOperation) it.next();
} catch (BackingStoreException exception) {
final Throwable cause = exception.getCause();
if (cause instanceof FactoryException) {
throw (FactoryException) cause;
} else {
throw exception;
}
}
modified.add(replace(operation));
}
return modified;
}
/**
* Releases resources immediately instead of waiting for the garbage
* collector. This method do <strong>not</strong> dispose the resources of
* wrapped factories (e.g. {@link #crsFactory crsFactory}), because they may
* still in use by other classes.
*/
public synchronized void dispose() throws FactoryException {
pool.clear();
super.dispose();
}
}