package com.revolsys.raster.io.format.tiff; import java.awt.image.RenderedImage; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.media.jai.JAI; import javax.media.jai.OperationRegistry; import org.libtiff.jai.codec.XTIFF; import org.libtiff.jai.codec.XTIFFDirectory; import org.libtiff.jai.codec.XTIFFField; import org.libtiff.jai.codecimpl.XTIFFCodec; import org.libtiff.jai.operator.XTIFFDescriptor; import com.revolsys.collection.map.IntHashMap; import com.revolsys.collection.map.Maps; import com.revolsys.geometry.cs.Area; import com.revolsys.geometry.cs.Authority; import com.revolsys.geometry.cs.Axis; import com.revolsys.geometry.cs.CoordinateSystem; import com.revolsys.geometry.cs.GeographicCoordinateSystem; import com.revolsys.geometry.cs.LinearUnit; import com.revolsys.geometry.cs.ProjectedCoordinateSystem; import com.revolsys.geometry.cs.Projection; import com.revolsys.geometry.cs.ProjectionParameterNames; import com.revolsys.geometry.cs.epsg.EpsgAuthority; import com.revolsys.geometry.cs.epsg.EpsgCoordinateSystems; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.logging.Logs; import com.revolsys.raster.JaiGeoreferencedImage; import com.revolsys.spring.resource.Resource; import com.sun.media.jai.codec.ImageCodec; @SuppressWarnings("deprecation") public class TiffImage extends JaiGeoreferencedImage { /** ProjFalseEastingGeoKey (3082) */ public static final int FALSE_EASTING_KEY = 3082; /** ProjFalseNorthingGeoKey (3083) */ public static final int FALSE_NORTHING_KEY = 3083; /** GeographicTypeGeoKey (2048) */ public static final int GEOGRAPHIC_COORDINATE_SYSTEM_ID = 2048; /** ProjNatOriginLatGeoKey (3081) */ public static final int LATITUDE_OF_CENTER_2_KEY = 3081; /** ProjLinearUnitsGeoKey (3076) */ public static final int LINEAR_UNIT_ID = 3076; /** ProjNatOriginLongGeoKey (3080) */ public static final int LONGITUDE_OF_CENTER_2_KEY = 3080; /** ProjectedCSTypeGeoKey (3072) */ public static final int PROJECTED_COORDINATE_SYSTEM_ID = 3072; /** ProjCoordTransGeoKey (3075) */ public static final int PROJECTION_ID = 3075; private static IntHashMap<String> PROJECTION_NAMES = new IntHashMap<>(); /** ProjStdParallel1GeoKey (3078) */ public static final int STANDARD_PARALLEL_1_KEY = 3078; /** ProjStdParallel2GeoKey (3079) */ public static final int STANDARD_PARALLEL_2_KEY = 3079; public static final int TAG_X_RESOLUTION = 282; public static final int TAG_Y_RESOLUTION = 283; static { try { final OperationRegistry reg = JAI.getDefaultInstance().getOperationRegistry(); ImageCodec.unregisterCodec("tiff"); reg.unregisterOperationDescriptor("tiff"); ImageCodec.registerCodec(new XTIFFCodec()); final XTIFFDescriptor descriptor = new XTIFFDescriptor(); reg.registerDescriptor(descriptor); } catch (final Throwable t) { } } static { PROJECTION_NAMES.put(1, "Transverse_Mercator"); // CT_TransvMercator_Modified_Alaska = 2 // CT_ObliqueMercator = 3 // CT_ObliqueMercator_Laborde = 4 // CT_ObliqueMercator_Rosenmund = 5 // CT_ObliqueMercator_Spherical = 6 PROJECTION_NAMES.put(7, "Mercator"); PROJECTION_NAMES.put(8, "Lambert_Conic_Conformal_(2SP)"); PROJECTION_NAMES.put(9, "Lambert_Conic_Conformal_(1SP)"); // CT_LambertAzimEqualArea = 10 PROJECTION_NAMES.put(11, "Albers_Equal_Area"); // CT_AzimuthalEquidistant = 12 // CT_EquidistantConic = 13 // CT_Stereographic = 14 // CT_PolarStereographic = 15 // CT_ObliqueStereographic = 16 // CT_Equirectangular = 17 // CT_CassiniSoldner = 18 // CT_Gnomonic = 19 // CT_MillerCylindrical = 20 // CT_Orthographic = 21 // CT_Polyconic = 22 // CT_Robinson = 23 // CT_Sinusoidal = 24 // CT_VanDerGrinten = 25 // CT_NewZealandMapGrid = 26 // CT_TransvMercator_SouthOriented= 27 // registerCoordinatesProjection("Popular_Visualisation_Pseudo_Mercator", // WebMercator.class); // registerCoordinatesProjection("Mercator_(1SP)", Mercator1SP.class); // registerCoordinatesProjection("Mercator_(2SP)", Mercator2SP.class); // registerCoordinatesProjection("Mercator_(1SP)_(Spherical)", // Mercator1SPSpherical.class); // registerCoordinatesProjection("Lambert_Conic_Conformal_(2SP_Belgium)", // LambertConicConformal.class); } public static void addDoubleParameter(final Map<String, Object> parameters, final String name, final Map<Integer, Object> geoKeys, final int key) { final Double value = Maps.getDouble(geoKeys, key); if (value != null) { parameters.put(name, value); } } private static void addGeoKey(final Map<Integer, Object> geoKeys, final XTIFFDirectory dir, final int keyId, final int tiffTag, final int valueCount, final int valueOrOffset) { int type = XTIFFField.TIFF_SHORT; Object value = null; if (tiffTag > 0) { // Values are in another tag: final XTIFFField values = dir.getField(tiffTag); if (values != null) { type = values.getType(); if (type == XTIFFField.TIFF_ASCII) { final String string = values.getAsString(0).substring(valueOrOffset, valueOrOffset + valueCount - 1); value = string; } else if (type == XTIFFField.TIFF_DOUBLE) { final double number = values.getAsDouble(valueOrOffset); value = number; } } else { throw new IllegalArgumentException("GeoTIFF tag not found"); } } else { // value is SHORT, stored in valueOrOffset type = XTIFFField.TIFF_SHORT; value = (short)valueOrOffset; } geoKeys.put(keyId, value); } private static Map<Integer, Object> getGeoKeys(final XTIFFDirectory dir) { final Map<Integer, Object> geoKeys = new LinkedHashMap<>(); final XTIFFField geoKeyTag = dir.getField(XTIFF.TIFFTAG_GEO_KEY_DIRECTORY); if (geoKeyTag != null) { final char[] keys = geoKeyTag.getAsChars(); for (int i = 4; i < keys.length; i += 4) { final int keyId = keys[i]; final int tiffTag = keys[i + 1]; final int valueCount = keys[i + 2]; final int valueOrOffset = keys[i + 3]; addGeoKey(geoKeys, dir, keyId, tiffTag, valueCount, valueOrOffset); } } return geoKeys; } public static LinearUnit getLinearUnit(final Map<Integer, Object> geoKeys) { final int linearUnitId = Maps.getInteger(geoKeys, LINEAR_UNIT_ID, 0); return EpsgCoordinateSystems.getLinearUnit(linearUnitId); } public static Projection getProjection(final Map<Integer, Object> geoKeys) { final int projectionId = Maps.getInteger(geoKeys, PROJECTION_ID, 0); final String projectionName = PROJECTION_NAMES.get(projectionId); if (projectionName == null) { return null; } else { final Authority projectionAuthority = new EpsgAuthority(projectionId); final Projection projection = new Projection(projectionName, projectionAuthority); return projection; } } public TiffImage(final Resource imageResource) { super(imageResource); } private double getFieldAsDouble(final XTIFFDirectory directory, final int fieldIndex, final double defaultValue) { final XTIFFField field = directory.getField(fieldIndex); if (field == null) { return defaultValue; } else { return field.getAsDouble(0); } } @Override public String getWorldFileExtension() { return "tfw"; } private boolean loadGeoTiffMetaData(final XTIFFDirectory directory) { try { final int xResolution = (int)getFieldAsDouble(directory, TAG_X_RESOLUTION, 1); final int yResolution = (int)getFieldAsDouble(directory, TAG_Y_RESOLUTION, 1); setDpi(xResolution, yResolution); } catch (final Throwable e) { Logs.error(this, e); } GeometryFactory geometryFactory = null; final Map<Integer, Object> geoKeys = getGeoKeys(directory); int coordinateSystemId = Maps.getInteger(geoKeys, PROJECTED_COORDINATE_SYSTEM_ID, 0); if (coordinateSystemId == 0) { coordinateSystemId = Maps.getInteger(geoKeys, GEOGRAPHIC_COORDINATE_SYSTEM_ID, 0); if (coordinateSystemId != 0) { geometryFactory = GeometryFactory.floating(coordinateSystemId, 2); } } else if (coordinateSystemId <= 0 || coordinateSystemId == 32767) { final int geoSrid = Maps.getInteger(geoKeys, GEOGRAPHIC_COORDINATE_SYSTEM_ID, 0); if (geoSrid != 0) { if (geoSrid > 0 && geoSrid < 32767) { final GeographicCoordinateSystem geographicCoordinateSystem = EpsgCoordinateSystems .getCoordinateSystem(geoSrid); final String name = "unknown"; final Projection projection = getProjection(geoKeys); final Area area = null; final Map<String, Object> parameters = new LinkedHashMap<>(); addDoubleParameter(parameters, ProjectionParameterNames.STANDARD_PARALLEL_1, geoKeys, STANDARD_PARALLEL_1_KEY); addDoubleParameter(parameters, ProjectionParameterNames.STANDARD_PARALLEL_2, geoKeys, STANDARD_PARALLEL_2_KEY); addDoubleParameter(parameters, ProjectionParameterNames.LONGITUDE_OF_CENTER, geoKeys, LONGITUDE_OF_CENTER_2_KEY); addDoubleParameter(parameters, ProjectionParameterNames.LATITUDE_OF_CENTER, geoKeys, LATITUDE_OF_CENTER_2_KEY); addDoubleParameter(parameters, ProjectionParameterNames.FALSE_EASTING, geoKeys, FALSE_EASTING_KEY); addDoubleParameter(parameters, ProjectionParameterNames.FALSE_NORTHING, geoKeys, FALSE_NORTHING_KEY); final LinearUnit linearUnit = getLinearUnit(geoKeys); final List<Axis> axis = null; final Authority authority = null; final ProjectedCoordinateSystem coordinateSystem = new ProjectedCoordinateSystem( coordinateSystemId, name, geographicCoordinateSystem, area, projection, parameters, linearUnit, axis, authority, false); final CoordinateSystem epsgCoordinateSystem = EpsgCoordinateSystems .getCoordinateSystem(coordinateSystem); geometryFactory = GeometryFactory.floating(epsgCoordinateSystem.getCoordinateSystemId(), 2); } } } else { geometryFactory = GeometryFactory.floating(coordinateSystemId, 2); } if (geometryFactory != null) { setGeometryFactory(geometryFactory); } final XTIFFField tiePoints = directory.getField(XTIFF.TIFFTAG_GEO_TIEPOINTS); if (tiePoints == null) { final XTIFFField geoTransform = directory.getField(XTIFF.TIFFTAG_GEO_TRANS_MATRIX); if (geoTransform == null) { return false; } else { final double x1 = geoTransform.getAsDouble(3); final double y1 = geoTransform.getAsDouble(7); final double pixelWidth = geoTransform.getAsDouble(0); final double pixelHeight = geoTransform.getAsDouble(5); final double xRotation = geoTransform.getAsDouble(4); final double yRotation = geoTransform.getAsDouble(1); setResolution(pixelWidth); // TODO rotation setBoundingBox(x1, y1, pixelWidth, pixelHeight); return true; } } else { final XTIFFField pixelScale = directory.getField(XTIFF.TIFFTAG_GEO_PIXEL_SCALE); if (pixelScale == null) { return false; } else { final double rasterXOffset = tiePoints.getAsDouble(0); final double rasterYOffset = tiePoints.getAsDouble(1); if (rasterXOffset != 0 && rasterYOffset != 0) { // These should be 0, not sure what to do if they are not throw new IllegalArgumentException( "Exepectig 0 for the raster x,y tie points in a GeoTIFF"); } // double rasterZOffset = fieldModelTiePoints.getAsDouble(2); // setTopLeftRasterPoint(new PointDouble( // rasterXOffset, // rasterYOffset)); // Top left corner of image in model coordinates final double x1 = tiePoints.getAsDouble(3); final double y1 = tiePoints.getAsDouble(4); // double modelZOffset = fieldModelTiePoints.getAsDouble(5); // setTopLeftModelPoint(new PointDouble( // modelXOffset, // modelYOffset)); final double pixelWidth = pixelScale.getAsDouble(0); final double pixelHeight = pixelScale.getAsDouble(1); setResolution(pixelWidth); setBoundingBox(x1, y1, pixelWidth, -pixelHeight); return true; } } } @Override protected void loadMetaDataFromImage() { final RenderedImage image = getRenderedImage(); final Object tiffDirectory = image.getProperty("tiff.directory"); if (tiffDirectory == null) { throw new IllegalArgumentException("This is not a (geo)tiff file. Missing TIFF directory."); } else { if (!(tiffDirectory instanceof XTIFFDirectory) || !loadGeoTiffMetaData((XTIFFDirectory)tiffDirectory)) { } } } }