/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2004-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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.geotoolkit.referencing.factory; import java.util.*; import java.awt.RenderingHints; import javax.measure.IncommensurableException; import javax.measure.quantity.Length; import javax.measure.Unit; import org.opengis.referencing.cs.*; import org.opengis.referencing.crs.*; import org.opengis.referencing.datum.*; import org.opengis.referencing.operation.*; import org.opengis.referencing.IdentifiedObject; import org.opengis.metadata.Identifier; import org.opengis.metadata.citation.Citation; import org.opengis.util.FactoryException; import org.opengis.util.Factory; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.Utilities; import org.geotoolkit.factory.Hints; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.factory.FactoryRegistry; import org.geotoolkit.factory.DynamicFactoryRegistry; import org.geotoolkit.internal.referencing.Identifier3D; import org.geotoolkit.metadata.Citations; import org.apache.sis.internal.metadata.VerticalDatumTypes; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.metadata.iso.ImmutableIdentifier; import org.geotoolkit.referencing.cs.Axes; import org.geotoolkit.referencing.CRS; import org.geotoolkit.resources.Errors; import org.apache.sis.measure.Units; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.crs.AbstractCRS; import org.apache.sis.referencing.cs.AxesConvention; import org.apache.sis.referencing.cs.CoordinateSystems; import org.apache.sis.referencing.operation.DefaultConversion; import static org.geotoolkit.internal.FactoryUtilities.addImplementationHints; /** * * A container of factories frequently used together, with utility methods. * This class serves two purpose: * <p> * <ol> * <li>A container for the following factories: * <ul> * <li>{@link DatumFactory}</li> * <li>{@link CSFactory}</li> * <li>{@link CRSFactory}</li> * <li>{@link MathTransformFactory}</li> * </ul></li> * <li>Utilities methods for creating new CRS derived from an existing one.</li> * </ol> * * {@note The <code>CoordinateOperationFactory</code> factory is intentionally excluded from * this container, because <code>CoordinateOperationFactory</code> is more a processing class * than a factory creating directly objects from the parameters.} * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.16 * * @since 2.1 * @level advanced * @module */ @Deprecated public class ReferencingFactoryContainer extends org.geotoolkit.factory.Factory implements Factory { // "ReferencingFactoryContainer" name is LGPL. /** * 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 Geotk // implementation of CoordinateOperationFactory has complex dependencies to all of those, // and even with authority factories. /** * 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} for the system-wide default. * @return A factory group created from the specified set of hints. */ public static ReferencingFactoryContainer instance(Hints hints) { if (hints == null) { hints = new Hints(); // Get the system-wide default hints. } /* * Use the same synchronization lock than FactoryFinder (instead of this class) in order * to reduce the risk of dead lock. This is because ReferencingFactoryContainer creation * may queries FactoryFinder, and some implementations managed by FactoryFinder may ask * for a ReferencingFactoryContainer in turn. */ synchronized (FactoryFinder.class) { if (cache == null) { cache = new DynamicFactoryRegistry(ReferencingFactoryContainer.class); cache.registerServiceProvider(new ReferencingFactoryContainer(null), ReferencingFactoryContainer.class); } return cache.getServiceProvider(ReferencingFactoryContainer.class, null, hints, null); } } /** * 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.geotoolkit.factory.DynamicFactoryRegistry} * usage. Consider invoking <code>{@linkplain #instance instance}(userHints)</code> instead. * * @param userHints The hints, or {@code null} if none (<strong>not</strong> the * system-wide default; factory constructors consume the hints verbatism). */ public ReferencingFactoryContainer(Hints userHints) { if (userHints != null) { userHints = userHints.clone(); /* * If some factories were explicitly specified, fetch them immediately (no lazy * instantiation for them). Any factory not explicitly specified will be left to * null for now. */ datumFactory = (DatumFactory) getFactory(userHints, Hints.DATUM_FACTORY); csFactory = (CSFactory) getFactory(userHints, Hints.CS_FACTORY); crsFactory = (CRSFactory) getFactory(userHints, Hints.CRS_FACTORY); mtFactory = (MathTransformFactory) getFactory(userHints, Hints.MATH_TRANSFORM_FACTORY); /* * Remove the entries consumed in the search for factories. We can do that only after * the search is done for all factories. If there is no remaining hints, then it doesn't * matter if some fields are still null. If there is some remaining hints, then we need * to fetch all factories that were not fetched by the above code because we can't guess * which hints are relevant and which ones are not. */ if (datumFactory != null) userHints.remove(Hints.DATUM_FACTORY); if (csFactory != null) userHints.remove(Hints.CS_FACTORY); if (crsFactory != null) userHints.remove(Hints.CRS_FACTORY); if (mtFactory != null) userHints.remove(Hints.MATH_TRANSFORM_FACTORY); if (!userHints.isEmpty()) { declaredFactoryHints(userHints); // Adds the hints for the factories we fetched above. addImplementationHints(userHints, hints); // Copies temporarily the result in super.hints map. fetchAllFactories(); // Forces fetching of remaining factories. hints.clear(); // Cancel addImplementationHints(userHints). } } } /** * 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}). The given hints are left untouched. */ private static Factory getFactory(final Map<?,?> hints, final Hints.Key key) { final Object candidate = hints.get(key); return (candidate instanceof Factory) ? (Factory) candidate : null; } /** * Forces the initialization of all factories. This method is invoked in one of the * following situations: * <p> * <ul> * <li>When we need to consume all hints specified at construction time.</li> * <li>When we need to resolve all hints used by the factories.</li> * </ul> * * {@note We try to create the factories in typical dependency order, with the CRS factory * last because it has the greatest chances to depends on other factories.} */ private void fetchAllFactories() { mtFactory = getMathTransformFactory(); datumFactory = getDatumFactory(); csFactory = getCSFactory(); crsFactory = getCRSFactory(); } /** * Puts all available factories into the specified map of hints. This method is invoked * before or after {@link #fetchAllFactories}, depending on whatever we are consuming or * resolving hints. */ private void declaredFactoryHints(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 the vendor responsible for creating this factory implementation. * Many implementations from different vendors may be available for the same * factory interface. * <p> * The default for Geotk implementations is to return * {@linkplain Citations#GEOTOOLKIT Geotoolkit.org}. * * @return The vendor for this factory implementation. * * @see Citations#GEOTOOLKIT */ @Override public Citation getVendor() { return getClass().getName().startsWith("org.geotoolkit.") ? Citations.GEOTOOLKIT : Citations.UNKNOWN; } /** * 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 synchronized Map<RenderingHints.Key, ?> getImplementationHints() { if (hints.isEmpty()) { fetchAllFactories(); declaredFactoryHints(hints); } return super.getImplementationHints(); } /** * Returns the hints to be used for lazy creation of <em>default</em> factories in various * {@code getFoo} methods. At the difference of {@link #getImplementationHints}, this method * do not force fetching of factories that were not already obtained. */ private Hints getCurrentHints() { final Hints completed = EMPTY_HINTS.clone(); assert Thread.holdsLock(this); completed.putAll(hints); declaredFactoryHints(completed); return completed; } /** * Returns the {@linkplain Datum datum} factory. * * @return The Datum factory. */ public synchronized DatumFactory getDatumFactory() { if (datumFactory == null) { datumFactory = FactoryFinder.getDatumFactory(getCurrentHints()); } return datumFactory; } /** * Returns the {@linkplain CoordinateSystem coordinate system} factory. * * @return The Coordinate System factory. */ public synchronized CSFactory getCSFactory() { if (csFactory == null) { csFactory = FactoryFinder.getCSFactory(getCurrentHints()); } return csFactory; } /** * Returns the {@linkplain CoordinateReferenceSystem coordinate reference system} factory. * * @return The Coordinate Reference System factory. */ public synchronized CRSFactory getCRSFactory() { if (crsFactory == null) { crsFactory = FactoryFinder.getCRSFactory(getCurrentHints()); } return crsFactory; } /** * Returns the {@linkplain MathTransform math transform} factory. * * @return The Math Transform factory. */ public synchronized MathTransformFactory getMathTransformFactory() { if (mtFactory == null) { mtFactory = FactoryFinder.getMathTransformFactory(getCurrentHints()); } return mtFactory; } /** * Adds an ellipsoidal height to the given CRS, if not already presents. This method accepts * only {@linkplain GeographicCRS geographic} or {@linkplain ProjectedCRS projected} CRS. If * the given CRS is already three-dimensional, then it is returned unchanged. Otherwise an * ellipsoidal height <em>in the same units than the {@linkplain Ellipsoid#getAxisUnit() * ellipsoid axis units}</em> (typically metres) is appended as the third dimension of the * given CRS, and the resulting three-dimensional CRS is returned. * * @param crs The geographic or projected CRS to make three-dimensional. * @return The given CRS with an ellipsoidal height. * @throws FactoryException If an ellipsoidal height can not be added to the given CRS. * * @since 3.16 */ @SuppressWarnings("fallthrough") public SingleCRS toGeodetic3D(SingleCRS crs) throws FactoryException { if (crs instanceof GeographicCRS || crs instanceof ProjectedCRS) { switch (crs.getCoordinateSystem().getDimension()) { case 2: { CoordinateSystemAxis vertical = Axes.ELLIPSOIDAL_HEIGHT; final Unit<Length> units = ((GeodeticDatum) crs.getDatum()).getEllipsoid().getAxisUnit(); if (!Units.METRE.equals(units)) { vertical = getCSFactory().createCoordinateSystemAxis( IdentifiedObjects.getProperties(vertical), vertical.getAbbreviation(), vertical.getDirection(), units); } crs = toGeodetic3D(null, crs, vertical, true); // Fall through } case 3: { return crs; } } } throw new FactoryException(Errors.format(Errors.Keys.UnsupportedCrs_1, crs.getName())); } /** * Returns a CRS equivalents to the given one, with some components replaced by their 3D * counterpart when possible. More specifically, if the given compound CRS contains two * consecutive {@linkplain CompoundCRS#getComponents() components CRS} where: * <p> * <ul> * <li>One component is a two-dimensional {@linkplain GeographicCRS geographic} or * {@linkplain ProjectedCRS projected} CRS;</li> * <li>The other component is a one-dimensional {@linkplain VerticalCRS vertical} CRS * and its datum type is {@code ELLIPSOIDAL} (height above the ellipsoid)</li> * </ul> * <p> * Then this method replaces those two components by a single three-dimensional component. * The other components (for example {@linkplain TemporalCRS temporal} CRS) are included * unchanged in the returned CRS. * <p> * If there is no (2D + 1D) components that this method could replace by a 3D component, * then this method returns the {@code crs} argument unchanged. In any case, the transform * from the given CRS to the returned CRS shall be an identity transform. * * @param crs The CRS in which to replace (2D + 1D) components by 3D components. * @return A CRS equivalents to the given one with the replacements performed, if any. * @throws FactoryException If a new CRS needs to be created and the call to its factory * method failed. */ public CoordinateReferenceSystem toGeodetic3D(final CompoundCRS crs) throws FactoryException { SingleCRS horizontal = null; VerticalCRS vertical = null; int hi = -2, vi = -2; // Initial condition: Math.abs(vi-hi)!=1 even when hi==0 or vi==0. /* * Get a copy of the components list and iterate in reverse order, * because we may remove elements from that list while iterating. */ final List<SingleCRS> components = new ArrayList<>(org.apache.sis.referencing.CRS.getSingleComponents(crs)); final int count = components.size(); for (int i=count; --i>=0;) { final SingleCRS component = components.get(i); if (component instanceof GeographicCRS || component instanceof ProjectedCRS) { if (component.getCoordinateSystem().getDimension() == 2) { horizontal = component; hi = i; } } else if (component instanceof VerticalCRS) { final VerticalCRS candidate = (VerticalCRS) component; if (VerticalDatumTypes.ELLIPSOIDAL.equals(candidate.getDatum().getVerticalDatumType())) { vertical = candidate; vi = i; } } if (Math.abs(vi - hi) == 1) { /* * Found a horizontal and a vertical CRS, and those two CRS are consecutive. Replace * them by a new 3D CRS, and continue the search in case new (2D, 1D) pairs are found. */ final boolean xyFirst = (hi < vi); final int iMin = xyFirst ? hi : vi; components.remove(iMin); components.set(iMin, toGeodetic3D(CRS.getCompoundCRS(crs, horizontal, vertical), horizontal, vertical.getCoordinateSystem().getAxis(0), xyFirst)); } } /* * At this point, all replacements (if any) have been performed. If the length of the * component array didn't changed, then no replacement were performed and the original * CRS can be returned unchanged. */ final int r = components.size(); if (r == 1) { return components.get(0); } else if (r == count) { return crs; } else { return getCRSFactory().createCompoundCRS(IdentifiedObjects.getProperties(crs), components.toArray(new SingleCRS[components.size()])); } } /** * Implementation of {@link #toGeodetic3D(CompoundCRS)} invoked after the horizontal and * vertical parts have been identified. This method may invoke itself recursively if the * horizontal CRS is a derived one. * * @param crs The compound CRS to convert to 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 axis 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 CoordinateSystemAxis vertical, final boolean xyFirst) throws FactoryException { /* * Creates the set of axes 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; Map<String,?> csName, crsName; if (crs != null) { csName = IdentifiedObjects.getProperties(crs.getCoordinateSystem()); crsName = IdentifiedObjects.getProperties(crs); } else { csName = getTemporaryName(cs, " (3D)"); crsName = getTemporaryName(horizontal, " (3D)"); } crsName = Identifier3D.addHorizontalCRS(crsName, 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, true); final Matrix append = toStandard(sourceCRS, false); final MathTransformFactory mtFactory = getMathTransformFactory(); Conversion projection = sourceCRS.getConversionFromBase(); MathTransform mt = projection.getMathTransform(); mt = mtFactory.createConcatenatedTransform( mtFactory.createConcatenatedTransform( mtFactory.createAffineTransform(prepend), mt), mtFactory.createAffineTransform(append)); mt = mtFactory.createPassThroughTransform(0, mt, 1); projection = new DefaultConversion(IdentifiedObjects.getProperties(projection), projection.getMethod(), mt, null); return crsFactory.createProjectedCRS(crsName, base3D, projection, targetCS); } // Should never happen. throw new AssertionError(horizontal.getClass()); } /** * Delegates to {@link AbstractCS#standard(CoordinateSystem)}. * * @param crs The CRS from which to extract the CS to standardize. * @param inverse {@code true} for the transform from the standardized to the non-standardized * CS, instead of the usual opposite way. * @return The matrix of an affine transform performing the requested standardization. */ private static Matrix toStandard(final CoordinateReferenceSystem crs, final boolean inverse) throws FactoryException { Exception failure; try { final CoordinateSystem sourceCS = crs.getCoordinateSystem(); final CoordinateSystem targetCS = CoordinateSystems.replaceAxes(sourceCS, AxesConvention.NORMALIZED); if (inverse) { return CoordinateSystems.swapAndScaleAxes(targetCS, sourceCS); } else { return CoordinateSystems.swapAndScaleAxes(sourceCS, targetCS); } } catch (IllegalArgumentException | IncommensurableException e) { failure = e; } throw new FactoryException(Errors.format(Errors.Keys.UnsupportedCrs_1, crs.getName()), failure); } /** * Returns a new coordinate reference system with only the specified dimension. This method can * be used for example in order to get a component of a {@linkplain CompoundCRS compound CRS}. * * @todo The current implementation has incomplete support of 3D Geographic CRS. * See <a href="http://jira.geotoolkit.org/browse/GEOTK-129">GEOTK-129</a>. * * @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. * * @see org.apache.sis.referencing.CRS#getComponentAt(CoordinateReferenceSystem, int, int) */ public CoordinateReferenceSystem separate(final CoordinateReferenceSystem crs, final int... dimensions) throws FactoryException { final int length = dimensions.length; final CoordinateSystem cs = crs.getCoordinateSystem(); final int crsDimension = cs.getDimension(); if (length == 0 || dimensions[0] < 0 || dimensions[length-1] >= crsDimension || !ArraysExt.isSorted(dimensions, true)) { throw new IllegalArgumentException(Errors.format( Errors.Keys.IllegalArgument_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 = ((CompoundCRS) crs).getComponents(); final CoordinateReferenceSystem[] 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, " (subset)"), ArraysExt.resize(targets, count)); } /* * Three-dimensional geographic CRS. * * TODO: needs a better inspection of axis order. */ if (crsDimension == 3 && (cs instanceof EllipsoidalCS) && (crs instanceof GeodeticCRS)) { switch (dimensions.length) { case 1: { if (dimensions[0] == 2) { return CommonCRS.Vertical.ELLIPSOIDAL.crs(); } break; } case 2: { if (dimensions[0] == 0 && dimensions[1] == 1) { if (Utilities.equalsIgnoreMetadata(crs, AbstractCRS.castOrCopy(CommonCRS.WGS84.geographic3D()).forConvention(AxesConvention.RIGHT_HANDED))) { // Common case. return CommonCRS.WGS84.normalizedGeographic(); } return getCRSFactory().createGeographicCRS(getTemporaryName(crs, " (subset)"), ((GeodeticCRS) crs).getDatum(), getCSFactory().createEllipsoidalCS(getTemporaryName(cs, " (subset)"), cs.getAxis(0), cs.getAxis(1))); } break; } } } /* * 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( Errors.Keys.CantSeparateCrs_1, crs.getName().getCode())); } /** * Returns a temporary name for object derived from the specified one. */ private static Map<String,?> getTemporaryName(final IdentifiedObject source, final String suffix) { final Identifier id = source.getName(); return Collections.singletonMap(IdentifiedObject.NAME_KEY, new ImmutableIdentifier(null, // Null because we are inventing a code. id.getCodeSpace(), id.getCode() + suffix)); } }