001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2004, 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
020: import java.util.*;
021:
022: import javax.units.Unit;
023: import javax.units.ConversionException;
024:
025: // OpenGIS dependencies
026: import org.opengis.metadata.Identifier; // For javadoc
027: import org.opengis.parameter.ParameterValueGroup;
028: import org.opengis.referencing.IdentifiedObject;
029: import org.opengis.referencing.FactoryException;
030: import org.opengis.referencing.NoSuchIdentifierException;
031: import org.opengis.referencing.cs.*;
032: import org.opengis.referencing.crs.*;
033: import org.opengis.referencing.datum.*;
034: import org.opengis.referencing.operation.*;
035:
036: // Geotools dependencies
037: import org.geotools.factory.GeoTools;
038: import org.geotools.factory.Hints;
039: import org.geotools.factory.Factory;
040: import org.geotools.factory.FactoryCreator;
041: import org.geotools.factory.FactoryRegistry;
042: import org.geotools.parameter.Parameters;
043: import org.geotools.referencing.ReferencingFactoryFinder;
044: import org.geotools.referencing.AbstractIdentifiedObject;
045: import org.geotools.referencing.operation.DefiningConversion;
046: import org.geotools.referencing.operation.MathTransformProvider;
047: import org.geotools.referencing.operation.DefaultMathTransformFactory;
048: import org.geotools.referencing.operation.matrix.MatrixFactory;
049: import org.geotools.referencing.crs.DefaultProjectedCRS;
050: import org.geotools.referencing.crs.DefaultCompoundCRS;
051: import org.geotools.referencing.cs.AbstractCS;
052: import org.geotools.resources.i18n.ErrorKeys;
053: import org.geotools.resources.i18n.Errors;
054: import org.geotools.resources.CRSUtilities;
055: import org.geotools.resources.XArray;
056:
057: /**
058: * A set of utilities methods working on factories. Many of those methods requires more than
059: * one factory. Consequently, they can't be a method in a single factory. Furthermore, since
060: * they are helper methods and somewhat implementation-dependent, they are not part of GeoAPI.
061: *
062: * @since 2.4
063: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/factory/ReferencingFactoryContainer.java $
064: * @version $Id: ReferencingFactoryContainer.java 26090 2007-06-29 08:09:27Z desruisseaux $
065: * @author Martin Desruisseaux
066: */
067: public class ReferencingFactoryContainer extends ReferencingFactory {
068: /**
069: * A factory registry used as a cache for factory groups created up to date.
070: */
071: private static FactoryRegistry cache;
072:
073: /**
074: * The {@linkplain Datum datum} factory.
075: * If null, then a default factory will be created only when first needed.
076: */
077: private DatumFactory datumFactory;
078:
079: /**
080: * The {@linkplain CoordinateSystem coordinate system} factory.
081: * If null, then a default factory will be created only when first needed.
082: */
083: private CSFactory csFactory;
084:
085: /**
086: * The {@linkplain CoordinateReferenceSystem coordinate reference system} factory.
087: * If null, then a default factory will be created only when first needed.
088: */
089: private CRSFactory crsFactory;
090:
091: /**
092: * The {@linkplain MathTransform math transform} factory.
093: * If null, then a default factory will be created only when first needed.
094: */
095: private MathTransformFactory mtFactory;
096:
097: // WARNING: Do NOT put a CoordinateOperationFactory field in this class. We tried that in
098: // Geotools 2.2, and removed it in Geotools 2.3 because it leads to very tricky recursivity
099: // problems when we try to initialize it with FactoryFinder.getCoordinateOperationFactory.
100: // The Datum, CS, CRS and MathTransform factories above are standalone, while the Geotools
101: // implementation of CoordinateOperationFactory has complex dependencies with all of those,
102: // and even with authority factories.
103:
104: /**
105: * The operation method for the last CRS created, or {@code null} if none. This field
106: * may be the operation name as a {@link String} rather than a {@link OperationMethod}
107: * if the math transform was not created by a Geotools implementation of factory.
108: */
109: private final ThreadLocal/*<Object>*/lastMethod = new ThreadLocal();
110:
111: /**
112: * Constructs an instance using the specified factories. If any factory is null,
113: * a default instance will be created by {@link ReferencingFactoryFinder} when first needed.
114: *
115: * @param datumFactory The {@linkplain Datum datum} factory.
116: * @param csFactory The {@linkplain CoordinateSystem coordinate system} factory.
117: * @param crsFactory The {@linkplain CoordinateReferenceSystem coordinate reference system}
118: * factory.
119: * @param mtFactory The {@linkplain MathTransform math transform} factory.
120: *
121: * @deprecated Use {@link #createInstance} instead. The fate of this constructor is
122: * incertain. It may be removed in Geotools 2.4, or refactored as a new
123: * {@code createInstance} convenience method.
124: */
125: public ReferencingFactoryContainer(final DatumFactory datumFactory,
126: final CSFactory csFactory, final CRSFactory crsFactory,
127: final MathTransformFactory mtFactory) {
128: this .datumFactory = datumFactory;
129: this .csFactory = csFactory;
130: this .crsFactory = crsFactory;
131: this .mtFactory = mtFactory;
132: }
133:
134: /**
135: * Creates an instance from the specified hints. This constructor recognizes the
136: * {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
137: * and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
138: * <p>
139: * This constructor is public mainly for {@link org.geotools.factory.FactoryCreator} usage.
140: * Consider invoking <code>{@linkplain #createInstance createInstance}(userHints)</code> instead.
141: *
142: * @param userHints The hints, or {@code null} if none.
143: */
144: public ReferencingFactoryContainer(final Hints userHints) {
145: final Hints reduced = new Hints(userHints);
146: /*
147: * If hints are provided, we will fetch factory immediately (instead of storing the hints
148: * in an inner field) because most factories will retain few hints, while the Hints map
149: * may contains big objects. If no hints were provided, we will construct factories only
150: * when first needed.
151: */
152: datumFactory = (DatumFactory) extract(reduced,
153: Hints.DATUM_FACTORY);
154: csFactory = (CSFactory) extract(reduced, Hints.CS_FACTORY);
155: crsFactory = (CRSFactory) extract(reduced, Hints.CRS_FACTORY);
156: mtFactory = (MathTransformFactory) extract(reduced,
157: Hints.MATH_TRANSFORM_FACTORY);
158: /*
159: * Checks if we still have some hints that need to be taken in account. Since we can't guess
160: * which hints are relevant and which ones are not, we have to create all factories now.
161: */
162: if (!reduced.isEmpty()) {
163: setHintsInto(reduced);
164: hints.putAll(reduced);
165: initialize();
166: hints.clear();
167: }
168: }
169:
170: /**
171: * Returns the factory for the specified hint, or {@code null} if the hint is not a factory
172: * instance. It could be for example a {@link Class}.
173: */
174: private static Factory extract(final Map reduced,
175: final Hints.Key key) {
176: if (reduced != null) {
177: final Object candidate = reduced.get(key);
178: if (candidate instanceof Factory) {
179: reduced.remove(key);
180: return (Factory) candidate;
181: }
182: }
183: return null;
184: }
185:
186: /**
187: * Creates an instance from the specified hints. This method recognizes the
188: * {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
189: * and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
190: *
191: * @param hints The hints, or {@code null} if none.
192: * @return A factory group created from the specified set of hints.
193: */
194: public static ReferencingFactoryContainer instance(final Hints hints) {
195: final Hints completed = GeoTools.getDefaultHints();
196: if (hints != null) {
197: completed.add(hints);
198: }
199: /*
200: * Use the same synchronization lock than ReferencingFactoryFinder (instead of this class)
201: * in order to reduce the risk of dead lock. This is because ReferencingFactoryContainer
202: * creation may queries ReferencingFactoryFinder, and some implementations managed by
203: * ReferencingFactoryFinder may ask for a ReferencingFactoryContainer in turn.
204: */
205: synchronized (ReferencingFactoryFinder.class) {
206: if (cache == null) {
207: cache = new FactoryCreator(
208: Arrays
209: .asList(new Class[] { ReferencingFactoryContainer.class }));
210: cache.registerServiceProvider(
211: new ReferencingFactoryContainer(null),
212: ReferencingFactoryContainer.class);
213: }
214: return (ReferencingFactoryContainer) cache
215: .getServiceProvider(
216: ReferencingFactoryContainer.class, null,
217: completed, null);
218: }
219: }
220:
221: /**
222: * Forces the initialisation of all factories. Implementation note: we try to create the
223: * factories in typical dependency order (CRS all because it has the greatest chances to
224: * depends on other factories).
225: */
226: private void initialize() {
227: mtFactory = getMathTransformFactory();
228: datumFactory = getDatumFactory();
229: csFactory = getCSFactory();
230: crsFactory = getCRSFactory();
231: }
232:
233: /**
234: * Put all factories available in this group into the specified map of hints.
235: */
236: private void setHintsInto(final Map hints) {
237: if (crsFactory != null)
238: hints.put(Hints.CRS_FACTORY, crsFactory);
239: if (csFactory != null)
240: hints.put(Hints.CS_FACTORY, csFactory);
241: if (datumFactory != null)
242: hints.put(Hints.DATUM_FACTORY, datumFactory);
243: if (mtFactory != null)
244: hints.put(Hints.MATH_TRANSFORM_FACTORY, mtFactory);
245: }
246:
247: /**
248: * Returns all factories in this group. The returned map contains values for the
249: * {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS}, {@link Hints#DATUM_FACTORY DATUM}
250: * and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM} {@code FACTORY} hints.
251: */
252: public Map getImplementationHints() {
253: synchronized (hints) {
254: if (hints.isEmpty()) {
255: initialize();
256: setHintsInto(hints);
257: }
258: }
259: return super .getImplementationHints();
260: }
261:
262: /**
263: * Returns the hints to be used for lazy creation of <em>default</em> factories in various
264: * {@code getFoo} methods. This is different from {@link #getImplementationHints} because
265: * the later may returns non-default factories.
266: */
267: private Hints hints() {
268: final Hints completed = new Hints(hints);
269: setHintsInto(completed);
270: return completed;
271: }
272:
273: /**
274: * Returns the {@linkplain Datum datum} factory.
275: */
276: public DatumFactory getDatumFactory() {
277: if (datumFactory == null) {
278: synchronized (hints) {
279: datumFactory = ReferencingFactoryFinder
280: .getDatumFactory(hints());
281: }
282: }
283: return datumFactory;
284: }
285:
286: /**
287: * Returns the {@linkplain CoordinateSystem coordinate system} factory.
288: */
289: public CSFactory getCSFactory() {
290: if (csFactory == null) {
291: synchronized (hints) {
292: csFactory = ReferencingFactoryFinder
293: .getCSFactory(hints());
294: }
295: }
296: return csFactory;
297: }
298:
299: /**
300: * Returns the {@linkplain CoordinateReferenceSystem coordinate reference system} factory.
301: */
302: public CRSFactory getCRSFactory() {
303: if (crsFactory == null) {
304: synchronized (hints) {
305: crsFactory = ReferencingFactoryFinder
306: .getCRSFactory(hints());
307: }
308: }
309: return crsFactory;
310: }
311:
312: /**
313: * Returns the {@linkplain MathTransform math transform} factory.
314: */
315: public MathTransformFactory getMathTransformFactory() {
316: if (mtFactory == null) {
317: synchronized (hints) {
318: mtFactory = ReferencingFactoryFinder
319: .getMathTransformFactory(hints());
320: }
321: }
322: return mtFactory;
323: }
324:
325: /**
326: * Returns the operation method for the specified name.
327: * If the {@linkplain #getMathTransformFactory underlying math transform factory} is the
328: * {@linkplain DefaultMathTransformFactory Geotools implementation}, then this method just
329: * delegates the call to it. Otherwise this method scans all operations registered in the
330: * math transform factory until a match is found.
331: *
332: * @param name The case insensitive {@linkplain Identifier#getCode identifier code}
333: * of the operation method to search for (e.g. {@code "Transverse_Mercator"}).
334: * @return The operation method.
335: * @throws NoSuchIdentifierException if there is no operation method registered for the
336: * specified name.
337: *
338: * @see DefaultMathTransformFactory#getOperationMethod
339: */
340: public OperationMethod getOperationMethod(final String name)
341: throws NoSuchIdentifierException {
342: final MathTransformFactory mtFactory = getMathTransformFactory();
343: if (mtFactory instanceof DefaultMathTransformFactory) {
344: // Special processing for Geotools implementation.
345: return ((DefaultMathTransformFactory) mtFactory)
346: .getOperationMethod(name);
347: }
348: // Not a geotools implementation. Scan all methods.
349: final Set operations = mtFactory
350: .getAvailableMethods(Operation.class);
351: for (final Iterator it = operations.iterator(); it.hasNext();) {
352: final OperationMethod method = (OperationMethod) it.next();
353: if (AbstractIdentifiedObject.nameMatches(method, name)) {
354: return method;
355: }
356: }
357: throw new NoSuchIdentifierException(Errors.format(
358: ErrorKeys.NO_TRANSFORM_FOR_CLASSIFICATION_$1, name),
359: name);
360: }
361:
362: /**
363: * Returns the operation method for the last call to a {@code create} method in the currently
364: * running thread. This method may be invoked after any of the following methods:
365: * <p>
366: * <ul>
367: * <li>{@link #createParameterizedTransform}</li>
368: * <li>{@link #createBaseToDerived}</li>
369: * </ul>
370: *
371: * @return The operation method for the last call to a {@code create} method, or
372: * {@code null} if none.
373: *
374: * @see DefaultMathTransformFactory#getLastUsedMethod
375: */
376: public OperationMethod getLastUsedMethod() {
377: final Object candidate = lastMethod.get();
378: if (candidate instanceof OperationMethod) {
379: return (OperationMethod) candidate;
380: }
381: if (candidate instanceof String) {
382: /*
383: * The last math transform was not created by a Geotools implementation
384: * of the factory. Scans all methods until a match is found.
385: */
386: final MathTransformFactory mtFactory = getMathTransformFactory();
387: final Set operations = mtFactory
388: .getAvailableMethods(Operation.class);
389: final String classification = (String) candidate;
390: for (final Iterator it = operations.iterator(); it
391: .hasNext();) {
392: final OperationMethod method = (OperationMethod) it
393: .next();
394: if (AbstractIdentifiedObject.nameMatches(method
395: .getParameters(), classification)) {
396: lastMethod.set(method);
397: return method;
398: }
399: }
400: }
401: return null;
402: }
403:
404: /**
405: * Creates a transform from a group of parameters. This method delegates the work to the
406: * {@linkplain #getMathTransformFactory underlying math transform factory} and keep trace
407: * of the {@linkplain OperationMethod operation method} used. The later can be obtained
408: * by a call to {@link #getLastUsedMethod}.
409: *
410: * @param parameters The parameter values.
411: * @return The parameterized transform.
412: * @throws NoSuchIdentifierException if there is no transform registered for the method.
413: * @throws FactoryException if the object creation failed. This exception is thrown
414: * if some required parameter has not been supplied, or has illegal value.
415: *
416: * @see MathTransformFactory#createParameterizedTransform
417: */
418: public MathTransform createParameterizedTransform(
419: ParameterValueGroup parameters)
420: throws NoSuchIdentifierException, FactoryException {
421: // lastMethod.remove(); // TODO: uncomment when we will be allowed to target J2SE 1.5.
422: final MathTransformFactory mtFactory = getMathTransformFactory();
423: final MathTransform transform = mtFactory
424: .createParameterizedTransform(parameters);
425: if (mtFactory instanceof DefaultMathTransformFactory) {
426: // Special processing for Geotools implementation.
427: lastMethod.set(((DefaultMathTransformFactory) mtFactory)
428: .getLastUsedMethod());
429: } else {
430: // Not a geotools implementation. Will try to guess the method later.
431: lastMethod.set(parameters.getDescriptor().getName()
432: .getCode());
433: }
434: return transform;
435: }
436:
437: /**
438: * Creates a transform from a group of parameters and add the method used to a list.
439: * This variant of {@code createParameterizedTransform(...)} provides a way for
440: * the client to keep trace of any {@linkplain OperationMethod operation method}
441: * used by this factory.
442: *
443: * @param parameters The parameter values.
444: * @param methods A collection where to add the operation method that apply to the transform,
445: * or {@code null} if none.
446: * @return The parameterized transform.
447: * @throws NoSuchIdentifierException if there is no transform registered for the method.
448: * @throws FactoryException if the object creation failed. This exception is thrown
449: * if some required parameter has not been supplied, or has illegal value.
450: *
451: * @deprecated Replaced by {@link #createParameterizedTransform(ParameterValueGroup)}
452: * followed by a call to {@link #getLastUsedMethod}.
453: */
454: public MathTransform createParameterizedTransform(
455: ParameterValueGroup parameters, Collection methods)
456: throws NoSuchIdentifierException, FactoryException {
457: final MathTransform transform = createParameterizedTransform(parameters);
458: if (methods != null) {
459: methods.add(getLastUsedMethod());
460: }
461: return transform;
462: }
463:
464: /**
465: * Creates a {@linkplain #createParameterizedTransform parameterized transform} from a base
466: * CRS to a derived CS. If the {@code "semi_major"} and {@code "semi_minor"} parameters are
467: * not explicitly specified, they will be inferred from the {@linkplain Ellipsoid ellipsoid}
468: * and added to {@code parameters}. In addition, this method performs axis switch as needed.
469: * <p>
470: * The {@linkplain OperationMethod operation method} used can be obtained by a call to
471: * {@link #getLastUsedMethod}.
472: *
473: * @param baseCRS The source coordinate reference system.
474: * @param parameters The parameter values for the transform.
475: * @param derivedCS the target coordinate system.
476: * @return The parameterized transform.
477: * @throws NoSuchIdentifierException if there is no transform registered for the method.
478: * @throws FactoryException if the object creation failed. This exception is thrown
479: * if some required parameter has not been supplied, or has illegal value.
480: */
481: public MathTransform createBaseToDerived(
482: final CoordinateReferenceSystem baseCRS,
483: final ParameterValueGroup parameters,
484: final CoordinateSystem derivedCS)
485: throws NoSuchIdentifierException, FactoryException {
486: /*
487: * If the user's parameter do not contains semi-major and semi-minor axis length, infers
488: * them from the ellipsoid. This is a convenience service since the user often omit those
489: * parameters (because they duplicate datum information).
490: */
491: final Ellipsoid ellipsoid = CRSUtilities
492: .getHeadGeoEllipsoid(baseCRS);
493: if (ellipsoid != null) {
494: final Unit axisUnit = ellipsoid.getAxisUnit();
495: Parameters.ensureSet(parameters, "semi_major", ellipsoid
496: .getSemiMajorAxis(), axisUnit, false);
497: Parameters.ensureSet(parameters, "semi_minor", ellipsoid
498: .getSemiMinorAxis(), axisUnit, false);
499: }
500: /*
501: * Computes matrix for swapping axis and performing units conversion.
502: * There is one matrix to apply before projection on (longitude,latitude)
503: * coordinates, and one matrix to apply after projection on (easting,northing)
504: * coordinates.
505: */
506: final CoordinateSystem sourceCS = baseCRS.getCoordinateSystem();
507: final Matrix swap1, swap3;
508: try {
509: swap1 = AbstractCS.swapAndScaleAxis(sourceCS, AbstractCS
510: .standard(sourceCS));
511: swap3 = AbstractCS.swapAndScaleAxis(AbstractCS
512: .standard(derivedCS), derivedCS);
513: } catch (IllegalArgumentException cause) {
514: // User-specified axis don't match.
515: throw new FactoryException(cause);
516: } catch (ConversionException cause) {
517: // A Unit conversion is non-linear.
518: throw new FactoryException(cause);
519: }
520: /*
521: * Prepares the concatenation of the matrix computed above and the projection.
522: * Note that at this stage, the dimensions between each step may not be compatible.
523: * For example the projection (step2) is usually two-dimensional while the source
524: * coordinate system (step1) may be three-dimensional if it has a height.
525: */
526: MathTransformFactory mtFactory = getMathTransformFactory();
527: MathTransform step1 = mtFactory.createAffineTransform(swap1);
528: MathTransform step3 = mtFactory.createAffineTransform(swap3);
529: MathTransform step2 = createParameterizedTransform(parameters);
530: // IMPORTANT: From this point, 'createParameterizedTransform' should not be invoked
531: // anymore, directly or indirectly, in order to preserve the 'lastMethod'
532: // value. It will be checked by the last assert before return.
533: /*
534: * If the target coordinate system has a height, instructs the projection to pass
535: * the height unchanged from the base CRS to the target CRS. After this block, the
536: * dimensions of 'step2' and 'step3' should match.
537: */
538: final int numTrailingOrdinates = step3.getSourceDimensions()
539: - step2.getTargetDimensions();
540: if (numTrailingOrdinates > 0) {
541: step2 = mtFactory.createPassThroughTransform(0, step2,
542: numTrailingOrdinates);
543: }
544: /*
545: * If the source CS has a height but the target CS doesn't, drops the extra coordinates.
546: * After this block, the dimensions of 'step1' and 'step2' should match.
547: */
548: final int sourceDim = step1.getTargetDimensions();
549: final int targetDim = step2.getSourceDimensions();
550: if (sourceDim > targetDim) {
551: final Matrix drop = MatrixFactory.create(targetDim + 1,
552: sourceDim + 1);
553: drop.setElement(targetDim, sourceDim, 1);
554: step1 = mtFactory.createConcatenatedTransform(mtFactory
555: .createAffineTransform(drop), step1);
556: }
557: final MathTransform transform = mtFactory
558: .createConcatenatedTransform(mtFactory
559: .createConcatenatedTransform(step1, step2),
560: step3);
561: assert AbstractIdentifiedObject.nameMatches(parameters
562: .getDescriptor(), getLastUsedMethod());
563: return transform;
564: }
565:
566: /**
567: * Creates a {@linkplain #createParameterizedTransform parameterized transform} from a base
568: * CRS to a derived CS. If the <code>"semi_major"</code> and <code>"semi_minor"</code>
569: * parameters are not explicitly specified, they will be inferred from the
570: * {@linkplain Ellipsoid ellipsoid} and added to {@code parameters}.
571: * In addition, this method performs axis switch as needed.
572: *
573: * @param baseCRS The source coordinate reference system.
574: * @param parameters The parameter values for the transform.
575: * @param derivedCS the target coordinate system.
576: * @param methods A collection where to add the operation method that apply to the transform,
577: * or {@code null} if none.
578: * @return The parameterized transform.
579: * @throws NoSuchIdentifierException if there is no transform registered for the method.
580: * @throws FactoryException if the object creation failed. This exception is thrown
581: * if some required parameter has not been supplied, or has illegal value.
582: *
583: * @deprecated Replaced by {@link #createBaseToDerived}
584: * followed by a call to {@link #getLastUsedMethod}.
585: */
586: public MathTransform createBaseToDerived(
587: final CoordinateReferenceSystem baseCRS,
588: final ParameterValueGroup parameters,
589: final CoordinateSystem derivedCS, final Collection methods)
590: throws NoSuchIdentifierException, FactoryException {
591: final MathTransform transform = createBaseToDerived(baseCRS,
592: parameters, derivedCS);
593: if (methods != null) {
594: methods.add(getLastUsedMethod());
595: }
596: return transform;
597: }
598:
599: /**
600: * Creates a projected coordinate reference system from a conversion.
601: *
602: * @param properties Name and other properties to give to the new object.
603: * @param baseCRS Geographic coordinate reference system to base projection on.
604: * @param conversionFromBase The {@linkplain DefiningConversion defining conversion}.
605: * @param derivedCS The coordinate system for the projected CRS.
606: * @throws FactoryException if the object creation failed.
607: *
608: * @todo Current implementation creates directly a Geotools implementation, because there
609: * is not yet a suitable method in GeoAPI interfaces.
610: */
611: public ProjectedCRS createProjectedCRS(Map properties,
612: final GeographicCRS baseCRS,
613: final Conversion conversionFromBase,
614: final CartesianCS derivedCS) throws FactoryException {
615: final ParameterValueGroup parameters = conversionFromBase
616: .getParameterValues();
617: final MathTransform mt = createBaseToDerived(baseCRS,
618: parameters, derivedCS);
619: OperationMethod method = conversionFromBase.getMethod();
620: if (!(method instanceof MathTransformProvider)) {
621: /*
622: * Our Geotools implementation of DefaultProjectedCRS may not be able to detect
623: * the conversion type (PlanarProjection, CylindricalProjection, etc.) because
624: * we rely on the Geotools-specific MathTransformProvider for that. We will try
625: * to help it with the optional "conversionType" hint, providing that the user
626: * do not already provides this hint.
627: */
628: if (!properties
629: .containsKey(DefaultProjectedCRS.CONVERSION_TYPE_KEY)) {
630: method = getLastUsedMethod();
631: if (method instanceof MathTransformProvider) {
632: properties = new HashMap(properties);
633: properties.put(
634: DefaultProjectedCRS.CONVERSION_TYPE_KEY,
635: ((MathTransformProvider) method)
636: .getOperationType());
637: }
638: }
639: }
640: return new DefaultProjectedCRS(properties, conversionFromBase,
641: baseCRS, mt, derivedCS);
642: }
643:
644: /**
645: * Creates a projected coordinate reference system from a set of parameters. If the
646: * {@code "semi_major"} and {@code "semi_minor"} parameters are not explicitly specified,
647: * they will be inferred from the {@linkplain Ellipsoid ellipsoid} and added to the
648: * {@code parameters}. This method also checks for axis order and unit conversions.
649: *
650: * @param properties Name and other properties to give to the new object.
651: * @param baseCRS Geographic coordinate reference system to base projection on.
652: * @param method The operation method, or {@code null} for a default one.
653: * @param parameters The parameter values to give to the projection.
654: * @param derivedCS The coordinate system for the projected CRS.
655: * @throws FactoryException if the object creation failed.
656: */
657: public ProjectedCRS createProjectedCRS(Map properties,
658: GeographicCRS baseCRS, OperationMethod method,
659: ParameterValueGroup parameters, CartesianCS derivedCS)
660: throws FactoryException {
661: final MathTransform mt = createBaseToDerived(baseCRS,
662: parameters, derivedCS);
663: if (method == null) {
664: method = getLastUsedMethod();
665: }
666: return getCRSFactory().createProjectedCRS(properties, method,
667: baseCRS, mt, derivedCS);
668: }
669:
670: /**
671: * Converts a 2D + 1D compound CRS into a 3D CRS, if possible. More specifically,
672: * if the specified {@linkplain CompoundCRS compound CRS} is made of a
673: * {@linkplain GeographicCRS geographic} (or {@linkplain ProjectedCRS projected}) and a
674: * {@linkplain VerticalCRS vertical} CRS, and if the vertical CRS datum type is
675: * {@linkplain VerticalDatumType#ELLIPSOIDAL height above the ellipsoid}, then this method
676: * converts the compound CRS in a single 3D CRS. Otherwise, the {@code crs} argument is
677: * returned unchanged.
678: *
679: * @param crs The compound CRS to converts in a 3D geographic or projected CRS.
680: * @return The 3D geographic or projected CRS, or {@code crs} if the change can't be applied.
681: * @throws FactoryException if the object creation failed.
682: */
683: public CoordinateReferenceSystem toGeodetic3D(final CompoundCRS crs)
684: throws FactoryException {
685: final SingleCRS[] components = DefaultCompoundCRS
686: .getSingleCRS(crs);
687: SingleCRS horizontal = null;
688: VerticalCRS vertical = null;
689: int hi = 0, vi = 0;
690: for (int i = 0; i < components.length; i++) {
691: final SingleCRS candidate = components[i];
692: if (candidate instanceof VerticalCRS) {
693: if (vertical == null) {
694: vertical = (VerticalCRS) candidate;
695: if (VerticalDatumType.ELLIPSOIDAL.equals( // TODO: remove cast with J2SE 1.5.
696: ((VerticalDatum) vertical.getDatum())
697: .getVerticalDatumType())) {
698: vi = i;
699: continue;
700: }
701: }
702: return crs;
703: }
704: if (candidate instanceof GeographicCRS
705: || candidate instanceof ProjectedCRS) {
706: if (horizontal == null) {
707: horizontal = (SingleCRS) candidate;
708: if (horizontal.getCoordinateSystem().getDimension() == 2) {
709: hi = i;
710: continue;
711: }
712: }
713: return crs;
714: }
715: }
716: if (horizontal != null && vertical != null
717: && Math.abs(vi - hi) == 1) {
718: /*
719: * Exactly one horizontal and one vertical CRS has been found, and those two CRS are
720: * consecutives. Constructs the new 3D CS. If the two above-cited components are the
721: * only one, the result is returned directly. Otherwise, a new compound CRS is created.
722: */
723: final boolean classic = (hi < vi);
724: final SingleCRS single = toGeodetic3D(
725: components.length == 2 ? crs : null, horizontal,
726: vertical, classic);
727: if (components.length == 2) {
728: return single;
729: }
730: final CoordinateReferenceSystem[] c = new CoordinateReferenceSystem[components.length - 1];
731: final int i = classic ? hi : vi;
732: System.arraycopy(components, 0, c, 0, i);
733: c[i] = single;
734: System.arraycopy(components, i + 2, c, i + 1,
735: components.length - (i + 2));
736: return crsFactory.createCompoundCRS(
737: AbstractIdentifiedObject.getProperties(crs), c);
738: }
739: return crs;
740: }
741:
742: /**
743: * Implementation of {@link #toGeodetic3D(CompoundCRS)} invoked after the horizontal and
744: * vertical parts have been identified. This method may invokes itself recursively if the
745: * horizontal CRS is a derived one.
746: *
747: * @param crs The compound CRS to converts in a 3D geographic CRS, or {@code null}.
748: * Used only in order to infer the name properties of objects to create.
749: * @param horizontal The horizontal component of {@code crs}.
750: * @param vertical The vertical component of {@code crs}.
751: * @param classic {@code true} if the horizontal component appears before the vertical
752: * component, or {@code false} for the converse.
753: * @return The 3D geographic or projected CRS.
754: * @throws FactoryException if the object creation failed.
755: */
756: private SingleCRS toGeodetic3D(final CompoundCRS crs,
757: final SingleCRS horizontal, final VerticalCRS vertical,
758: final boolean classic) throws FactoryException {
759: final CoordinateSystemAxis[] axis = new CoordinateSystemAxis[3];
760: final CoordinateSystem cs = horizontal.getCoordinateSystem();
761: axis[classic ? 0 : 1] = cs.getAxis(0);
762: axis[classic ? 1 : 2] = cs.getAxis(1);
763: axis[classic ? 2 : 0] = vertical.getCoordinateSystem().getAxis(
764: 0);
765: final Map csName, crsName;
766: if (crs != null) {
767: csName = AbstractIdentifiedObject.getProperties(crs
768: .getCoordinateSystem());
769: crsName = AbstractIdentifiedObject.getProperties(crs);
770: } else {
771: csName = getTemporaryName(cs);
772: crsName = getTemporaryName(horizontal);
773: }
774: final CSFactory csFactory = getCSFactory();
775: final CRSFactory crsFactory = getCRSFactory();
776: final SingleCRS single;
777: if (horizontal instanceof GeographicCRS) {
778: /*
779: * Merges a 2D geographic CRS with the vertical CRS.
780: */
781: single = crsFactory.createGeographicCRS(crsName,
782: (GeodeticDatum) horizontal.getDatum(), csFactory
783: .createEllipsoidalCS(csName, axis[0],
784: axis[1], axis[2]));
785: } else if (horizontal instanceof ProjectedCRS) {
786: /*
787: * Merges a 2D projected CRS with the vertical CRS.
788: */
789: final ProjectedCRS projected = (ProjectedCRS) horizontal;
790: GeographicCRS baseCRS = (GeographicCRS) projected
791: .getBaseCRS();
792: baseCRS = (GeographicCRS) toGeodetic3D(null, baseCRS,
793: vertical, classic);
794: final Conversion projection = projected
795: .getConversionFromBase();
796: single = createProjectedCRS(crsName, baseCRS, projection,
797: csFactory.createCartesianCS(csName, axis[0],
798: axis[1], axis[2]));
799: } else {
800: // Should never happen.
801: throw new AssertionError(horizontal);
802: }
803: return single;
804: }
805:
806: /**
807: * Returns a new coordinate reference system with only the specified dimension.
808: * This method is used for example in order to get a component of a
809: * {@linkplain CompoundCRS compound CRS}.
810: *
811: * @param crs The original (usually compound) CRS.
812: * @param dimensions The dimensions to keep.
813: * @return The CRS with only the specified dimensions.
814: */
815: public CoordinateReferenceSystem separate(
816: final CoordinateReferenceSystem crs, final int[] dimensions)
817: throws FactoryException {
818: final int length = dimensions.length;
819: final int crsDimension = crs.getCoordinateSystem()
820: .getDimension();
821: if (length == 0 || dimensions[0] < 0
822: || dimensions[length - 1] >= crsDimension
823: || !XArray.isStrictlySorted(dimensions)) {
824: throw new IllegalArgumentException(Errors.format(
825: ErrorKeys.ILLEGAL_ARGUMENT_$1, "dimension"));
826: }
827: if (length == crsDimension) {
828: return crs;
829: }
830: /*
831: * If the CRS is a compound one, separate each components independently.
832: * For each component, we search the sub-array of 'dimensions' that apply
833: * to this component and invoke 'separate' recursively.
834: */
835: if (crs instanceof CompoundCRS) {
836: int count = 0, lowerDimension = 0, lowerIndex = 0;
837: final List/*<CoordinateReferenceSystem>*/sources;
838: final CoordinateReferenceSystem[] targets;
839: sources = ((CompoundCRS) crs)
840: .getCoordinateReferenceSystems();
841: targets = new CoordinateReferenceSystem[sources.size()];
842: search: for (final Iterator it = sources.iterator(); it
843: .hasNext();) {
844: final CoordinateReferenceSystem source = (CoordinateReferenceSystem) it
845: .next();
846: final int upperDimension = lowerDimension
847: + source.getCoordinateSystem().getDimension();
848: /*
849: * 'source' CRS applies to dimension 'lowerDimension' inclusive to 'upperDimension'
850: * exclusive. Now search the smallest range in the user-specified 'dimensions' that
851: * cover the [lowerDimension .. upperDimension] range.
852: */
853: if (lowerIndex == dimensions.length) {
854: break search;
855: }
856: while (dimensions[lowerIndex] < lowerDimension) {
857: if (++lowerIndex == dimensions.length) {
858: break search;
859: }
860: }
861: int upperIndex = lowerIndex;
862: while (dimensions[upperIndex] < upperDimension) {
863: if (++upperIndex == dimensions.length) {
864: break;
865: }
866: }
867: if (lowerIndex != upperIndex) {
868: final int[] sub = new int[upperIndex - lowerIndex];
869: for (int j = 0; j < sub.length; j++) {
870: sub[j] = dimensions[j + lowerIndex]
871: - lowerDimension;
872: }
873: targets[count++] = separate(source, sub);
874: }
875: lowerDimension = upperDimension;
876: lowerIndex = upperIndex;
877: }
878: if (count == 1) {
879: return targets[0];
880: }
881: return getCRSFactory().createCompoundCRS(
882: getTemporaryName(crs),
883: (CoordinateReferenceSystem[]) XArray.resize(
884: targets, count));
885: }
886: /*
887: * TODO: Implement other cases here (3D-GeographicCRS, etc.).
888: * It may requires the creation of new CoordinateSystem objects,
889: * which is why this method live in ReferencingFactoryContainer.
890: */
891: throw new FactoryException(Errors
892: .format(ErrorKeys.CANT_SEPARATE_CRS_$1, crs.getName()
893: .getCode()));
894: }
895:
896: /**
897: * Returns a temporary name for object derived from the specified one.
898: */
899: private static Map getTemporaryName(final IdentifiedObject source) {
900: return Collections.singletonMap(IdentifiedObject.NAME_KEY,
901: source.getName().getCode() + " (3D)");
902: }
903: }
|