Package org.geotools.coverage.processing

Source Code of org.geotools.coverage.processing.CoverageProcessor$CacheableCoverageProcessor

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
*    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.geotools.coverage.processing;

import java.awt.RenderingHints;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.media.jai.Interpolation;
import javax.media.jai.JAI;
import javax.media.jai.TileCache;

import org.geotools.coverage.AbstractCoverage;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.Interpolator2D;
import org.geotools.factory.FactoryRegistry;
import org.geotools.factory.Hints;
import org.geotools.resources.Arguments;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.SoftValueHashMap;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.processing.Operation;
import org.opengis.coverage.processing.OperationNotFoundException;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.InternationalString;


/**
* Base class for {@linkplain Coverage coverage} processor implementations.
*
* @since 2.2
*
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Simone Giannecchini, GeoSolutions S.A.S.
*/
public class CoverageProcessor {
    /**
     * The logger for coverage processing operations.
     */
    public static final Logger LOGGER = Logging.getLogger("org.geotools.coverage.processing");

    /**
     * The logging level for reporting coverage operations.
     * This level is equals or slightly lower than {@link Level#INFO}.
     */
    public static final Level OPERATION = new LogLevel("OPERATION", 780);

  /**
   * The comparator for ordering operation names.
   */
  private static final Comparator<String> COMPARATOR = new Comparator<String>() {
          public int compare(final String name1, final String name2) {
              return name1.toLowerCase().compareTo(name2.toLowerCase());
          }
      };

    /**
     * The grid coverage logging level type.
     */
    private static final class LogLevel extends Level {
        /**
     *
     */
    private static final long serialVersionUID = 1L;

    protected LogLevel(final String name, final int level) {
            super(name, level);
        }
    }

    /**
     * The default coverage processor. Will be constructed only when first requested.
     *
     * @todo This is a temporary field, to be removed when a GeoAPI interfaces for coverage
     *       processing while be redesigned along the lines of ISO 19123.
     */
    private static CoverageProcessor DEFAULT;
   
    private final static SoftValueHashMap<Hints, CoverageProcessor> processorsPool= new SoftValueHashMap<Hints, CoverageProcessor>();
   
    /**
     * Cacheable instance of the {@link CoverageProcessor}. It prevents users to add operations manually calling {@link #addOperation(Operation)}.
     *
     * @author Simone Giannecchini, GeoSolutions S.A.S.
     *
     */
    private final static class CacheableCoverageProcessor extends CoverageProcessor {

      public CacheableCoverageProcessor() {
        super();
      }

      /**
       * @param hints
       */
      public CacheableCoverageProcessor(RenderingHints hints) {
        super(hints);
      }

    @Override
    protected void addOperation(Operation operation)
        throws IllegalStateException {
      throw new UnsupportedOperationException();
    }

    }

    /**
     * Augments the amount of memory allocated for the JAI tile cache.
     */
    static {
        final long targetCapacity = 0x4000000; // 64 Mo.
        final long maxMemory = Runtime.getRuntime().maxMemory();
        final TileCache cache = JAI.getDefaultInstance().getTileCache();
        if (maxMemory >= 2*targetCapacity) {
            if (cache.getMemoryCapacity() < targetCapacity) {
                cache.setMemoryCapacity(targetCapacity);
            }
        }
        LOGGER.config("Java Advanced Imaging: " + JAI.getBuildVersion() +
                    ", TileCache capacity="+(float)(cache.getMemoryCapacity()/(1024*1024))+" Mb");
        /*
         * Verifies that the tile cache has some reasonable value. A lot of users seem to
         * misunderstand the memory setting in Java and set wrong values. If the user set
         * a tile cache greater than the maximum heap size, tell him that he is looking
         * for serious trouble.
         */
        if (cache.getMemoryCapacity() + (4*1024*1024) >= maxMemory) {
            final LogRecord record = Loggings.format(Level.SEVERE,
                    LoggingKeys.EXCESSIVE_TILE_CACHE_$1, maxMemory / (1024 * 1024.0));
            record.setLoggerName(LOGGER.getName());
            LOGGER.log(record);
        }
    }
   
