/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2010-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2010-2012, Geomatys * * 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.geotoolkit.coverage.io; import java.util.Locale; import java.util.logging.Level; import java.util.logging.LogRecord; import javax.imageio.spi.ImageReaderWriterSpi; import java.awt.geom.AffineTransform; import java.awt.Dimension; import org.opengis.util.InternationalString; import org.opengis.coverage.grid.GridCoverage; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.referencing.operation.MathTransform2D; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.NoninvertibleTransformException; import org.geotoolkit.lang.Static; import org.geotoolkit.resources.Loggings; import org.geotoolkit.resources.Vocabulary; import org.apache.sis.util.Classes; import org.geotoolkit.nio.IOUtilities; import org.geotoolkit.internal.image.io.Formats; import org.geotoolkit.coverage.AbstractCoverage; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.apache.sis.referencing.IdentifiedObjects; import static org.geotoolkit.image.io.MultidimensionalImageStore.*; import static org.geotoolkit.coverage.io.GridCoverageStore.LOGGER; import static org.geotoolkit.internal.InternalUtilities.adjustForRoundingError; /** * Static utilities methods for use by {@link ImageCoverageReader} and {@link ImageCoverageStore}. * Current implementation contains mostly method related to logging. They are isolated in this * class in order to reduce the amount of code de load when there is no logging. To make that * effective, it is caller responsibility to check if logging are enabled before to invoke any * method in this class. * * @author Martin Desruisseaux (Geomatys) * @version 3.16 * * @since 3.15 * @module */ final class ImageCoverageStore extends Static { /** * Do not allow instantiation of this class. */ private ImageCoverageStore() { } /** * Logs a "Created encoder|decoder of class Foo" message. This method can * be invoked from {@code setInput} or {@code setOutput} methods only. * <p> * The message is logged at the level returned by {@link GridCoverageStore#getFineLevel()}. * * @param store The object which is invoking this method. * @param caller The caller class (not necessarily {@code object.getClass()}. * @param codec The object for which to write the class name. * @param The provider (for image reader/writer), or {@code null}. */ static void logCodecCreation(final GridCoverageStore store, final Class<?> caller, final Object codec, final ImageReaderWriterSpi spi) { assert caller.isInstance(store) : caller; final boolean write = (store instanceof GridCoverageWriter); final Locale locale = store.locale; String message = Loggings.getResources(locale).getString( Loggings.Keys.CreatedCodecOfClass_2, write ? 1 : 0, codec.getClass().getName()); if (spi != null) { final StringBuilder buffer = new StringBuilder(message).append('\n'); Formats.formatDescription(spi, locale, buffer); message = buffer.toString(); } final LogRecord record = new LogRecord(store.getFineLevel(), message); record.setLoggerName(LOGGER.getName()); record.setSourceClassName(caller.getName()); record.setSourceMethodName(write ? "setOutput" : "setInput"); LOGGER.log(record); } /** * Logs a read or write operation. This method can be invoked from * {@code read} or {@code write} methods only. * * @param level The logging level, as provided by {@link GridCoverageStore#getLogLevel(long)}. * @param locale The locale to use for formatting messages. * @param caller The caller class. * @param write {@code true} for a write operation, or {@code false} for a read operation. * @param stream The input or output file or stream. * @param imageIndex The index of the image being read or written. * @param coverage The coverage read or written. * @param actualSize The actual image size (may be different than the coverage grid envelope), * or {@code null} to compute it from the grid envelope. * @param crs The coordinate reference system (may be different than the coverage CRS), * or {@code null} for the coverage CRS. * @param destToExtractedGrid The transform from the destination grid to the extracted source * grid, or {@code null}. * @param timeNanos The elapsed execution time, in nanoseconds. */ static void logOperation( final Level level, final Locale locale, final Class<?> caller, final boolean write, Object stream, final int imageIndex, final GridCoverage coverage, final Dimension actualSize, CoordinateReferenceSystem crs, final MathTransform2D destToExtractedGrid, final long timeNanos) { /* * Get a string representation of the input or output. If the input/output is * a character sequence, a file or a URI/URL, then its string representation is * returned. Otherwise we will use only the class name. * * In the particular where the input is an array, we assume that each array element * is for an image at the corresponding index. This is what MosaicImageReader does. */ if (stream instanceof Object[]) { final Object[] array = (Object[]) stream; if (imageIndex < array.length) { stream = array[imageIndex]; } } final String streamName; if (IOUtilities.canProcessAsPath(stream)) { streamName = IOUtilities.filename(stream); } else { streamName = Classes.getShortClassName(stream); } /* * Get the coverage name, or "untitled" if unknown. This is often "unknown" * unless the ImageReader implements the NamedImageStore interface, as for * the NetCDF format. */ InternationalString name = null; if (coverage instanceof AbstractCoverage) { name = ((AbstractCoverage) coverage).getName(); } if (name == null) { name = Vocabulary.formatInternational(Vocabulary.Keys.Untitled); } /* * Get the view types (PHOTOGRAPHIC, RENDERED, PACKED, NATIVE, GEOPHYSICS). * This is specific to GridCoverage2D. For all other cases, we will write "none". */ final String viewTypes; if (coverage instanceof GridCoverage2D) { viewTypes = ((GridCoverage2D) coverage).getViewTypes().toString(); } else { viewTypes = Vocabulary.getResources(locale).getString(Vocabulary.Keys.None); } /* * Get the coverage dimension. In the special case of ImageCoverageWriter, the * dimension of the image actually written may be different than the dimension * of the coverage, if a subregion has been supplied in the ImageWriteParam. * The 'actualSize' argument allows to specify the dimension actually written. */ final GridEnvelope ge = coverage.getGridGeometry().getExtent(); final int dimension = ge.getDimension(); final StringBuilder buffer = new StringBuilder(); for (int i=0; i<dimension; i++) { int span = ge.getSpan(i); if (actualSize != null) { switch (i) { case X_DIMENSION: span = actualSize.width; break; case Y_DIMENSION: span = actualSize.height; break; } } if (i != 0) { buffer.append(" \u00D7 "); } buffer.append(span); } final String size = buffer.toString(); /* * Format the CRS name and its identifier (if any). In the case of ImageCoverageWriter, * the formatted CRS may be different than the coverage CRS since a resampling may have * been performed on-the-fly. */ String crsName = null; if (crs == null) { crs = coverage.getCoordinateReferenceSystem(); } if (crs != null) { buffer.setLength(0); String t = IdentifiedObjects.getName(crs, null); if (t != null) { buffer.append(t); } final String id = IdentifiedObjects.getIdentifierOrName(crs); if (id != null) { buffer.append(" (").append(id).append(')'); } if (buffer.length() != 0) { crsName = buffer.toString(); } } if (crsName == null) { crsName = Vocabulary.getResources(locale).getString(Vocabulary.Keys.Undefined); } /* * Get the "source to destination" transform. We will format affine transform * in a special way since it is the most common case and usually contains only * translation and scale factors. */ String transform = null; if (destToExtractedGrid != null && !destToExtractedGrid.isIdentity()) try { if (destToExtractedGrid instanceof AffineTransform) { final AffineTransform tr = (AffineTransform) destToExtractedGrid.inverse(); if (tr.getShearX() == 0 && tr.getShearY() == 0) { buffer.setLength(0); transform = buffer.append("AffineTransform[scale=(") .append(adjustForRoundingError(tr.getScaleX())).append(", ") .append(adjustForRoundingError(tr.getScaleY())).append("), translation=(") .append(adjustForRoundingError(tr.getTranslateX())).append(", ") .append(adjustForRoundingError(tr.getTranslateY())).append(")]").toString(); } else { // The 'new' is for avoiding AffineTransform2D.toString(). transform = new AffineTransform(tr).toString(); } } else { transform = Classes.getShortClassName(destToExtractedGrid); } } catch (NoninvertibleTransformException e) { transform = e.toString(); } if (transform == null) { transform = Vocabulary.getResources(locale).getString(Vocabulary.Keys.None); } /* * Put everything in a log record. */ final LogRecord record = Loggings.getResources(locale).getLogRecord( level, Loggings.Keys.CoverageStore_8, new Object[] { write ? 1 : 0, streamName, name.toString(locale), viewTypes, size, crsName, transform, timeNanos / 1E+6 }); record.setLoggerName(LOGGER.getName()); record.setSourceClassName(caller.getName()); record.setSourceMethodName(write ? "write" : "read"); LOGGER.log(record); } }