/* * (c) 2004 Mike Nidel * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * Take, Modify, Distribute freely * Buy, Sell, Pass it off as your own * * Use this code at your own risk, the author makes no guarantee * of performance and retains no liability for the failure of this * software. * * If you feel like it, send any suggestions for improvement or * bug fixes, or modified sourceFile code to mike@gelbin.org * * Do not taunt Happy Fun Ball. * */ /* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.grid.imageio.geotiff.metadata; import it.geosolutions.imageio.plugins.tiff.GeoTIFFTagSet; import java.awt.geom.AffineTransform; import java.util.Collection; import java.util.HashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import org.geotools.coverage.grid.imageio.geotiff.metadata.codes.GeoTiffGCSCodes; import org.geotools.coverage.grid.io.imageio.geotiff.GeoKeyEntry; import org.geotools.coverage.grid.io.imageio.geotiff.GeoTiffConstants; import org.geotools.coverage.grid.io.imageio.geotiff.PixelScale; import org.geotools.coverage.grid.io.imageio.geotiff.TiePoint; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * This class provides an abstraction from the details of TIFF data access for * the purpose of retrieving GeoTIFFWritingUtilities metadata from an image. * * <p> * All of the GeoKey values are included here as constants, and the portions of * the GeoTIFFWritingUtilities specification pertaining to each have been copied * for easy access. * </p> * * <p> * The majority of the possible GeoKey values and their meanings are NOT * reproduced here. Only the most important GeoKey code values have been copied, * for others see the specification. * </p> * * <p> * Convenience methods have been included to retrieve the various TIFFFields * that are not part of the GeoKey directory, such as the Model Transformation * and Model TiePoints. Retrieving a GeoKey from the GeoKey directory is a bit * more specialized and requires knowledge of the correct key code. * </p> * * <p> * Making use of the geographic metadata still requires some basic understanding * of the GeoKey values that is not provided here. * </p> * * <p> * For more information see the GeoTIFFWritingUtilities specification at * http://www.remotesensing.org/geotiff/spec/geotiffhome.html * </p> * * @author Mike Nidel * @author Simone Giannecchini, GeoSolutions * * * @source $URL$ */ public final class GeoTiffIIOMetadataDecoder { /** {@link Logger}. */ private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GeoTiffIIOMetadataDecoder.class); private final HashMap<Integer, GeoKeyEntry> geoKeys; private int geoKeyDirVersion; private int geoKeyRevision; private int geoKeyMinorRevision; private int geoKeyDirTagsNum; private final PixelScale pixelScale; private final TiePoint[] tiePoints; private final double noData; private final AffineTransform modelTransformation; private IIOMetadataNode rootNode; /** * The constructor builds a metadata adapter for the image metadata root IIOMetadataNode. * * @param imageMetadata * The image metadata */ public GeoTiffIIOMetadataDecoder(final IIOMetadata imageMetadata) { if (imageMetadata == null) { throw new IllegalArgumentException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "imageMetadata")); } // getting the image metadata root node. rootNode = (IIOMetadataNode) imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()); if (rootNode == null) { throw new NullPointerException("Unable to retrieve metadata"); } // getting the geokey directory IIOMetadataNode geoKeyDir = GeoTiffMetadataUtilities.getTiffField(rootNode, GeoTIFFTagSet.TAG_GEO_KEY_DIRECTORY); geoKeys = new HashMap<Integer, GeoKeyEntry>(); if (geoKeyDir != null) { NodeList geoKeyDirEntries = geoKeyDir.getFirstChild().getChildNodes(); for (int i = 4; i < geoKeyDirEntries.getLength(); i += 4) { int keyID = GeoTiffMetadataUtilities.getIntValueAttribute(geoKeyDirEntries.item(i)); GeoKeyEntry key = new GeoKeyEntry(keyID,// key GeoTiffMetadataUtilities.getIntValueAttribute(geoKeyDirEntries.item(i + 1)),// location GeoTiffMetadataUtilities.getIntValueAttribute(geoKeyDirEntries.item(i + 2)),// count GeoTiffMetadataUtilities.getIntValueAttribute(geoKeyDirEntries.item(i + 3)));// offset if (!geoKeys.containsKey(keyID)) { geoKeys.put(keyID, key); } } // GeoKeyDirVersion and the other parameters geoKeyDirVersion = GeoTiffMetadataUtilities.getTiffShort(geoKeyDir, GeoTiffGCSCodes.GEO_KEY_DIRECTORY_VERSION_INDEX); geoKeyRevision = GeoTiffMetadataUtilities.getTiffShort(geoKeyDir, GeoTiffGCSCodes.GEO_KEY_REVISION_INDEX); if (geoKeyRevision != 1) { geoKeyRevision = 1; // I had to remove this because I did not want to have wrong // revision numbers blocking us. // throw new UnsupportedOperationException("Unsupported revision"); } geoKeyMinorRevision = GeoTiffMetadataUtilities.getTiffShort(geoKeyDir, GeoTiffGCSCodes.GEO_KEY_MINOR_REVISION_INDEX); // loading the number of geokeys inside the geokeydirectory geoKeyDirTagsNum = GeoTiffMetadataUtilities.getTiffShort(geoKeyDir, GeoTiffGCSCodes.GEO_KEY_NUM_KEYS_INDEX); } else { if(LOGGER.isLoggable(Level.FINE)) LOGGER.log(Level.FINE,"Unable to find the geo key directory tag"); } pixelScale = GeoTiffMetadataUtilities.calculateModelPixelScales(rootNode); tiePoints = GeoTiffMetadataUtilities.calculateTiePoints(rootNode); noData = GeoTiffMetadataUtilities.calculateNoData(rootNode); modelTransformation = GeoTiffMetadataUtilities.calculateModelTransformation(rootNode); } /** * Gets the version of the GeoKey directory. This is typically a value of 1 and can be used to * check that the data is of a valid format. * * @return DOCUMENT ME! * * @throws UnsupportedOperationException * DOCUMENT ME! */ public int getGeoKeyDirectoryVersion() { // now get the value from the correct TIFFShort location return geoKeyDirVersion; } /** * Gets the revision number of the GeoKeys in this metadata. * * @return DOCUMENT ME! * * @throws UnsupportedOperationException * DOCUMENT ME! */ public int getGeoKeyRevision() { // Get the value from the correct TIFFShort return geoKeyRevision; } /** * Gets the minor revision number of the GeoKeys in this metadata. * * @return DOCUMENT ME! * * @throws UnsupportedOperationException * DOCUMENT ME! */ public int getGeoKeyMinorRevision() { // Get the value from the correct TIFFShort return geoKeyMinorRevision; } /** * Gets the number of GeoKeys in the geokeys directory. * * @return DOCUMENT ME! * * @throws UnsupportedOperationException * DOCUMENT ME! */ public int getNumGeoKeys() { return geoKeyDirTagsNum; } /** * Gets a GeoKey value as a String. This implementation should be "quiet" in the sense * that it should not throw any exceptions but only return null in the event that the data * organization is not as expected. * * @param keyID * The numeric ID of the GeoKey * * @return A string representing the value, or null if the key was not found. */ public String getGeoKey(final int keyID) { final GeoKeyEntry rec = getGeoKeyRecord(keyID); if (rec == null) { return null; } if (rec.getTiffTagLocation() == 0) { // value is stored directly in the GeoKey record return String.valueOf(rec.getValueOffset()); } // value is stored externally // get the TIFF field where the data is actually stored final IIOMetadataNode field = GeoTiffMetadataUtilities.getTiffField(rootNode, rec.getTiffTagLocation()); if (field == null) { return null; } final Node sequence = field.getFirstChild(); if (sequence == null) { return null; } return sequence.getNodeName().equals(GeoTiffConstants.GEOTIFF_ASCIIS_TAG) ? GeoTiffMetadataUtilities.getTiffAscii( (IIOMetadataNode) sequence, rec.getValueOffset(), rec.getCount()) : GeoTiffMetadataUtilities.getValueAttribute(sequence.getChildNodes().item(rec.getValueOffset())); } /** * Gets a record containing the four TIFFShort values for a geokey entry. For more information * see the GeoTIFFWritingUtilities specification. * * @param keyID * DOCUMENT ME! * * @return the record with the given keyID, or null if none is found * * @throws UnsupportedOperationException * DOCUMENT ME! */ public GeoKeyEntry getGeoKeyRecord(int keyID) { return geoKeys.get(keyID); } /** * Return the GeoKeys. * * @return **/ Collection<GeoKeyEntry> getGeoKeys() { return geoKeys.values(); } /** * Gets the model pixel scales from the correct TIFFField */ public PixelScale getModelPixelScales() { return pixelScale; } /** * Gets the model tie points from the appropriate TIFFField * * @return the tie points, or null if not found */ public TiePoint[] getModelTiePoints() { return tiePoints.clone(); } /** * Gets the noData from the related TIFFField. Check metadata has noData using * {@link #hasNoData()} method before calling this method. * * @return the noData value or {@link Double#NaN} in case of unable to get noData. * */ public double getNoData() { return noData; } /** * Tells me if the underlying {@link IIOMetadata} contains ModelTiepointTag tag for * {@link TiePoint}. * * @return true if ModelTiepointTag is present, false otherwise. */ public boolean hasTiePoints() { return tiePoints != null && tiePoints.length > 0; } /** * Tells me if the underlying {@link IIOMetadata} contains ModelTiepointTag tag for * {@link TiePoint}. * * @return true if ModelTiepointTag is present, false otherwise. */ public boolean hasPixelScales() { if (pixelScale == null) { return false; } else { final double[] values = pixelScale.getValues(); for (int i = 0; i < values.length; i++) { if (Double.isInfinite(values[i]) || Double.isNaN(values[i])) { return false; } } return true; } } /** * Tells me if the underlying {@link IIOMetadata} contains NoData Tag. * * @return true if NoData Tag is present, false otherwise. * @see GeoTiffConstants#TIFFTAG_NODATA */ public boolean hasNoData() { return !Double.isNaN(noData); } /** * Gets the model tie points from the appropriate TIFFField * * <p> * Attention, for the moment we support only 2D baseline transformations. * * @return the transformation, or null if not found */ public AffineTransform getModelTransformation() { return modelTransformation; } /** * Tells me if the underlying {@link IIOMetadata} contains ModelTransformationTag tag for * {@link AffineTransform} that map from Raster Space to World Space. * * @return true if ModelTransformationTag is present, false otherwise. * */ public boolean hasModelTrasformation() { return modelTransformation != null; } // private utility methods /** * Return <code>true</code> if the geokey directory is present, <code>false</code> otherwise. In * case no geokey dir is present no CRS can be constructed from this set of metadata. * * <p> * A prj can be used otherwise. * * @return <code>true</code> if the geokey directory is present, <code>false</code> otherwise. */ public boolean hasGeoKey() { return !geoKeys.isEmpty(); } } // end of class GeoTiffIIOMetadataDecoder