001: /*
002: * (c) 2004 Mike Nidel
003: *
004: * Take, Modify, Distribute freely
005: * Buy, Sell, Pass it off as your own
006: *
007: * Use this code at your own risk, the author makes no guarantee
008: * of performance and retains no liability for the failure of this
009: * software.
010: *
011: * If you feel like it, send any suggestions for improvement or
012: * bug fixes, or modified source code to mike@gelbin.org
013: *
014: * Do not taunt Happy Fun Ball.
015: *
016: */
017: package org.geotools.gce.geotiff.IIOMetadataAdpaters;
018:
019: import java.awt.geom.AffineTransform;
020:
021: import javax.imageio.metadata.IIOMetadata;
022: import javax.imageio.metadata.IIOMetadataNode;
023:
024: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.GeoTiffConstants;
025: import org.geotools.gce.geotiff.IIOMetadataAdpaters.utils.codes.GeoTiffGCSCodes;
026: import org.w3c.dom.Node;
027: import org.w3c.dom.NodeList;
028:
029: import com.sun.media.imageio.plugins.tiff.GeoTIFFTagSet;
030:
031: /**
032: * This class provides an abstraction from the details of TIFF data access for
033: * the purpose of retrieving GeoTIFFWritingUtilities metadata from an image.
034: *
035: * <p>
036: * All of the GeoKey values are included here as constants, and the portions of
037: * the GeoTIFFWritingUtilities specification pertaining to each have been copied
038: * for easy access.
039: * </p>
040: *
041: * <p>
042: * The majority of the possible GeoKey values and their meanings are NOT
043: * reproduced here. Only the most important GeoKey code values have been copied,
044: * for others see the specification.
045: * </p>
046: *
047: * <p>
048: * Convenience methods have been included to retrieve the various TIFFFields
049: * that are not part of the GeoKey directory, such as the Model Transformation
050: * and Model TiePoints. Retrieving a GeoKey from the GeoKey directory is a bit
051: * more specialized and requires knowledge of the correct key code.
052: * </p>
053: *
054: * <p>
055: * Making use of the geographic metadata still requires some basic understanding
056: * of the GeoKey values that is not provided here.
057: * </p>
058: *
059: * <p>
060: * For more information see the GeoTIFFWritingUtilities specification at
061: * http://www.remotesensing.org/geotiff/spec/geotiffhome.html
062: * </p>
063: *
064: * @author Mike Nidel
065: * @author Simone Giannecchini
066: *
067: * @source $URL:
068: * http://svn.geotools.org/geotools/trunk/gt/plugin/geotiff/src/org/geotools/gce/geotiff/IIOMetadataAdpaters/GeoTiffIIOMetadataDecoder.java $
069: * @todo we can improve a little bt this class caching the pixel scale, the transformation, etc...
070: */
071: public final class GeoTiffIIOMetadataDecoder {
072:
073: /** The root of the metadata DOM tree */
074: private IIOMetadataNode rootNode = null;
075:
076: private IIOMetadataNode geoKeyDir = null;
077:
078: private NodeList geoKeyDirEntries = null;
079:
080: private int geoKeyDirEntriesNum = 0;
081:
082: private IIOMetadataNode tiffTagsEntries;
083:
084: private int numTiffTasEntries;
085:
086: private int geoKeyDirVersion;
087:
088: private int geoKeyRevision;
089:
090: private int geoKeyMinorRevision;
091:
092: private int geoKeyDirTagsNum;
093:
094: private IIOMetadataNode geoKeyDoubleParams;
095:
096: private IIOMetadataNode geoKeyAsciiParams;
097:
098: /**
099: * The constructor builds a metadata adapter for the image metadata root
100: * IIOMetadataNode.
101: *
102: * @param imageMetadata
103: * The image metadata
104: */
105: public GeoTiffIIOMetadataDecoder(final IIOMetadata imageMetadata) {
106: // getting the image metadata root node.
107: rootNode = (IIOMetadataNode) imageMetadata
108: .getAsTree(imageMetadata.getNativeMetadataFormatName());
109: tiffTagsEntries = (IIOMetadataNode) rootNode.getFirstChild()
110: .getChildNodes();
111: numTiffTasEntries = tiffTagsEntries.getLength();
112: // getting the geokey ddirectory
113: geoKeyDir = getTiffField(GeoTIFFTagSet.TAG_GEO_KEY_DIRECTORY);
114: if (geoKeyDir == null) {
115: throw new UnsupportedOperationException(
116: "GeoKey directory does not exist");
117: }
118: if (rootNode == null) {
119: throw new UnsupportedOperationException(
120: "Unable to retrieve metadata");
121: }
122:
123: // getting all the entries and its nunber
124: geoKeyDirEntries = geoKeyDir.getFirstChild().getChildNodes();
125: // GeoKeyDirVersion and the other parameters
126: geoKeyDirVersion = getTiffShort(geoKeyDir,
127: GeoTiffGCSCodes.GEO_KEY_DIRECTORY_VERSION_INDEX);
128: geoKeyRevision = getTiffShort(geoKeyDir,
129: GeoTiffGCSCodes.GEO_KEY_REVISION_INDEX);
130: if (geoKeyRevision != 1) {
131: geoKeyRevision = 1;
132: // I had to remove this because I did not want to have wrong
133: // revision numbers blocking us.
134: // throw new UnsupportedOperationException("Unsupported revision");
135: }
136: geoKeyMinorRevision = getTiffShort(geoKeyDir,
137: GeoTiffGCSCodes.GEO_KEY_MINOR_REVISION_INDEX);
138: // loading the number of geokeys inside the geokeydirectory
139: geoKeyDirTagsNum = getTiffShort(geoKeyDir,
140: GeoTiffGCSCodes.GEO_KEY_NUM_KEYS_INDEX);
141: // each geokey has 4 entries
142: geoKeyDirEntriesNum = geoKeyDirEntries.getLength();
143:
144: geoKeyDoubleParams = getTiffField(GeoTIFFTagSet.TAG_GEO_DOUBLE_PARAMS);
145: geoKeyAsciiParams = getTiffField(GeoTIFFTagSet.TAG_GEO_ASCII_PARAMS);
146:
147: }
148:
149: /**
150: * Gets the version of the GeoKey directory. This is typically a value of 1
151: * and can be used to check that the data is of a valid format.
152: *
153: * @return DOCUMENT ME!
154: *
155: * @throws UnsupportedOperationException
156: * DOCUMENT ME!
157: */
158: public int getGeoKeyDirectoryVersion() {
159:
160: // now get the value from the correct TIFFShort location
161: return geoKeyDirVersion;
162:
163: }
164:
165: /**
166: * Gets the revision number of the GeoKeys in this metadata.
167: *
168: * @return DOCUMENT ME!
169: *
170: * @throws UnsupportedOperationException
171: * DOCUMENT ME!
172: */
173: public int getGeoKeyRevision() {
174:
175: // Get the value from the correct TIFFShort
176: return geoKeyRevision;
177: }
178:
179: /**
180: * Gets the minor revision number of the GeoKeys in this metadata.
181: *
182: * @return DOCUMENT ME!
183: *
184: * @throws UnsupportedOperationException
185: * DOCUMENT ME!
186: */
187: public int getGeoKeyMinorRevision() {
188:
189: // Get the value from the correct TIFFShort
190: return geoKeyMinorRevision;
191: }
192:
193: /**
194: * Gets the number of GeoKeys in the geokeys directory.
195: *
196: * @return DOCUMENT ME!
197: *
198: * @throws UnsupportedOperationException
199: * DOCUMENT ME!
200: */
201: public int getNumGeoKeys() {
202:
203: return geoKeyDirTagsNum;
204: }
205:
206: /**
207: * Gets a GeoKey value as a String. This implementation should be
208: * "quiet" in the sense that it should not throw any exceptions but
209: * only return null in the event that the data organization is not as
210: * expected.
211: *
212: * @param keyID
213: * The numeric ID of the GeoKey
214: *
215: * @return A string representing the value, or null if the key was not
216: * found.
217: */
218: public String getGeoKey(final int keyID) {
219:
220: final GeoKeyEntry rec = getGeoKeyRecord(keyID);
221: if (rec == null)
222: return null;
223: if (rec.getTiffTagLocation() == 0)
224: // value is stored directly in the GeoKey record
225: return String.valueOf(rec.getValueOffset());
226:
227: // value is stored externally
228: // get the TIFF field where the data is actually stored
229: final IIOMetadataNode field = getTiffField(rec
230: .getTiffTagLocation());
231:
232: if (field != null) {
233: final Node sequence = field.getFirstChild();
234:
235: if (sequence != null) {
236: if (sequence.getNodeName().equals(
237: GeoTiffConstants.GEOTIFF_ASCIIS_TAG)) {
238: // TIFFAscii values are handled specially
239: return getTiffAscii((IIOMetadataNode) sequence, rec
240: .getValueOffset(), rec.getCount());
241: } else {
242: // value is numeric
243: return getValueAttribute(sequence.getChildNodes()
244: .item(rec.getValueOffset()));
245: }
246: }
247: }
248:
249: return null;
250: }
251:
252: /**
253: * Gets a record containing the four TIFFShort values for a geokey entry.
254: * For more information see the GeoTIFFWritingUtilities specification.
255: *
256: * @param keyID
257: * DOCUMENT ME!
258: *
259: * @return the record with the given keyID, or null if none is found
260: *
261: * @throws UnsupportedOperationException
262: * DOCUMENT ME!
263: */
264: public GeoKeyEntry getGeoKeyRecord(int keyID) {
265:
266: int this KeyID = 0;
267: // embed the exit condition in the for loop
268: for (int i = 4; i < geoKeyDirEntriesNum; i += 4) {
269:
270: this KeyID = getIntValueAttribute(geoKeyDirEntries.item(i));// key
271:
272: if (this KeyID == keyID) {
273: // we've found the right GeoKey, now build it
274: return new GeoKeyEntry(this KeyID,
275: getIntValueAttribute(geoKeyDirEntries
276: .item(i + 1)),// location
277: getIntValueAttribute(geoKeyDirEntries
278: .item(i + 2)),// count
279: getIntValueAttribute(geoKeyDirEntries
280: .item(i + 3)));// offset
281: }
282: }
283:
284: return null;
285: }
286:
287: /**
288: * Gets a record containing the four TIFFShort values for a geokey entry.
289: * For more information see the GeoTIFFWritingUtilities specification.
290: *
291: * @param index
292: * DOCUMENT ME!
293: *
294: * @return the record with the given keyID, or null if none is found
295: *
296: * @throws UnsupportedOperationException
297: * DOCUMENT ME!
298: */
299: public GeoKeyEntry getGeoKeyRecordByIndex(int index) {
300: index *= 4;
301: return new GeoKeyEntry(getIntValueAttribute(geoKeyDirEntries
302: .item(index)), getIntValueAttribute(geoKeyDirEntries
303: .item(index + 1)),
304: getIntValueAttribute(geoKeyDirEntries.item(index + 2)),
305: getIntValueAttribute(geoKeyDirEntries.item(index + 3)));
306:
307: }
308:
309: /**
310: * Gets the model pixel scales from the correct TIFFField
311: *
312: */
313: public PixelScale getModelPixelScales() {
314: final double[] pixScales = getTiffDoubles(getTiffField(GeoTIFFTagSet.TAG_MODEL_PIXEL_SCALE));
315: if (pixScales == null)
316: return null;
317: final int length = pixScales.length;
318: final PixelScale retVal = new PixelScale();
319: for (int i = 0; i < length; i++)
320: switch (i) {
321: case 0:
322: retVal.setScaleX(pixScales[i]);
323: break;
324: case 1:
325: retVal.setScaleY(pixScales[i]);
326: break;
327: case 2:
328: retVal.setScaleZ(pixScales[i]);
329: break;
330: }
331: return retVal;
332:
333: }
334:
335: /**
336: * Gets the model tie points from the appropriate TIFFField
337: *
338: * @return the tie points, or null if not found
339: */
340: public TiePoint[] getModelTiePoints() {
341:
342: IIOMetadataNode node = getTiffField(GeoTIFFTagSet.TAG_MODEL_TIE_POINT);
343: if (node == null)
344: return null;
345: final double tiePoints[] = getTiffDoubles(node);
346: if (tiePoints == null || tiePoints.length <= 0)
347: return null;
348: final int numTiePoints = tiePoints.length / 6;
349: final TiePoint retVal[] = new TiePoint[numTiePoints];
350: int initialIndex = 0;
351: for (int i = 0; i < numTiePoints; i++) {
352: initialIndex = i * 6;
353: retVal[i] = new TiePoint(tiePoints[initialIndex],
354: tiePoints[initialIndex + 1],
355: tiePoints[initialIndex + 2],
356: tiePoints[initialIndex + 3],
357: tiePoints[initialIndex + 4],
358: tiePoints[initialIndex + 5]);
359: }
360: return retVal;
361:
362: }
363:
364: /**
365: * Tells me if the underlying {@link IIOMetadata} contains ModelTiepointTag
366: * tag for {@link TiePoint}.
367: *
368: * @return true if ModelTiepointTag is present, false otherwise.
369: */
370: public boolean hasTiePoints() {
371: IIOMetadataNode node = getTiffField(GeoTIFFTagSet.TAG_MODEL_TIE_POINT);
372: if (node == null)
373: return false;
374: final double tiePoints[] = getTiffDoubles(node);
375: if (tiePoints == null || tiePoints.length <= 0)
376: return false;
377: return true;
378:
379: }
380:
381: /**
382: * Tells me if the underlying {@link IIOMetadata} contains ModelTiepointTag
383: * tag for {@link TiePoint}.
384: *
385: * @return true if ModelTiepointTag is present, false otherwise.
386: */
387: public boolean hasPixelScales() {
388: final double[] pixScales = getTiffDoubles(getTiffField(GeoTIFFTagSet.TAG_MODEL_PIXEL_SCALE));
389: if (pixScales == null)
390: return false;
391: final int length = pixScales.length;
392: double tempVal;
393: for (int i = 0; i < length; i++) {
394: tempVal = pixScales[i];
395:
396: if (Double.isInfinite(tempVal) || Double.isNaN(tempVal))
397: return false;
398: }
399: return true;
400:
401: }
402:
403: /**
404: * Gets the model tie points from the appropriate TIFFField
405: *
406: * <p>
407: * Attention, for the moment we support only 2D baseline transformations.
408: *
409: * @return the transformation, or null if not found
410: */
411: public AffineTransform getModelTransformation() {
412:
413: final IIOMetadataNode node = getTiffField(GeoTIFFTagSet.TAG_MODEL_TRANSFORMATION);
414: if (node == null)
415: return null;
416: final double[] modelTransformation = getTiffDoubles(node);
417: if (modelTransformation == null)
418: return null;
419: AffineTransform transform = null;
420: if (modelTransformation.length == 9) {
421: transform = new AffineTransform(modelTransformation[0],
422: modelTransformation[4], modelTransformation[1],
423: modelTransformation[5], modelTransformation[6],
424: modelTransformation[7]);
425: } else if (modelTransformation.length == 16) {
426: transform = new AffineTransform(modelTransformation[0],
427: modelTransformation[4], modelTransformation[1],
428: modelTransformation[5], modelTransformation[3],
429: modelTransformation[7]);
430: }
431:
432: return transform;
433:
434: }
435:
436: /**
437: * Tells me if the underlying {@link IIOMetadata} contains
438: * ModelTransformationTag tag for {@link AffineTransform} that map from
439: * Raster Space to World Space.
440: *
441: * @return true if ModelTransformationTag is present, false otherwise.
442: *
443: */
444: public boolean hasModelTrasformation() {
445: final IIOMetadataNode node = getTiffField(GeoTIFFTagSet.TAG_MODEL_TRANSFORMATION);
446: if (node == null)
447: return false;
448: final double[] modelTransformation = getTiffDoubles(node);
449: if (modelTransformation == null)
450: return false;
451: return true;
452: }
453:
454: // private utility methods
455:
456: /**
457: * Gets the value attribute of the given Node.
458: *
459: * @param node
460: * A Node containing a value attribute, for example the node
461: * <TIFFShort value="123">
462: *
463: * @return A String containing the text from the value attribute. In the
464: * above example, the string would be 123
465: */
466: private String getValueAttribute(Node node) {
467: return node.getAttributes().getNamedItem(
468: GeoTiffConstants.VALUE_ATTR).getNodeValue();
469: }
470:
471: /**
472: * Gets the value attribute's contents and parses it as an int
473: *
474: * @param node
475: * DOCUMENT ME!
476: *
477: * @return DOCUMENT ME!
478: */
479: private int getIntValueAttribute(Node node) {
480: return Integer.parseInt(getValueAttribute(node));
481: }
482:
483: /**
484: * Gets a TIFFField node with the given tag number. This is done by
485: * searching for a TIFFField with attribute number whose value is the
486: * specified tag value.
487: *
488: * @param tag
489: * DOCUMENT ME!
490: *
491: * @return DOCUMENT ME!
492: */
493: private IIOMetadataNode getTiffField(final int tag) {
494: if (tag == GeoTIFFTagSet.TAG_GEO_ASCII_PARAMS
495: && this .geoKeyAsciiParams != null)
496: return this .geoKeyAsciiParams;
497: if (tag == GeoTIFFTagSet.TAG_GEO_DOUBLE_PARAMS
498: && this .geoKeyDoubleParams != null)
499: return this .geoKeyDoubleParams;
500: if (tag == GeoTIFFTagSet.TAG_GEO_KEY_DIRECTORY
501: && this .geoKeyDir != null)
502: return this .geoKeyDir;
503:
504: // embed the exit condition in the for loop
505: Node child = null;
506: Node number = null;
507:
508: for (int i = 0; i < numTiffTasEntries; i++) {
509: // search through all the TIFF fields to find the one with the
510: // given tag value
511: child = tiffTagsEntries.item(i);
512: number = child.getAttributes().getNamedItem(
513: GeoTiffConstants.NUMBER_ATTR);
514:
515: if (number != null) {
516: if (tag == Integer.parseInt(number.getNodeValue()))
517: return (IIOMetadataNode) child;
518:
519: }
520: }
521:
522: return null;
523: }
524:
525: /**
526: * Gets a single TIFFShort value at the given index.
527: *
528: * @param tiffField
529: * An IIOMetadataNode pointing to a TIFFField element that
530: * contains a TIFFShorts element.
531: * @param index
532: * The 0-based index of the desired short value
533: *
534: * @return DOCUMENT ME!
535: */
536: private int getTiffShort(final IIOMetadataNode tiffField,
537: final int index) {
538:
539: return getIntValueAttribute(((IIOMetadataNode) tiffField
540: .getFirstChild()).getElementsByTagName(
541: GeoTiffConstants.GEOTIFF_SHORT_TAG).item(index));
542:
543: }
544:
545: /**
546: * Gets an array of double values from a TIFFDoubles TIFFField.
547: *
548: * @param tiffField
549: * An IIOMetadataNode pointing to a TIFFField element that
550: * contains a TIFFDoubles element.
551: *
552: * @return DOCUMENT ME!
553: */
554: private double[] getTiffDoubles(final IIOMetadataNode tiffField) {
555:
556: if (tiffField == null)
557: return null;
558: final NodeList doubles = ((IIOMetadataNode) tiffField
559: .getFirstChild())
560: .getElementsByTagName(GeoTiffConstants.GEOTIFF_DOUBLE_TAG);
561: final int length = doubles.getLength();
562: final double[] result = new double[length];
563: for (int i = 0; i < length; i++) {
564: result[i] = Double.parseDouble(getValueAttribute(doubles
565: .item(i)));
566: }
567:
568: return result;
569: }
570:
571: /**
572: * Gets a portion of a TIFFAscii string with the specified start character
573: * and length;
574: *
575: * @param tiffField
576: * An IIOMetadataNode pointing to a TIFFField element that
577: * contains a TIFFAsciis element. This element should contain a
578: * single TiffAscii element.
579: * @param start
580: * DOCUMENT ME!
581: * @param length
582: * DOCUMENT ME!
583: *
584: * @return A substring of the value contained in the TIFFAscii node, with
585: * the final '|' character removed.
586: */
587: private String getTiffAscii(final IIOMetadataNode tiffField,
588: final int start, final int length) {
589:
590: // there should be only one, so get the first
591: // GeoTIFFWritingUtilities specification places a vertical bar '|' in
592: // place of \0
593: // null delimiters so drop off the vertical bar for Java Strings
594: return getValueAttribute(
595: ((IIOMetadataNode) tiffField.getFirstChild())
596: .getElementsByTagName(
597: GeoTiffConstants.GEOTIFF_ASCII_TAG)
598: .item(0)).substring(start, start + length - 1);
599:
600: }
601:
602: public IIOMetadataNode getRootNode() {
603: return rootNode;
604: }
605: } // end of class GeoTiffIIOMetadataDecoder
|