  /**
   * The set of operations for this coverage processor. Keys are operation's name.
   * Values are operations and should not contains duplicated values. Note that while
   * keys are {@link String} objects, the operation name are actually case-insensitive
   * because of the comparator used in the sorted map.
   */
  private final Map<String,Operation> operations = Collections.synchronizedMap(new TreeMap<String,Operation>(COMPARATOR));

  /**
   * The rendering hints for JAI operations (never {@code null}).
   * This field is usually given as argument to {@link OperationJAI} methods.
   */
  protected final Hints hints;

  /**
   * The service registry for finding {@link Operation} implementations.
   */
  protected final FactoryRegistry registry;

    /**
     * Constructs a coverage processor.
     */
    public CoverageProcessor() {
      this(null);
    }

    /**
     * Constructs a default coverage processor. The {@link #scanForPlugins} method will be
     * automatically invoked the first time an operation is required. Additional operations
     * can be added by subclasses with the {@link #addOperation} method. Rendering hints will
     * be initialized with the following hints:
     * <p>
     * <ul>
     *   <li>{@link JAI#KEY_REPLACE_INDEX_COLOR_MODEL} set to {@link Boolean#FALSE}.</li>
     *   <li>{@link JAI#KEY_TRANSFORM_ON_COLORMAP} set to {@link Boolean#FALSE}.</li>
     * </ul>
     *
     * @param hints A set of additional rendering hints, or {@code null} if none.
     */
    public CoverageProcessor(final RenderingHints hints) {
        registry = new FactoryRegistry(Arrays.asList(new Class<?>[] {
            Operation.class
        }));
        this.hints = new Hints();
        this.hints.put(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE);
        this.hints.put(JAI.KEY_TRANSFORM_ON_COLORMAP,     Boolean.FALSE);
       
        // override with user hints
        if(hints!=null)
          this.hints.add(hints);
       
    }
   
    /**
     * Returns a default processor instance.
     * <p>
     * <strong>Note:</strong> this is a temporary method, until we have GeoAPI interface for
     * coverage processor and a factory finder for their implementations.
     */
    public static synchronized CoverageProcessor getInstance() {
      return getInstance(null);
    }
   
    /**
     * Returns a default processor instance.
     * <p>
     * <strong>Note:</strong> this is a temporary method, until we have GeoAPI interface for
     * coverage processor and a factory finder for their implementations.
     */
    public static synchronized CoverageProcessor getInstance(final Hints hints)   {
      if(hints==null||hints.isEmpty()){
          if (DEFAULT == null) {
              DEFAULT = new CacheableCoverageProcessor();
          }
          return DEFAULT;
      }
      if(processorsPool.containsKey(hints))
        return processorsPool.get(hints);
      final CoverageProcessor processor=new CacheableCoverageProcessor(hints);
      processorsPool.put(hints, processor);
      return processor;
    }



    /**
     * The locale for logging message or reporting errors. The default implementations
     * returns the {@linkplain Locale#getDefault default locale}. Subclasses can override
     * this method if a different locale is wanted.
     */
    public Locale getLocale() {
        return Locale.getDefault();
    }

    /**
     * Logs a message for an operation. The message will be logged only if the source grid
     * coverage is different from the result (i.e. if the operation did some work).
     *
     * @param source The source grid coverage.
     * @param result The resulting grid coverage.
     * @param operationName the operation name.
     * @param fromCache {@code true} if the result has been fetch from the cache.
     */
    final void log(final Coverage source,
                   final Coverage result,
                   final String   operationName,
                   final boolean  fromCache)
    {
        if (source != result) {
            String interp = "Nearest";
            if (result instanceof Interpolator2D) {
                interp = ImageUtilities.getInterpolationName(
                            ((Interpolator2D) result).getInterpolation());
            }
            final Locale locale = getLocale();
            final LogRecord record = Loggings.getResources(locale).getLogRecord(
                                     OPERATION, LoggingKeys.APPLIED_OPERATION_$4,
                                     getName((source!=null) ? source : result, locale),
                                     operationName, interp, Integer.valueOf(fromCache ? 1 : 0));
            // Note: DefaultProcessor is the class that will use this method.
            record.setSourceClassName("org.geotools.coverage.processing.DefaultProcessor");
            record.setSourceMethodName("doOperation");
            record.setLoggerName(LOGGER.getName());
            LOGGER.log(record);
        }
    }

