You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2016/12/07 04:14:47 UTC
svn commit: r1773010 [7/8] - in /sis/branches/JDK7: ./
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-featur...
Copied: sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java (from r1769011, sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java?p2=sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java&p1=sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java&r1=1769011&r2=1773010&rev=1773010&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoKeys.java [UTF-8] Wed Dec 7 04:14:44 2016
@@ -22,8 +22,10 @@ import org.opengis.referencing.operation
/**
- * All Geographic Keys needed for building {@link CoordinateReferenceSystem} instances
- * and {@link MathTransform} "grid to CRS" from TIFF tags values.
+ * GeoTIFF keys associated to values needed for building {@link CoordinateReferenceSystem} instances
+ * and {@link MathTransform} "grid to CRS". In this class, field names are close to GeoTIFF key names
+ * with the {@code "GeoKey"} suffix omitted. For that reason, many of those field names do not follow
+ * usual Java convention for constants.
*
* @author R�mi Mar�chal (Geomatys)
* @since 0.8
@@ -37,340 +39,105 @@ final class GeoKeys {
private GeoKeys() {
}
- ////////////////////////////////////////////////////////////////////////////
- // KEYS, values are taken from :
- // http://www.remotesensing.org/geotiff/spec/geotiff6.html#6
- ////////////////////////////////////////////////////////////////////////////
- static class Configuration {
+ // 6.2.1 GeoTIFF Configuration Keys
+ /** Section 6.3.1.1 Codes. */ public static final short ModelType = 1024;
+ /** Section 6.3.1.2 Codes. */ public static final short RasterType = 1025;
+ /** Documentation. */ public static final short Citation = 1026;
+
+ // 6.2.2 Geographic CS Parameter Keys
+ /** Section 6.3.2.1 Codes. */ public static final short GeographicType = 2048;
+ /** Documentation. */ public static final short GeogCitation = 2049;
+ /** Section 6.3.2.2 Codes. */ public static final short GeodeticDatum = 2050;
+ /** Section 6.3.2.4 codes. */ public static final short PrimeMeridian = 2051;
+ /** Section 6.3.1.3 Codes. */ public static final short GeogLinearUnits = 2052;
+ /** Relative to meters. */ public static final short GeogLinearUnitSize = 2053;
+ /** Section 6.3.1.4 Codes. */ public static final short AngularUnits = 2054;
+ /** Relative to radians. */ public static final short AngularUnitSize = 2055;
+ /** Section 6.3.2.3 Codes. */ public static final short Ellipsoid = 2056;
+ /** In GeogLinearUnits. */ public static final short SemiMajorAxis = 2057;
+ /** In GeogLinearUnits. */ public static final short SemiMinorAxis = 2058;
+ /** A ratio. */ public static final short InvFlattening = 2059;
+ /** Section 6.3.1.4 Codes. */ public static final short AzimuthUnits = 2060;
+ /** In AngularUnit. */ public static final short PrimeMeridianLong = 2061;
+
+ // 6.2.3 Projected CS Parameter Keys
+ /** Section 6.3.3.1 codes. */ public static final short ProjectedCSType = 3072;
+ /** Documentation. */ public static final short PCSCitation = 3073;
+ /** Section 6.3.3.2 codes. */ public static final short Projection = 3074;
+ /** Section 6.3.3.3 codes. */ public static final short CoordTrans = 3075;
+ /** Section 6.3.1.3 codes. */ public static final short LinearUnits = 3076;
+ /** Relative to meters. */ public static final short LinearUnitSize = 3077;
+ /** In AngularUnit. */ public static final short StdParallel1 = 3078; // First projection parameter
+ /** In AngularUnit. */ public static final short StdParallel2 = 3079;
+ /** In AngularUnit. */ public static final short NatOriginLong = 3080;
+ /** In AngularUnit. */ public static final short NatOriginLat = 3081;
+ /** In LinearUnits. */ public static final short FalseEasting = 3082;
+ /** In LinearUnits. */ public static final short FalseNorthing = 3083;
+ /** In AngularUnit. */ public static final short FalseOriginLong = 3084;
+ /** In AngularUnit. */ public static final short FalseOriginLat = 3085;
+ /** In LinearUnits. */ public static final short FalseOriginEasting = 3086;
+ /** In LinearUnits. */ public static final short FalseOriginNorthing = 3087;
+ /** In AngularUnit. */ public static final short CenterLong = 3088;
+ /** In AngularUnit. */ public static final short CenterLat = 3089;
+ /** In LinearUnits. */ public static final short CenterEasting = 3090;
+ /** In LinearUnits. */ public static final short CenterNorthing = 3091;
+ /** A ratio. */ public static final short ScaleAtNatOrigin = 3092;
+ /** A ratio. */ public static final short ScaleAtCenter = 3093;
+ /** In AzimuthUnit. */ public static final short AzimuthAngle = 3094;
+ /** In AngularUnit. */ public static final short StraightVertPoleLong = 3095; // Last projection parameter (for now)
+
+ // 6.2.4 Vertical CS Keys
+ /** Section 6.3.4.1 codes. */ public static final short VerticalCSType = 4096;
+ /** Documentation. */ public static final short VerticalCitation = 4097;
+ /** Section 6.3.4.2 codes. */ public static final short VerticalDatum = 4098;
+ /** Section 6.3.1.3 codes. */ public static final short VerticalUnits = 4099;
- //6.2.1 GeoTIFF Configuration Keys
- static final int GTModelTypeGeoKey = 1024; /* Section 6.3.1.1 Codes */
- static final int GTRasterTypeGeoKey = 1025; /* Section 6.3.1.2 Codes */
- static final int GTCitationGeoKey = 1026; /* documentation */
-
- /*
- * 6.3.1.1 Model Type Codes
- *
- * Ranges:
- * 0 = undefined
- * [ 1, 32766] = GeoTIFF Reserved Codes
- * 32767 = user-defined
- * [32768, 65535] = Private User Implementations
- *
- * Notes:
- * 1. ModelTypeGeographic and ModelTypeProjected
- * correspond to the FGDC metadata Geographic and
- * Planar-Projected coordinate system types.
- */
- //GeoTIFF defined CS Model Type Codes:
- static final int ModelTypeProjected = 1; /* Projection Coordinate System */
- static final int ModelTypeGeographic = 2; /* Geographic latitude-longitude System */
- static final int ModelTypeGeocentric = 3; /* Geocentric (X,Y,Z) Coordinate System */
- ////////////////////////////////////////////////////////////////////////////
- // Codes
- ////////////////////////////////////////////////////////////////////////////
-
- static final short GTUserDefinedGeoKey = 32767;
- static final String GTUserDefinedGeoKey_String = "32767";
-
- /**
- * Return tag Name from {@link GeoTiffConstants} class.
- *
- * @param tag
- * @return tag Name from {@link GeoTiffConstants} class.
- */
- static String getName(final int tag) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == tag) {
- return field.getName();
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return Integer.toHexString(tag);
- }
-
- static boolean contain(int key) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == key) {
- return true;
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return false;
- }
- }
-
- static class Operation_Method {
- /*
- * 6.3.3.3 Coordinate Transformation Codes
- * Ranges:
- * 0 = undefined
- * [ 1, 16383] = GeoTIFF Coordinate Transformation codes
- * [16384, 32766] = Reserved by GeoTIFF
- * 32767 = user-defined
- * [32768, 65535] = Private User Implementations
- */
- static final int CT_TransverseMercator = 1;
- static final int CT_TransvMercator_Modified_Alaska = 2;
- static final int CT_ObliqueMercator = 3;
- static final int CT_ObliqueMercator_Laborde = 4;
- static final int CT_ObliqueMercator_Rosenmund = 5;
- static final int CT_ObliqueMercator_Spherical = 6;
- static final int CT_Mercator = 7;
- static final int CT_LambertConfConic_2SP = 8;
- static final int CT_LambertConfConic_1SP = 9;
- static final int CT_LambertAzimEqualArea = 10;
- static final int CT_AlbersEqualArea = 11;
- static final int CT_AzimuthalEquidistant = 12;
- static final int CT_EquidistantConic = 13;
- static final int CT_Stereographic = 14;
- static final int CT_PolarStereographic = 15;
- static final int CT_ObliqueStereographic = 16;
- static final int CT_Equirectangular = 17;
- static final int CT_CassiniSoldner = 18;
- static final int CT_Gnomonic = 19;
- static final int CT_MillerCylindrical = 20;
- static final int CT_Orthographic = 21;
- static final int CT_Polyconic = 22;
- static final int CT_Robinson = 23;
- static final int CT_Sinusoidal = 24;
- static final int CT_VanDerGrinten = 25;
- static final int CT_NewZealandMapGrid = 26;
- static final int CT_TransvMercator_SouthOriented= 27;
- //Aliases:
- static final int CT_AlaskaConformal = CT_TransvMercator_Modified_Alaska;
- static final int CT_TransvEquidistCylindrical = CT_CassiniSoldner;
- static final int CT_ObliqueMercator_Hotine = CT_ObliqueMercator;
- static final int CT_SwissObliqueCylindrical = CT_ObliqueMercator_Rosenmund;
- static final int CT_GaussBoaga = CT_TransverseMercator;
- static final int CT_GaussKruger = CT_TransverseMercator;
- static final int CT_LambertConfConic = CT_LambertConfConic_2SP ;
- static final int CT_LambertConfConic_Helmert = CT_LambertConfConic_1SP;
- static final int CT_SouthOrientedGaussConformal = CT_TransvMercator_SouthOriented;
-
- /**
- * Return tag Name from {@link GeoTiffConstants} class.
- *
- * @param tag
- * @return tag Name from {@link GeoTiffConstants} class.
- */
- static String getName(final int tag) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == tag) {
- return field.getName();
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return Integer.toHexString(tag);
- }
-
- static boolean contain(int key) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == key) {
- return true;
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return false;
- }
- }
-
- static class Units {
- /*
- * 6.3.1.3 Linear Units Codes
- *
- * There are several different kinds of units that may be used in geographically related raster data: linear units, angular units, units of time (e.g. for radar-return), CCD-voltages, etc. For this reason there will be a single, unique range for each kind of unit, broken down into the following currently defined ranges:
- * Ranges:
- * 0 = undefined
- * [ 1, 2000] = Obsolete GeoTIFF codes
- * [2001, 8999] = Reserved by GeoTIFF
- * [9000, 9099] = EPSG Linear Units.
- * [9100, 9199] = EPSG Angular Units.
- * 32767 = user-defined unit
- * [32768, 65535]= Private User Implementations
- * Linear Unit Values (See the ESPG/POSC tables for definition):
- */
- static final int Linear_Meter = 9001;
- static final int Linear_Foot = 9002;
- static final int Linear_Foot_US_Survey = 9003;
- static final int Linear_Foot_Modified_American = 9004;
- static final int Linear_Foot_Clarke = 9005;
- static final int Linear_Foot_Indian = 9006;
- static final int Linear_Link = 9007;
- static final int Linear_Link_Benoit = 9008;
- static final int Linear_Link_Sears = 9009;
- static final int Linear_Chain_Benoit = 9010;
- static final int Linear_Chain_Sears = 9011;
- static final int Linear_Yard_Sears = 9012;
- static final int Linear_Yard_Indian = 9013;
- static final int Linear_Fathom = 9014;
- static final int Linear_Mile_International_Nautical = 9015;
-
- /*
- * 6.3.1.4 Angular Units Codes
- * These codes shall be used for any key that requires specification of an angular unit of measurement.
- */
- static final int Angular_Radian = 9101;
- static final int Angular_Degree = 9102;
- static final int Angular_Arc_Minute = 9103;
- static final int Angular_Arc_Second = 9104;
- static final int Angular_Grad = 9105;
- static final int Angular_Gon = 9106;
- static final int Angular_DMS = 9107;
- static final int Angular_DMS_Hemisphere = 9108;
-
- /**
- * Return tag Name from {@link GeoTiffConstants} class.
- *
- * @param tag
- * @return tag Name from {@link GeoTiffConstants} class.
- */
- static String getName(final int tag) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == tag) {
- return field.getName();
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return Integer.toHexString(tag);
- }
+ /**
+ * Enumeration of return values for the {@link #unitOf(short)} method.
+ */
+ static final int RATIO = 0, LINEAR = 1, ANGULAR = 2, AZIMUTH = 3;
- static boolean contain(int key) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == key) {
- return true;
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return false;
+ /**
+ * Returns the unit of measurement for the given map projection parameter.
+ *
+ * @param key GeoTIFF key for which to get the unit of associated map projection parameter value.
+ * @return one of {@link #RATIO}, {@link #LINEAR}, {@link #ANGULAR}, {@link #AZIMUTH} codes,
+ * or -1 if the given key is not for a map projection parameter.
+ */
+ static int unitOf(final short key) {
+ if (key < StdParallel1 || key > StraightVertPoleLong) {
+ return -1;
+ }
+ switch (key) {
+ case FalseEasting:
+ case FalseNorthing:
+ case FalseOriginEasting:
+ case FalseOriginNorthing:
+ case CenterEasting:
+ case CenterNorthing: return LINEAR;
+ case ScaleAtNatOrigin:
+ case ScaleAtCenter: return RATIO;
+ case AzimuthAngle: return AZIMUTH;
+ default: return ANGULAR;
}
}
-
- static class CRS {
-
- //6.2.2 Geographic CS Parameter Keys
- static final int GeographicTypeGeoKey = 2048; /* Section 6.3.2.1 Codes */
- static final int GeogCitationGeoKey = 2049; /* documentation */
- static final int GeogGeodeticDatumGeoKey = 2050; /* Section 6.3.2.2 Codes */
- static final int GeogPrimeMeridianGeoKey = 2051; /* Section 6.3.2.4 codes */
- static final int GeogLinearUnitsGeoKey = 2052; /* Section 6.3.1.3 Codes */
- static final int GeogLinearUnitSizeGeoKey = 2053; /* meters */
- static final int GeogAngularUnitsGeoKey = 2054; /* Section 6.3.1.4 Codes */
- static final int GeogAngularUnitSizeGeoKey = 2055; /* radians */
- static final int GeogEllipsoidGeoKey = 2056; /* Section 6.3.2.3 Codes */
- static final int GeogSemiMajorAxisGeoKey = 2057; /* GeogLinearUnits */
- static final int GeogSemiMinorAxisGeoKey = 2058; /* GeogLinearUnits */
- static final int GeogInvFlatteningGeoKey = 2059; /* ratio */
- static final int GeogAzimuthUnitsGeoKey = 2060; /* Section 6.3.1.4 Codes */
- static final int GeogPrimeMeridianLongGeoKey = 2061; /* GeogAngularUnit */
-
- //6.2.3 Projected CS Parameter Keys
- static final int ProjectedCSTypeGeoKey = 3072; /* Section 6.3.3.1 codes */
- static final int PCSCitationGeoKey = 3073; /* documentation */
- static final int ProjectionGeoKey = 3074; /* Section 6.3.3.2 codes */
- static final int ProjCoordTransGeoKey = 3075; /* Section 6.3.3.3 codes */
- static final int ProjLinearUnitsGeoKey = 3076; /* Section 6.3.1.3 codes */
- static final int ProjLinearUnitSizeGeoKey = 3077; /* meters */
- static final int ProjStdParallel1GeoKey = 3078; /* GeogAngularUnit */
- static final int ProjStdParallel2GeoKey = 3079; /* GeogAngularUnit */
- static final int ProjNatOriginLongGeoKey = 3080; /* GeogAngularUnit */
- static final int ProjNatOriginLatGeoKey = 3081; /* GeogAngularUnit */
- static final int ProjFalseEastingGeoKey = 3082; /* ProjLinearUnits */
- static final int ProjFalseNorthingGeoKey = 3083; /* ProjLinearUnits */
- static final int ProjFalseOriginLongGeoKey = 3084; /* GeogAngularUnit */
- static final int ProjFalseOriginLatGeoKey = 3085; /* GeogAngularUnit */
- static final int ProjFalseOriginEastingGeoKey = 3086; /* ProjLinearUnits */
- static final int ProjFalseOriginNorthingGeoKey = 3087; /* ProjLinearUnits */
- static final int ProjCenterLongGeoKey = 3088; /* GeogAngularUnit */
- static final int ProjCenterLatGeoKey = 3089; /* GeogAngularUnit */
- static final int ProjCenterEastingGeoKey = 3090; /* ProjLinearUnits */
- static final int ProjCenterNorthingGeoKey = 3091; /* ProjLinearUnits */
- static final int ProjScaleAtNatOriginGeoKey = 3092; /* ratio */
- static final int ProjScaleAtCenterGeoKey = 3093; /* ratio */
- static final int ProjAzimuthAngleGeoKey = 3094; /* GeogAzimuthUnit */
- static final int ProjStraightVertPoleLongGeoKey = 3095; /* GeogAngularUnit */
- //Aliases:
- static final int ProjStdParallelGeoKey = ProjStdParallel1GeoKey;
- static final int ProjOriginLongGeoKey = ProjNatOriginLongGeoKey;
- static final int ProjOriginLatGeoKey = ProjNatOriginLatGeoKey;
- static final int ProjScaleAtOriginGeoKey = ProjScaleAtNatOriginGeoKey;
-
- //6.2.4 Vertical CS Keys
- static final int VerticalCSTypeGeoKey = 4096; /* Section 6.3.4.1 codes */
- static final int VerticalCitationGeoKey = 4097; /* documentation */
- static final int VerticalDatumGeoKey = 4098; /* Section 6.3.4.2 codes */
- static final int VerticalUnitsGeoKey = 4099; /* Section 6.3.1.3 codes */
-
- /**
- * Return tag Name from {@link GeoTiffConstants} class.
- *
- * @param tag
- * @return tag Name from {@link GeoTiffConstants} class.
- */
- static String getName(final int tag) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == tag) {
- return field.getName();
- }
- }
- }
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
- }
- return Integer.toHexString(tag);
- }
-
- static boolean contain(int key) {
- try {
- for (final Field field : CRS.class.getDeclaredFields()) {
- if (field.getType() == Integer.TYPE) {
- if (field.getInt(null) == key) {
- return true;
- }
+ /**
+ * Returns the name of the given key. Implementation of this method is inefficient,
+ * but it should rarely be invoked (mostly for formatting error messages).
+ */
+ static String name(final short key) {
+ try {
+ for (final Field field : GeoKeys.class.getFields()) {
+ if (field.getType() == Short.TYPE) {
+ if (field.getShort(null) == key) {
+ return field.getName();
}
}
- } catch (ReflectiveOperationException ex) {
- throw new AssertionError(ex); // Should never happen.
}
- return false;
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e); // Should never happen because we asked only for public fields.
}
+ return Integer.toHexString(key & 0xFFFF);
}
-
- static class GridToCrs {
-
- }
-
}
Modified: sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java?rev=1773010&r1=1773009&r2=1773010&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTIFF.java [UTF-8] Wed Dec 7 04:14:44 2016
@@ -40,12 +40,18 @@ import org.apache.sis.internal.geotiff.R
*/
abstract class GeoTIFF implements Closeable {
/**
- * The timezone specified at construction time, or {@code null} for the default.
+ * The timezone for the date and time parsing, or {@code null} for the default.
* This is not yet configurable, but may become in a future version.
*/
private static final TimeZone TIMEZONE = null;
/**
+ * The locale to use for parsers or formatter. This is <strong>not</strong> the locale
+ * for warnings or other messages emitted to the users.
+ */
+ static final Locale LOCALE = Locale.US;
+
+ /**
* The store which created this reader or writer.
*/
final GeoTiffStore owner;
@@ -81,7 +87,7 @@ abstract class GeoTIFF implements Closea
*/
final DateFormat getDateFormat() {
if (dateFormat == null) {
- dateFormat = new SimpleDateFormat("yyy:MM:dd HH:mm:ss", Locale.US);
+ dateFormat = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", LOCALE);
if (TIMEZONE != null) {
dateFormat.setTimeZone(TIMEZONE);
}
Modified: sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java?rev=1773010&r1=1773009&r2=1773010&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/GeoTiffStore.java [UTF-8] Wed Dec 7 04:14:44 2016
@@ -19,13 +19,19 @@ package org.apache.sis.storage.geotiff;
import java.util.Locale;
import java.io.IOException;
import java.nio.charset.Charset;
+import java.util.logging.LogRecord;
+import java.nio.charset.StandardCharsets;
+import org.opengis.util.FactoryException;
import org.opengis.metadata.Metadata;
+import org.opengis.metadata.maintenance.ScopeCode;
import org.apache.sis.setup.OptionKey;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.StorageConnector;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.DataStoreContentException;
import org.apache.sis.internal.storage.ChannelDataInput;
+import org.apache.sis.internal.storage.MetadataBuilder;
+import org.apache.sis.metadata.sql.MetadataStoreException;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
@@ -64,12 +70,13 @@ public class GeoTiffStore extends DataSt
* This constructor invokes {@link StorageConnector#closeAllExcept(Object)},
* keeping open only the needed resource.
*
- * @param storage information about the storage (URL, stream, <i>etc</i>).
+ * @param storage information about the storage (URL, stream, <i>etc</i>).
* @throws DataStoreException if an error occurred while opening the GeoTIFF file.
*/
public GeoTiffStore(final StorageConnector storage) throws DataStoreException {
ArgumentChecks.ensureNonNull("storage", storage);
- encoding = storage.getOption(OptionKey.ENCODING);
+ final Charset encoding = storage.getOption(OptionKey.ENCODING);
+ this.encoding = (encoding != null) ? encoding : StandardCharsets.US_ASCII;
final ChannelDataInput input = storage.getStorageAs(ChannelDataInput.class);
if (input == null) {
throw new DataStoreException(Errors.format(Errors.Keys.IllegalInputTypeForReader_2,
@@ -93,18 +100,28 @@ public class GeoTiffStore extends DataSt
*/
@Override
public synchronized Metadata getMetadata() throws DataStoreException {
- if (metadata == null) try {
- int n = 0;
- ImageFileDirectory dir;
+ if (metadata == null) {
+ final MetadataBuilder builder = reader.metadata;
+ try {
+ builder.setFormat("GeoTIFF");
+ } catch (MetadataStoreException e) {
+ warning(null, e);
+ }
+ builder.add(encoding);
+ builder.add(ScopeCode.COVERAGE);
final Locale locale = getLocale();
- while ((dir = reader.getImageFileDirectory(n++)) != null) {
- dir.completeMetadata(reader.metadata, locale);
+ int n = 0;
+ try {
+ ImageFileDirectory dir;
+ while ((dir = reader.getImageFileDirectory(n++)) != null) {
+ dir.completeMetadata(builder, locale);
+ }
+ metadata = builder.build(true);
+ } catch (IOException e) {
+ throw new DataStoreException(reader.errors().getString(Errors.Keys.CanNotRead_1, reader.input.filename), e);
+ } catch (FactoryException | ArithmeticException e) {
+ throw new DataStoreContentException(reader.canNotDecode(), e);
}
- metadata = reader.metadata.build(true);
- } catch (IOException e) {
- throw new DataStoreException(reader.errors().getString(Errors.Keys.CanNotRead_1, reader.input.filename), e);
- } catch (ArithmeticException e) {
- throw new DataStoreContentException(reader.canNotDecode(), e);
}
return metadata;
}
@@ -135,4 +152,23 @@ public class GeoTiffStore extends DataSt
final void warning(final String message, final Exception exception) {
listeners.warning(message, exception);
}
+
+ /**
+ * Reports a warning contained in the given {@link LogRecord}.
+ * Note that the given record will not necessarily be sent to the logging framework;
+ * if the user as registered at least one listener, then the record will be sent to the listeners instead.
+ *
+ * <p>This method sets the {@linkplain LogRecord#setSourceClassName(String) source class name} and
+ * {@linkplain LogRecord#setSourceMethodName(String) source method name} to hard-coded values.
+ * Those values assume that the warnings occurred indirectly from a call to {@link #getMetadata()}
+ * in this class. We do not report private classes or methods as the source of warnings.</p>
+ *
+ * @param record the warning to report.
+ */
+ final void warning(final LogRecord record) {
+ // Logger name will be set by listeners.warning(record).
+ record.setSourceClassName(GeoTiffStore.class.getName());
+ record.setSourceMethodName("getMetadata");
+ listeners.warning(record);
+ }
}
Modified: sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java?rev=1773010&r1=1773009&r2=1773010&view=diff
==============================================================================
--- sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] (original)
+++ sis/branches/JDK7/storage/sis-geotiff/src/main/java/org/apache/sis/storage/geotiff/ImageFileDirectory.java [UTF-8] Wed Dec 7 04:14:44 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 \u201ccomponent planes\u201d), 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 \u201ccomponent planes\u201d.
@@ -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: \u201cCopyright, John Smith, 1992. All rights reserved.\u201d
*/
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(\u2026)} 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)));
}
}