001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.referencing.operation;
017:
018: // J2SE dependencies
019: import java.util.Map;
020: import java.util.Iterator;
021:
022: // OpenGIS dependencies
023: import org.opengis.referencing.FactoryException;
024: import org.opengis.referencing.crs.CoordinateReferenceSystem;
025: import org.opengis.referencing.operation.CoordinateOperation;
026: import org.opengis.referencing.operation.CoordinateOperationFactory;
027: import org.opengis.referencing.operation.OperationMethod;
028: import org.opengis.referencing.operation.OperationNotFoundException;
029:
030: // Geotools dependencies
031: import org.geotools.factory.Hints;
032: import org.geotools.factory.BufferedFactory;
033: import org.geotools.resources.Utilities;
034: import org.geotools.util.SoftValueHashMap;
035: import org.geotools.referencing.ReferencingFactoryFinder;
036:
037: /**
038: * Caches the {@linkplain CoordinateOperation coordinate operations} created by an other factory.
039: * Those coordinate operations may be expensive to create. During rendering and during data I/O,
040: * some implementations make use a lof of coordinate transformations, hence caching them might
041: * help.
042: * <p>
043: * In most cases, users should not need to create an instance of this class explicitly. An instance
044: * of {@code BufferedCoordinateOperationFactory} should be automatically registered and returned
045: * by {@link ReferencingFactoryFinder} in default Geotools configuration.
046: *
047: * @since 2.3
048: * @version $Id: BufferedCoordinateOperationFactory.java 25476 2007-05-09 17:24:32Z desruisseaux $
049: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/referencing/operation/BufferedCoordinateOperationFactory.java $
050: * @author Simone Giannecchini
051: * @author Martin Desruisseaux
052: */
053: public class BufferedCoordinateOperationFactory extends
054: AbstractCoordinateOperationFactory implements BufferedFactory {
055: /**
056: * The priority level for this factory.
057: */
058: static final int PRIORITY = AuthorityBackedFactory.PRIORITY + 10;
059:
060: /**
061: * Helper class used in order to build an hashing for a pair of source-destination
062: * {@link CoordinateReferenceSystem} objects. This is used to cache the transformations
063: * that are pretty time-consuming to build each time.
064: */
065: private static final class CRSPair {
066: /**
067: * The hash code value, computed once for ever at construction time.
068: */
069: private final int hash;
070:
071: /**
072: * The source and target CRS.
073: */
074: private final CoordinateReferenceSystem sourceCRS, targetCRS;
075:
076: /**
077: * Creates a {@code CRSPair} for the specified source and target CRS.
078: */
079: public CRSPair(final CoordinateReferenceSystem sourceCRS,
080: final CoordinateReferenceSystem targetCRS) {
081: this .sourceCRS = sourceCRS;
082: this .targetCRS = targetCRS;
083: this .hash = (37 * sourceCRS.hashCode())
084: + targetCRS.hashCode();
085: }
086:
087: /**
088: * Returns the hash code value.
089: */
090: public int hashCode() {
091: return hash;
092: }
093:
094: /**
095: * Compares this pair to the specified object for equality.
096: * <p>
097: * <strong>Note:</strong> we perform the CRS comparaison using strict equality, not using
098: * {@code equalsIgnoreMetadata}, because metadata matter since they are attributes of the
099: * {@link CoordinateOperation} object to be created.
100: */
101: public boolean equals(final Object object) {
102: if (object == this ) {
103: return true;
104: }
105: if (object instanceof CRSPair) {
106: final CRSPair that = (CRSPair) object;
107: return Utilities.equals(this .sourceCRS, that.sourceCRS)
108: && Utilities.equals(this .targetCRS,
109: that.targetCRS);
110: }
111: return false;
112: }
113: }
114:
115: /**
116: * The wrapped factory. If {@code null}, will be fetched when first needed.
117: * We should not initialize this field using {@link ReferencingFactoryFinder} from the
118: * no-argument constructor, since this constructor is typically invoked while
119: * {@link ReferencingFactoryFinder} is still iterating over the registered implementations.
120: */
121: private CoordinateOperationFactory factory;
122:
123: /**
124: * The pool of cached transformations. This map can not be static, because the values may
125: * be different for the same ({@code sourceCRS}, {@code targetCRS}) pair dependending of
126: * hint values like {@link Hints#LENIENT_DATUM_SHIFT}.
127: */
128: private final Map/*<CRSPair, CoordinateOperation>*/pool = new SoftValueHashMap();
129:
130: /**
131: * Creates a buffered factory wrapping the {@linkplain AuthorityBackedFactory default one}.
132: */
133: public BufferedCoordinateOperationFactory() {
134: super (null, PRIORITY);
135: /*
136: * Do not use FactoryFinder here (directly or indirectly through the call
137: * to an other constructor), because this constructor is typically invoked
138: * while FactoryFinder is iterating over registered implementations. We
139: * left the 'factory' field uninitialized and will initialize it when first
140: * needed.
141: */
142: }
143:
144: /**
145: * Creates a buffered factory wrapping an other factory selected according the specified hints.
146: *
147: * @param userHints The hints to use for choosing a backing factory.
148: */
149: public BufferedCoordinateOperationFactory(final Hints userHints) {
150: this (userHints, PRIORITY);
151: }
152:
153: /**
154: * Creates a buffered factory wrapping an other factory selected according the specified hints.
155: *
156: * @param userHints The hints to use for choosing a backing factory.
157: * @param priority The priority for this factory, as a number between
158: * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
159: * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
160: */
161: public BufferedCoordinateOperationFactory(final Hints userHints,
162: final int priority) {
163: this (getBackingFactory(userHints), userHints, priority);
164: }
165:
166: /**
167: * Wraps the specified factory.
168: *
169: * @param factory The factory to wrap.
170: * @param priority The priority for this factory, as a number between
171: * {@link #MINIMUM_PRIORITY MINIMUM_PRIORITY} and
172: * {@link #MAXIMUM_PRIORITY MAXIMUM_PRIORITY} inclusive.
173: */
174: public BufferedCoordinateOperationFactory(
175: final CoordinateOperationFactory factory, final int priority) {
176: this (factory, null, priority);
177: }
178:
179: /**
180: * Work around for RFE #4093999 in Sun's bug database
181: * ("Relax constraint on placement of this()/super() call in constructors").
182: */
183: private BufferedCoordinateOperationFactory(
184: final CoordinateOperationFactory factory,
185: final Hints userHints, final int priority) {
186: super (factory, userHints, priority);
187: this .factory = factory;
188: ensureNonNull("factory", factory);
189: }
190:
191: /**
192: * Returns a backing factory from the specified hints.
193: */
194: private static CoordinateOperationFactory getBackingFactory(
195: final Hints hints) {
196: for (final Iterator it = ReferencingFactoryFinder
197: .getCoordinateOperationFactories(hints).iterator(); it
198: .hasNext();) {
199: final CoordinateOperationFactory candidate = (CoordinateOperationFactory) it
200: .next();
201: if (!(candidate instanceof BufferedCoordinateOperationFactory)) {
202: return candidate;
203: }
204: }
205: // The following is likely to thrown a FactoryNotFoundException,
206: // which is the intended behavior.
207: return ReferencingFactoryFinder
208: .getCoordinateOperationFactory(hints);
209: }
210:
211: /**
212: * Returns the backing factory. Coordinate operation creation will be delegated to this
213: * factory when not available in the cache.
214: */
215: private final CoordinateOperationFactory getBackingFactory() {
216: assert Thread.holdsLock(hints); // Same lock than the one used by getImplementationHints().
217: if (factory == null) {
218: factory = getBackingFactory(null);
219: }
220: return factory;
221: }
222:
223: /**
224: * Invoked by {@link #AbstractCoordinateOperationFactory} when the {@link #hints} map should
225: * be initialized. The {@link Hints#COORDINATE_OPERATION_FACTORY} can not always be provided
226: * at construction time, because the backing factory may be lazily created.
227: */
228: // @Override
229: void initializeHints() {
230: super .initializeHints();
231: hints.put(Hints.COORDINATE_OPERATION_FACTORY,
232: getBackingFactory());
233: }
234:
235: /**
236: * Returns an operation for conversion or transformation between two coordinate reference
237: * systems. If an operation was already created and still in the cache, the cached operation
238: * is returned. Otherwise the operation creation is delegated to the
239: * {@linkplain CoordinateOperationFactory coordinate operation factory} specified at
240: * construction time and the result is cached.
241: *
242: * @param sourceCRS Input coordinate reference system.
243: * @param targetCRS Output coordinate reference system.
244: * @return A coordinate operation from {@code sourceCRS} to {@code targetCRS}.
245: * @throws OperationNotFoundException if no operation path was found from {@code sourceCRS}
246: * to {@code targetCRS}.
247: * @throws FactoryException if the operation creation failed for some other reason.
248: */
249: public CoordinateOperation createOperation(
250: final CoordinateReferenceSystem sourceCRS,
251: final CoordinateReferenceSystem targetCRS)
252: throws OperationNotFoundException, FactoryException {
253: ensureNonNull("sourceCRS", sourceCRS);
254: ensureNonNull("targetCRS", targetCRS);
255: final CRSPair key = new CRSPair(sourceCRS, targetCRS);
256: CoordinateOperation op;
257: synchronized (hints) { // This lock is indirectly required by getBackingFactory().
258: op = (CoordinateOperation) pool.get(key);
259: if (op == null) {
260: op = getBackingFactory().createOperation(sourceCRS,
261: targetCRS);
262: pool.put(key, op);
263: }
264: }
265: return op;
266: }
267:
268: /**
269: * Returns an operation for conversion or transformation between two coordinate reference
270: * systems using the specified method. The current implementation delegates to the
271: * {@linkplain CoordinateOperationFactory coordinate operation factory} specified at
272: * construction time with no caching.
273: *
274: * @todo Consider if we should cache the operations for this method too. As of Geotools 2.3,
275: * this is not needed since Geotools doesn't implement this method yet.
276: */
277: public CoordinateOperation createOperation(
278: final CoordinateReferenceSystem sourceCRS,
279: final CoordinateReferenceSystem targetCRS,
280: final OperationMethod method)
281: throws OperationNotFoundException, FactoryException {
282: synchronized (hints) { // This lock is indirectly required by getBackingFactory().
283: return getBackingFactory().createOperation(sourceCRS,
284: targetCRS, method);
285: }
286: }
287: }
|