/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2007-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.image.io.metadata; import java.text.Format; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import java.util.logging.Logger; import java.util.logging.LogRecord; import javax.imageio.ImageReader; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOInvalidTreeException; import org.w3c.dom.Node; import org.geotools.util.logging.LoggedFormat; import org.geotools.util.logging.Logging; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.OptionalDependencies; import org.geotools.image.io.GeographicImageReader; import org.geotools.image.io.GeographicImageWriter; /** * Geographic informations encoded in image as metadata. This class provides various methods for * reading and writting attribute values in {@link IIOMetadataNode} according the {@linkplain * GeographicMetadataFormat geographic metadata format}. If some inconsistency are found while * reading (for example if the coordinate system dimension doesn't match the envelope dimension), * then the default implementation {@linkplain #warningOccurred logs a warning}. We do not throw * an exception because minor errors are not uncommon in geographic data, and we want to process * the data on a "<cite>best effort</cite>" basis. However because every warnings are logged * through the {@link #warningOccurred} method, subclasses can override this method if they want * treat some warnings as fatal errors. * * @since 2.4 * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public class GeographicMetadata extends IIOMetadata { /** * The {@link ImageReader} or {@link ImageWriter} that holds the metadata, * or {@code null} if none. */ private final Object owner; /** * The root node to be returned by {@link #getAsTree}. */ private Node root; /** * The coordinate reference system node. * Will be created only when first needed. */ private ImageReferencing referencing; /** * The geometry information. * Will be created only when first needed. */ private ImageGeometry geometry; /** * The list of {@linkplain Band bands}. * Will be created only when first needed. */ private ChildList<Band> bands; /** * The standard date format. Will be created only when first needed. */ private transient LoggedFormat<Date> dateFormat; /** * Creates a default metadata instance. This constructor defines no standard or native format. * The only format defined is the {@linkplain GeographicMetadataFormat geographic} one. */ public GeographicMetadata() { this((Object) null); } /** * Creates a default metadata instance for the given reader. * * @param reader The source image reader, or {@code null} if none. */ public GeographicMetadata(final ImageReader reader) { this((Object) reader); } /** * Creates a default metadata instance for the given writer. * * @param writer The target image writer, or {@code null} if none. */ public GeographicMetadata(final ImageWriter writer) { this((Object) writer); } /** * Creates a default metadata instance. This constructor defines no standard or native format. * The only format defined is the {@linkplain GeographicMetadataFormat geographic} one. */ private GeographicMetadata(final Object owner) { super(false, // Can not return or accept a DOM tree using the standard metadata format. null, // There is no native metadata format. null, // There is no native metadata format. new String[] { GeographicMetadataFormat.FORMAT_NAME }, new String[] { "org.geotools.image.io.metadata.GeographicMetadataFormat" }); this.owner = owner; } /** * Constructs a geographic metadata instance with the given format names and format class names. * This constructor passes the arguments to the {@linkplain IIOMetadata#IIOMetadata(boolean, * String, String, String[], String[]) super-class constructor} unchanged. * * @param standardMetadataFormatSupported {@code true} if this object can return or accept * a DOM tree using the standard metadata format. * @param nativeMetadataFormatName The name of the native metadata, or {@code null} if none. * @param nativeMetadataFormatClassName The name of the class of the native metadata format, * or {@code null} if none. * @param extraMetadataFormatNames Additional formats supported by this object, * or {@code null} if none. * @param extraMetadataFormatClassNames The class names of any additional formats * supported by this object, or {@code null} if none. */ public GeographicMetadata(final boolean standardMetadataFormatSupported, final String nativeMetadataFormatName, final String nativeMetadataFormatClassName, final String[] extraMetadataFormatNames, final String[] extraMetadataFormatClassNames) { super(standardMetadataFormatSupported, nativeMetadataFormatName, nativeMetadataFormatClassName, extraMetadataFormatNames, extraMetadataFormatClassNames); owner = null; } /** * Returns {@code false} since this node support some write operations. */ public boolean isReadOnly() { return false; } /** * Returns the root of a tree of metadata contained within this object * according to the conventions defined by a given metadata format. */ final Node getRootNode() { if (root == null) { root = new IIOMetadataNode(GeographicMetadataFormat.FORMAT_NAME); } return root; } /** * Returns the grid referencing. */ public ImageReferencing getReferencing() { if (referencing == null) { referencing = new ImageReferencing(this); } return referencing; } /** * Returns the grid geometry. */ public ImageGeometry getGeometry() { if (geometry == null) { geometry = new ImageGeometry(this); } return geometry; } /** * Returns the list of all {@linkplain Band bands}. */ final ChildList<Band> getBands() { if (bands == null) { bands = new ChildList.Bands(this); } return bands; } /** * Returns the sample type (typically {@value GeographicMetadataFormat#GEOPHYSICS} or * {@value GeographicMetadataFormat#PACKED}), or {@code null} if none. This type applies * to all {@linkplain Band bands}. */ public String getSampleType() { return getBands().getAttributeAsString("type"); } /** * Set the sample type for all {@linkplain Band bands}. Valid types include * {@value GeographicMetadataFormat#GEOPHYSICS} and {@value GeographicMetadataFormat#PACKED}. * * @param type The sample type, or {@code null} if none. */ public void setSampleType(final String type) { getBands().setAttributeAsEnum("type", type, GeographicMetadataFormat.SAMPLE_TYPES); } /** * Returns the number of {@linkplain Band bands} in the coverage. */ public int getNumBands() { return getBands().childCount(); } /** * Returns the band at the specified index. * * @param bandIndex the band index, ranging from 0 inclusive to {@link #getNumBands} exclusive. * @throws IndexOutOfBoundsException if the index is out of bounds. */ public Band getBand(final int bandIndex) throws IndexOutOfBoundsException { return getBands().getChild(bandIndex); } /** * Creates a new band and returns it. * * @param name The name for the new band. */ public Band addBand(final String name) { final Band band = getBands().addChild(); band.setName(name); return band; } /** * Checks the format name. */ private void checkFormatName(final String formatName) throws IllegalArgumentException { if (!GeographicMetadataFormat.FORMAT_NAME.equals(formatName)) { throw new IllegalArgumentException(Errors.getResources(getLocale()).getString( ErrorKeys.ILLEGAL_ARGUMENT_$2, "formatName", formatName)); } } /** * Returns the root of a tree of metadata contained within this object * according to the conventions defined by a given metadata format. * * @param formatName the desired metadata format. * @return The node forming the root of metadata tree. * @throws IllegalArgumentException if the format name is {@code null} or is not * one of the names returned by {@link #getMetadataFormatNames}. */ public Node getAsTree(final String formatName) throws IllegalArgumentException { checkFormatName(formatName); return getRootNode(); } /** * Alters the internal state of this metadata from a tree whose syntax is defined by * the given metadata format. The default implementation simply replaces all existing * state with the contents of the given tree. * * @param formatName The desired metadata format. * @param root An XML DOM Node object forming the root of a tree. * * @todo We need to performs a real merge; this is required by mosaic image readers. * See {@link MetadataMerge}. */ public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException { checkFormatName(formatName); reset(); this.root = root; } /** * Alters the internal state of this metadata from a tree defined by the specified metadata. * The default implementation expects the {@value GeographicMetadataFormat#FORMAT_NAME} format. * * @param metadata The metadata to merge to this object. * @throws IIOInvalidTreeException If the metadata can not be merged. */ public void mergeTree(final IIOMetadata metadata) throws IIOInvalidTreeException { final Node tree; try { tree = metadata.getAsTree(GeographicMetadataFormat.FORMAT_NAME); } catch (IllegalArgumentException exception) { throw new IIOInvalidTreeException(Errors.format( ErrorKeys.GEOTOOLS_EXTENSION_REQUIRED_$1, "mergeTree"), exception, null); } mergeTree(GeographicMetadataFormat.FORMAT_NAME, tree); } /** * Resets all the data stored in this object to default values. */ public void reset() { root = null; referencing = null; geometry = null; bands = null; } /** * Returns the language to use when {@linkplain #warningOccurred logging a warning}, * or {@code null} if none has been set. The default implementation delegates to * {@link ImageReader#getLocale} or {@link ImageWriter#getLocale} if possible, or * returns {@code null} otherwise. */ public Locale getLocale() { if (owner instanceof ImageReader) { return ((ImageReader) owner).getLocale(); } if (owner instanceof ImageWriter) { return ((ImageWriter) owner).getLocale(); } return null; } /** * Invoked when a warning occured. This method is invoked when some inconsistency has * been detected in the geographic metadata. The default implementation delegates to * {@link GeographicImageReader#warningOccurred} if possible, or send the record to * the {@code "org.geotools.image.io.metadata"} logger otherwise. * <p> * Subclasses may override this method if more processing is wanted, or for * throwing exception if some warnings should be considered as fatal errors. */ protected void warningOccurred(final LogRecord record) { if (owner instanceof GeographicImageReader) { ((GeographicImageReader) owner).warningOccurred(record); } else if (owner instanceof GeographicImageWriter) { ((GeographicImageWriter) owner).warningOccurred(record); } else { final Logger logger = Logging.getLogger(GeographicMetadata.class); record.setLoggerName(logger.getName()); logger.log(record); } } /** * A {@link LoggedFormat} which use the {@link GeographicMetadata#getLocale reader locale} * for warnings. */ private final class FormatAdapter<T> extends LoggedFormat<T> { private static final long serialVersionUID = -1108933164506428318L; FormatAdapter(final Format format, final Class<T> type) { super(format, type); } @Override protected Locale getWarningLocale() { return getLocale(); } @Override protected void logWarning(final LogRecord warning) { warningOccurred(warning); } } /** * Wraps the specified format in order to either parse fully a string, or log a warning. * * @param format The format to use for parsing and formatting. * @param type The expected type of parsed values. */ protected <T> LoggedFormat<T> createLoggedFormat(final Format format, final Class<T> type) { return new FormatAdapter<T>(format, type); } /** * Returns a standard date format to be shared by {@link MetadataAccessor}. */ final LoggedFormat<Date> dateFormat() { if (dateFormat == null) { final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); format.setTimeZone(TimeZone.getTimeZone("UTC")); dateFormat = createLoggedFormat(format, Date.class); dateFormat.setLogger("org.geotools.image.io.metadata"); dateFormat.setCaller(MetadataAccessor.class, "getDate"); } return dateFormat; } /** * Returns a string representation of this metadata, mostly for debugging purpose. */ @Override public String toString() { return OptionalDependencies.toString( OptionalDependencies.xmlToSwing(getAsTree(GeographicMetadataFormat.FORMAT_NAME))); } }