001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2007, GeoTools Project Managment Committee (PMC)
005: * (C) 2007, Geomatys
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.image.io.metadata;
018:
019: import java.text.Format;
020: import java.text.DateFormat;
021: import java.text.SimpleDateFormat;
022: import java.util.Date;
023: import java.util.Locale;
024: import java.util.TimeZone;
025: import java.util.logging.LogRecord;
026: import javax.imageio.ImageReader;
027: import javax.imageio.ImageWriter;
028: import javax.imageio.metadata.IIOMetadata;
029: import javax.imageio.metadata.IIOMetadataNode;
030: import javax.imageio.metadata.IIOInvalidTreeException;
031: import org.w3c.dom.Node;
032:
033: import org.geotools.util.logging.LoggedFormat;
034: import org.geotools.util.logging.Logging;
035: import org.geotools.resources.i18n.Errors;
036: import org.geotools.resources.i18n.ErrorKeys;
037: import org.geotools.resources.OptionalDependencies;
038: import org.geotools.image.io.GeographicImageReader;
039: import org.geotools.image.io.GeographicImageWriter;
040:
041: /**
042: * Geographic informations encoded in image as metadata. This class provides various methods for
043: * reading and writting attribute values in {@link IIOMetadataNode} according the {@linkplain
044: * GeographicMetadataFormat geographic metadata format}. If some inconsistency are found while
045: * reading (for example if the coordinate system dimension doesn't match the envelope dimension),
046: * then the default implementation {@linkplain #warningOccurred logs a warning}. We do not throw
047: * an exception because minor errors are not uncommon in geographic data, and we want to process
048: * the data on a "<cite>best effort</cite>" basis. However because every warnings are logged
049: * through the {@link #warningOccurred} method, subclasses can override this method if they want
050: * treat some warnings as fatal errors.
051: *
052: * @since 2.4
053: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/metadata/GeographicMetadata.java $
054: * @version $Id: GeographicMetadata.java 27862 2007-11-12 19:51:19Z desruisseaux $
055: * @author Martin Desruisseaux
056: */
057: public class GeographicMetadata extends IIOMetadata {
058: /**
059: * The {@link ImageReader} or {@link ImageWriter} that holds the metadata,
060: * or {@code null} if none.
061: */
062: private final Object owner;
063:
064: /**
065: * The root node to be returned by {@link #getAsTree}.
066: */
067: private Node root;
068:
069: /**
070: * The coordinate reference system node.
071: * Will be created only when first needed.
072: */
073: private ImageReferencing referencing;
074:
075: /**
076: * The grid geometry node.
077: * Will be created only when first needed.
078: */
079: private ImageGeometry geometry;
080:
081: /**
082: * The list of {@linkplain Band bands}.
083: * Will be created only when first needed.
084: */
085: private ChildList/*<Bands>*/bands;
086:
087: /**
088: * The standard date format. Will be created only when first needed.
089: */
090: private transient LoggedFormat/*<Date>*/dateFormat;
091:
092: /**
093: * Creates a default metadata instance. This constructor defines no standard or native format.
094: * The only format defined is the {@linkplain GeographicMetadataFormat geographic} one.
095: */
096: public GeographicMetadata() {
097: this ((Object) null);
098: }
099:
100: /**
101: * Creates a default metadata instance for the given reader.
102: *
103: * @param reader The source image reader, or {@code null} if none.
104: */
105: public GeographicMetadata(final ImageReader reader) {
106: this ((Object) reader);
107: }
108:
109: /**
110: * Creates a default metadata instance for the given writer.
111: *
112: * @param writer The target image writer, or {@code null} if none.
113: */
114: public GeographicMetadata(final ImageWriter writer) {
115: this ((Object) writer);
116: }
117:
118: /**
119: * Creates a default metadata instance. This constructor defines no standard or native format.
120: * The only format defined is the {@linkplain GeographicMetadataFormat geographic} one.
121: */
122: private GeographicMetadata(final Object owner) {
123: super (
124: false, // Can not return or accept a DOM tree using the standard metadata format.
125: null, // There is no native metadata format.
126: null, // There is no native metadata format.
127: new String[] { GeographicMetadataFormat.FORMAT_NAME },
128: new String[] { "org.geotools.image.io.metadata.GeographicMetadataFormat" });
129: this .owner = owner;
130: }
131:
132: /**
133: * Constructs a geographic metadata instance with the given format names and format class names.
134: * This constructor passes the arguments to the {@linkplain IIOMetadata#IIOMetadata(boolean,
135: * String, String, String[], String[]) super-class constructor} unchanged.
136: *
137: * @param standardMetadataFormatSupported {@code true} if this object can return or accept
138: * a DOM tree using the standard metadata format.
139: * @param nativeMetadataFormatName The name of the native metadata, or {@code null} if none.
140: * @param nativeMetadataFormatClassName The name of the class of the native metadata format,
141: * or {@code null} if none.
142: * @param extraMetadataFormatNames Additional formats supported by this object,
143: * or {@code null} if none.
144: * @param extraMetadataFormatClassNames The class names of any additional formats
145: * supported by this object, or {@code null} if none.
146: */
147: public GeographicMetadata(
148: final boolean standardMetadataFormatSupported,
149: final String nativeMetadataFormatName,
150: final String nativeMetadataFormatClassName,
151: final String[] extraMetadataFormatNames,
152: final String[] extraMetadataFormatClassNames) {
153: super (standardMetadataFormatSupported,
154: nativeMetadataFormatName,
155: nativeMetadataFormatClassName,
156: extraMetadataFormatNames, extraMetadataFormatClassNames);
157: owner = null;
158: }
159:
160: /**
161: * Returns {@code false} since this node support some write operations.
162: */
163: public boolean isReadOnly() {
164: return false;
165: }
166:
167: /**
168: * Returns the root of a tree of metadata contained within this object
169: * according to the conventions defined by a given metadata format.
170: */
171: final Node getRootNode() {
172: if (root == null) {
173: root = new IIOMetadataNode(
174: GeographicMetadataFormat.FORMAT_NAME);
175: }
176: return root;
177: }
178:
179: /**
180: * Returns the grid referencing.
181: */
182: public ImageReferencing getReferencing() {
183: if (referencing == null) {
184: referencing = new ImageReferencing(this );
185: }
186: return referencing;
187: }
188:
189: /**
190: * Returns the grid geometry.
191: */
192: public ImageGeometry getGeometry() {
193: if (geometry == null) {
194: geometry = new ImageGeometry(this );
195: }
196: return geometry;
197: }
198:
199: /**
200: * Returns the list of all {@linkplain Band bands}.
201: */
202: final ChildList/*<Bands>*/getBands() {
203: if (bands == null) {
204: bands = new ChildList.Bands(this );
205: }
206: return bands;
207: }
208:
209: /**
210: * Returns the sample type (typically {@value GeographicMetadataFormat#GEOPHYSICS} or
211: * {@value GeographicMetadataFormat#PACKED}), or {@code null} if none. This type applies
212: * to all {@linkplain Band bands}.
213: */
214: public String getSampleType() {
215: return getBands().getString("type");
216: }
217:
218: /**
219: * Set the sample type for all {@linkplain Band bands}. Valid types include
220: * {@value GeographicMetadataFormat#GEOPHYSICS} and {@value GeographicMetadataFormat#PACKED}.
221: *
222: * @param type The sample type, or {@code null} if none.
223: */
224: public void setSampleType(final String type) {
225: getBands().setEnum("type", type,
226: GeographicMetadataFormat.SAMPLE_TYPES);
227: }
228:
229: /**
230: * Returns the number of {@linkplain Band bands} in the coverage.
231: */
232: public int getNumBands() {
233: return getBands().childCount();
234: }
235:
236: /**
237: * Returns the band at the specified index.
238: *
239: * @param bandIndex the band index, ranging from 0 inclusive to {@link #getNumBands} exclusive.
240: * @throws IndexOutOfBoundsException if the index is out of bounds.
241: */
242: public Band getBand(final int bandIndex)
243: throws IndexOutOfBoundsException {
244: return (Band) getBands().getChild(bandIndex);
245: }
246:
247: /**
248: * Creates a new band and returns it.
249: *
250: * @param name The name for the new band.
251: */
252: public Band addBand(final String name) {
253: final Band band = (Band) getBands().addChild();
254: band.setName(name);
255: return band;
256: }
257:
258: /**
259: * Checks the format name.
260: */
261: private void checkFormatName(final String formatName)
262: throws IllegalArgumentException {
263: if (!GeographicMetadataFormat.FORMAT_NAME.equals(formatName)) {
264: throw new IllegalArgumentException(Errors.getResources(
265: getLocale()).getString(
266: ErrorKeys.ILLEGAL_ARGUMENT_$2, "formatName",
267: formatName));
268: }
269: }
270:
271: /**
272: * Returns the root of a tree of metadata contained within this object
273: * according to the conventions defined by a given metadata format.
274: *
275: * @param formatName the desired metadata format.
276: * @return The node forming the root of metadata tree.
277: * @throws IllegalArgumentException if the format name is {@code null} or is not
278: * one of the names returned by {@link #getMetadataFormatNames()
279: * getMetadataFormatNames()}.
280: */
281: public Node getAsTree(final String formatName)
282: throws IllegalArgumentException {
283: checkFormatName(formatName);
284: return getRootNode();
285: }
286:
287: /**
288: * Alters the internal state of this metadata from a tree whose syntax is defined by
289: * the given metadata format. The default implementation simply replaces all existing
290: * state with the contents of the given tree.
291: *
292: * @param formatName The desired metadata format.
293: * @param root An XML DOM Node object forming the root of a tree.
294: */
295: public void mergeTree(final String formatName, final Node root)
296: throws IIOInvalidTreeException {
297: checkFormatName(formatName);
298: reset();
299: this .root = root;
300: }
301:
302: /**
303: * Alters the internal state of this metadata from a tree defined by the specified metadata.
304: * The default implementation expect the {@value GeographicMetadataFormat#FORMAT_NAME} format.
305: *
306: * @param metadata The metadata to merge to this object.
307: */
308: public void mergeTree(final IIOMetadata metadata)
309: throws IIOInvalidTreeException {
310: mergeTree(GeographicMetadataFormat.FORMAT_NAME, metadata
311: .getAsTree(GeographicMetadataFormat.FORMAT_NAME));
312: }
313:
314: /**
315: * Resets all the data stored in this object to default values.
316: */
317: public void reset() {
318: root = null;
319: referencing = null;
320: geometry = null;
321: bands = null;
322: }
323:
324: /**
325: * Returns the language to use when {@linkplain #warningOccurred logging a warning},
326: * or {@code null} if none has been set. The default implementation delegates to
327: * {@link ImageReader#getLocale} or {@link ImageWriter#getLocale} if possible, or
328: * returns {@code null} otherwise.
329: */
330: public Locale getLocale() {
331: if (owner instanceof ImageReader) {
332: return ((ImageReader) owner).getLocale();
333: }
334: if (owner instanceof ImageWriter) {
335: return ((ImageWriter) owner).getLocale();
336: }
337: return null;
338: }
339:
340: /**
341: * Invoked when a warning occured. This method is invoked when some inconsistency has
342: * been detected in the geographic metadata. The default implementation delegates to
343: * {@link GeographicImageReader#warningOccurred} if possible, or send the record to
344: * the {@code "org.geotools.image.io.metadata"} logger otherwise.
345: * <p>
346: * Subclasses may override this method if more processing is wanted, or for
347: * throwing exception if some warnings should be considered as fatal errors.
348: */
349: protected void warningOccurred(final LogRecord record) {
350: if (owner instanceof GeographicImageReader) {
351: ((GeographicImageReader) owner).warningOccurred(record);
352: } else if (owner instanceof GeographicImageWriter) {
353: ((GeographicImageWriter) owner).warningOccurred(record);
354: } else {
355: Logging.getLogger("org.geotools.image.io.metadata").log(
356: record);
357: }
358: }
359:
360: /**
361: * Wraps the specified format in order to either parse fully a string, or log a warning.
362: *
363: * @param format The format to use for parsing and formatting.
364: * @param type The expected type of parsed values.
365: */
366: protected/*<T>*/LoggedFormat createLoggedFormat(
367: final Format format, final Class/*<T>*/type) {
368: return new LoggedFormat/*<T>*/(format, type) {
369: //@Override
370: protected Locale getWarningLocale() {
371: return getLocale();
372: }
373:
374: //@Override
375: protected void logWarning(final LogRecord warning) {
376: warningOccurred(warning);
377: }
378: };
379: }
380:
381: /**
382: * Returns a standard date format to be shared by {@link MetadataAccessor}.
383: */
384: final LoggedFormat/*<Date>*/dateFormat() {
385: if (dateFormat == null) {
386: final DateFormat format = new SimpleDateFormat(
387: "yyyy-MM-dd HH:mm:ss", Locale.US);
388: format.setTimeZone(TimeZone.getTimeZone("UTC"));
389: dateFormat = createLoggedFormat(format, Date.class);
390: dateFormat.setLogger("org.geotools.image.io.metadata");
391: dateFormat.setCaller(MetadataAccessor.class, "getDate");
392: }
393: return dateFormat;
394: }
395:
396: /**
397: * Returns a string representation of this metadata, mostly for debugging purpose.
398: */
399: public String toString() {
400: return OptionalDependencies
401: .toString(OptionalDependencies
402: .xmlToSwing(getAsTree(GeographicMetadataFormat.FORMAT_NAME)));
403: }
404: }
|