/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2016, Geomatys * * 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.geotoolkit.metadata.geotiff; import com.sun.media.imageio.plugins.tiff.BaselineTIFFTagSet; import com.sun.media.imageio.plugins.tiff.GeoTIFFTagSet; import java.util.Collection; import java.awt.geom.AffineTransform; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.NullArgumentException; import org.w3c.dom.Element; import org.w3c.dom.Node; import static org.geotoolkit.metadata.geotiff.GeoTiffMetaDataUtils.*; import static org.geotoolkit.metadata.geotiff.GeoTiffConstants.*; import static org.geotoolkit.util.DomUtilities.*; /** * * @author Johann Sorel (Geomatys) * @module * * note : with java 9 class will become only accessible by its module (no public signature on class header). */ public final class GeoTiffMetaDataStack { /** * Date formatter to format in accordance with tiff specification.<br><br> * * More informations at : http://www.awaresystems.be/imaging/tiff/tifftags/datetime.html. */ private static final SimpleDateFormat S_DATE_FORMAT = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); static { //Force removing timezone S_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); } private final Element ifd; //what needs to be written when flush is called private final List<KeyDirectoryEntry> entries = new ArrayList<KeyDirectoryEntry>(); private final StringBuilder asciiValues = new StringBuilder(); private final List<Double> doubleValues = new ArrayList<Double>(); private final List<TiePoint> tiePoints = new ArrayList<TiePoint>(); private Node nPixelScale = null; private Node nTransform = null; private List<Node> noDatas = new ArrayList<Node>(); private Node minSampleValue = null; private Node maxSampleValue = null; private Node date = null; public GeoTiffMetaDataStack(Node tiffTree) { ArgumentChecks.ensureNonNull("tiffTree", tiffTree); Element tmpIfd = (Element) getNodeByLocalName(tiffTree, TAG_GEOTIFF_IFD); if (tmpIfd == null) { ifd = (Element) tiffTree.appendChild(createNode(TAG_GEOTIFF_IFD)); } else { ifd = tmpIfd; } //remove previous tags if exists final Node nAscii = getNodeByNumber(ifd, getGeoAsciiParamsTag().getNumber()); if(nAscii != null){ ifd.removeChild(nAscii); } final Node nDoubles = getNodeByNumber(ifd, getGeoDoubleParamsTag().getNumber()); if(nDoubles != null){ ifd.removeChild(nDoubles); } } void addShort(final int keyId, final int value){ final KeyDirectoryEntry entry = new KeyDirectoryEntry(keyId, 0, 1, value); entries.add(entry); } void addDouble(final int keyID, final double value) { final KeyDirectoryEntry entry = new KeyDirectoryEntry( keyID, getGeoDoubleParamsTag().getNumber(), 1, doubleValues.size()); entries.add(entry); doubleValues.add(value); } void addAscii(final int keyID, final String value) { //-- if already exist data separate by a "|" if (asciiValues.length() > 0) asciiValues.append('|'); final KeyDirectoryEntry entry = new KeyDirectoryEntry( keyID, getGeoAsciiParamsTag().getNumber(), value.length(), asciiValues.length()); entries.add(entry); asciiValues.append(value); } void setModelPixelScale(final double x, final double y, final double z) { nPixelScale = createTiffField(getModelPixelScaleTag()); nPixelScale.appendChild(createTiffDoubles(x,y,z)); } /** * Set Nodata values into this {@link GeoTiffMetaDataStack} in aim of build or write metadata. * * @param noDataValue expected setted nodata. * @throws NullArgumentException if noDataValue is {@code null}. * @throws IllegalArgumentException if noDataValue is empty. */ void setNoData(final String noDataValue) { ArgumentChecks.ensureNonNull("noDataValue", noDataValue); if (noDataValue.isEmpty()) throw new IllegalArgumentException("GeotiffMetadataStack : you try to " + "set an empty Nodata Value (String) into metadata tree node."); Node noData = createTiffField(GeoTiffConstants.GDAL_NODATA_KEY, "noData"); noData.appendChild(createTiffAsciis(noDataValue)); noDatas.add(noData); } /** * Set minimum sample values into this {@link GeoTiffMetaDataStack} in aim of build or write metadata. * * @param minimumSampleValues minimum sample value for each image bands. * @throws NullArgumentException if maximumSampleValues array is {@code null}. */ void setMinSampleValue(final int ...minimumSampleValues) { minSampleValue = createTiffField(GeoTiffConstants.MinSampleValue, "minSampleValue"); minSampleValue.appendChild(createTiffShorts(minimumSampleValues)); } /** * Set maximum sample values into this {@link GeoTiffMetaDataStack} in aim of build or write metadata. * * @param maximumSampleValue maximum sample value for each image bands. * @throws NullArgumentException if maximumSampleValues array is {@code null}. */ void setMaxSampleValue(final int ...maximumSampleValues) { maxSampleValue = createTiffField(GeoTiffConstants.MaxSampleValue, "maxSampleValue"); maxSampleValue.appendChild(createTiffShorts(maximumSampleValues)); } /** * Set date into this {@link GeoTiffMetaDataStack} in aim of build or write metadata. * * @param date which be set into metadata node. * @throws NullArgumentException if date is {@code null}. */ synchronized void setDate(final Date date) { ArgumentChecks.ensureNonNull("date", date); this.date = createTiffField(GeoTiffConstants.DateTime, "date"); final String sdat = S_DATE_FORMAT.format(date); this.date.appendChild(createTiffAsciis(sdat)); } void addModelTiePoint(final TiePoint tp) { tiePoints.add(tp); } public void setModelTransformation(final AffineTransform gridToCRS) { // See pag 28 of the spec for an explanation final double[] modelTransformation = new double[16]; modelTransformation[0] = gridToCRS.getScaleX(); modelTransformation[1] = gridToCRS.getShearX(); modelTransformation[2] = 0; modelTransformation[3] = gridToCRS.getTranslateX(); modelTransformation[4] = gridToCRS.getShearY(); modelTransformation[5] = gridToCRS.getScaleY(); modelTransformation[6] = 0; modelTransformation[7] = gridToCRS.getTranslateY(); modelTransformation[8] = 0; modelTransformation[9] = 0; modelTransformation[10] = 0; modelTransformation[11] = 0; modelTransformation[12] = 0; modelTransformation[13] = 0; modelTransformation[14] = 0; modelTransformation[15] = 1; nTransform = createTiffField(getModelTransformationTag()); final Node nValues = createTiffDoubles(modelTransformation); nTransform.appendChild(nValues); } static Node createModelTransformationElement(final double ... values) { final Node nTransformation = createTiffField(getModelTransformationTag()); final Node nValues = createTiffDoubles(values); nTransformation.appendChild(nValues); return nTransformation; } static Node createModelTiePointsElement(final Collection<? extends TiePoint> tiePoints) { final Node nTiePoints = createTiffField(getModelTiePointTag()); final Node nValues = createNode(TAG_GEOTIFF_DOUBLES); nTiePoints.appendChild(nValues); for(final TiePoint tp : tiePoints) { nValues.appendChild(createTiffDouble(tp.rasterI)); nValues.appendChild(createTiffDouble(tp.rasterJ)); nValues.appendChild(createTiffDouble(tp.rasterK)); nValues.appendChild(createTiffDouble(tp.coverageX)); nValues.appendChild(createTiffDouble(tp.coverageY)); nValues.appendChild(createTiffDouble(tp.coverageZ)); } return nTiePoints; } /** * Write all stored informations in the tiff metadata tree. */ public void flush(){ if (!entries.isEmpty()) { //write GeoKeyDirectory //first line (4 int) contain the version and number of keys //Header={KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys} final int[] values = new int[4 + 4*entries.size()]; values[0] = GEOTIFF_VERSION; values[1] = REVISION_MAJOR; values[2] = REVISION_MINOR; values[3] = entries.size(); for (int i = 0, l = 4, n = entries.size(); i < n; i++, l += 4) { final KeyDirectoryEntry entry = entries.get(i); values[l] = entry.valueKey; values[l+1] = entry.valuelocation; values[l+2] = entry.valueNb; values[l+3] = entry.valueOffset; } final Node nGeoKeyDir = createTiffField(getGeoKeyDirectoryTag()); nGeoKeyDir.appendChild(createTiffShorts(values)); ifd.appendChild(nGeoKeyDir); } //write tagsets ifd.setAttribute(ATT_TAGSETS, BaselineTIFFTagSet.class.getName() + "," + GeoTIFFTagSet.class.getName()); if (nPixelScale != null) ifd.appendChild(nPixelScale); if (!tiePoints.isEmpty()) { ifd.appendChild(createModelTiePointsElement(tiePoints)); } else if (nTransform != null) { ifd.appendChild(nTransform); } if (minSampleValue != null) ifd.appendChild(minSampleValue); if (maxSampleValue != null) ifd.appendChild(maxSampleValue); if (!noDatas.isEmpty()) for (Node nd : noDatas) ifd.appendChild(nd); if (date != null) ifd.appendChild(date); if (!doubleValues.isEmpty()) { final Node nDoubles = createTiffField(getGeoDoubleParamsTag()); final Node nValues = createTiffDoubles(doubleValues); nDoubles.appendChild(nValues); ifd.appendChild(nDoubles); } if (asciiValues.length() > 0) { final Node nAsciis = createTiffField(getGeoAsciiParamsTag()); final Node nValues = createTiffAsciis(asciiValues.toString()); nAsciis.appendChild(nValues); ifd.appendChild(nAsciis); } } }