    /**
     * Returns the primary source coverage from the specified parameters, or {@code null} if none.
     */
    static Coverage getPrimarySource(final ParameterValueGroup parameters) {
        try {
            return (Coverage) parameters.parameter("Source").getValue();
        } catch (ParameterNotFoundException exception) {
            /*
             * "Source" parameter may not exists. Conservatively
             * assumes that the operation will do some usefull work.
             */
            return null;
        }
    }

    /**
     * Returns the operation name for the specified parameters.
     */
    static String getOperationName(final ParameterValueGroup parameters) {
        return parameters.getDescriptor().getName().getCode().trim();
    }

    /**
     * Returns the coverage name in the specified locale.
     */
    private static String getName(final Coverage coverage, final Locale locale) {
        if (coverage instanceof AbstractCoverage) {
            final InternationalString name = ((AbstractCoverage) coverage).getName();
            if (name != null) {
                return name.toString(locale);
            }
        }
        return Vocabulary.getResources(locale).getString(VocabularyKeys.UNTITLED);
    }

    /**
     * Lists a summary of all operations to the specified stream.
     *
     * @param  out The destination stream.
     * @throws IOException if an error occured will writing to the stream.
     */
    public void listOperations(final Writer out) throws IOException {
        final Collection<Operation> operations = getOperations();
        final CoverageParameterWriter writer = new CoverageParameterWriter(out);
        final List<ParameterDescriptorGroup> descriptors = new ArrayList<ParameterDescriptorGroup>(operations.size());
        for (final Iterator<Operation> it=operations.iterator(); it.hasNext();) {
            final Operation operation = it.next();
            if (operation instanceof AbstractOperation) {
                descriptors.add(((AbstractOperation) operation).descriptor);
            }
        }
        writer.summary(descriptors, null);
    }

    /**
     * Prints a description of operations to the specified stream. If the {@code names} array
     * is non-null, then only the specified operations are printed. Otherwise, all operations
     * are printed. The description details include operation names and lists of parameters.
     *
     * @param  out The destination stream.
     * @param  names The operation to print, or an empty array for none, or {@code null} for all.
     * @throws IOException if an error occured will writing to the stream.
     * @throws OperationNotFoundException if an operation named in {@code names} was not found.
     */
    public void printOperations(final Writer out, final String[] names)
            throws OperationNotFoundException, IOException
    {
        final CoverageParameterWriter writer = new CoverageParameterWriter(out);
        final String lineSeparator = System.getProperty("line.separator", "\n");
        if (names != null) {
            for (int i=0; i<names.length; i++) {
                final Operation operation = getOperation(names[i]);
                if (operation instanceof AbstractOperation) {
                    out.write(lineSeparator);
                    writer.format(((AbstractOperation) operation).descriptor);
                }
            }
        } else {
            final Collection<Operation> operations = getOperations();
            for (final Iterator<Operation> it=operations.iterator(); it.hasNext();) {
                final Operation operation = it.next();
                if (operation instanceof AbstractOperation) {
                    out.write(lineSeparator);
                    writer.format(((AbstractOperation) operation).descriptor);
                }
            }
        }
    }

    /**
   * Add the specified operation to this processor. This method is usually invoked
   * at construction time before this processor is made accessible.
   *
   * @param  operation The operation to add.
   * @throws IllegalStateException if an operation already exists with the same name.
   */
  protected void addOperation(final Operation operation)
      throws IllegalStateException {
          Utilities.ensureNonNull("operation", operation);
          synchronized (operations) {

            if (operations.isEmpty()) {
                scanForPlugins();
            }
            addOperation0(operation);
        }
      }

