/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.gce.geotiff.adapters;
import it.geosolutions.imageio.plugins.tiff.BaselineTIFFTagSet;
import it.geosolutions.imageio.plugins.tiff.GeoTIFFTagSet;
import it.geosolutions.imageio.plugins.tiff.TIFFTag;
import java.awt.geom.AffineTransform;
import org.geotools.util.KeySortedList;
import org.jdom.Element;
/**
* This class is responsible for encoding the geotiff tags into suitable
* metadata for the ImageIO library.
*
* <p>
* Basically it is and encoder/adapter that collects all the different tags,
* order it accordingly to the spec and then organize then into a dom tree ready
* to be used by the ImageIO metadata mechanism.
*
*
*
* @author Simone Giannecchini, GeoSolutions
* @since 2.3
*
*
* @source $URL$
*/
public final class GeoTiffIIOMetadataEncoder {
private int numModelTiePoints;
private TiePoint[] modelTiePoints;
private PixelScale modelPixelScale;
private double[] modelTransformation;
private int numGeoTiffEntries;
private KeySortedList geoTiffEntries;
private int numGeoTiffDoubleParams;
private double[] geoTiffDoubleParams;
private int numGeoTiffAsciiParams;
private StringBuffer geoTiffAsciiParams;
private double noData;
private boolean isNodataSet = false;
public GeoTiffIIOMetadataEncoder() {
this(GeoTiffConstants.DEFAULT_GEOTIFF_VERSION,
GeoTiffConstants.DEFAULT_KEY_REVISION_MAJOR,
GeoTiffConstants.DEFAULT_KEY_REVISION_MINOR);
}
public GeoTiffIIOMetadataEncoder(final int geoTIFFVersion,
final int keyRevisionMajor, final int keyRevisionMinor) {
geoTiffEntries = new KeySortedList();
geoTiffDoubleParams = new double[GeoTiffConstants.ARRAY_ELEM_INCREMENT];
geoTiffAsciiParams = new StringBuffer();
modelTiePoints = new TiePoint[GeoTiffConstants.ARRAY_ELEM_INCREMENT];
modelPixelScale = new PixelScale();
modelTransformation = new double[16];
addGeoKeyEntry(geoTIFFVersion, keyRevisionMajor, 1, keyRevisionMinor);
}
public static boolean isTiffUShort(final int value) {
return (value >= GeoTiffConstants.USHORT_MIN)
&& (value <= GeoTiffConstants.USHORT_MAX);
}
public int getGeoTIFFVersion() {
return getGeoKeyEntryAt(0).getKeyID();
}
public void setGeoTIFFVersion(int version) {
getGeoKeyEntryAt(0).setKeyID(version);
}
public int getKeyRevisionMajor() {
return getGeoKeyEntryAt(0).getTiffTagLocation();
}
public int getKeyRevisionMinor() {
return getGeoKeyEntryAt(0).getCount();
}
public void setKeyRevision(int major, int minor) {
getGeoKeyEntryAt(0).setTiffTagLocation(major);
getGeoKeyEntryAt(0).setCount(minor);
}
public double getModelPixelScaleX() {
return modelPixelScale.getScaleX();
}
public double getModelPixelScaleY() {
return modelPixelScale.getScaleY();
}
public double getModelPixelScaleZ() {
return modelPixelScale.getScaleZ();
}
public void setModelPixelScale(double x, double y) {
setModelPixelScale(x, y, 0.0);
}
public void setModelPixelScale(double x, double y, double z) {
if (isModelTransformationSet())
throw new IllegalStateException(
"ModelTransformationTag already set. It is not possible to set the ModelPixelScale.");
modelPixelScale.setScaleX(x);
modelPixelScale.setScaleY(y);
modelPixelScale.setScaleZ(z);
}
public int getNumModelTiePoints() {
return numModelTiePoints;
}
public TiePoint getModelTiePoint() {
return getModelTiePointAt(0);
}
public TiePoint getModelTiePointAt(int index) {
return modelTiePoints[index];
}
public void setModelTiePoint(double i, double j, double x, double y) {
setModelTiePoint(i, j, 0.0, x, y, 0.0);
}
public void setModelTiePoint(double i, double j, double k, double x,
double y, double z) {
if (isModelTransformationSet())
throw new IllegalStateException(
"ModelTransformationTag already set. It is not possible to set the ModelTiePoint.");
if (getNumModelTiePoints() > 0) {
getModelTiePointAt(0).set(i, j, k, x, y, z);
} else {
addModelTiePoint(i, j, k, x, y, z);
}
}
public void addModelTiePoint(double i, double j, double x, double y) {
addModelTiePoint(i, j, 0.0, x, y, 0.0);
}
public void addModelTiePoint(double i, double j, double k, double x,
double y, double z) {
final int numTiePoints = numModelTiePoints;
if (numTiePoints >= (modelTiePoints.length - 1)) {
final TiePoint[] tiePoints = new TiePoint[numTiePoints
+ GeoTiffConstants.ARRAY_ELEM_INCREMENT];
System.arraycopy(modelTiePoints, 0, tiePoints, 0, numTiePoints);
modelTiePoints = tiePoints;
}
modelTiePoints[numTiePoints] = new TiePoint(i, j, k, x, y, z);
numModelTiePoints++;
}
public int getNumGeoKeyEntries() {
return numGeoTiffEntries;
}
public GeoKeyEntry getGeoKeyEntryAt(int index) {
// got to retrieve the eleme at a certain index
final Object it = this.geoTiffEntries.get(index);
if (it != null)
return (GeoKeyEntry) (it);
return null;
}
public GeoKeyEntry getGeoKeyEntry(int keyID) {
GeoKeyEntry retVal = null;
final Object o = geoTiffEntries.first(new Integer(keyID));
if (o != null)
retVal = (GeoKeyEntry) o;
return retVal;
}
public boolean hasGeoKeyEntry(int keyID) {
return getGeoKeyEntry(keyID) != null;
}
public int getGeoShortParam(int keyID) {
final GeoKeyEntry entry = getNonNullKeyEntry(keyID);
final int tag = entry.getTiffTagLocation();
final int value = entry.getValueOffset();
checkParamTag(tag, 0);
return value;
}
public double getGeoDoubleParam(int keyID) {
final GeoKeyEntry entry = getNonNullKeyEntry(keyID);
final int tag = entry.getTiffTagLocation();
final int offset = entry.getValueOffset();
checkParamTag(tag, getGeoDoubleParamsTag().getNumber());
return geoTiffDoubleParams[offset];
}
public double[] getGeoDoubleParams(int keyID) {
return getGeoDoubleParams(keyID, null);
}
public double[] getGeoDoubleParams(int keyID, double[] values) {
final GeoKeyEntry entry = getNonNullKeyEntry(keyID);
final int tag = entry.getTiffTagLocation();
final int count = entry.getCount();
final int offset = entry.getValueOffset();
checkParamTag(tag, getGeoDoubleParamsTag().getNumber());
if (values == null) {
values = new double[count];
}
System.arraycopy(geoTiffDoubleParams, offset, values, 0, count);
return values;
}
public String getGeoAsciiParam(int keyID) {
final GeoKeyEntry entry = getNonNullKeyEntry(keyID);
final int tag = entry.getTiffTagLocation();
final int count = entry.getCount();
final int offset = entry.getValueOffset();
checkParamTag(tag, getGeoAsciiParamsTag().getNumber());
return geoTiffAsciiParams.substring(offset, (offset + count) - 1);
}
public void addGeoShortParam(int keyID, int value) {
addGeoKeyEntry(keyID, 0, 1, value);
}
public void addGeoDoubleParam(int keyID, double value) {
addGeoDoubleParamsRef(keyID, 1);
addDoubleParam(value);
}
public void addGeoDoubleParams(int keyID, double[] values) {
addGeoDoubleParamsRef(keyID, values.length);
final int length = values.length;
for (int i = 0; i < length; i++) {
addDoubleParam(values[i]);
}
}
public void addGeoAscii(int keyID, String value) {
addGeoAsciiParamsRef(keyID, value.length() + 1);
// +1 for the '|' character to be appended
addAsciiParam(value);
}
private void addGeoKeyEntry(int keyID, int tag, int count, int offset) {
if (!isTiffUShort(keyID)) {
throw new IllegalArgumentException("keyID is not a TIFF USHORT");
}
if (!isTiffUShort(tag)) {
throw new IllegalArgumentException("tag is not a TIFF USHORT");
}
if (!isTiffUShort(count)) {
throw new IllegalArgumentException("count is not a TIFF USHORT");
}
if (!isTiffUShort(offset)) {
throw new IllegalArgumentException("offset is not a TIFF USHORT");
}
final int numKeyEntries = numGeoTiffEntries;
geoTiffEntries.add(new Integer(keyID), new GeoKeyEntry(keyID, tag,
count, offset));
getGeoKeyEntryAt(0).setCount(numKeyEntries);
numGeoTiffEntries++;
}
public void assignTo(Element element) {
if (!element.getName().equals(
GeoTiffConstants.GEOTIFF_IIO_ROOT_ELEMENT_NAME)) {
throw new IllegalArgumentException("root not found: "
+ GeoTiffConstants.GEOTIFF_IIO_ROOT_ELEMENT_NAME);
}
final Element ifd1 = element.getChild(GeoTiffConstants.GEOTIFF_IFD_TAG);
if (ifd1 == null) {
throw new IllegalArgumentException("Unable to find child "
+ GeoTiffConstants.GEOTIFF_IFD_TAG);
}
final Element ifd2 = createIFD();
ifd1.setAttribute(GeoTiffConstants.GEOTIFF_TAGSETS_ATT_NAME, ifd2
.getAttributeValue(GeoTiffConstants.GEOTIFF_TAGSETS_ATT_NAME));
final Element[] childElems = (Element[]) ifd2.getChildren().toArray(
new Element[0]);
final int length = childElems.length;
Element child;
for (int i = 0; i < length; i++) {
child = childElems[i];
ifd2.removeContent(child);
ifd1.addContent(child);
}
}
public Element createRootTree() {
final Element rootElement = new Element(
GeoTiffConstants.GEOTIFF_IIO_ROOT_ELEMENT_NAME);
rootElement.addContent(createIFD());
return rootElement;
}
protected static TIFFTag getGeoKeyDirectoryTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_GEO_KEY_DIRECTORY);
}
protected static TIFFTag getGeoDoubleParamsTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_GEO_DOUBLE_PARAMS);
}
protected static TIFFTag getGeoAsciiParamsTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_GEO_ASCII_PARAMS);
}
protected static TIFFTag getModelPixelScaleTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_MODEL_PIXEL_SCALE);
}
protected static TIFFTag getModelTiePointTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_MODEL_TIE_POINT);
}
protected static TIFFTag getModelTransformationTag() {
return GeoTIFFTagSet.getInstance().getTag(
GeoTIFFTagSet.TAG_MODEL_TRANSFORMATION);
}
protected static TIFFTag getNoDataTag() {
return GeoTiffConstants.NODATA_TAG;
}
private GeoKeyEntry getNonNullKeyEntry(int keyID) {
final GeoKeyEntry entry = getGeoKeyEntry(keyID);
if (entry == null) {
throw new IllegalArgumentException(
"Unable to find an entry for the provided geo key " + keyID);
}
return entry;
}
private void checkParamTag(final int tag, final int expectedTag) {
if (tag != expectedTag) {
if (expectedTag == 0) {
throw new IllegalArgumentException(
"invalid key access, not a GeoTIFF SHORT parameter");
} else if (expectedTag == getGeoDoubleParamsTag().getNumber()) {
throw new IllegalArgumentException(
"invalid key access, not a GeoTIFF DOUBLE parameter");
} else if (expectedTag == getGeoAsciiParamsTag().getNumber()) {
throw new IllegalArgumentException(
"invalid key access, not a GeoTIFF ASCII parameter");
} else {
throw new IllegalStateException();
}
}
}
private void addDoubleParam(double param) {
final int numDoubleParams = numGeoTiffDoubleParams;
if (numDoubleParams >= (geoTiffDoubleParams.length - 1)) {
final double[] doubleParams = new double[numDoubleParams
+ GeoTiffConstants.ARRAY_ELEM_INCREMENT];
System.arraycopy(geoTiffDoubleParams, 0, doubleParams, 0,
numDoubleParams);
geoTiffDoubleParams = doubleParams;
}
geoTiffDoubleParams[numDoubleParams] = param;
numGeoTiffDoubleParams++;
}
private void addAsciiParam(String param) {
geoTiffAsciiParams.append(param);
geoTiffAsciiParams.append('|');
numGeoTiffAsciiParams++;
}
private void addGeoDoubleParamsRef(int keyID, int count) {
addGeoKeyEntry(keyID, getGeoDoubleParamsTag().getNumber(), count,
getCurrentGeoDoublesOffset());
}
private void addGeoAsciiParamsRef(int keyID, int length) {
addGeoKeyEntry(keyID, getGeoAsciiParamsTag().getNumber(), length,
getCurrentGeoAsciisOffset());
}
private int getCurrentGeoDoublesOffset() {
return numGeoTiffDoubleParams;
}
private int getCurrentGeoAsciisOffset() {
return geoTiffAsciiParams.length();
}
private Element createIFD() {
Element ifd = new Element(GeoTiffConstants.GEOTIFF_IFD_TAG);
ifd.setAttribute(GeoTiffConstants.GEOTIFF_TAGSETS_ATT_NAME,
BaselineTIFFTagSet.class.getName() + ","
+ GeoTIFFTagSet.class.getName());
if (modelPixelScale.isSet()) {
ifd.addContent(createModelPixelScaleElement());
}
if (isModelTiePointsSet()) {
ifd.addContent(createModelTiePointsElement());
} else if (isModelTransformationSet()) {
ifd.addContent(createModelTransformationElement());
}
if (getNumGeoKeyEntries() > 1) {
ifd.addContent(createGeoKeyDirectoryElement());
}
if (numGeoTiffDoubleParams > 0) {
ifd.addContent(createGeoDoubleParamsElement());
}
if (numGeoTiffAsciiParams > 0) {
ifd.addContent(createGeoAsciiParamsElement());
}
if (isNodataSet) {
ifd.addContent(createNoDataElement());
}
return ifd;
}
private boolean isModelTiePointsSet() {
return numModelTiePoints > 0;
}
private boolean isModelTransformationSet() {
final int length = modelTransformation.length;
for (int i = 0; i < length; i++) {
if (modelTransformation[i] != 0.0) {
return true;
}
}
return false;
}
public void setModelTransformation(final AffineTransform rasterToModel) {
if (modelPixelScale!=null&&modelPixelScale.isSet())
throw new IllegalStateException(
"ModelPixelScaleTag already set. It is not possible to set the ModelTransformation.");
if (isModelTiePointsSet())
throw new IllegalStateException(
"ModelTiePointsTag already set. It is not possible to set the ModelTransformation.");
// //
//
// See pag 28 of the spec for an explanation
//
// //
// a
modelTransformation[0] = rasterToModel.getScaleX();
// b
modelTransformation[1] = rasterToModel.getShearX();
// c
modelTransformation[2] = 0;
// d
modelTransformation[3] = rasterToModel.getTranslateX();
// e
modelTransformation[4] = rasterToModel.getShearY();
// f
modelTransformation[5] = rasterToModel.getScaleY();
// g
modelTransformation[6] = 0;
// h
modelTransformation[7] = rasterToModel.getTranslateY();
// i
modelTransformation[8] = 0;
// j
modelTransformation[9] = 0;
// k
modelTransformation[10] = 0;
// l
modelTransformation[11] = 0;
// m
modelTransformation[12] = 0;
// n
modelTransformation[13] = 0;
// o
modelTransformation[14] = 0;
// p
modelTransformation[15] = 1;
}
private Element createGeoKeyDirectoryElement() {
Element field = createFieldElement(getGeoKeyDirectoryTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_SHORTS_TAG);
field.addContent(data);
int[] values;
int lenght;
Element GeoKeyRecord;
//GeoKey directory root tag
values = getGeoKeyEntryAt(0).getValues();
data.addContent(createShortElement(values[0]));
data.addContent(createShortElement(values[1]));
data.addContent(createShortElement(values[3]));
data.addContent(createShortElement(values[2]));
//GeoKeys
for (int i = 1; i < numGeoTiffEntries; i++) {
values = getGeoKeyEntryAt(i).getValues();
lenght = values.length;
for (int j = 0; j < lenght; j++) {
GeoKeyRecord = createShortElement(values[j]);
data.addContent(GeoKeyRecord);
}
}
return field;
}
private Element createGeoDoubleParamsElement() {
Element field = createFieldElement(getGeoDoubleParamsTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_DOUBLES_TAG);
field.addContent(data);
Element param;
for (int i = 0; i < numGeoTiffDoubleParams; i++) {
param = createDoubleElement(geoTiffDoubleParams[i]);
data.addContent(param);
}
return field;
}
private Element createGeoAsciiParamsElement() {
Element field = createFieldElement(getGeoAsciiParamsTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_ASCIIS_TAG);
field.addContent(data);
data.addContent(createAsciiElement(geoTiffAsciiParams.toString()));
return field;
}
private Element createModelPixelScaleElement() {
Element field = createFieldElement(getModelPixelScaleTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_DOUBLES_TAG);
field.addContent(data);
addDoubleElements(data, modelPixelScale.getValues());
return field;
}
private Element createModelTransformationElement() {
Element field = createFieldElement(getModelTransformationTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_DOUBLES_TAG);
field.addContent(data);
addDoubleElements(data, modelTransformation);
return field;
}
private Element createModelTiePointsElement() {
Element field = createFieldElement(getModelTiePointTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_DOUBLES_TAG);
field.addContent(data);
for (int i = 0; i < numModelTiePoints; i++) {
addDoubleElements(data, modelTiePoints[i].getData());
}
return field;
}
private Element createNoDataElement() {
Element field = createFieldElement(getNoDataTag());
Element data = new Element(GeoTiffConstants.GEOTIFF_ASCIIS_TAG);
field.addContent(data);
data.addContent(createAsciiElement(Double.toString(noData)));
return field;
}
private Element createFieldElement(final TIFFTag tag) {
Element field = new Element(GeoTiffConstants.GEOTIFF_FIELD_TAG);
field.setAttribute(GeoTiffConstants.NUMBER_ATTRIBUTE, String.valueOf(tag
.getNumber()));
field.setAttribute(GeoTiffConstants.NAME_ATTRIBUTE, tag.getName());
return field;
}
private Element createShortElement(final int value) {
Element GeoKeyRecord = new Element(GeoTiffConstants.GEOTIFF_SHORT_TAG);
GeoKeyRecord.setAttribute(GeoTiffConstants.VALUE_ATTRIBUTE, String
.valueOf(value));
return GeoKeyRecord;
}
private Element createDoubleElement(final double value) {
Element param = new Element(GeoTiffConstants.GEOTIFF_DOUBLE_TAG);
param.setAttribute(GeoTiffConstants.VALUE_ATTRIBUTE, String.valueOf(value));
return param;
}
private Element createAsciiElement(final String value) {
Element param = new Element(GeoTiffConstants.GEOTIFF_ASCII_TAG);
param.setAttribute(GeoTiffConstants.VALUE_ATTRIBUTE, String.valueOf(value));
return param;
}
private void addDoubleElements(Element data, final double[] values) {
final int length = values.length;
Element GeoKeyRecord;
for (int j = 0; j < length; j++) {
GeoKeyRecord = createDoubleElement(values[j]);
data.addContent(GeoKeyRecord);
}
}
public double getNoData() {
return noData;
}
public void setNoData(double noData) {
this.noData = noData;
isNodataSet = true;
}
}