sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1773274 [8/9] - in /sis/trunk: ./ application/sis-console/src/main/artifact/ application/sis-openoffice/src/main/unopkg/ core/sis-build-helper/src/main/javadoc/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/ma...
Date Thu, 08 Dec 2016 17:42:52 GMT
Modified: sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] (original)
+++ sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -16,15 +16,26 @@
  */
 package org.apache.sis.storage.geotiff;
 
-import java.util.Locale;
 import java.io.IOException;
 import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.NoSuchElementException;
+import java.nio.charset.Charset;
+import javax.measure.Unit;
+import javax.measure.quantity.Length;
 import org.opengis.metadata.citation.DateType;
+import org.opengis.util.FactoryException;
+import org.apache.sis.internal.jdk8.JDK8;
 import org.apache.sis.internal.geotiff.Resources;
+import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.internal.storage.MetadataBuilder;
 import org.apache.sis.math.Vector;
+import org.apache.sis.measure.Units;
 
 
 /**
@@ -42,6 +53,18 @@ import org.apache.sis.math.Vector;
  */
 final class ImageFileDirectory {
     /**
+     * Possible value for the {@link #tileTagFamily} field. That field tells whether image tiling
+     * was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*} family.
+     */
+    private static final byte TILE = 1, STRIP = 2;
+
+    /**
+     * The GeoTIFF reader which contain this {@code ImageFileDirectory}.
+     * Used for fetching information like the input channel and where to report warnings.
+     */
+    private final Reader reader;
+
+    /**
      * {@code true} if this {@code ImageFileDirectory} has not yet read all deferred entries.
      * When this flag is {@code true}, the {@code ImageFileDirectory} is not yet ready for use.
      */
@@ -58,28 +81,68 @@ final class ImageFileDirectory {
 
     /**
      * The size of each tile, or -1 if the information has not be found.
-     * Tiles should be small enough for fitting in memory.
+     * Tiles shall be small enough for fitting in memory, typically in a {@link java.awt.image.Raster} object.
+     * The TIFF specification requires that tile width and height must be a multiple of 16, but the SIS reader
+     * implementation works for any size. Tiles need not be square.
+     *
+     * <p>Assuming integer arithmetic, the number of tiles in an image can be computed as below
+     * (these computed values are not TIFF fields):</p>
+     *
+     * {@preformat math
+     *   tilesAcross   = (imageWidth  + tileWidth  - 1) / tileWidth
+     *   tilesDown     = (imageHeight + tileHeight - 1) / tileHeight
+     *   tilesPerImage = tilesAcross * tilesDown
+     * }
+     *
+     * Note that {@link #imageWidth} can be less than {@code tileWidth} and/or {@link #imageHeight} can be less
+     * than {@code tileHeight}. Such case means that the tiles are too large or that the tiled image is too small,
+     * neither of which is recommended.
      *
      * <p><b>Note:</b>
      * the {@link #tileHeight} attribute is named {@code TileLength} in TIFF specification.</p>
+     *
+     * <div class="section">Strips considered as tiles</div>
+     * The TIFF specification also defines a {@code RowsPerStrip} tag, which is equivalent to the
+     * height of tiles having the same width than the image. While the TIFF specification handles
+     * "tiles" and "strips" separately, Apache SIS handles strips as a special kind of tiles where
+     * only {@code tileHeight} is specified and {@code tileWidth} defaults to {@link #imageWidth}.
      */
     private int tileWidth = -1, tileHeight = -1;
 
     /**
-     * The number of components per pixel.
-     * The {@code samplesPerPixel} value is usually 1 for bilevel, grayscale and palette-color images,
-     * and 3 for RGB images. If this value is higher, then the {@code ExtraSamples} TIFF tag should
-     * give an indication of the meaning of the additional channels.
+     * For each tile, the byte offset of that tile, as compressed and stored on disk.
+     * The offset is specified with respect to the beginning of the TIFF file.
+     * Each tile has a location independent of the locations of other tiles
+     *
+     * <p>Offsets are ordered left-to-right and top-to-bottom. if {@link #isPlanar} is {@code true}
+     * (i.e. components are stored in separate “component planes”), then the offsets for the first
+     * component plane are stored first, followed by all the offsets for the second component plane,
+     * and so on.</p>
+     *
+     * <div class="section">Strips considered as tiles</div>
+     * The TIFF specification also defines a {@code StripOffsets} tag, which contains the byte offset
+     * of each strip. In Apache SIS implementation, strips are considered as a special kind of tiles
+     * having a width equals to {@link #imageWidth}.
      */
-    private short samplesPerPixel = 1;
+    private Vector tileOffsets;
 
     /**
-     * Number of bits per component.
-     * The TIFF specification allows a different number of bits per component for each component corresponding to a pixel.
-     * For example, RGB color data could use a different number of bits per component for each of the three color planes.
-     * However, current Apache SIS implementation requires that all components have the same {@code BitsPerSample} value.
+     * For each tile, the number of (compressed) bytes in that tile.
+     * See {@link #tileOffsets} for a description of how the byte counts are ordered.
+     *
+     * <div class="section">Strips considered as tiles</div>
+     * The TIFF specification also defines a {@code RowsPerStrip} tag, which is the number
+     * of bytes in the strip after compression. In Apache SIS implementation, strips are
+     * considered as a special kind of tiles having a width equals to {@link #imageWidth}.
      */
-    private short bitsPerSample = 1;
+    private Vector tileByteCounts;
+
+    /**
+     * Whether the tiling was specified using the {@code Tile*} family of TIFF tags or the {@code Strip*}
+     * family of tags. Value can be {@link #TILE}, {@link #STRIP} or 0 if unspecified. This field is used
+     * for error detection since Each TIFF file shall use exactly one family of tags.
+     */
+    private byte tileTagFamily;
 
     /**
      * If {@code true}, the components are stored in separate “component planes”.
@@ -89,43 +152,192 @@ final class ImageFileDirectory {
     private boolean isPlanar;
 
     /**
-     * The compression method, or {@code null} if unknown. If the compression method is unknown
-     * or unsupported we can not read the image, but we still can read the metadata.
+     * Whether the bit order should be reversed. This boolean value is determined from the {@code FillOrder} TIFF tag.
+     *
+     * <ul>
+     *   <li>Value 1 (mapped to {@code false}) means that pixels with lower column values are stored in the
+     *       higher-order bits of the byte. This is the default value.</li>
+     *   <li>Value 2 (mapped to {@code true}) means that pixels with lower column values are stored in the
+     *       lower-order bits of the byte. In practice, this order is very uncommon and is not recommended.</li>
+     * </ul>
+     *
+     * Value 1 is mapped to {@code false} and 2 is mapped to {@code true}.
      */
-    private Compression compression;
+    private boolean reverseBitsOrder;
 
     /**
-     * The number of rows per strip.
-     * TIFF image data can be organized into strips for faster random access and efficient I/O buffering.
-     * The {@code rowsPerStrip} and {@link #imageHeight} fields together tell us the number of strips in the entire image.
-     * The equation is:
+     * Number of bits per component.
+     * The TIFF specification allows a different number of bits per component for each component corresponding to a pixel.
+     * For example, RGB color data could use a different number of bits per component for each of the three color planes.
+     * However, current Apache SIS implementation requires that all components have the same {@code BitsPerSample} value.
+     */
+    private short bitsPerSample;
+
+    /**
+     * The number of components per pixel.
+     * The {@code samplesPerPixel} value is usually 1 for bilevel, grayscale and palette-color images,
+     * and 3 for RGB images. If this value is higher, then the {@code ExtraSamples} TIFF tag should
+     * give an indication of the meaning of the additional channels.
+     */
+    private short samplesPerPixel;
+
+    /**
+     * Specifies that each pixel has {@code extraSamples.size()} extra components whose interpretation is defined
+     * by one of the values listed below. When this field is used, the {@link #samplesPerPixel} field has a value
+     * greater than what the {@link #photometricInterpretation} field suggests. For example, full-color RGB data
+     * normally has {@link #samplesPerPixel} = 3. If {@code samplesPerPixel} is greater than 3, then this
+     * {@code extraSamples} field describes the meaning of the extra samples. If {@code samplesPerPixel} is,
+     * say, 5 then this {@code extraSamples} field will contain 2 values, one for each extra sample.
      *
-     * {@preformat math
-     *     StripsPerImage = floor ((ImageLength + RowsPerStrip - 1) / RowsPerStrip)
-     * }
+     * <p>Extra components that are present must be stored as the last components in each pixel.
+     * For example, if {@code samplesPerPixel} is 4 and there is 1 extra component, then it is
+     * located in the last component location in each pixel.</p>
+     *
+     * <p>ExtraSamples is typically used to include non-color information, such as opacity, in an image.
+     * The possible values for each item are:</p>
+     *
+     * <ul>
+     *   <li>0 = Unspecified data.</li>
+     *   <li>1 = Associated alpha data (with pre-multiplied color).</li>
+     *   <li>2 = Unassociated alpha data.</li>
+     * </ul>
+     *
+     * Associated alpha is generally interpreted as true transparency information. Indeed, the original color
+     * values are lost in the case of complete transparency, and rounded in the case of partial transparency.
+     * Also, associated alpha is only logically possible as the single extra channel.
+     * Unassociated alpha channels, on the other hand, can be used to encode a number of independent masks.
+     * The original color data is preserved without rounding. Any number of unassociated alpha channels can
+     * accompany an image.
+     *
+     * <p>If an extra sample is used to encode information that has little or nothing to do with alpha,
+     * then {@code extraSample} = 0 ({@code EXTRASAMPLE_UNSPECIFIED}) is recommended.</p>
+     */
+    private Vector extraSamples;
+
+    /**
+     * The color space of the image data, or -1 if unspecified.
+     *
+     * <table>
+     *   <caption>Color space codes</caption>
+     *   <tr><th>Value</th> <th>Label</th>        <th>Description</th></tr>
+     *   <tr><td>0</td> <td>WhiteIsZero</td>      <td>For bilevel and grayscale images. 0 is imaged as white.</td></tr>
+     *   <tr><td>1</td> <td>BlackIsZero</td>      <td>For bilevel and grayscale images. 0 is imaged as black.</td></tr>
+     *   <tr><td>2</td> <td>RGB</td>              <td>RGB value of (0,0,0) represents black, and (255,255,255) represents white.</td></tr>
+     *   <tr><td>3</td> <td>PaletteColor</td>     <td>The value of the component is used as an index into the RGB values of the {@link #colorMap}.</td></tr>
+     *   <tr><td>4</td> <td>TransparencyMask</td> <td>Defines an irregularly shaped region of another image in the same TIFF file.</td></tr>
+     * </table>
+     */
+    private byte photometricInterpretation = -1;
+
+    /**
+     * A color map for palette color images ({@link #photometricInterpretation} = 3).
+     * This vector defines a Red-Green-Blue color map (often called a lookup table) for palette-color images.
+     * In a palette-color image, a pixel value is used to index into an RGB lookup table. For example, a
+     * palette-color pixel having a value of 0 would be displayed according to the 0th Red, Green, Blue triplet.
      *
-     * {@code StripsPerImage} is not a field. It is merely a value that a TIFF reader will want to compute
-     * because it specifies the number of {@code StripOffsets} and {@code StripByteCounts} for the image.
+     * <p>In a TIFF ColorMap, all the Red values come first, followed by all Green values, then all Blue values.
+     * The number of values for each color is 1 {@literal <<} {@link #bitsPerSample}. Therefore, the {@code ColorMap}
+     * vector for an 8-bit palette-color image would have 3 * 256 values. 0 represents the minimum intensity and 65535
+     * represents the maximum intensity. Black is represented by 0,0,0 and white by 65535, 65535, 65535.</p>
+     *
+     * <p>{@code ColorMap} must be included in all palette-color images.
+     * In Specification Supplement 1, support was added for color maps containing other then RGB values.
+     * This scheme includes the {@code Indexed} tag, with value 1, and a {@link #photometricInterpretation}
+     * different from {@code PaletteColor}.</p>
+     */
+    private Vector colorMap;
+
+    /**
+     * The size of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file.
+     * This field should be present only if {@code Threshholding} tag is 2 (an ordered dither or halftone
+     * technique has been applied to the image data). Special values:
      *
-     * <p>This field should be interpreted as an unsigned value.
-     * The default is 2^32 - 1, which is effectively infinity (i.e. the entire image is one strip).</p>
+     * <ul>
+     *   <li>-1 means that {@code Threshholding} is 1 or unspecified.</li>
+     *   <li>-2 means that {@code Threshholding} is 2 but the matrix size has not yet been specified.</li>
+     *   <li>-3 means that {@code Threshholding} is 3 (randomized process such as error diffusion).</li>
+     * </ul>
+     */
+    private short cellWidth = -1, cellHeight = -1;
+
+    /**
+     * The minimum or maximum sample value found in the image, or {@code NaN} if unspecified.
+     */
+    private double minValue = Double.NaN, maxValue = Double.NaN;
+
+    /**
+     * The number of pixels per {@link #resolutionUnit} in the {@link #imageWidth} and the {@link #imageHeight}
+     * directions, or {@link Double#NaN} is unspecified. Since ISO 19115 does not have separated resolution fields
+     * for image width and height, Apache SIS stores only the maximal value.
+     */
+    private double resolution = Double.NaN;
+
+    /**
+     * The unit of measurement for the {@linkplain #resolution} value, or {@code null} if none.
+     * A null value is used for images that may have a non-square aspect ratio, but no meaningful
+     * absolute dimensions. Default value for TIFF files is inch.
+     */
+    private Unit<Length> resolutionUnit = Units.INCH;
+
+    /**
+     * The compression method, or {@code null} if unknown. If the compression method is unknown
+     * or unsupported we can not read the image, but we still can read the metadata.
+     */
+    private Compression compression;
+
+    /**
+     * References the {@link GeoKeys} needed for building the Coordinate Reference System.
+     * This is a GeoTIFF extension to the TIFF specification.
+     * Content will be parsed by {@link CRSBuilder}.
+     */
+    private Vector geoKeyDirectory;
+
+    /**
+     * The numeric values referenced by the {@link #geoKeyDirectory}.
+     * This is a GeoTIFF extension to the TIFF specification.
+     * Content will be parsed by {@link CRSBuilder}.
+     */
+    private Vector numericGeoParameters;
+
+    /**
+     * The characters referenced by the {@link #geoKeyDirectory}.
+     * This is a GeoTIFF extension to the TIFF specification.
+     * Content will be parsed by {@link CRSBuilder}.
      */
-    private int rowsPerStrip = 0xFFFFFFFF;
+    private String asciiGeoParameters;
 
     /**
      * Creates a new image file directory.
+     *
+     * @param reader  information about the input stream to read, the metadata and the character encoding.
+     */
+    ImageFileDirectory(final Reader reader) {
+        this.reader = reader;
+    }
+
+    /**
+     * Shortcut for a frequently requested information.
      */
-    ImageFileDirectory() {
+    private ChannelDataInput input() {
+        return reader.input;
     }
 
     /**
-     * Adds the value read from the current position in the given stream
-     * for the entry identified by the given GeoTIFF tag.
+     * Shortcut for a frequently requested information.
+     */
+    private Charset encoding() {
+        return reader.owner.encoding;
+    }
+
+    /**
+     * Adds the value read from the current position in the given stream for the entry identified
+     * by the given GeoTIFF tag. This method may store the value either in a field of this class,
+     * or directly in the {@link MetadataBuilder}. However in the later case, this method should
+     * not write anything under the {@code "metadata/contentInfo"} node.
      *
-     * @param  reader   the input stream to read, the metadata and the character encoding.
-     * @param  tag      the GeoTIFF tag to decode.
-     * @param  type     the GeoTIFF type of the value to read.
-     * @param  count    the number of values to read.
+     * @param  tag    the GeoTIFF tag to decode.
+     * @param  type   the GeoTIFF type of the value to read.
+     * @param  count  the number of values to read.
      * @return {@code null} on success, or the unrecognized value otherwise.
      * @throws IOException if an error occurred while reading the stream.
      * @throws ParseException if the value need to be parsed as date and the parsing failed.
@@ -135,7 +347,7 @@ final class ImageFileDirectory {
      * @throws UnsupportedOperationException if the given type is {@link Type#UNDEFINED}.
      * @throws DataStoreException if a logical error is found or an unsupported TIFF feature is used.
      */
-    Object addEntry(final Reader reader, final int tag, final Type type, final long count)
+    Object addEntry(final short tag, final Type type, final long count)
             throws IOException, ParseException, DataStoreException
     {
         switch (tag) {
@@ -153,66 +365,117 @@ final class ImageFileDirectory {
              * 2 = Planar format. For example one plane of Red components, one plane of Green and one plane if Blue.
              */
             case Tags.PlanarConfiguration: {
-                final long value = type.readLong(reader.input, count);
-                if (value < 1 || value > 2) {
-                    return value;
+                final int value = type.readInt(input(), count);
+                switch (value) {
+                    case 1:  isPlanar = false; break;
+                    case 2:  isPlanar = true;  break;
+                    default: return value;                  // Cause a warning to be reported by the caller.
                 }
-                isPlanar = (value == 2);
                 break;
             }
             /*
              * The number of columns in the image, i.e., the number of pixels per row.
              */
             case Tags.ImageWidth: {
-                imageWidth = type.readUnsignedLong(reader.input, count);
+                imageWidth = type.readUnsignedLong(input(), count);
                 break;
             }
             /*
              * The number of rows of pixels in the image.
              */
             case Tags.ImageLength: {
-                imageHeight = type.readUnsignedLong(reader.input, count);
+                imageHeight = type.readUnsignedLong(input(), count);
                 break;
             }
             /*
-             * The number of rows per strip. RowsPerStrip and ImageLength together tell us the number of strips
-             * in the entire image: StripsPerImage = floor((ImageLength + RowsPerStrip - 1) / RowsPerStrip).
+             * The tile width in pixels. This is the number of columns in each tile.
+             */
+            case Tags.TileWidth: {
+                setTileTagFamily(TILE);
+                tileWidth = type.readInt(input(), count);
+                break;
+            }
+            /*
+             * The tile length (height) in pixels. This is the number of rows in each tile.
+             */
+            case Tags.TileLength: {
+                setTileTagFamily(TILE);
+                tileHeight = type.readInt(input(), count);
+                break;
+            }
+            /*
+             * The number of rows per strip. This is considered by SIS as a special kind of tiles.
+             * From this point of view, TileLength = RowPerStrip and TileWidth = ImageWidth.
              */
             case Tags.RowsPerStrip: {
-                // TODO
+                setTileTagFamily(STRIP);
+                tileHeight = type.readInt(input(), count);
                 break;
             }
             /*
-             * For each strip, the number of bytes in the strip after compression.
+             * The tile length (height) in pixels. This is the number of rows in each tile.
              */
-            case Tags.StripByteCounts: {
-                // TODO
+            case Tags.TileOffsets: {
+                setTileTagFamily(TILE);
+                tileOffsets = type.readVector(input(), count);
                 break;
             }
             /*
              * For each strip, the byte offset of that strip relative to the beginning of the TIFF file.
+             * In Apache SIS implementation, strips are considered as a special kind of tiles.
              */
             case Tags.StripOffsets: {
-                // TODO
+                setTileTagFamily(STRIP);
+                tileOffsets = type.readVector(input(), count);
                 break;
             }
             /*
+             * The tile width in pixels. This is the number of columns in each tile.
+             */
+            case Tags.TileByteCounts: {
+                setTileTagFamily(TILE);
+                tileByteCounts = type.readVector(input(), count);
+                break;
+            }
+            /*
+             * For each strip, the number of bytes in the strip after compression.
+             * In Apache SIS implementation, strips are considered as a special kind of tiles.
+             */
+            case Tags.StripByteCounts: {
+                setTileTagFamily(STRIP);
+                tileByteCounts = type.readVector(input(), count);
+                break;
+            }
+
+            ////////////////////////////////////////////////////////////////////////////////////////////////
+            ////                                                                                        ////
+            ////    Information that defines how the sample values are organized (their layout).        ////
+            ////    In Java2D, following information are needed for building the SampleModel.           ////
+            ////                                                                                        ////
+            ////////////////////////////////////////////////////////////////////////////////////////////////
+
+            /*
              * Compression scheme used on the image data.
              */
             case Tags.Compression: {
-                final long value = type.readLong(reader.input, count);
+                final long value = type.readLong(input(), count);
                 compression = Compression.valueOf(value);
                 if (compression == null) {
-                    return value;
+                    return value;                           // Cause a warning to be reported by the caller.
                 }
                 break;
             }
             /*
-             * The logical order of bits within a byte. If this value is 2, then bits order shall be reversed in every
-             * bytes before decompression.
+             * The logical order of bits within a byte. If this value is 2, then
+             * bits order shall be reversed in every bytes before decompression.
              */
             case Tags.FillOrder: {
-                // TODO
+                final int value = type.readInt(input(), count);
+                switch (value) {
+                    case 1: reverseBitsOrder = false; break;
+                    case 2: reverseBitsOrder = true;  break;
+                    default: return value;                  // Cause a warning to be reported by the caller.
+                }
                 break;
             }
             /*
@@ -221,7 +484,7 @@ final class ImageFileDirectory {
              * But the TIFF specification allows different values.
              */
             case Tags.BitsPerSample: {
-                final Vector values = type.readVector(reader.input, count);
+                final Vector values = type.readVector(input(), count);
                 /*
                  * The current implementation requires that all 'bitsPerSample' elements have the same value.
                  * This restriction may be revisited in future Apache SIS versions.
@@ -232,7 +495,7 @@ final class ImageFileDirectory {
                 for (int i = 1; i < length; i++) {
                     if (values.shortValue(i) != bitsPerSample) {
                         throw new DataStoreContentException(reader.resources().getString(
-                                Resources.Keys.ConstantValueRequired_3, "BitsPerSample", reader.input.filename, values));
+                                Resources.Keys.ConstantValueRequired_3, "BitsPerSample", input().filename, values));
                     }
                 }
                 break;
@@ -242,7 +505,7 @@ final class ImageFileDirectory {
              * and 3 for RGB images. Default value is 1.
              */
             case Tags.SamplesPerPixel: {
-                samplesPerPixel = type.readShort(reader.input, count);
+                samplesPerPixel = type.readShort(input(), count);
                 break;
             }
             /*
@@ -252,7 +515,7 @@ final class ImageFileDirectory {
              * describes the meaning of the extra samples. It may be an alpha channel, but not necessarily.
              */
             case Tags.ExtraSamples: {
-                // TODO
+                extraSamples = type.readVector(input(), count);
                 break;
             }
 
@@ -267,31 +530,33 @@ final class ImageFileDirectory {
              * The color space of the image data.
              * 0 = WhiteIsZero. For bilevel and grayscale images: 0 is imaged as white.
              * 1 = BlackIsZero. For bilevel and grayscale images: 0 is imaged as black.
-             * 2 = RGB. RGB value of (0,0,0) represents black, and (255,255,255) represents white.
-             * 3 = Palette color. The value of the component is used as an index into the RGB valuesthe ColorMap.
+             * 2 = RGB. RGB value of (0,0,0) represents black, and (65535,65535,65535) represents white.
+             * 3 = Palette color. The value of the component is used as an index into the RGB values of the ColorMap.
              * 4 = Transparency Mask the defines an irregularly shaped region of another image in the same TIFF file.
              */
             case Tags.PhotometricInterpretation: {
-                // TODO
+                final short value = type.readShort(input(), count);
+                if (value < 0 || value > Byte.MAX_VALUE) return value;
+                photometricInterpretation = (byte) value;
                 break;
             }
             /*
-             * The Red-Green-Blue lookup table map for palette-color images (IndexColorModel in Java2D).
-             * All the Red values come first, followed by the Green values, then the Blue values.
-             * The number of values for each color is (1 << BitsPerSample) and the type of each value is USHORT.
-             * 0 represents the minimum intensity (black is 0,0,0) and 65535 represents the maximum intensity.
-             * If the Indexed tag has value 1 and PhotometricInterpretation is different from PaletteColor,
-             * then the color space may be different than RGB.
+             * The lookup table for palette-color images. This is represented by IndexColorModel in Java2D.
+             * Color space is RGB if PhotometricInterpretation is "PaletteColor", or another color space otherwise.
+             * In the RGB case, all the Red values come first, followed by all Green values, then all Blue values.
+             * The number of values for each color is (1 << BitsPerSample) where 0 represents the minimum intensity
+             * (black is 0,0,0) and 65535 represents the maximum intensity.
              */
             case Tags.ColorMap: {
-                // TODO
+                colorMap = type.readVector(input(), count);
                 break;
             }
             /*
              * The minimum component value used. Default is 0.
              */
             case Tags.MinSampleValue: {
-                reader.metadata.addMinimumSampleValue(type.readDouble(reader.input, count));
+                final double v = type.readDouble(input(), count);
+                if (Double.isNaN(minValue) || v < minValue) minValue = v;
                 break;
             }
             /*
@@ -300,7 +565,8 @@ final class ImageFileDirectory {
              * visual appearance of an image, unless a map styling is applied.
              */
             case Tags.MaxSampleValue: {
-                reader.metadata.addMaximumSampleValue(type.readDouble(reader.input, count));
+                final double v = type.readDouble(input(), count);
+                if (Double.isNaN(maxValue) || v > maxValue) maxValue = v;
                 break;
             }
 
@@ -341,6 +607,36 @@ final class ImageFileDirectory {
             ////////////////////////////////////////////////////////////////////////////////////////////////
 
             /*
+             * References the "GeoKeys" needed for building the Coordinate Reference System.
+             * An array of unsigned SHORT values, which are primarily grouped into blocks of 4.
+             * The first 4 values are special, and contain GeoKey directory header information.
+             */
+            case Tags.GeoKeyDirectory: {
+                geoKeyDirectory = type.readVector(input(), count);
+                break;
+            }
+            /*
+             * Stores all of the 'double' valued GeoKeys, referenced by the GeoKeyDirectory.
+             */
+            case Tags.GeoDoubleParams: {
+                numericGeoParameters = type.readVector(input(), count);
+                break;
+            }
+            /*
+             * Stores all the characters referenced by the GeoKeyDirectory. Should contains exactly one string
+             * which will be splitted by CRSBuilder, but we allow an arbitrary amount as a paranoiac check.
+             * Note that TIFF files use 0 as the end delimiter in strings (C/C++ convention).
+             */
+            case Tags.GeoAsciiParams: {
+                final String[] values = type.readString(input(), count, encoding());
+                switch (values.length) {
+                    case 0:  break;
+                    case 1:  asciiGeoParameters = values[0]; break;
+                    default: asciiGeoParameters = JDK8.join("\u0000", values).concat("\u0000"); break;
+                }
+                break;
+            }
+            /*
              * The orientation of the image with respect to the rows and columns.
              * This is an integer numeroted from 1 to 7 inclusive (see TIFF specification for meaning).
              */
@@ -349,11 +645,11 @@ final class ImageFileDirectory {
                 break;
             }
 
-
             ////////////////////////////////////////////////////////////////////////////////////////////////
             ////                                                                                        ////
             ////    Metadata for discovery purposes, conditions of use, etc.                            ////
             ////    Those metadata are not "critical" information for reading the image.                ////
+            ////    Should not write anything under 'metadata/contentInfo' node.                        ////
             ////                                                                                        ////
             ////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -362,7 +658,7 @@ final class ImageFileDirectory {
              * For example, a user may wish to attach a comment such as "1988 company picnic" to an image.
              */
             case Tags.ImageDescription: {
-                for (final String value : type.readString(reader.input, count, reader.owner.encoding)) {
+                for (final String value : type.readString(input(), count, encoding())) {
                     reader.metadata.addTitle(value);
                 }
                 break;
@@ -372,7 +668,7 @@ final class ImageFileDirectory {
              * Copyright information, but Apache SIS does not support this legacy practice.
              */
             case Tags.Artist: {
-                for (final String value : type.readString(reader.input, count, reader.owner.encoding)) {
+                for (final String value : type.readString(input(), count, encoding())) {
                     reader.metadata.addAuthor(value);
                 }
                 break;
@@ -382,7 +678,7 @@ final class ImageFileDirectory {
              * Example: “Copyright, John Smith, 1992. All rights reserved.”
              */
             case Tags.Copyright: {
-                for (final String value : type.readString(reader.input, count, reader.owner.encoding)) {
+                for (final String value : type.readString(input(), count, encoding())) {
                     reader.metadata.parseLegalNotice(value);
                 }
                 break;
@@ -391,7 +687,7 @@ final class ImageFileDirectory {
              * Date and time of image creation. The format is: "YYYY:MM:DD HH:MM:SS" with 24-hour clock.
              */
             case Tags.DateTime: {
-                for (final String value : type.readString(reader.input, count, reader.owner.encoding)) {
+                for (final String value : type.readString(input(), count, encoding())) {
                     reader.metadata.add(reader.getDateFormat().parse(value), DateType.CREATION);
                 }
                 break;
@@ -400,14 +696,18 @@ final class ImageFileDirectory {
              * The computer and/or operating system in use at the time of image creation.
              */
             case Tags.HostComputer: {
-                // TODO
+                for (final String value : type.readString(input(), count, encoding())) {
+                    reader.metadata.addHostComputer(value);
+                }
                 break;
             }
             /*
              * Name and version number of the software package(s) used to create the image.
              */
             case Tags.Software: {
-                // TODO
+                for (final String value : type.readString(input(), count, encoding())) {
+                    reader.metadata.addSoftwareReference(value);
+                }
                 break;
             }
             /*
@@ -415,7 +715,8 @@ final class ImageFileDirectory {
              * Synthetic images should not include this field.
              */
             case Tags.Make: {
-                // TODO
+                // TODO: is Instrument.citation.citedResponsibleParty.party.name an appropriate place?
+                // what would be the citation title? A copy of Tags.Model?
                 break;
             }
             /*
@@ -423,55 +724,68 @@ final class ImageFileDirectory {
              * generate the image.
              */
             case Tags.Model: {
-                // TODO
-                break;
-            }
-            /*
-             * The number of pixels per ResolutionUnit in the ImageWidth direction.
-             */
-            case Tags.XResolution: {
-                // TODO
+                for (final String value : type.readString(input(), count, encoding())) {
+                    reader.metadata.addInstrument(value);
+                }
                 break;
             }
             /*
-             * The number of pixels per ResolutionUnit in the ImageLength direction.
+             * The number of pixels per ResolutionUnit in the ImageWidth or ImageHeight direction.
              */
+            case Tags.XResolution:
             case Tags.YResolution: {
-                // TODO
+                final double r = type.readDouble(input(), count);
+                if (Double.isNaN(resolution) || r > resolution) {
+                    resolution = r;
+                }
                 break;
             }
             /*
              * The unit of measurement for XResolution and YResolution.
-             * 1 = None, 2 = Inch, 3 = Centimeter.
+             *
+             *   1 = None. Used for images that may have a non-square aspect ratio.
+             *   2 = Inch (default).
+             *   3 = Centimeter.
              */
             case Tags.ResolutionUnit: {
-                // TODO
+                final short unit = type.readShort(input(), count);
+                switch (unit) {
+                    case 1:  resolutionUnit = null;             break;
+                    case 2:  resolutionUnit = Units.INCH;       break;
+                    case 3:  resolutionUnit = Units.CENTIMETRE; break;
+                    default: return unit;                   // Cause a warning to be reported by the caller.
+                }
                 break;
             }
             /*
-             * The technique used to convert from gray to black and white pixels (if applicable):
-             * 1 = No dithering or halftoning has been applied to the image data.
-             * 2 = An ordered dither or halftone technique has been applied to the image data.
-             * 3 = A randomized process such as error diffusion has been applied to the image data.
+             * For black and white TIFF files that represent shades of gray, the technique used to convert
+             * from gray to black and white pixels. The default value is 1 (nothing done on the image).
+             *
+             *   1 = No dithering or halftoning has been applied to the image data.
+             *   2 = An ordered dither or halftone technique has been applied to the image data.
+             *   3 = A randomized process such as error diffusion has been applied to the image data.
              */
             case Tags.Threshholding: {
-                // TODO
+                final short value = type.readShort(input(), count);
+                switch (value) {
+                    case 1:  break;
+                    case 2:  if (cellWidth >= 0 || cellHeight >= 0) return null; else break;
+                    case 3:  break;
+                    default: return value;                  // Cause a warning to be reported by the caller.
+                }
+                cellWidth = cellHeight = (short) -value;
                 break;
             }
             /*
-             * The width of the dithering or halftoning matrix used to create a dithered or halftoned
-             * bilevel file. Meaningful only if Threshholding = 2.
+             * The width and height of the dithering or halftoning matrix used to create
+             * a dithered or halftoned bilevel file. Meaningful only if Threshholding = 2.
              */
             case Tags.CellWidth: {
-                // TODO
+                cellWidth = type.readShort(input(), count);
                 break;
             }
-            /*
-             * The height of the dithering or halftoning matrix used to create a dithered or halftoned
-             * bilevel file. Meaningful only if Threshholding = 2.
-             */
             case Tags.CellLength: {
-                // TODO
+                cellHeight = type.readShort(input(), count);
                 break;
             }
 
@@ -493,7 +807,7 @@ final class ImageFileDirectory {
              */
             case Tags.GrayResponseCurve:
             case Tags.GrayResponseUnit: {
-                // TODO: log a warning saying that this tag is ignored.
+                warning(Level.FINE, Resources.Keys.IgnoredTag_1, Tags.name(tag));
                 break;
             }
         }
@@ -501,12 +815,276 @@ final class ImageFileDirectory {
     }
 
     /**
+     * Sets the {@link #tileTagFamily} field to the given value if it does not conflict with previous value.
+     *
+     * @param  family  either {@link #TILE} or {@link #STRIP}.
+     * @throws DataStoreContentException if {@link #tileTagFamily} is already set to another value.
+     */
+    private void setTileTagFamily(final byte family) throws DataStoreContentException {
+        if (tileTagFamily != family && tileTagFamily != 0) {
+            throw new DataStoreContentException(reader.resources().getString(
+                    Resources.Keys.InconsistentTileStrip_1, input().filename));
+        }
+        tileTagFamily = family;
+    }
+
+    /**
+     * Multiplies the given value by the number of bytes in one pixel,
+     * or return -1 if the result is not an integer.
+     *
+     * @throws ArithmeticException if the result overflows.
+     */
+    private long pixelToByteCount(long value) {
+        value = JDK8.multiplyExact(value, samplesPerPixel * (int) bitsPerSample);
+        return (value % Byte.SIZE == 0) ? value / Byte.SIZE : -1;
+    }
+
+    /**
+     * Computes the tile width or height from the other size,
+     * or returns a negative number if the size can not be computed.
+     *
+     * @param  knownSize the tile width or height.
+     * @return the tile width if the known size was height, or the tile height if the known size was width,
+     *         or a negative number if the width or height can not be computed.
+     * @throws ArithmeticException if the result overflows.
+     */
+    private int computeTileSize(final int knownSize) {
+        final int n = tileByteCounts.size();
+        if (n != 0) {
+            final long count = tileByteCounts.longValue(0);
+            int i = 0;
+            do if (++i == n) {
+                // At this point, we verified that all vector values are equal.
+                final long length = pixelToByteCount(knownSize);
+                if (count % length != 0) break;
+                return JDK8.toIntExact(count / length);
+            } while (tileByteCounts.longValue(i) == n);
+        }
+        return -1;
+    }
+
+    /**
+     * Verifies that the mandatory tags are present and consistent with each others.
+     * If a mandatory tag is absent, then there is a choice:
+     *
+     * <ul>
+     *   <li>If the tag can be inferred from other tag values, performs that computation and logs a warning.</li>
+     *   <li>Otherwise throws an exception.</li>
+     * </ul>
+     *
+     * @throws DataStoreContentException if a mandatory tag is missing and can not be inferred.
+     */
+    final void validateMandatoryTags() throws DataStoreContentException {
+        if (imageWidth  < 0) throw missingTag(Tags.ImageWidth);
+        if (imageHeight < 0) throw missingTag(Tags.ImageLength);
+        final short offsetsTag, byteCountsTag;
+        switch (tileTagFamily) {
+            case STRIP: {
+                if (tileWidth  < 0) tileWidth  = JDK8.toIntExact(imageWidth);
+                if (tileHeight < 0) tileHeight = JDK8.toIntExact(imageHeight);
+                offsetsTag    = Tags.StripOffsets;
+                byteCountsTag = Tags.StripByteCounts;
+                break;
+            }
+            case TILE:  {
+                offsetsTag    = Tags.TileOffsets;
+                byteCountsTag = Tags.TileByteCounts;
+                break;
+            }
+            default: {
+                throw new DataStoreContentException(reader.resources().getString(
+                        Resources.Keys.InconsistentTileStrip_1, input().filename));
+            }
+        }
+        if (tileOffsets == null) {
+            throw missingTag(offsetsTag);
+        }
+        if (samplesPerPixel == 0) {
+            samplesPerPixel = 1;
+            missingTag(Tags.SamplesPerPixel, 1, false);
+        }
+        if (bitsPerSample == 0) {
+            bitsPerSample = 1;
+            missingTag(Tags.BitsPerSample, 1, false);
+        }
+        if (colorMap != null) {
+            ensureSameLength(Tags.ColorMap, Tags.BitsPerSample, colorMap.size(),  3 * (1 << bitsPerSample));
+        }
+        /*
+         * All of tile width, height and length information should be provided. But if only one of them is missing,
+         * we can compute it provided that the file does not use any compression method. If there is a compression,
+         * then we set a bit for preventing the 'switch' block to perform a calculation but we let the code performs
+         * the other checks in order to get an exception to be thrown with a good message.
+         */
+        int missing = !isPlanar && compression.equals(Compression.NONE) ? 0 : 0b1000;
+        if (tileWidth      < 0)     missing |= 0b0001;
+        if (tileHeight     < 0)     missing |= 0b0010;
+        if (tileByteCounts == null) missing |= 0b0100;
+        switch (missing) {
+            case 0:
+            case 0b1000: {          // Every thing is ok.
+                break;
+            }
+            case 0b0001: {          // Compute missing tile width.
+                tileWidth = computeTileSize(tileHeight);
+                missingTag(Tags.TileWidth, tileWidth, true);
+                break;
+            }
+            case 0b0010: {          // Compute missing tile height.
+                tileHeight = computeTileSize(tileWidth);
+                missingTag(Tags.TileLength, tileHeight, true);
+                break;
+            }
+            case 0b0100: {          // Compute missing tile byte count.
+                final long tileByteCount = pixelToByteCount(JDK8.multiplyExact(tileWidth, tileHeight));
+                final long[] tileByteCountArray = new long[tileOffsets.size()];
+                Arrays.fill(tileByteCountArray, tileByteCount);
+                tileByteCounts = Vector.create(tileByteCountArray, true);
+                missingTag(byteCountsTag, tileByteCount, true);
+                break;
+            }
+            default: {
+                final short tag;
+                switch (Integer.lowestOneBit(missing)) {
+                    case 0b0001: tag = Tags.TileWidth;  break;
+                    case 0b0010: tag = Tags.TileLength; break;
+                    default:     tag = byteCountsTag;   break;
+                }
+                throw missingTag(tag);
+            }
+        }
+        /*
+         * Log a warning if the tile offset and tile byte count vectors do not have the same length. Then
+         * ensure that the number of tiles is equal to the expected number.  The formula below is the one
+         * documented in the TIFF specification and reproduced in tileWidth & tileHeight fields javadoc.
+         */
+        ensureSameLength(offsetsTag, byteCountsTag, tileOffsets.size(), tileByteCounts.size());
+        long expectedCount = JDK8.multiplyExact(
+                JDK8.addExact(imageWidth,  tileWidth  - 1) / tileWidth,
+                JDK8.addExact(imageHeight, tileHeight - 1) / tileHeight);
+        if (isPlanar) {
+            expectedCount = JDK8.multiplyExact(expectedCount, samplesPerPixel);
+        }
+        final int actualCount = Math.min(tileOffsets.size(), tileByteCounts.size());
+        if (actualCount != expectedCount) {
+            throw new DataStoreContentException(reader.resources().getString(Resources.Keys.UnexpectedTileCount_3,
+                    input().filename, expectedCount, actualCount));
+        }
+    }
+
+    /**
      * Completes the metadata with the information stored in the field of this IFD.
      * This method is invoked only if the user requested the ISO 19115 metadata.
+     * This method creates a new {@code "metadata/contentInfo"} node for this image.
+     * Information not under the {@code "metadata/contentInfo"} node will be merged
+     * with the current content of the given {@code MetadataBuilder}.
+     *
+     * @param metadata  where to write metadata information. Caller should have already invoked
+     *        {@link MetadataBuilder#setFormat(String)} before {@code completeMetadata(…)} calls.
      */
-    final void completeMetadata(final MetadataBuilder metadata, final Locale locale) {
+    final void completeMetadata(final MetadataBuilder metadata, final Locale locale)
+            throws DataStoreContentException, FactoryException
+    {
+        metadata.newCoverage(false);
         if (compression != null) {
             metadata.addCompression(compression.name().toLowerCase(locale));
         }
+        metadata.addMinimumSampleValue(minValue);
+        metadata.addMaximumSampleValue(maxValue);
+        /*
+         * Add the resolution into the metadata. Our current ISO 19115 implementation restricts
+         * the resolution unit to metres, but it may be relaxed in a future SIS version.
+         */
+        if (!Double.isNaN(resolution) && resolutionUnit != null) {
+            metadata.addResolution(resolutionUnit.getConverterTo(Units.METRE).convert(resolution));
+        }
+        /*
+         * Cell size is relevant only if the Threshholding TIFF tag value is 2. By convention in
+         * this implementation class, other Threshholding values are stored as negative cell sizes:
+         *
+         *   -1 means that Threshholding is 1 or unspecified.
+         *   -2 means that Threshholding is 2 but the matrix size has not yet been specified.
+         *   -3 means that Threshholding is 3 (randomized process such as error diffusion).
+         */
+        switch (Math.min(cellWidth, cellHeight)) {
+            case -1: {
+                // Nothing to report.
+                break;
+            }
+            case -3: {
+                metadata.addProcessDescription(Resources.formatInternational(Resources.Keys.RandomizedProcessApplied));
+                break;
+            }
+            default: {
+                metadata.addProcessDescription(Resources.formatInternational(
+                            Resources.Keys.DitheringOrHalftoningApplied_2,
+                            (cellWidth  >= 0) ? cellWidth  : '?',
+                            (cellHeight >= 0) ? cellHeight : '?'));
+                break;
+            }
+        }
+        /*
+         * Add Coordinate Reference System built from GeoTIFF tags.  Note that the CRS may not exist,
+         * in which case the CRS builder returns null. This is safe since all MetadataBuilder methods
+         * ignore null values (a design choice because this pattern come very often).
+         */
+        if (geoKeyDirectory != null) {
+            final CRSBuilder helper = new CRSBuilder(reader);
+            try {
+                metadata.add(helper.build(geoKeyDirectory, numericGeoParameters, asciiGeoParameters));
+                helper.complete(metadata);
+            } catch (ClassCastException e) {
+                reader.owner.warning(null, e);
+            } catch (NumberFormatException | NoSuchElementException e) {
+                // Ignore - a warning with a better message has already been emitted.
+            }
+            geoKeyDirectory      = null;            // Not needed anymore, so let GC do its work.
+            numericGeoParameters = null;
+            asciiGeoParameters   = null;
+        }
+    }
+
+    /**
+     * Reports a warning with a message created from the given resource keys and parameters.
+     *
+     * @param level       the logging level for the message to log.
+     * @param key         the {@code Resources} key of the message to format.
+     * @param parameters  the parameters to put in the message.
+     */
+    private void warning(final Level level, final short key, final Object... parameters) {
+        final LogRecord r = reader.resources().getLogRecord(level, key, parameters);
+        reader.owner.warning(r);
+    }
+
+    /**
+     * Verifies that the given tags have the same length and reports a warning if they do not.
+     */
+    private void ensureSameLength(final short tag1, final short tag2, final int length1, final int length2) {
+        if (length1 != length2) {
+            warning(Level.WARNING, Resources.Keys.MismatchedLength_4, Tags.name(tag1), Tags.name(tag2), length1, length2);
+        }
+    }
+
+    /**
+     * Reports a warning for a missing TIFF tag for which a default value can be computed.
+     *
+     * @param  missing   the numerical value of the missing tag.
+     * @param  value     the default value or the computed value.
+     * @param  computed  whether the default value has been computed.
+     */
+    private void missingTag(final short missing, final long value, final boolean computed) {
+        warning(computed ? Level.WARNING : Level.FINE,
+                computed ? Resources.Keys.ComputedValueForAttribute_2 : Resources.Keys.DefaultValueForAttribute_2,
+                Tags.name(missing), value);
+    }
+
+    /**
+     * Builds an exception for a missing TIFF tag for which no default value can be computed.
+     *
+     * @param  missing  the numerical value of the missing tag.
+     */
+    private DataStoreContentException missingTag(final short missing) {
+        return new DataStoreContentException(reader.resources().getString(
+                Resources.Keys.MissingValue_2, input().filename, Tags.name(missing)));
     }
 }

Modified: sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java [UTF-8] (original)
+++ sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Reader.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -127,7 +127,7 @@ final class Reader extends GeoTIFF {
         this.metadata = new MetadataBuilder();
         /*
          * A TIFF file begins with either "II" (0x4949) or "MM" (0x4D4D) characters.
-         * Those characters identify the byte order. Note we we do not need to care
+         * Those characters identify the byte order. Note that we do not need to care
          * about the byte order for this flag since the two bytes shall have the same value.
          */
         final short order = input.readShort();
@@ -139,7 +139,7 @@ final class Reader extends GeoTIFF {
              * The pointer type is implicitely the Java Integer type (4 bytes).
              *
              * Magic number of BigTIFF file is 43, followed by the pointer size.
-             * Currently, that pointer type canonly be the Java Long type (8 bytes),
+             * Currently, that pointer type can only be the Java Long type (8 bytes),
              * but a future BigTIFF version may allow 16 bytes wide pointers.
              */
             switch (input.readShort()) {
@@ -236,7 +236,7 @@ final class Reader extends GeoTIFF {
              * to get the pointer to the next IFD.
              */
             final int offsetSize = (Integer.SIZE / Byte.SIZE) << intSizeExpansion;
-            final ImageFileDirectory dir = new ImageFileDirectory();
+            final ImageFileDirectory dir = new ImageFileDirectory(this);
             for (long remaining = readUnsignedShort(); --remaining >= 0;) {
                 /*
                  * Each entry in the Image File Directory has the following format:
@@ -245,7 +245,7 @@ final class Reader extends GeoTIFF {
                  *   - The number of values of the indicated type.
                  *   - The value, or the file offset to the value elswhere in the file.
                  */
-                final int  tag   = input.readUnsignedShort();
+                final short tag  = (short) input.readUnsignedShort();
                 final Type type  = Type.valueOf(input.readShort());        // May be null.
                 final long count = readUnsignedInt();
                 final long size  = (type != null) ? JDK8.multiplyExact(type.size, count) : 0;
@@ -263,7 +263,7 @@ final class Reader extends GeoTIFF {
                              * recommends to ignore it (for allowing them to add new types in the future), or an entry
                              * without value (count = 0) - in principle illegal but we make this reader tolerant.
                              */
-                            error = dir.addEntry(this, tag, type, count);
+                            error = dir.addEntry(tag, type, count);
                         } catch (ParseException | RuntimeException e) {
                             error = e;
                         }
@@ -294,6 +294,7 @@ final class Reader extends GeoTIFF {
             resolveDeferredEntries(dir, Long.MAX_VALUE);
             dir.hasDeferredEntries = false;
         }
+        dir.validateMandatoryTags();
         return dir;
     }
 
@@ -329,7 +330,7 @@ final class Reader extends GeoTIFF {
                 input.seek(JDK8.addExact(origin, entry.offset));
                 Object error;
                 try {
-                    error = entry.owner.addEntry(this, entry.tag, entry.type, entry.count);
+                    error = entry.owner.addEntry(entry.tag, entry.type, entry.count);
                 } catch (ParseException | RuntimeException e) {
                     error = e;
                 }
@@ -348,7 +349,7 @@ final class Reader extends GeoTIFF {
      * @param tag    the tag than can not be read.
      * @param error  the value than can not be understand, or the exception that we got while trying to parse it.
      */
-    private void warning(final int tag, final Object error) {
+    private void warning(final short tag, final Object error) {
         final short key;
         final Object[] args;
         final Exception exception;

Modified: sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java [UTF-8] (original)
+++ sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Tags.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -20,7 +20,8 @@ import java.lang.reflect.Field;
 
 
 /**
- * Numerical values of GeoTIFF tags. In this class, field names are identical to TIFF tag names.
+ * Numerical values of GeoTIFF tags, as <strong>unsigned</strong> short integers.
+ * In this class, field names are identical to TIFF tag names.
  * For that reason, many of those field names do not follow usual Java convention for constants.
  *
  * <p>A useful (but unofficial) reference is the
@@ -37,91 +38,127 @@ final class Tags {
     //                  BASELINE TIFF TAGS                  //
     //////////////////////////////////////////////////////////
 
-    public static final int NewSubfileType              = 0x00FE;
-    public static final int SubfileType                 = 0x00FF;
-    public static final int ImageWidth                  = 0x0100;
-    public static final int ImageLength                 = 0x0101;
-    public static final int BitsPerSample               = 0x0102;
-    public static final int Compression                 = 0x0103;
-    public static final int PhotometricInterpretation   = 0x0106;
-    public static final int Threshholding               = 0x0107;
-    public static final int CellWidth                   = 0x0108;
-    public static final int CellLength                  = 0x0109;
-    public static final int FillOrder                   = 0x010A;
-    public static final int DocumentName                = 0x010D;
-    public static final int ImageDescription            = 0x010E;
-    public static final int Make                        = 0x010F;
-    public static final int Model                       = 0x0110;
-    public static final int StripOffsets                = 0x0111;
-    public static final int Orientation                 = 0x0112;
-    public static final int SamplesPerPixel             = 0x0115;
-    public static final int RowsPerStrip                = 0x0116;
-    public static final int StripByteCounts             = 0x0117;
-    public static final int MinSampleValue              = 0x0118;
-    public static final int MaxSampleValue              = 0x0119;
-    public static final int XResolution                 = 0x011A;
-    public static final int YResolution                 = 0x011B;
-    public static final int PlanarConfiguration         = 0x011C;
-    public static final int PageName                    = 0x011D;
-    public static final int XPosition                   = 0x011E;
-    public static final int YPosition                   = 0x011F;
-    public static final int FreeOffsets                 = 0x0120;
-    public static final int FreeByteCounts              = 0x0121;
-    public static final int GrayResponseUnit            = 0x0122;
-    public static final int GrayResponseCurve           = 0x0123;
-    public static final int T4Options                   = 0x0124;
-    public static final int T6Options                   = 0x0125;
-    public static final int ResolutionUnit              = 0x0128;
-    public static final int PageNumber                  = 0x0129;
-    public static final int TransferFunction            = 0x012D;
-    public static final int Software                    = 0x0131;
-    public static final int DateTime                    = 0x0132;
-    public static final int DateTimeOriginal            = 0x9003;
-    public static final int DateTimeDigitized           = 0x9004;
-    public static final int Artist                      = 0x013B;
-    public static final int HostComputer                = 0x013C;
-    public static final int Predictor                   = 0x013D;
-    public static final int WhitePoint                  = 0x013E;
-    public static final int PrimaryChromaticities       = 0x013F;
-    public static final int ColorMap                    = 0x0140;
-    public static final int HalftoneHints               = 0x0141;
-    public static final int TileWidth                   = 0x0142;
-    public static final int TileLength                  = 0x0143;
-    public static final int TileOffsets                 = 0x0144;
-    public static final int TileByteCounts              = 0x0145;
-    public static final int InkSet                      = 0x014C;
-    public static final int InkNames                    = 0x014D;
-    public static final int NumberOfInks                = 0x014E;
-    public static final int DotRange                    = 0x0150;
-    public static final int TargetPrinter               = 0x0151;
-    public static final int ExtraSamples                = 0x0152;
-    public static final int SampleFormat                = 0x0153;
-    public static final int SMinSampleValue             = 0x0154;
-    public static final int SMaxSampleValue             = 0x0155;
-    public static final int TransferRange               = 0x0156;
-    public static final int JPEGProc                    = 0x0200;
-    public static final int JPEGInterchangeFormat       = 0x0201;
-    public static final int JPEGInterchangeFormatLength = 0x0202;
-    public static final int JPEGRestartInterval         = 0x0203;
-    public static final int JPEGLosslessPredictors      = 0x0205;
-    public static final int JPEGPointTransforms         = 0x0206;
-    public static final int JPEGQTables                 = 0x0207;
-    public static final int JPEGDCTables                = 0x0208;
-    public static final int JPEGACTables                = 0x0209;
-    public static final int YCbCrCoefficients           = 0x0211;
-    public static final int YCbCrSubSampling            = 0x0212;
-    public static final int YCbCrPositioning            = 0x0213;
-    public static final int ReferenceBlackWhite         = 0x0214;
-    public static final int Copyright                   = 0x8298;
-
+    public static final short NewSubfileType              = 0x00FE;
+    public static final short SubfileType                 = 0x00FF;
+    public static final short ImageWidth                  = 0x0100;
+    public static final short ImageLength                 = 0x0101;
+    public static final short BitsPerSample               = 0x0102;
+    public static final short Compression                 = 0x0103;
+    public static final short PhotometricInterpretation   = 0x0106;
+    public static final short Threshholding               = 0x0107;
+    public static final short CellWidth                   = 0x0108;
+    public static final short CellLength                  = 0x0109;
+    public static final short FillOrder                   = 0x010A;
+    public static final short DocumentName                = 0x010D;
+    public static final short ImageDescription            = 0x010E;
+    public static final short Make                        = 0x010F;
+    public static final short Model                       = 0x0110;
+    public static final short StripOffsets                = 0x0111;
+    public static final short Orientation                 = 0x0112;
+    public static final short SamplesPerPixel             = 0x0115;
+    public static final short RowsPerStrip                = 0x0116;
+    public static final short StripByteCounts             = 0x0117;
+    public static final short MinSampleValue              = 0x0118;
+    public static final short MaxSampleValue              = 0x0119;
+    public static final short XResolution                 = 0x011A;
+    public static final short YResolution                 = 0x011B;
+    public static final short PlanarConfiguration         = 0x011C;
+    public static final short PageName                    = 0x011D;
+    public static final short XPosition                   = 0x011E;
+    public static final short YPosition                   = 0x011F;
+    public static final short FreeOffsets                 = 0x0120;
+    public static final short FreeByteCounts              = 0x0121;
+    public static final short GrayResponseUnit            = 0x0122;
+    public static final short GrayResponseCurve           = 0x0123;
+    public static final short T4Options                   = 0x0124;
+    public static final short T6Options                   = 0x0125;
+    public static final short ResolutionUnit              = 0x0128;
+    public static final short PageNumber                  = 0x0129;
+    public static final short TransferFunction            = 0x012D;
+    public static final short Software                    = 0x0131;
+    public static final short DateTime                    = 0x0132;
+    public static final short DateTimeOriginal    = (short) 0x9003;
+    public static final short DateTimeDigitized   = (short) 0x9004;
+    public static final short Artist                      = 0x013B;
+    public static final short HostComputer                = 0x013C;
+    public static final short Predictor                   = 0x013D;
+    public static final short WhitePoint                  = 0x013E;
+    public static final short PrimaryChromaticities       = 0x013F;
+    public static final short ColorMap                    = 0x0140;
+    public static final short HalftoneHints               = 0x0141;
+    public static final short TileWidth                   = 0x0142;
+    public static final short TileLength                  = 0x0143;
+    public static final short TileOffsets                 = 0x0144;
+    public static final short TileByteCounts              = 0x0145;
+    public static final short InkSet                      = 0x014C;
+    public static final short InkNames                    = 0x014D;
+    public static final short NumberOfInks                = 0x014E;
+    public static final short DotRange                    = 0x0150;
+    public static final short TargetPrinter               = 0x0151;
+    public static final short ExtraSamples                = 0x0152;
+    public static final short SampleFormat                = 0x0153;
+    public static final short SMinSampleValue             = 0x0154;
+    public static final short SMaxSampleValue             = 0x0155;
+    public static final short TransferRange               = 0x0156;
+    public static final short JPEGProc                    = 0x0200;
+    public static final short JPEGInterchangeFormat       = 0x0201;
+    public static final short JPEGInterchangeFormatLength = 0x0202;
+    public static final short JPEGRestartInterval         = 0x0203;
+    public static final short JPEGLosslessPredictors      = 0x0205;
+    public static final short JPEGPointTransforms         = 0x0206;
+    public static final short JPEGQTables                 = 0x0207;
+    public static final short JPEGDCTables                = 0x0208;
+    public static final short JPEGACTables                = 0x0209;
+    public static final short YCbCrCoefficients           = 0x0211;
+    public static final short YCbCrSubSampling            = 0x0212;
+    public static final short YCbCrPositioning            = 0x0213;
+    public static final short ReferenceBlackWhite         = 0x0214;
+    public static final short Copyright           = (short) 0x8298;
 
 
     /////////////////////////////////////////////////////////
     //                 GDAL EXTENSION TAGS                 //
     /////////////////////////////////////////////////////////
 
-    public static final int GDAL_METADATA = 42112;  // http://www.awaresystems.be/imaging/tiff/tifftags/gdal_metadata.html
-    public static final int GDAL_NODATA   = 42113;  // http://www.awaresystems.be/imaging/tiff/tifftags/gdal_nodata.html
+    /**
+     * holds an XML list of name=value 'metadata' values about the image as a whole, and about specific samples.
+     *
+     * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags/gdal_metadata.html">TIFF Tag GDAL_METADATA</a>
+     */
+    public static final short GDAL_METADATA = (short) 0xA480;             // 42112
+
+    /**
+     * Contains an ASCII encoded nodata or background pixel value.
+     *
+     * @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags/gdal_nodata.html">TIFF Tag GDAL_NODATA</a>
+     */
+    public static final short GDAL_NODATA = (short) 0xA481;               // 42113
+
+
+    /////////////////////////////////////////////////////////
+    //                 GEOTIFF EXTENSION TAGS              //
+    /////////////////////////////////////////////////////////
+
+    /**
+     * References all "GeoKeys" needed for building the Coordinate Reference System.
+     * GeoTIFF keys are stored in a kind of directory inside the TIFF directory, with
+     * the keys enumerated in the {@link CRSBuilder} class.
+     *
+     * @see GeoKeys
+     */
+    public static final short GeoKeyDirectory = (short) 0x87AF;           // 34735
+
+    /**
+     * References all {@code double} values referenced by the {@link GeoKeys}.
+     * The keys are stored in the entry referenced by {@link #GeoKeyDirectory}.
+     */
+    public static final short GeoDoubleParams = (short) 0x87B0;           // 34736
+
+    /**
+     * References all {@link String} values referenced by the {@link GeoKeys}.
+     * The keys are stored in the entry referenced by {@link #GeoKeyDirectory}.
+     */
+    public static final short GeoAsciiParams = (short) 0x87B1;            // 34737
 
     /**
      * Do not allow instantiation of this class.
@@ -133,11 +170,11 @@ final class Tags {
      * Returns the name of the given tag. Implementation of this method is inefficient,
      * but it should rarely be invoked (mostly for formatting error messages).
      */
-    static String name(final int tag) {
+    static String name(final short tag) {
         try {
             for (final Field field : Tags.class.getFields()) {
-                if (field.getType() == Integer.TYPE) {
-                    if (field.getInt(null) == tag) {
+                if (field.getType() == Short.TYPE) {
+                    if (field.getShort(null) == tag) {
                         return field.getName();
                     }
                 }
@@ -145,6 +182,6 @@ final class Tags {
         } catch (IllegalAccessException e) {
             throw new AssertionError(e);        // Should never happen because we asked only for public fields.
         }
-        return Integer.toHexString(tag);
+        return Integer.toHexString(tag & 0xFFFF);
     }
 }

Modified: sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java [UTF-8] (original)
+++ sis/trunk/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/Type.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.nio.charset.Charset;
 import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.internal.util.Numerics;
+import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.Vector;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
@@ -223,7 +224,7 @@ enum Type {
 
         @Override double readDouble(final ChannelDataInput input, final long count) throws IOException {
             ensureSingleton(count);
-            return input.readFloat();
+            return DecimalFunctions.floatToDouble(input.readFloat());
         }
 
         @Override Object readArray(final ChannelDataInput input, final int count) throws IOException {
@@ -458,6 +459,27 @@ enum Type {
         }
         throw new ArithmeticException(canNotConvert(Long.toString(value)));
     }
+
+    /**
+     * Reads a single value and returns it as a signed {@code int} type, performing conversion if needed.
+     * This method should be invoked when the caller expects a single value.
+     *
+     * @param  input  the input from where to read the value.
+     * @param  count  the amount of values (normally exactly 1).
+     * @return the value as an {@code int}.
+     * @throws IOException if an error occurred while reading the stream.
+     * @throws NumberFormatException if the value was stored in ASCII and can not be parsed.
+     * @throws ArithmeticException if the value can not be represented in the Java signed {@code int} type.
+     * @throws IllegalArgumentException if the value is not a singleton.
+     * @throws UnsupportedOperationException if this type is {@link #UNDEFINED}.
+     */
+    final int readInt(final ChannelDataInput input, final long count) throws IOException {
+        final long value = readLong(input, count);
+        if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {
+            return (int) value;
+        }
+        throw new ArithmeticException(canNotConvert(Long.toString(value)));
+    }
 
     /**
      * Reads a single value which is expected to be positive. A negative value may be an encoding error in the

Modified: sis/trunk/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java [UTF-8] (original)
+++ sis/trunk/storage/sis-geotiff/src/test/java/org/apache/sis/test/suite/GeoTiffTestSuite.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -31,7 +31,9 @@ import org.junit.BeforeClass;
  */
 @Suite.SuiteClasses({
     org.apache.sis.storage.geotiff.TypeTest.class,
-    org.apache.sis.storage.geotiff.CompressionTest.class
+    org.apache.sis.storage.geotiff.CompressionTest.class,
+    org.apache.sis.storage.geotiff.GeoKeysTest.class,
+    org.apache.sis.storage.geotiff.CRSBuilderTest.class
 })
 public final strictfp class GeoTiffTestSuite extends TestSuite {
     /**

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Variable.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -160,7 +160,7 @@ public abstract class Variable extends N
      *
      * @param  attributeName  the name of the attribute for which to get the values.
      * @param  numeric {@code true} if the values are expected to be numeric, or {@code false} for strings.
-     * @return The sequence of {@link String} or {@link Number} values for the named attribute.
+     * @return the sequence of {@link String} or {@link Number} values for the named attribute.
      */
     public abstract Object[] getAttributeValues(String attributeName, boolean numeric);
 

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java?rev=1773274&r1=1773273&r2=1773274&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] Thu Dec  8 17:42:51 2016
@@ -71,6 +71,7 @@ import org.apache.sis.measure.Units;
 
 // The following dependency is used only for static final String constants.
 // Consequently the compiled class files should not have this dependency.
+import ucar.nc2.constants.CDM;
 import ucar.nc2.constants.CF;
 
 import static java.util.Collections.singleton;
@@ -848,7 +849,7 @@ final class MetadataReader {
      * @throws IOException if an I/O operation was necessary but failed.
      */
     private Band createSampleDimension(final Variable variable) throws IOException {
-        final DefaultBand band = new DefaultBand();
+        final DefaultBand band = new DefaultBand();     // TODO: not necessarily a band.
         String name = variable.getName();
         if (name != null && !(name = name.trim()).isEmpty()) {
             if (nameFactory == null) {
@@ -868,6 +869,16 @@ final class MetadataReader {
         } catch (ClassCastException | ParserException e) {
             decoder.listeners.warning(errors().getString(Errors.Keys.CanNotAssignUnitToDimension_2, name, units), e);
         }
+        Object[] v = variable.getAttributeValues(CDM.SCALE_FACTOR, true);
+        if (v.length == 1) {
+            band.setScaleFactor(((Number) v[0]).doubleValue());
+            band.setTransferFunctionType(TransferFunctionType.LINEAR);
+        }
+        v = variable.getAttributeValues(CDM.ADD_OFFSET, true);
+        if (v.length == 1) {
+            band.setOffset(((Number) v[0]).doubleValue());
+            band.setTransferFunctionType(TransferFunctionType.LINEAR);
+        }
         return band;
     }
 



Mime
View raw message