  /**
   * Implementation of {@link #addOperation} method. Also used by {@link #scanForPlugins}
   * instead of the public method in order to avoid never-ending loop.
   */
  private void addOperation0(final Operation operation) throws IllegalStateException {
      final String name = operation.getName().trim();
      final Operation old = operations.put(name, operation);
      if (old!=null && !old.equals(operation)) {
          operations.put(old.getName().trim(), old);
          throw new IllegalStateException(Errors.getResources(getLocale()).getString(
                    ErrorKeys.OPERATION_ALREADY_BOUND_$1, operation.getName()));
      }
  }

  /**
   * Retrieves grid processing operations information. Each operation information contains
   * the name of the operation as well as a list of its parameters.
   */
  public Collection<Operation> getOperations() {
    synchronized (operations) {
        if (operations.isEmpty()) {
            scanForPlugins();
        }
        return operations.values();
       
    }
  }

  /**
   * Returns the operation for the specified name.
   *
   * @param  name Name of the operation (case insensitive).
   * @return The operation for the given name.
   * @throws OperationNotFoundException if there is no operation for the specified name.
   */
  public Operation getOperation(String name)
      throws OperationNotFoundException {
        Utilities.ensureNonNull("name", name);
        name = name.trim();
        synchronized (operations) {
         
          if (operations.isEmpty()) {
              scanForPlugins();
          }
          final Operation operation = operations.get(name);
          if (operation != null) {
              return operation;
          }
          throw new OperationNotFoundException(Errors.getResources(getLocale()).getString(
                  ErrorKeys.OPERATION_NOT_FOUND_$1, name));
      }
    }

  /**
   * Returns a rendering hint.
   *
   * @param  key The hint key (e.g. {@link Hints#JAI_INSTANCE}).
   * @return The hint value for the specified key, or {@code null} if there is no hint for the
   *         specified key.
   */
  public final Object getRenderingHint(final RenderingHints.Key key) {
      return hints.get(key);
  }

  /**
   * Applies a process operation to a coverage. The default implementation checks if source
   * coverages use an interpolation, and then invokes {@link AbstractOperation#doOperation}.
   * If all source coverages used the same interpolation, then this interpolation is applied
   * to the resulting coverage (except if the resulting coverage has already an interpolation).
   *
   * @param  parameters Parameters required for the operation. The easiest way to construct them
   *         is to invoke <code>operation.{@link Operation#getParameters getParameters}()</code>
   *         and to modify the returned group.
   * @return The result as a coverage.
   * @throws OperationNotFoundException if there is no operation for the parameter group name.
   */
  @SuppressWarnings("unchecked")
  public Coverage doOperation(final ParameterValueGroup parameters, final Hints hints){
    Coverage source = getPrimarySource(parameters);
      final String operationName = getOperationName(parameters);
      final Operation  operation = getOperation(operationName);
      /*
       * Detects the interpolation type for the source grid coverage.
       * The same interpolation will be applied on the result.
       */
      Interpolation[] interpolations = null;
      if (!operationName.equalsIgnoreCase("Interpolate")) {
          for (final GeneralParameterValue param : parameters.values()) {
              if (param instanceof ParameterValue) {
                  final Object value = ((ParameterValue) param).getValue();
                  if (value instanceof Interpolator2D) {
                      // If all sources use the same interpolation, preserves the
                      // interpolation for the resulting coverage. Otherwise, uses
                      // the default interpolation (nearest neighbor).
                      final Interpolation[] interp = ((Interpolator2D) value).getInterpolations();
                      if (interpolations == null) {
                          interpolations = interp;
                      } else if (!Arrays.equals(interpolations, interp)) {
                          // Set to no interpolation.
                          interpolations = null;
                          break;
                      }
                  }
              }
          }
      }
      /*
       * Applies the operation, applies the same interpolation and log a message.
       * Note: we don't use "if (operation instanceof AbstractOperation)" below
       *       because if it is not, we want the ClassCastException as the cause
       *       for the failure.
       */
      final AbstractOperation op;
      try {
          op = (AbstractOperation) operation;
      } catch (ClassCastException cause) {
          final OperationNotFoundException exception = new OperationNotFoundException(
                      Errors.getResources(getLocale()).getString(
                      ErrorKeys.OPERATION_NOT_FOUND_$1, operationName));
          exception.initCause(cause);
          throw exception;
      }
     
      //set up hints
      final Hints localMergeHints=this.hints.clone();
      if(hints!=null)
        localMergeHints.add(hints);
     
      // processwith local hints
      Coverage coverage = op.doOperation(parameters, localMergeHints);
      if (interpolations != null && (coverage instanceof GridCoverage2D) &&
                                   !(coverage instanceof Interpolator2D)){
          coverage = Interpolator2D.create((GridCoverage2D) coverage, interpolations);
      }
      log(source, coverage, operationName, false);
      return coverage;
  }
  /**
   * Applies a process operation to a coverage. The default implementation checks if source
   * coverages use an interpolation, and then invokes {@link AbstractOperation#doOperation}.
   * If all source coverages used the same interpolation, then this interpolation is applied
   * to the resulting coverage (except if the resulting coverage has already an interpolation).
   *
   * @param  parameters Parameters required for the operation. The easiest way to construct them
   *         is to invoke <code>operation.{@link Operation#getParameters getParameters}()</code>
   *         and to modify the returned group.
   * @return The result as a coverage.
   * @throws OperationNotFoundException if there is no operation for the parameter group name.
   */
  public Coverage doOperation(final ParameterValueGroup parameters)
      throws OperationNotFoundException {
    return doOperation(parameters, null);
  }

