001: /*
002: * Geotools 2 - OpenSource mapping toolkit
003: * (C) 2003, Geotools Project Management Committee (PMC)
004: * (C) 2001, Institut de Recherche pour le Développement
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; either
009: * version 2.1 of the License, or (at your option) any later version.
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: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.geotools.coverage.processing;
021:
022: // J2SE dependencies
023: import java.awt.RenderingHints;
024: import java.util.Arrays;
025: import java.util.Collections;
026: import java.util.Collection;
027: import java.util.Comparator;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.TreeMap;
032: import java.util.logging.Level;
033:
034: // JAI dependencies
035: import javax.media.jai.Interpolation;
036: import javax.media.jai.JAI;
037: import javax.media.jai.TileCache;
038:
039: // OpenGIS dependencies
040: import org.opengis.coverage.Coverage;
041: import org.opengis.coverage.processing.Operation;
042: import org.opengis.coverage.processing.OperationNotFoundException;
043: import org.opengis.parameter.GeneralParameterValue;
044: import org.opengis.parameter.ParameterValue;
045: import org.opengis.parameter.ParameterValueGroup;
046:
047: // Geotools dependencies
048: import org.geotools.coverage.grid.GridCoverage2D;
049: import org.geotools.coverage.grid.Interpolator2D;
050: import org.geotools.factory.FactoryRegistry;
051: import org.geotools.factory.Hints;
052: import org.geotools.resources.i18n.Errors;
053: import org.geotools.resources.i18n.ErrorKeys;
054: import org.geotools.resources.i18n.Logging;
055: import org.geotools.resources.i18n.LoggingKeys;
056:
057: /**
058: * Default implementation of a {@linkplain Coverage coverage} processor.
059: * This default implementation makes the following assumptions:
060: * <p>
061: * <ul>
062: * <li>Operations are declared in the
063: * {@code META-INF/services/org.opengis.coverage.processing.Operation} file.</li>
064: * <li>Operations are actually instances of {@link AbstractOperation} (note: this constraint
065: * may be relaxed in a future version after GeoAPI interfaces for grid coverage will be
066: * redesigned).</li>
067: * <li>Most operations are backed by <cite>Java Advanced Imaging</cite>.</li>
068: * </ul>
069: * <p>
070: * <strong>Note:</strong> This implementation do not caches produced coverages. Since coverages
071: * may be big, consider wrapping {@code DefaultProcessor} instances in {@link BufferedProcessor}.
072: *
073: * @since 2.2
074: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/DefaultProcessor.java $
075: * @version $Id: DefaultProcessor.java 22482 2006-10-31 02:58:00Z desruisseaux $
076: * @author Martin Desruisseaux
077: */
078: public class DefaultProcessor extends AbstractProcessor {
079: /**
080: * Augments the amout of memory allocated for the JAI tile cache.
081: */
082: static {
083: final long targetCapacity = 0x4000000; // 64 Mo.
084: final long maxMemory = Runtime.getRuntime().maxMemory();
085: final TileCache cache = JAI.getDefaultInstance().getTileCache();
086: if (maxMemory >= 2 * targetCapacity) {
087: if (cache.getMemoryCapacity() < targetCapacity) {
088: cache.setMemoryCapacity(targetCapacity);
089: }
090: }
091: LOGGER.config("Java Advanced Imaging: " + JAI.getBuildVersion()
092: + ", TileCache capacity="
093: + (float) (cache.getMemoryCapacity() / (1024 * 1024))
094: + " Mb");
095: /*
096: * Verifies that the tile cache has some reasonable value. A lot of users seem to
097: * misunderstand the memory setting in Java and set wrong values. If the user set
098: * a tile cache greater than the maximum heap size, tell him that he is looking
099: * for serious trouble.
100: */
101: if (cache.getMemoryCapacity() + (4 * 1024 * 1024) >= maxMemory) {
102: LOGGER.log(Logging.format(Level.SEVERE,
103: LoggingKeys.EXCESSIVE_TILE_CACHE_$1, new Double(
104: maxMemory / (1024 * 1024.0))));
105: }
106: }
107:
108: /**
109: * The comparator for ordering operation names.
110: */
111: private static final Comparator COMPARATOR = new Comparator() {
112: public int compare(final Object name1, final Object name2) {
113: return ((String) name1).toLowerCase().compareTo(
114: ((String) name2).toLowerCase());
115: }
116: };
117:
118: /**
119: * The set of operations for this coverage processor. Keys are operation's name.
120: * Values are operations and should not contains duplicated values. Note that while
121: * keys are {@link String} objects, the operation name are actually case-insensitive
122: * because of the comparator used in the sorted map.
123: */
124: private final Map/*<String,Operation>*/operations = new TreeMap(
125: COMPARATOR);
126:
127: /**
128: * The rendering hints for JAI operations (never {@code null}).
129: * This field is usually given as argument to {@link OperationJAI} methods.
130: */
131: private final Hints hints;
132:
133: /**
134: * The service registry for finding {@link Operation} implementations.
135: */
136: private final FactoryRegistry registry;
137:
138: /**
139: * Constructs a default coverage processor. The {@link #scanForPlugins} method will be
140: * automatically invoked the first time an operation is required. Additional operations
141: * can be added by subclasses with the {@link #addOperation} method. Rendering hints will
142: * be initialized with the following hints:
143: * <p>
144: * <ul>
145: * <li>{@link JAI#KEY_REPLACE_INDEX_COLOR_MODEL} set to {@link Boolean#FALSE}.</li>
146: * <li>{@link JAI#KEY_TRANSFORM_ON_COLORMAP} set to {@link Boolean#FALSE}.</li>
147: * </ul>
148: *
149: * @param hints A set of additional rendering hints, or {@code null} if none.
150: */
151: public DefaultProcessor(final RenderingHints hints) {
152: registry = new FactoryRegistry(Collections
153: .singleton(Operation.class));
154: this .hints = new Hints(hints);
155: this .hints
156: .put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
157: this .hints.put(JAI.KEY_TRANSFORM_ON_COLORMAP, Boolean.FALSE);
158: this .hints.put(Hints.GRID_COVERAGE_PROCESSOR, this ); // Must overwrites user setting.
159: }
160:
161: /**
162: * Sets the GRID_COVERAGE_PROCESSOR hint to the specified value.
163: * This is used by {@link BufferedProcessor} only.
164: */
165: final void setProcessor(final AbstractProcessor processor) {
166: hints.put(Hints.GRID_COVERAGE_PROCESSOR, processor);
167: }
168:
169: /**
170: * Removes the GRID_COVERAGE_PROCESSOR hint. The {@link AbstractOperation#getProcessor} method
171: * will automatically returns the default instance when this hint is not defined. Removing this
172: * hint provides three advantages for the common case when the default processor is used:
173: *
174: * <ul>
175: * <li>Avoid a strong reference to this processor in {@link RenderedImage} properties.</li>
176: * <li>Avoid serialization of this processor when a {@link RenderedImage} is serialized.</li>
177: * <li>Allows {@link AbstractOperation#getProcessor} to returns the {@link BufferedProcessor}
178: * instance instead of this instance.</li>
179: * </ul>
180: */
181: void setAsDefault() {
182: hints.remove(Hints.GRID_COVERAGE_PROCESSOR);
183: }
184:
185: /**
186: * Add the specified operation to this processor. This method is usually invoked
187: * at construction time before this processor is made accessible.
188: *
189: * @param operation The operation to add.
190: * @throws IllegalStateException if an operation already exists with the same name.
191: */
192: protected synchronized void addOperation(final Operation operation)
193: throws IllegalStateException {
194: ensureNonNull("operation", operation);
195: if (operations.isEmpty()) {
196: scanForPlugins();
197: }
198: addOperation0(operation);
199: }
200:
201: /**
202: * Implementation of {@link #addOperation} method. Also used by {@link #scanForPlugins}
203: * instead of the public method in order to avoid never-ending loop.
204: */
205: private void addOperation0(final Operation operation)
206: throws IllegalStateException {
207: final String name = operation.getName().trim();
208: final Operation old = (Operation) operations.put(name,
209: operation);
210: if (old != null && !old.equals(operation)) {
211: operations.put(old.getName().trim(), old);
212: throw new IllegalStateException(Errors.getResources(
213: getLocale()).getString(
214: ErrorKeys.OPERATION_ALREADY_BOUND_$1,
215: operation.getName()));
216: }
217: }
218:
219: /**
220: * Retrieves grid processing operations information. Each operation information contains
221: * the name of the operation as well as a list of its parameters.
222: */
223: public synchronized Collection/*<Operation>*/getOperations() {
224: if (operations.isEmpty()) {
225: scanForPlugins();
226: }
227: return operations.values();
228: }
229:
230: /**
231: * Returns the operation for the specified name.
232: *
233: * @param name Name of the operation (case insensitive).
234: * @return The operation for the given name.
235: * @throws OperationNotFoundException if there is no operation for the specified name.
236: */
237: public synchronized Operation getOperation(String name)
238: throws OperationNotFoundException {
239: ensureNonNull("name", name);
240: name = name.trim();
241: if (operations.isEmpty()) {
242: scanForPlugins();
243: }
244: final Operation operation = (Operation) operations.get(name);
245: if (operation != null) {
246: return operation;
247: }
248: throw new OperationNotFoundException(Errors.getResources(
249: getLocale()).getString(
250: ErrorKeys.OPERATION_NOT_FOUND_$1, name));
251: }
252:
253: /**
254: * Returns a rendering hint.
255: *
256: * @param key The hint key (e.g. {@link Hints#JAI_INSTANCE}).
257: * @return The hint value for the specified key, or {@code null} if there is no hint for the
258: * specified key.
259: */
260: public final Object getRenderingHint(final RenderingHints.Key key) {
261: return hints.get(key);
262: }
263:
264: /**
265: * Applies a process operation to a coverage. The default implementation checks if source
266: * coverages use an interpolation, and then invokes {@link AbstractOperation#doOperation}.
267: * If all source coverages used the same interpolation, then this interpolation is applied
268: * to the resulting coverage (except if the resulting coverage has already an interpolation).
269: *
270: * @param parameters Parameters required for the operation. The easiest way to construct them
271: * is to invoke <code>operation.{@link Operation#getParameters getParameters}()</code>
272: * and to modify the returned group.
273: * @return The result as a coverage.
274: * @throws OperationNotFoundException if there is no operation for the parameter group name.
275: */
276: public synchronized Coverage doOperation(
277: final ParameterValueGroup parameters)
278: throws OperationNotFoundException {
279: Coverage source = getPrimarySource(parameters);
280: final String operationName = getOperationName(parameters);
281: final Operation operation = getOperation(operationName);
282: /*
283: * Detects the interpolation type for the source grid coverage.
284: * The same interpolation will be applied on the result.
285: */
286: Interpolation[] interpolations = null;
287: if (!operationName.equalsIgnoreCase("Interpolate")) {
288: for (final Iterator it = parameters.values().iterator(); it
289: .hasNext();) {
290: final GeneralParameterValue param = (GeneralParameterValue) it
291: .next();
292: if (param instanceof ParameterValue) {
293: final Object value = ((ParameterValue) param)
294: .getValue();
295: if (value instanceof Interpolator2D) {
296: // If all sources use the same interpolation, preserves the
297: // interpolation for the resulting coverage. Otherwise, uses
298: // the default interpolation (nearest neighbor).
299: final Interpolation[] interp = ((Interpolator2D) value)
300: .getInterpolations();
301: if (interpolations == null) {
302: interpolations = interp;
303: } else if (!Arrays.equals(interpolations,
304: interp)) {
305: // Set to no interpolation.
306: interpolations = null;
307: break;
308: }
309: }
310: }
311: }
312: }
313: /*
314: * Applies the operation, applies the same interpolation and log a message.
315: * Note: we don't use "if (operation instanceof AbstractOperation)" below
316: * because if it is not, we want the ClassCastException as the cause
317: * for the failure.
318: */
319: final AbstractOperation op;
320: try {
321: op = (AbstractOperation) operation;
322: } catch (ClassCastException cause) {
323: final OperationNotFoundException exception = new OperationNotFoundException(
324: Errors.getResources(getLocale()).getString(
325: ErrorKeys.OPERATION_NOT_FOUND_$1,
326: operationName));
327: exception.initCause(cause);
328: throw exception;
329: }
330: Coverage coverage = op.doOperation(parameters, hints);
331: if (interpolations != null
332: && (coverage instanceof GridCoverage2D)
333: && !(coverage instanceof Interpolator2D)) {
334: coverage = Interpolator2D.create((GridCoverage2D) coverage,
335: interpolations);
336: }
337: log(source, coverage, operationName, false);
338: return coverage;
339: }
340:
341: /**
342: * Scans for factory plug-ins on the application class path. This method is needed because the
343: * application class path can theoretically change, or additional plug-ins may become available.
344: * Rather than re-scanning the classpath on every invocation of the API, the class path is
345: * scanned automatically only on the first invocation. Clients can call this method to prompt
346: * a re-scan. Thus this method need only be invoked by sophisticated applications which
347: * dynamically make new plug-ins available at runtime.
348: */
349: public synchronized void scanForPlugins() {
350: for (final Iterator it = registry
351: .getServiceProviders(Operation.class); it.hasNext();) {
352: final Operation operation = (Operation) it.next();
353: final String name = operation.getName().trim();
354: if (!operations.containsKey(name)) {
355: addOperation0(operation);
356: }
357: }
358: }
359: }
|