001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.referencing.operation;
018:
019: // J2SE dependencies
020: import java.util.Map;
021: import java.util.Set;
022: import java.util.List;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.logging.Level;
026: import java.util.logging.LogRecord;
027:
028: // OpenGIS dependencies
029: import org.opengis.metadata.Identifier;
030: import org.opengis.metadata.citation.Citation;
031: import org.opengis.referencing.AuthorityFactory;
032: import org.opengis.referencing.FactoryException;
033: import org.opengis.referencing.NoSuchAuthorityCodeException;
034: import org.opengis.referencing.crs.CoordinateReferenceSystem;
035: import org.opengis.referencing.operation.*;
036:
037: // Geotools dependencies
038: import org.geotools.factory.Hints;
039: import org.geotools.factory.OptionalFactory;
040: import org.geotools.factory.FactoryRegistryException;
041: import org.geotools.referencing.AbstractIdentifiedObject;
042: import org.geotools.referencing.ReferencingFactoryFinder;
043: import org.geotools.referencing.factory.BackingStoreException;
044: import org.geotools.resources.i18n.Logging;
045: import org.geotools.resources.i18n.LoggingKeys;
046:
047: /**
048: * A {@linkplain CoordinateOperationFactory coordinate operation factory} extended with the extra
049: * informations provided by an {@linkplain CoordinateOperationAuthorityFactory authority factory}.
050: * Such authority factory may help to find transformation paths not available otherwise (often
051: * determined from empirical parameters). Authority factories can also provide additional
052: * informations like the
053: * {@linkplain CoordinateOperation#getValidArea area of validity},
054: * {@linkplain CoordinateOperation#getScope scope} and
055: * {@linkplain CoordinateOperation#getPositionalAccuracy positional accuracy}.
056: * <p>
057: * When <code>{@linkplain #createOperation createOperation}(sourceCRS, targetCRS)</code> is invoked,
058: * {@code AuthorityBackedFactory} fetch the authority codes for source and target CRS and submits
059: * them to the {@linkplain #getAuthorityFactory underlying authority factory} through a call to its
060: * <code>{@linkplain CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes
061: * createFromCoordinateReferenceSystemCodes}(sourceCode, targetCode)</code> method. If the
062: * authority factory doesn't know about the specified CRS, then the default (standalone)
063: * process from the super-class is used as a fallback.
064: *
065: * @since 2.2
066: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/AuthorityBackedFactory.java $
067: * @version $Id: AuthorityBackedFactory.java 25485 2007-05-11 19:12:35Z desruisseaux $
068: * @author Martin Desruisseaux
069: */
070: public class AuthorityBackedFactory extends
071: DefaultCoordinateOperationFactory implements OptionalFactory {
072: /**
073: * The priority level for this factory.
074: */
075: static final int PRIORITY = DefaultCoordinateOperationFactory.PRIORITY + 10;
076:
077: /**
078: * The default authority factory to use.
079: */
080: private static final String DEFAULT_AUTHORITY = "EPSG";
081:
082: /**
083: * The authority factory to use for creating new operations.
084: * If {@code null}, a default factory will be fetched when first needed.
085: */
086: private CoordinateOperationAuthorityFactory authorityFactory;
087:
088: /**
089: * Used as a guard against infinite recursivity.
090: */
091: private final ThreadLocal/*<Boolean>*/processing = new ThreadLocal();
092:
093: /**
094: * Creates a new factory backed by a default EPSG authority factory.
095: * This factory will uses a priority slightly higher than the
096: * {@linkplain DefaultCoordinateOperationFactory default (standalone) factory}.
097: */
098: public AuthorityBackedFactory() {
099: this (null);
100: }
101:
102: /**
103: * Creates a new factory backed by an authority factory fetched using the specified hints.
104: * This constructor recognizes the {@link Hints#CRS_FACTORY CRS}, {@link Hints#CS_FACTORY CS},
105: * {@link Hints#DATUM_FACTORY DATUM} and {@link Hints#MATH_TRANSFORM_FACTORY MATH_TRANSFORM}
106: * {@code FACTORY} hints.
107: *
108: * @param userHints The hints, or {@code null} if none.
109: */
110: public AuthorityBackedFactory(Hints userHints) {
111: super (userHints, PRIORITY);
112: /*
113: * Removes the hint processed by the super-class. This include hints like
114: * LENIENT_DATUM_SHIFT, which usually don't apply to authority factories.
115: * An other way to see this is to said that this class "consumed" the hints.
116: * By removing them, we increase the chances to get an empty map of remaining hints,
117: * which in turn help to get the default CoordinateOperationAuthorityFactory
118: * (instead of forcing a new instance).
119: */
120: userHints = new Hints(userHints);
121: userHints.keySet().removeAll(hints.keySet());
122: if (!userHints.isEmpty()) {
123: noForce(userHints);
124: authorityFactory = ReferencingFactoryFinder
125: .getCoordinateOperationAuthorityFactory(
126: DEFAULT_AUTHORITY, userHints);
127: }
128: }
129:
130: /**
131: * Makes sure that every {@code FORCE_*} hints are set to false. We do that because we want
132: * {@link CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes} to
133: * returns coordinate operations straight from the EPSG database; we don't want an instance
134: * like {@link org.geotools.referencing.factory.OrderedAxisAuthorityFactory}. Axis swapping
135: * are performed by {@link #createFromDatabase} in this class <strong>after</strong> we invoked
136: * {@link CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes}. An
137: * {@code OrderedAxisAuthorityFactory} instance in this class would be in the way and cause
138: * an infinite recursivity.
139: *
140: * @see http://jira.codehaus.org/browse/GEOT-1161
141: */
142: private static void noForce(final Hints userHints) {
143: userHints.put(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER,
144: Boolean.FALSE);
145: userHints.put(Hints.FORCE_STANDARD_AXIS_DIRECTIONS,
146: Boolean.FALSE);
147: userHints.put(Hints.FORCE_STANDARD_AXIS_UNITS, Boolean.FALSE);
148: }
149:
150: /**
151: * Returns the underlying coordinate operation authority factory.
152: */
153: protected CoordinateOperationAuthorityFactory getAuthorityFactory() {
154: /*
155: * No need to synchronize. This is not a big deal if FactoryFinder is invoked twice.
156: * Actually, we should not synchronize at all. All methods from the super-class are
157: * thread-safe without synchronized statements, and we should preserve this advantage
158: * in order to reduce the risk of thread lock.
159: */
160: if (authorityFactory == null) {
161: /*
162: * Factory creation at this stage will happen only if null hints were specified at
163: * construction time, which explain why it is correct to use {@link FactoryFinder}
164: * with null hints here.
165: */
166: final Hints hints = new Hints(null);
167: noForce(hints);
168: authorityFactory = ReferencingFactoryFinder
169: .getCoordinateOperationAuthorityFactory(
170: DEFAULT_AUTHORITY, hints);
171: }
172: return authorityFactory;
173: }
174:
175: /**
176: * Returns an operation for conversion or transformation between two coordinate reference
177: * systems. The default implementation extracts the authority code from the supplied
178: * {@code sourceCRS} and {@code targetCRS}, and submit them to the
179: * <code>{@linkplain CoordinateOperationAuthorityFactory#createFromCoordinateReferenceSystemCodes
180: * createFromCoordinateReferenceSystemCodes}(sourceCode, targetCode)</code> methods.
181: * If no operation is found for those codes, then this method returns {@code null}.
182: * <p>
183: * Note that this method may be invoked recursively. For example no operation may be available
184: * from the {@linkplain #getAuthorityFactory underlying authority factory} between two
185: * {@linkplain org.opengis.referencing.crs.CompoundCRS compound CRS}, but an operation
186: * may be available between two components of those compound CRS.
187: *
188: * @param sourceCRS Input coordinate reference system.
189: * @param targetCRS Output coordinate reference system.
190: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}, or {@code null}
191: * if no such operation is explicitly defined in the underlying database.
192: *
193: * @since 2.3
194: */
195: // @Override
196: protected CoordinateOperation createFromDatabase(
197: final CoordinateReferenceSystem sourceCRS,
198: final CoordinateReferenceSystem targetCRS) {
199: /*
200: * Safety check against recursivity: returns null if the given source and target CRS
201: * are already under examination by a previous call to this method. Note: there is no
202: * need to synchronize since the Boolean is thread-local.
203: */
204: if (Boolean.TRUE.equals(processing.get())) {
205: return null;
206: }
207: /*
208: * Now performs the real work.
209: */
210: final CoordinateOperationAuthorityFactory authorityFactory = getAuthorityFactory();
211: final Citation authority = authorityFactory.getAuthority();
212: final Identifier sourceID = AbstractIdentifiedObject
213: .getIdentifier(sourceCRS, authority);
214: if (sourceID == null) {
215: return null;
216: }
217: final Identifier targetID = AbstractIdentifiedObject
218: .getIdentifier(targetCRS, authority);
219: if (targetID == null) {
220: return null;
221: }
222: final String sourceCode = sourceID.getCode().trim();
223: final String targetCode = targetID.getCode().trim();
224: if (sourceCode.equals(targetCode)) {
225: /*
226: * NOTE: This check is mandatory because this method may be invoked in some situations
227: * where (sourceCode == targetCode) but (sourceCRS != targetCRS). Such situation
228: * should be illegal (or at least the MathTransform from sourceCRS to targetCRS
229: * should be the identity transform), but unfortunatly it still happen because
230: * EPSG defines axis order as (latitude,longitude) for geographic CRS while most
231: * softwares expect (longitude,latitude) no matter what the EPSG authority said.
232: * We will need to computes a transform from sourceCRS to targetCRS ignoring the
233: * source and target codes. The superclass can do that, providing that we prevent
234: * the authority database to (legitimately) claims that the transformation from
235: * sourceCode to targetCode is the identity transform. See GEOT-854.
236: */
237: return null;
238: }
239: final boolean inverse;
240: Set operations;
241: try {
242: operations = authorityFactory
243: .createFromCoordinateReferenceSystemCodes(
244: sourceCode, targetCode);
245: inverse = (operations == null || operations.isEmpty());
246: if (inverse) {
247: /*
248: * No operation from 'source' to 'target' available. But maybe there is an inverse
249: * operation. This is typically the case when the user wants to convert from a
250: * projected to a geographic CRS. The EPSG database usually contains transformation
251: * paths for geographic to projected CRS only.
252: */
253: operations = authorityFactory
254: .createFromCoordinateReferenceSystemCodes(
255: targetCode, sourceCode);
256: }
257: } catch (NoSuchAuthorityCodeException exception) {
258: /*
259: * sourceCode or targetCode is unknow to the underlying authority factory.
260: * Ignores the exception and fallback on the generic algorithm provided by
261: * the super-class.
262: */
263: return null;
264: } catch (FactoryException exception) {
265: /*
266: * Other kind of error. It may be more serious, but the super-class is capable
267: * to provides a raisonable default behavior. Log as a warning and lets continue.
268: */
269: log(exception, authorityFactory);
270: return null;
271: }
272: if (operations != null) {
273: for (final Iterator it = operations.iterator(); it
274: .hasNext();) {
275: CoordinateOperation candidate;
276: try {
277: candidate = (CoordinateOperation) it.next();
278: if (candidate == null) {
279: continue;
280: }
281: if (inverse) {
282: candidate = inverse(candidate);
283: }
284: } catch (NoninvertibleTransformException e) {
285: // The transform is non invertible. Do not log any error message, since it
286: // may be a normal failure - the transform is not required to be invertible.
287: continue;
288: } catch (FactoryException exception) {
289: // Other kind of error. Log a warning and try the next coordinate operation.
290: log(exception, authorityFactory);
291: continue;
292: } catch (BackingStoreException exception) {
293: log(exception, authorityFactory);
294: continue;
295: }
296: /*
297: * It is possible that the Identifier in user's CRS is not quite right. For
298: * example the user may have created his source and target CRS from WKT using
299: * a different axis order than the official one and still call it "EPSG:xxxx"
300: * as if it were the official CRS. Checks if the source and target CRS for the
301: * operation just created are really the same (ignoring metadata) than the one
302: * specified by the user.
303: */
304: CoordinateReferenceSystem source = candidate
305: .getSourceCRS();
306: CoordinateReferenceSystem target = candidate
307: .getTargetCRS();
308: try {
309: final MathTransform prepend, append;
310: if (!equalsIgnoreMetadata(sourceCRS, source))
311: try {
312: processing.set(Boolean.TRUE);
313: prepend = createOperation(sourceCRS, source)
314: .getMathTransform();
315: source = sourceCRS;
316: } finally {
317: processing.set(Boolean.FALSE);
318: // TODO: use processing.remove() when we will be allowed to compile for J2SE 1.5.
319: }
320: else {
321: prepend = null;
322: }
323: if (!equalsIgnoreMetadata(target, targetCRS))
324: try {
325: processing.set(Boolean.TRUE);
326: append = createOperation(target, targetCRS)
327: .getMathTransform();
328: target = targetCRS;
329: } finally {
330: processing.set(Boolean.FALSE);
331: // TODO: use processing.remove() when we will be allowed to compile for J2SE 1.5.
332: }
333: else {
334: append = null;
335: }
336: candidate = transform(source, prepend, candidate,
337: append, target);
338: } catch (FactoryException exception) {
339: /*
340: * We have been unable to create a transform from the user-provided CRS to the
341: * authority-provided CRS. In theory, the two CRS should have been the same and
342: * the transform would have been the identity transform. In practice, it is not
343: * always the case because of axis swapping issue (see GEOT-854). The transform
344: * that we just tried to create in the two previous calls to the createOperation
345: * method should have been merely an affine transform for swapping axis. If they
346: * failed, then we are likely to fail for all other transforms provided in the
347: * database. So stop the loop now (at the very least, do not log the same
348: * warning for every pass of this loop!)
349: */
350: log(exception, authorityFactory);
351: return null;
352: }
353: if (accept(candidate)) {
354: return candidate;
355: }
356: }
357: }
358: return null;
359: }
360:
361: /**
362: * Appends or prepends the specified math transforms to the
363: * {@linkplain CoordinateOperation#getMathTransform operation math transform}.
364: * The new coordinate operation (if any) will share the same metadata
365: * than the original operation, including the authority code.
366: * <p>
367: * This method is used in order to change axis order when the user-specified CRS
368: * disagree with the authority-supplied CRS.
369: *
370: * @param sourceCRS The source CRS to give to the new operation.
371: * @param prepend The transform to prepend to the operation math transform.
372: * @param operation The operation in which to prepend the math transforms.
373: * @param append The transform to append to the operation math transform.
374: * @param targetCRS The target CRS to give to the new operation.
375: * @return A new operation, or {@code operation} if {@code prepend} and {@code append} were
376: * nulls or identity transforms.
377: * @throws FactoryException if the operation can't be constructed.
378: */
379: private CoordinateOperation transform(
380: final CoordinateReferenceSystem sourceCRS,
381: final MathTransform prepend,
382: final CoordinateOperation operation,
383: final MathTransform append,
384: final CoordinateReferenceSystem targetCRS)
385: throws FactoryException {
386: if ((prepend == null || prepend.isIdentity())
387: && (append == null || append.isIdentity())) {
388: return operation;
389: }
390: final Map properties = AbstractIdentifiedObject
391: .getProperties(operation);
392: /*
393: * In the particular case of concatenated operations, we can not prepend or append a math
394: * transform to the operation as a whole (the math transform for a concatenated operation
395: * is computed automatically as the concatenation of the math transform from every single
396: * operations, and we need to stay consistent with that). Instead, we prepend to the first
397: * single operation and append to the last single operation.
398: */
399: if (operation instanceof ConcatenatedOperation) {
400: final List/*<CoordinateOperation>*/c = ((ConcatenatedOperation) operation)
401: .getOperations();
402: final CoordinateOperation[] op = (CoordinateOperation[]) c
403: .toArray(new CoordinateOperation[c.size()]);
404: if (op.length != 0) {
405: final CoordinateOperation first = op[0];
406: if (op.length == 1) {
407: op[0] = transform(sourceCRS, prepend, first,
408: append, targetCRS);
409: } else {
410: final CoordinateOperation last = op[op.length - 1];
411: op[0] = transform(sourceCRS, prepend, first, null,
412: first.getTargetCRS());
413: op[op.length - 1] = transform(last.getSourceCRS(),
414: null, last, append, targetCRS);
415: }
416: return createConcatenatedOperation(properties, op);
417: }
418: }
419: /*
420: * Single operation case.
421: */
422: MathTransform transform = operation.getMathTransform();
423: final MathTransformFactory mtFactory = getMathTransformFactory();
424: if (prepend != null) {
425: transform = mtFactory.createConcatenatedTransform(prepend,
426: transform);
427: }
428: if (append != null) {
429: transform = mtFactory.createConcatenatedTransform(
430: transform, append);
431: }
432: assert !transform.equals(operation.getMathTransform()) : transform;
433: final Class type = AbstractCoordinateOperation
434: .getType(operation);
435: final OperationMethod method = (operation instanceof Operation) ? ((Operation) operation)
436: .getMethod()
437: : null;
438: return createFromMathTransform(properties, sourceCRS,
439: targetCRS, transform, method, type);
440: }
441:
442: /**
443: * Logs a warning when an object can't be created from the specified factory.
444: */
445: private static void log(final Exception exception,
446: final AuthorityFactory factory) {
447: final LogRecord record = Logging.format(Level.WARNING,
448: LoggingKeys.CANT_CREATE_COORDINATE_OPERATION_$1,
449: factory.getAuthority().getTitle());
450: record.setSourceClassName(AuthorityBackedFactory.class
451: .getName());
452: record.setSourceMethodName("createFromDatabase");
453: record.setThrown(exception);
454: LOGGER.log(record);
455: }
456:
457: /**
458: * Returns {@code true} if the specified operation is acceptable. This method is invoked
459: * automatically by <code>{@linkplain #createFromDatabase createFromDatabase}(...)</code>
460: * for every operation candidates found. The default implementation returns always {@code
461: * true}. Subclasses should override this method if they wish to filter the coordinate
462: * operations to be returned.
463: *
464: * @since 2.3
465: */
466: protected boolean accept(final CoordinateOperation operation) {
467: return true;
468: }
469:
470: /**
471: * Returns {@code true} if this factory and its underlying
472: * {@linkplain #getAuthorityFactory authority factory} are available for use.
473: */
474: public boolean isAvailable() {
475: try {
476: final CoordinateOperationAuthorityFactory authorityFactory = getAuthorityFactory();
477: if (authorityFactory instanceof OptionalFactory) {
478: return ((OptionalFactory) authorityFactory)
479: .isAvailable();
480: }
481: return true;
482: } catch (FactoryRegistryException exception) {
483: // No factory found. Ignore the exception since it is the
484: // purpose of this method to figure out this kind of case.
485: return false;
486: }
487: }
488: }
|