  /**
   * Scans for factory plug-ins on the application class path. This method is needed because the
   * application class path can theoretically change, or additional plug-ins may become available.
   * Rather than re-scanning the classpath on every invocation of the API, the class path is
   * scanned automatically only on the first invocation. Clients can call this method to prompt
   * a re-scan. Thus this method need only be invoked by sophisticated applications which
   * dynamically make new plug-ins available at runtime.
   */
  public void scanForPlugins() {
    synchronized (operations) {
     
        final Iterator<Operation> it = registry.getServiceProviders(Operation.class, null, null);
        while (it.hasNext()) {
            final Operation operation = it.next();
            final String name = operation.getName().trim();
            if (!operations.containsKey(name)) {
                addOperation0(operation);
            }
        }
    }
  }

  /**
     * Dumps to the {@linkplain System#out standard output stream} a list of operations for the
     * default processor. If no argument is provided, then only a summary of operations is printed.
     * If arguments are provided, then the operation parameters are printed for all operation names
     * given in arguments. This method can been invoked from the command line. For example:
     *
     * <blockquote><pre>
     * java org.geotools.coverage.processing.DefaultProcessor Interpolate
     * </pre></blockquote>
     *
     * <strong>Note for Windows users:</strong> If the output contains strange
     * symbols, try to supply an "{@code -encoding}" argument. Example:
     *
     * <blockquote><pre>
     * java org.geotools.coverage.processing.DefaultProcessor -encoding Cp850
     * </pre></blockquote>
     *
     * The codepage number (850 in the previous example) can be fetch from the DOS
     * command line by entering the "{@code chcp}" command with no arguments.
     */
    public static void main(String[] args) {
        final Arguments arguments = new Arguments(args);
        final boolean all = arguments.getFlag("-all");
        args = arguments.getRemainingArguments(Integer.MAX_VALUE);
        final CoverageProcessor processor = getInstance();
        try {
            if (args.length == 0) {
                processor.listOperations(arguments.out);
            } else {
                processor.printOperations(arguments.out, all ? null : args);
            }
        } catch (OperationNotFoundException exception) {
            arguments.out.println(exception.getLocalizedMessage());
        } catch (IOException exception) {
            // Should not occurs
            exception.printStackTrace(arguments.out);
        }
        arguments.out.flush();
    }
}
TOP

Related Classes of org.geotools.coverage.processing.CoverageProcessor$CacheableCoverageProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.