/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2004-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.*;
import java.awt.RenderingHints;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchIdentifierException;
import org.opengis.referencing.cs.*;
import org.opengis.referencing.crs.*;
import org.opengis.referencing.datum.*;
import org.opengis.referencing.operation.*;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.factory.Factory;
import org.geotools.factory.FactoryCreator;
import org.geotools.factory.FactoryRegistry;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.AbstractIdentifiedObject;
import org.geotools.referencing.operation.DefiningConversion;
import org.geotools.referencing.operation.DefaultMathTransformFactory;
import org.geotools.referencing.crs.DefaultCompoundCRS;
import org.geotools.referencing.cs.AbstractCS;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.XArray;
/**
* A set of utilities methods working on factories. Many of those methods requires more than
* one factory. Consequently, they can't be a method in a single factory. Furthermore, since
* they are helper methods and somewhat implementation-dependent, they are not part of GeoAPI.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
public class ReferencingFactoryContainer extends ReferencingFactory {
/**
* A factory registry used as a cache for factory groups created up to date.
*/
private static FactoryRegistry cache;
/**
* The {@linkplain Datum datum} factory.
* If null, then a default factory will be created only when first needed.
*/
private DatumFactory datumFactory;
/**
* The {@linkplain CoordinateSystem coordinate system} factory.
* If null, then a default factory will be created only when first needed.
*/
private CSFactory csFactory;
/**
* The {@linkplain CoordinateReferenceSystem coordinate reference system} factory.
* If null, then a default factory will be created only when first needed.
*/
private CRSFactory crsFactory;
/**
* The {@linkplain MathTransform math transform} factory.
* If null, then a default factory will be created only when first needed.
*/
private MathTransformFactory mtFactory;
// WARNING: Do NOT put a CoordinateOperationFactory field in this class. We tried that in
// Geotools 2.2, and removed it in Geotools 2.3 because it leads to very tricky recursivity
// problems when we try to initialize it with FactoryFinder.getCoordinateOperationFactory.
// The Datum, CS, CRS and MathTransform factories above are standalone, while the Geotools
// implementation of CoordinateOperationFactory has complex dependencies with all of those,
// and even with authority factories.
/**
* Creates an instance from the specified hints. This constructor recognizes the
* {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
* and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
* <p>
* This constructor is public mainly for {@link org.geotools.factory.FactoryCreator} usage.
* Consider invoking <code>{@linkplain #createInstance createInstance}(userHints)</code> instead.
*
* @param userHints The hints, or {@code null} if none.
*/
public ReferencingFactoryContainer(final Hints userHints) {
final Hints reduced = new Hints(userHints);
/*
* If hints are provided, we will fetch factory immediately (instead of storing the hints
* in an inner field) because most factories will retain few hints, while the Hints map
* may contains big objects. If no hints were provided, we will construct factories only
* when first needed.
*/
datumFactory = (DatumFactory) extract(reduced, Hints.DATUM_FACTORY);
csFactory = (CSFactory) extract(reduced, Hints.CS_FACTORY);
crsFactory = (CRSFactory) extract(reduced, Hints.CRS_FACTORY);
mtFactory = (MathTransformFactory) extract(reduced, Hints.MATH_TRANSFORM_FACTORY);
/*
* Checks if we still have some hints that need to be taken in account. Since we can't guess
* which hints are relevant and which ones are not, we have to create all factories now.
*/
if (!reduced.isEmpty()) {
setHintsInto(reduced);
addImplementationHints(reduced);
initialize();
hints.clear();
}
}
/**
* Returns the factory for the specified hint, or {@code null} if the hint is not a factory
* instance. It could be for example a {@link Class}.
*/
private static Factory extract(final Map<?,?> reduced, final Hints.Key key) {
if (reduced != null) {
final Object candidate = reduced.get(key);
if (candidate instanceof Factory) {
reduced.remove(key);
return (Factory) candidate;
}
}
return null;
}
/**
* Creates an instance from the specified hints. This method recognizes the
* {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
* and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
*
* @param hints The hints, or {@code null} if none.
* @return A factory group created from the specified set of hints.
*/
public static ReferencingFactoryContainer instance(final Hints hints) {
final Hints completed = GeoTools.getDefaultHints();
if (hints != null) {
completed.add(hints);
}
/*
* Use the same synchronization lock than ReferencingFactoryFinder (instead of this class)
* in order to reduce the risk of dead lock. This is because ReferencingFactoryContainer
* creation may queries ReferencingFactoryFinder, and some implementations managed by
* ReferencingFactoryFinder may ask for a ReferencingFactoryContainer in turn.
*/
synchronized (ReferencingFactoryFinder.class) {
if (cache == null) {
cache = new FactoryCreator(Arrays.asList(new Class<?>[] {
ReferencingFactoryContainer.class
}));
cache.registerServiceProvider(new ReferencingFactoryContainer(null),
ReferencingFactoryContainer.class);
}
return cache.getServiceProvider(ReferencingFactoryContainer.class, null, completed, null);
}
}
/**
* Forces the initialisation of all factories. Implementation note: we try to create the
* factories in typical dependency order (CRS all because it has the greatest chances to
* depends on other factories).
*/
private void initialize() {
mtFactory = getMathTransformFactory();
datumFactory = getDatumFactory();
csFactory = getCSFactory();
crsFactory = getCRSFactory();
}
/**
* Put all factories available in this group into the specified map of hints.
*/
private void setHintsInto(final Map<? super RenderingHints.Key, Object> hints) {
if ( crsFactory != null) hints.put(Hints. CRS_FACTORY, crsFactory);
if ( csFactory != null) hints.put(Hints. CS_FACTORY, csFactory);
if (datumFactory != null) hints.put(Hints. DATUM_FACTORY, datumFactory);
if ( mtFactory != null) hints.put(Hints.MATH_TRANSFORM_FACTORY, mtFactory);
}
/**
* Returns all factories in this group. The returned map contains values for the
* {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
* and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
*/
@Override
public Map<RenderingHints.Key, ?> getImplementationHints() {
synchronized (hints) {
if (hints.isEmpty()) {
initialize();
setHintsInto(hints);
}
}
return super.getImplementationHints();
}
/**
* Returns the hints to be used for lazy creation of <em>default</em> factories in various
* {@code getFoo} methods. This is different from {@link #getImplementationHints} because
* the later may returns non-default factories.
*/
private Hints hints() {
final Hints completed = new Hints(hints);
setHintsInto(completed);
return completed;
}
/**
* Returns the {@linkplain Datum datum} factory.
*
* @return The Datum factory.
*/
public DatumFactory getDatumFactory() {
if (datumFactory == null) {
synchronized (hints) {
datumFactory = ReferencingFactoryFinder.getDatumFactory(hints());
}
}
return datumFactory;
}
/**
* Returns the {@linkplain CoordinateSystem coordinate system} factory.
*
* @return The Coordinate System factory.
*/
public CSFactory getCSFactory() {
if (csFactory == null) {
synchronized (hints) {
csFactory = ReferencingFactoryFinder.getCSFactory(hints());
}
}
return csFactory;
}
/**
* Returns the {@linkplain CoordinateReferenceSystem coordinate reference system} factory.
*
* @return The Coordinate Reference System factory.
*/
public CRSFactory getCRSFactory() {
if (crsFactory == null) {
synchronized (hints) {
crsFactory = ReferencingFactoryFinder.getCRSFactory(hints());
}
}
return crsFactory;
}
/**
* Returns the {@linkplain MathTransform math transform} factory.
*
* @return The Math Transform factory.
*/
public MathTransformFactory getMathTransformFactory() {
if (mtFactory == null) {
synchronized (hints) {
mtFactory = ReferencingFactoryFinder.getMathTransformFactory(hints());
}
}
return mtFactory;
}
/**
* Returns the operation method for the specified name.
* If the {@linkplain #getMathTransformFactory underlying math transform factory} is the
* {@linkplain DefaultMathTransformFactory Geotools implementation}, then this method just
* delegates the call to it. Otherwise this method scans all operations registered in the
* math transform factory until a match is found.
*
* @param name The case insensitive {@linkplain org.opengis.metadata.Identifier#getCode
* identifier code} of the operation method to search for
* (e.g. {@code "Transverse_Mercator"}).
* @return The operation method.
* @throws NoSuchIdentifierException if there is no operation method registered for the
* specified name.
*
* @see DefaultMathTransformFactory#getOperationMethod
*
* @deprecated Use {@link DefaultMathTransformFactory#getOperationMethod}. This method
* was inefficient for other implementations.
*/
@Deprecated
public OperationMethod getOperationMethod(final String name)
throws NoSuchIdentifierException
{
final MathTransformFactory mtFactory = getMathTransformFactory();
if (mtFactory instanceof DefaultMathTransformFactory) {
// Special processing for Geotools implementation.
return ((DefaultMathTransformFactory) mtFactory).getOperationMethod(name);
}
// Not a geotools implementation. Scan all methods.
for (final OperationMethod method : mtFactory.getAvailableMethods(Operation.class)) {
if (AbstractIdentifiedObject.nameMatches(method, name)) {
return method;
}
}
throw new NoSuchIdentifierException(Errors.format(
ErrorKeys.NO_TRANSFORM_FOR_CLASSIFICATION_$1, name), name);
}
/**
* Returns the operation method for the last call to a {@code create} method in the currently
* running thread. This method may be invoked after any of the following methods:
* <p>
* <ul>
* <li>{@link #createParameterizedTransform}</li>
* <li>{@link #createBaseToDerived}</li>
* </ul>
*
* @return The operation method for the last call to a {@code create} method, or
* {@code null} if none.
*
* @see MathTransformFactory#getLastMethodUsed
*
* @deprecated Moved to the {@link MathTransformFactory} interface.
*/
@Deprecated
public OperationMethod getLastUsedMethod() {
return getMathTransformFactory().getLastMethodUsed();
}
/**
* Creates a transform from a group of parameters. This method delegates the work to the
* {@linkplain #getMathTransformFactory underlying math transform factory} and keep trace
* of the {@linkplain OperationMethod operation method} used. The later can be obtained
* by a call to {@link #getLastUsedMethod}.
*
* @param parameters The parameter values.
* @return The parameterized transform.
* @throws NoSuchIdentifierException if there is no transform registered for the method.
* @throws FactoryException if the object creation failed. This exception is thrown
* if some required parameter has not been supplied, or has illegal value.
*
* @see MathTransformFactory#createParameterizedTransform
*
* @deprecated Use the {@link MathTransformFactory} interface instead.
*/
@Deprecated
public MathTransform createParameterizedTransform(ParameterValueGroup parameters)
throws NoSuchIdentifierException, FactoryException
{
return getMathTransformFactory().createParameterizedTransform(parameters);
}
/**
* Creates a {@linkplain #createParameterizedTransform parameterized transform} from a base
* CRS to a derived CS. If the {@code "semi_major"} and {@code "semi_minor"} parameters are
* not explicitly specified, they will be inferred from the {@linkplain Ellipsoid ellipsoid}
* and added to {@code parameters}. In addition, this method performs axis switch as needed.
* <p>
* The {@linkplain OperationMethod operation method} used can be obtained by a call to
* {@link #getLastUsedMethod}.
*
* @param baseCRS The source coordinate reference system.
* @param parameters The parameter values for the transform.
* @param derivedCS the target coordinate system.
* @return The parameterized transform.
* @throws NoSuchIdentifierException if there is no transform registered for the method.
* @throws FactoryException if the object creation failed. This exception is thrown
* if some required parameter has not been supplied, or has illegal value.
*
* @see MathTransformFactory#createBaseToDerived
*
* @deprecated Moved to the {@link MathTransformFactory} interface.
*/
@Deprecated
public MathTransform createBaseToDerived(final CoordinateReferenceSystem baseCRS,
final ParameterValueGroup parameters,
final CoordinateSystem derivedCS)
throws NoSuchIdentifierException, FactoryException
{
return getMathTransformFactory().createBaseToDerived(baseCRS, parameters, derivedCS);
}
/**
* Creates a projected coordinate reference system from a conversion.
*
* @param properties Name and other properties to give to the new object.
* @param baseCRS Geographic coordinate reference system to base projection on.
* @param conversionFromBase The {@linkplain DefiningConversion defining conversion}.
* @param derivedCS The coordinate system for the projected CRS.
* @throws FactoryException if the object creation failed.
*
* @deprecated Moved to the {@link CRSFactory} interface.
*/
@Deprecated
public ProjectedCRS createProjectedCRS(Map<String,?> properties,
final GeographicCRS baseCRS,
final Conversion conversionFromBase,
final CartesianCS derivedCS)
throws FactoryException
{
return getCRSFactory().createProjectedCRS(properties, baseCRS, conversionFromBase, derivedCS);
}
/**
* Creates a projected coordinate reference system from a set of parameters. If the
* {@code "semi_major"} and {@code "semi_minor"} parameters are not explicitly specified,
* they will be inferred from the {@linkplain Ellipsoid ellipsoid} and added to the
* {@code parameters}. This method also checks for axis order and unit conversions.
*
* @param properties Name and other properties to give to the new object.
* @param baseCRS Geographic coordinate reference system to base projection on.
* @param method The operation method, or {@code null} for a default one.
* @param parameters The parameter values to give to the projection.
* @param derivedCS The coordinate system for the projected CRS.
* @throws FactoryException if the object creation failed.
*
* @deprecated Use {@link CRSFactory#createDefiningConversion} followed by
* {@link CRSFactory#createProjectedCRS} instead.
*/
@Deprecated
public ProjectedCRS createProjectedCRS(Map<String,?> properties,
GeographicCRS baseCRS,
OperationMethod method,
ParameterValueGroup parameters,
CartesianCS derivedCS)
throws FactoryException
{
final MathTransform mt = createBaseToDerived(baseCRS, parameters, derivedCS);
if (method == null) {
method = getLastUsedMethod();
}
return ((ReferencingObjectFactory) getCRSFactory())
.createProjectedCRS(properties, method, baseCRS, mt, derivedCS);
}
/**
* Converts a 2D + 1D compound CRS into a 3D CRS, if possible. More specifically,
* if the specified {@linkplain CompoundCRS compound CRS} is made of a
* {@linkplain GeographicCRS geographic} (or {@linkplain ProjectedCRS projected}) and a
* {@linkplain VerticalCRS vertical} CRS, and if the vertical CRS datum type is
* {@linkplain VerticalDatumType#ELLIPSOIDAL height above the ellipsoid}, then this method
* converts the compound CRS in a single 3D CRS. Otherwise, the {@code crs} argument is
* returned unchanged.
*
* @param crs The compound CRS to converts in a 3D geographic or projected CRS.
* @return The 3D geographic or projected CRS, or {@code crs} if the change can't be applied.
* @throws FactoryException if the object creation failed.
*/
public CoordinateReferenceSystem toGeodetic3D(final CompoundCRS crs) throws FactoryException {
List<SingleCRS> components = DefaultCompoundCRS.getSingleCRS(crs);
final int count = components.size();
SingleCRS horizontal = null;
VerticalCRS vertical = null;
int hi=0, vi=0;
for (int i=0; i<count; i++) {
final SingleCRS candidate = components.get(i);
if (candidate instanceof VerticalCRS) {
if (vertical == null) {
vertical = (VerticalCRS) candidate;
if (VerticalDatumType.ELLIPSOIDAL.equals(
vertical.getDatum().getVerticalDatumType()))
{
vi = i;
continue;
}
}
return crs;
}
if (candidate instanceof GeographicCRS || candidate instanceof ProjectedCRS) {
if (horizontal == null) {
horizontal = candidate;
if (horizontal.getCoordinateSystem().getDimension() == 2) {
hi = i;
continue;
}
}
return crs;
}
}
if (horizontal != null && vertical != null && Math.abs(vi-hi) == 1) {
/*
* Exactly one horizontal and one vertical CRS has been found, and those two CRS are
* consecutives. Constructs the new 3D CS. If the two above-cited components are the
* only ones, the result is returned directly. Otherwise, a new compound CRS is created.
*/
final boolean xyFirst = (hi < vi);
final SingleCRS single =
toGeodetic3D(count == 2 ? crs : null, horizontal, vertical, xyFirst);
if (count == 2) {
return single;
}
final int i = xyFirst ? hi : vi;
components = new ArrayList<SingleCRS>(components);
components.remove(i);
components.set(i, single);
final SingleCRS[] c = components.toArray(new SingleCRS[components.size()]);
return crsFactory.createCompoundCRS(AbstractIdentifiedObject.getProperties(crs), c);
}
return crs;
}
/**
* Implementation of {@link #toGeodetic3D(CompoundCRS)} invoked after the horizontal and
* vertical parts have been identified. This method may invokes itself recursively if the
* horizontal CRS is a derived one.
*
* @param crs The compound CRS to converts in a 3D geographic CRS, or {@code null}.
* Used only in order to infer the name properties of objects to create.
* @param horizontal The horizontal component of {@code crs}.
* @param vertical The vertical component of {@code crs}.
* @param xyFirst {@code true} if the horizontal component appears before the vertical
* component, or {@code false} for the converse.
* @return The 3D geographic or projected CRS.
* @throws FactoryException if the object creation failed.
*/
private SingleCRS toGeodetic3D(final CompoundCRS crs, final SingleCRS horizontal,
final VerticalCRS vertical, final boolean xyFirst)
throws FactoryException
{
/*
* Creates the set of axis in an order which depends of the xyFirst argument.
* Then creates the property maps to be given to the object to be created.
* They are common to whatever CRS type this method will create.
*/
final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[3];
final CoordinateSystem cs = horizontal.getCoordinateSystem();
axis[xyFirst ? 0 : 1] = cs.getAxis(0);
axis[xyFirst ? 1 : 2] = cs.getAxis(1);
axis[xyFirst ? 2 : 0] = vertical.getCoordinateSystem().getAxis(0);
final Map<String,?> csName, crsName;
if (crs != null) {
csName = AbstractIdentifiedObject.getProperties(crs.getCoordinateSystem());
crsName = AbstractIdentifiedObject.getProperties(crs);
} else {
csName = getTemporaryName(cs);
crsName = getTemporaryName(horizontal);
}
final CSFactory csFactory = getCSFactory();
final CRSFactory crsFactory = getCRSFactory();
if (horizontal instanceof GeographicCRS) {
/*
* Merges a 2D geographic CRS with the vertical CRS. This is the easiest
* part - we just give the 3 axis all together to a new GeographicCRS.
*/
final GeographicCRS sourceCRS = (GeographicCRS) horizontal;
final EllipsoidalCS targetCS = csFactory.createEllipsoidalCS(csName, axis[0], axis[1], axis[2]);
return crsFactory.createGeographicCRS(crsName, sourceCRS.getDatum(), targetCS);
}
if (horizontal instanceof ProjectedCRS) {
/*
* Merges a 2D projected CRS with the vertical CRS. This part is more tricky,
* since we need a defining conversion which does not include axis swapping or
* unit conversions. We revert them with concatenation of "CS to standardCS"
* transform. The axis swapping will be added back by createProjectedCRS(...)
* but not in the same place (they will be performed sooner than they would be
* otherwise).
*/
final ProjectedCRS sourceCRS = (ProjectedCRS) horizontal;
final CartesianCS targetCS = csFactory.createCartesianCS(csName, axis[0], axis[1], axis[2]);
final GeographicCRS base2D = sourceCRS.getBaseCRS();
final GeographicCRS base3D = (GeographicCRS) toGeodetic3D(null, base2D, vertical, xyFirst);
final Matrix prepend = toStandard(base2D, false);
final Matrix append = toStandard(sourceCRS, true);
Conversion projection = sourceCRS.getConversionFromBase();
if (!prepend.isIdentity() || !append.isIdentity()) {
final MathTransformFactory mtFactory = getMathTransformFactory();
MathTransform mt = projection.getMathTransform();
mt = mtFactory.createConcatenatedTransform(
mtFactory.createConcatenatedTransform(
mtFactory.createAffineTransform(prepend), mt),
mtFactory.createAffineTransform(append));
projection = new DefiningConversion(AbstractCS.getProperties(projection),
projection.getMethod(), mt);
}
return crsFactory.createProjectedCRS(crsName, base3D, projection, targetCS);
}
// Should never happen.
throw new AssertionError(horizontal);
}
private static Matrix toStandard(final CoordinateReferenceSystem crs, final boolean inverse) {
final CoordinateSystem sourceCS = crs.getCoordinateSystem();
final CoordinateSystem targetCS = AbstractCS.standard(sourceCS);
if (inverse) {
return AbstractCS.swapAndScaleAxis(targetCS, sourceCS);
} else {
return AbstractCS.swapAndScaleAxis(sourceCS, targetCS);
}
}
/**
* Returns a new coordinate reference system with only the specified dimension.
* This method is used for example in order to get a component of a
* {@linkplain CompoundCRS compound CRS}.
*
* @param crs The original (usually compound) CRS.
* @param dimensions The dimensions to keep.
* @return The CRS with only the specified dimensions.
* @throws FactoryException if the given dimensions can not be isolated in the given CRS.
*/
public CoordinateReferenceSystem separate(final CoordinateReferenceSystem crs,
final int[] dimensions)
throws FactoryException
{
final int length = dimensions.length;
final int crsDimension = crs.getCoordinateSystem().getDimension();
if (length==0 || dimensions[0]<0 || dimensions[length-1]>=crsDimension ||
!XArray.isStrictlySorted(dimensions))
{
throw new IllegalArgumentException(
Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$1, "dimension"));
}
if (length == crsDimension) {
return crs;
}
/*
* If the CRS is a compound one, separate each components independently.
* For each component, we search the sub-array of 'dimensions' that apply
* to this component and invoke 'separate' recursively.
*/
if (crs instanceof CompoundCRS) {
int count=0, lowerDimension=0, lowerIndex=0;
final List<CoordinateReferenceSystem> sources;
final CoordinateReferenceSystem[] targets;
sources = ((CompoundCRS) crs).getCoordinateReferenceSystems();
targets = new CoordinateReferenceSystem[sources.size()];
search: for (final CoordinateReferenceSystem source : sources) {
final int upperDimension = lowerDimension + source.getCoordinateSystem().getDimension();
/*
* 'source' CRS applies to dimension 'lowerDimension' inclusive to 'upperDimension'
* exclusive. Now search the smallest range in the user-specified 'dimensions' that
* cover the [lowerDimension .. upperDimension] range.
*/
if (lowerIndex == dimensions.length) {
break search;
}
while (dimensions[lowerIndex] < lowerDimension) {
if (++lowerIndex == dimensions.length) {
break search;
}
}
int upperIndex = lowerIndex;
while (dimensions[upperIndex] < upperDimension) {
if (++upperIndex == dimensions.length) {
break;
}
}
if (lowerIndex != upperIndex) {
final int[] sub = new int[upperIndex - lowerIndex];
for (int j=0; j<sub.length; j++) {
sub[j] = dimensions[j+lowerIndex] - lowerDimension;
}
targets[count++] = separate(source, sub);
}
lowerDimension = upperDimension;
lowerIndex = upperIndex;
}
if (count == 1) {
return targets[0];
}
return getCRSFactory().createCompoundCRS(getTemporaryName(crs),
XArray.resize(targets, count));
}
/*
* TODO: Implement other cases here (3D-GeographicCRS, etc.).
* It may requires the creation of new CoordinateSystem objects,
* which is why this method live in ReferencingFactoryContainer.
*/
throw new FactoryException(Errors.format(ErrorKeys.CANT_SEPARATE_CRS_$1,
crs.getName().getCode()));
}
/**
* Returns a temporary name for object derived from the specified one.
*/
private static Map<String,?> getTemporaryName(final IdentifiedObject source) {
return Collections.singletonMap(IdentifiedObject.NAME_KEY,
source.getName().getCode() + " (3D)");
}
}