/* * Numerous modifications by John Lindsay (2013) including support for additional * types of geotiff files. */ /* * Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata * * Portions of this software were developed by the Unidata Program at the * University Corporation for Atmospheric Research. * * Access and use of this software shall impose the following obligations * and understandings on the user. The user is granted the right, without * any fee or cost, to use, copy, modify, alter, enhance and distribute * this software, and any derivative works thereof, and its supporting * documentation for any purpose whatsoever, provided that this entire * notice appears in all copies of the software, derivative works and * supporting documentation. Further, UCAR requests that the user credit * UCAR/Unidata in any publications that result from the use of this * software or in any product that includes this software. The names UCAR * and/or Unidata, however, may not be used in any advertising or publicity * to endorse or promote any products or commercial entity unless specific * written permission is obtained from UCAR/Unidata. The user also * understands that UCAR/Unidata is not obligated to provide the user with * any support, consulting, training or assistance of any kind with regard * to the use, operation and performance of this software nor to provide * the user with any updates, revisions, new versions or "bug fixes." * * THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL, * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE. */ package whitebox.geospatialfiles; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.io.RandomAccessFile; import java.nio.*; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Low level read/write geotiff files. * * @author John Caron * @author Yuan Ho * @author John Lindsay * @see GeotiffWriter */ public class GeoTiff { private String filename; private RandomAccessFile file; private FileChannel channel; private List<IFDEntry> tags = new ArrayList<>(); private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN; private boolean readonly; private boolean showBytes = false, debugRead = false, debugReadGeoKey = false; private boolean showHeaderBytes = false; /** * Constructor. Does not open or create the file. * * @param filename pathname of file */ public GeoTiff(String filename) { this.filename = filename; } /** * Close the Geotiff file. * * @throws java.io.IOException on io error */ public void close() throws IOException { if (channel != null) { if (!readonly) { channel.force(true); channel.truncate(nextOverflowData); } channel.close(); } if (file != null) { file.close(); } } ///////////////////////////////////////////////////////////////////////////// // writing private int headerSize = 8; private int firstIFD = 0; private int lastIFD = 0; private int startOverflowData = 0; private int nextOverflowData = 0; void addTag(IFDEntry ifd) { tags.add(ifd); } void deleteTag(IFDEntry ifd) { tags.remove(ifd); } void setTransform(double xStart, double yStart, double xInc, double yInc) { // tie the raster 0, 0 to xStart, yStart addTag(new IFDEntry(Tag.ModelTiepointTag, FieldType.DOUBLE).setValue( new double[]{0.0, 0.0, 0.0, xStart, yStart, 0.0})); // define the "affine transformation" : requires grid to be regular (!) addTag(new IFDEntry(Tag.ModelPixelScaleTag, FieldType.DOUBLE).setValue( new double[]{xInc, yInc, 0.0})); } private List<GeoKey> geokeys = new ArrayList<>(); void addGeoKey(GeoKey geokey) { geokeys.add(geokey); } private void writeGeoKeys() { if (geokeys.isEmpty()) { return; } // count extras int extra_chars = 0; int extra_ints = 0; int extra_doubles = 0; for (GeoKey geokey : geokeys) { if (geokey.isDouble) { extra_doubles += geokey.count(); } else if (geokey.isString) { extra_chars += geokey.valueString().length() + 1; } else if (geokey.count() > 1) { extra_ints += geokey.count(); } } int n = (geokeys.size() + 1) * 4; int[] values = new int[n + extra_ints]; double[] dvalues = new double[extra_doubles]; char[] cvalues = new char[extra_chars]; int icounter = n; int dcounter = 0; int ccounter = 0; values[0] = 1; values[1] = 1; values[2] = 0; values[3] = geokeys.size(); int count = 4; for (GeoKey geokey : geokeys) { values[count++] = geokey.tagCode(); if (geokey.isDouble) { values[count++] = Tag.GeoDoubleParamsTag.getCode(); // extra double values here values[count++] = geokey.count(); values[count++] = dcounter; for (int k = 0; k < geokey.count(); k++) { dvalues[dcounter++] = geokey.valueD(k); } } else if (geokey.isString) { String s = geokey.valueString(); values[count++] = Tag.GeoAsciiParamsTag.getCode(); // extra double values here values[count++] = s.length(); // dont include trailing 0 in the count values[count++] = ccounter; for (int k = 0; k < s.length(); k++) { cvalues[ccounter++] = s.charAt(k); } cvalues[ccounter++] = (char) 0; } else if (geokey.count() > 1) { // more than one int value values[count++] = Tag.GeoKeyDirectoryTag.getCode(); // extra int values here values[count++] = geokey.count(); values[count++] = icounter; for (int k = 0; k < geokey.count(); k++) { values[icounter++] = geokey.value(k); } } else { // normal case of one int value values[count++] = 0; values[count++] = 1; values[count++] = geokey.value(); } } // loop over geokeys addTag(new IFDEntry(Tag.GeoKeyDirectoryTag, FieldType.SHORT).setValue(values)); if (extra_doubles > 0) { addTag(new IFDEntry(Tag.GeoDoubleParamsTag, FieldType.DOUBLE).setValue(dvalues)); } if (extra_chars > 0) { addTag(new IFDEntry(Tag.GeoAsciiParamsTag, FieldType.ASCII).setValue(new String(cvalues))); } } int writeData(byte[] data, int imageNumber) throws IOException { if (file == null) { init(); } if (imageNumber == 1) { channel.position(headerSize); } else { channel.position(nextOverflowData); } ByteBuffer buffer = ByteBuffer.wrap(data); channel.write(buffer); if (imageNumber == 1) { firstIFD = headerSize + data.length; } else { firstIFD = data.length + nextOverflowData; } return nextOverflowData; } int writeData(float[] data, int imageNumber) throws IOException { if (file == null) { init(); } if (imageNumber == 1) { channel.position(headerSize); } else { channel.position(nextOverflowData); } // no way around making a copy ByteBuffer direct = ByteBuffer.allocateDirect(4 * data.length); FloatBuffer buffer = direct.asFloatBuffer(); buffer.put(data); //buffer.flip(); channel.write(direct); if (imageNumber == 1) { firstIFD = headerSize + 4 * data.length; } else { firstIFD = 4 * data.length + nextOverflowData; } return nextOverflowData; } void writeMetadata(int imageNumber) throws IOException { if (file == null) { init(); } // geokeys all get added at once writeGeoKeys(); // tags gotta be in order Collections.sort(tags); int start = 0; if (imageNumber == 1) { start = writeHeader(channel); } else { //now this is not the first image we need to fill the Offset of nextIFD channel.position(lastIFD); ByteBuffer buffer = ByteBuffer.allocate(4); if (debugRead) { System.out.println("position before writing nextIFD= " + channel.position() + " IFD is " + firstIFD); } buffer.putInt(firstIFD); buffer.flip(); channel.write(buffer); } writeIFD(channel, firstIFD); } private int writeHeader(FileChannel channel) throws IOException { channel.position(0); ByteBuffer buffer = ByteBuffer.allocate(8); buffer.put((byte) 'M'); buffer.put((byte) 'M'); buffer.putShort((short) 42); buffer.putInt(firstIFD); buffer.flip(); channel.write(buffer); return firstIFD; } public void initTags() throws IOException { tags = new ArrayList<>(); geokeys = new ArrayList<>(); } private void init() throws IOException { file = new RandomAccessFile(filename, "rw"); channel = file.getChannel(); if (debugRead) { System.out.println("Opened file to write: '" + filename + "', size=" + channel.size()); } readonly = false; } private void writeIFD(FileChannel channel, int start) throws IOException { channel.position(start); ByteBuffer buffer = ByteBuffer.allocate(2); int n = tags.size(); buffer.putShort((short) n); buffer.flip(); channel.write(buffer); start += 2; startOverflowData = start + 12 * tags.size() + 4; nextOverflowData = startOverflowData; for (IFDEntry elem : tags) { writeIFDEntry(channel, elem, start); start += 12; } // firstIFD = startOverflowData; // position to where the "next IFD" goes channel.position(startOverflowData - 4); lastIFD = startOverflowData - 4; if (debugRead) { System.out.println("pos before writing nextIFD= " + channel.position()); } buffer = ByteBuffer.allocate(4); buffer.putInt(0); buffer.flip(); channel.write(buffer); } private void writeIFDEntry(FileChannel channel, IFDEntry ifd, int start) throws IOException { channel.position(start); ByteBuffer buffer = ByteBuffer.allocate(12); buffer.putShort((short) ifd.tag.getCode()); buffer.putShort((short) ifd.type.code); buffer.putInt(ifd.count); int size = ifd.count * ifd.type.size; if (size <= 4) { int done = writeValues(buffer, ifd); for (int k = 0; k < 4 - done; k++) // fill out to 4 bytes { buffer.put((byte) 0); } buffer.flip(); channel.write(buffer); } else { // write offset buffer.putInt(nextOverflowData); buffer.flip(); channel.write(buffer); // write data channel.position(nextOverflowData); //System.out.println(" write offset = "+ifd.tag.getName()); ByteBuffer vbuffer = ByteBuffer.allocate(size); writeValues(vbuffer, ifd); vbuffer.flip(); channel.write(vbuffer); nextOverflowData += size; } } private int writeValues(ByteBuffer buffer, IFDEntry ifd) { int done = 0; if (ifd.type == FieldType.ASCII) { return writeSValue(buffer, ifd); } else if (ifd.type == FieldType.RATIONAL) { for (int i = 0; i < ifd.count * 2; i++) { done += writeIntValue(buffer, ifd, ifd.value[i]); } } else if (ifd.type == FieldType.FLOAT) { for (int i = 0; i < ifd.count; i++) { buffer.putFloat((float) ifd.valueD[i]); } done += ifd.count * 4; } else if (ifd.type == FieldType.DOUBLE) { for (int i = 0; i < ifd.count; i++) { buffer.putDouble(ifd.valueD[i]); } done += ifd.count * 8; } else { for (int i = 0; i < ifd.count; i++) { done += writeIntValue(buffer, ifd, ifd.value[i]); } } return done; } private int writeIntValue(ByteBuffer buffer, IFDEntry ifd, int v) { switch (ifd.type.code) { case 1: buffer.put((byte) v); return 1; case 3: buffer.putShort((short) v); return 2; case 4: buffer.putInt(v); return 4; case 5: buffer.putInt(v); return 4; } return 0; } private int writeSValue(ByteBuffer buffer, IFDEntry ifd) { buffer.put(ifd.valueS.getBytes()); int size = ifd.valueS.length(); if ((size % 2) == 1) { size++; } return size; } ///////////////////////////////////////////////////////////////////////////// // reading /** * Read the geotiff file, using the filename passed in the constructor. * * @throws IOException on read error */ public void read() throws IOException { file = new RandomAccessFile(filename, "r"); channel = file.getChannel(); if (debugRead) { System.out.println("Opened file to read:'" + filename + "', size=" + channel.size()); } readonly = true; int nextOffset = readHeader(channel); while (nextOffset > 0) { nextOffset = readIFD(channel, nextOffset); parseGeoInfo(); } nCols = getNumberColumns(); nRows = getNumberRows(); nodata = getNoData(); if (findTag(Tag.TileOffsets) != null) { tiledFormat = true; tileWidth = findTag(Tag.TileWidth).value[0]; tileLength = findTag(Tag.TileLength).value[0]; nTilesX = (nCols + tileWidth - 1) / tileWidth; nTilesY = (nRows + tileLength - 1) / tileLength; } else { rowsPerStrip = findTag(Tag.RowsPerStrip).value[0]; } } private IFDEntry findTag(Tag tag) { if (tag == null) { return null; } for (IFDEntry ifd : tags) { if (ifd.tag == tag) { return ifd; } } return null; } public int getNumberRows() { return findTag(Tag.ImageLength).value[0]; } public int getNumberColumns() { return findTag(Tag.ImageWidth).value[0]; } public int getNumberBitsPerSample() { int[] bitsPerSample = findTag(Tag.BitsPerSample).value; int totalBitsPerSample = 0; for (int a = 0; a < bitsPerSample.length; a++) { totalBitsPerSample += bitsPerSample[a]; } return totalBitsPerSample; //findTag(Tag.BitsPerSample).value[0]; } public int getSampleFormat() { return findTag(Tag.SampleFormat).value[0]; } public String getFileName() { return filename; } // public String getProjectionInfo() { // return findTag(Tag.GeoKeyDirectoryTag).value[0]; // } public double getNorth() { IFDEntry ModelTiepointTag = findTag(Tag.ModelTiepointTag); IFDEntry ModelPixelScaleTag = findTag(Tag.ModelPixelScaleTag); if (ModelTiepointTag != null && ModelPixelScaleTag != null) { double[] modelTiepoint = ModelTiepointTag.valueD.clone(); double[] modelPixelScale = ModelPixelScaleTag.valueD.clone(); return modelTiepoint[4] + modelTiepoint[1] * modelPixelScale[1]; } else { return (double) getNumberRows(); } } public double getSouth() { IFDEntry ModelTiepointTag = findTag(Tag.ModelTiepointTag); IFDEntry ModelPixelScaleTag = findTag(Tag.ModelPixelScaleTag); if (ModelTiepointTag != null && ModelPixelScaleTag != null) { double[] modelTiepoint = ModelTiepointTag.valueD.clone(); double[] modelPixelScale = ModelPixelScaleTag.valueD.clone(); return modelTiepoint[4] - (getNumberRows() - modelTiepoint[1]) * modelPixelScale[1]; } else { return 0; } } public double getWest() { IFDEntry ModelTiepointTag = findTag(Tag.ModelTiepointTag); IFDEntry ModelPixelScaleTag = findTag(Tag.ModelPixelScaleTag); if (ModelTiepointTag != null && ModelPixelScaleTag != null) { double[] modelTiepoint = ModelTiepointTag.valueD.clone(); double[] modelPixelScale = ModelPixelScaleTag.valueD.clone(); return modelTiepoint[3] - modelTiepoint[0] * modelPixelScale[0]; } else { return 0; } } public double getEast() { IFDEntry ModelTiepointTag = findTag(Tag.ModelTiepointTag); IFDEntry ModelPixelScaleTag = findTag(Tag.ModelPixelScaleTag); if (ModelTiepointTag != null && ModelPixelScaleTag != null) { double[] modelTiepoint = ModelTiepointTag.valueD.clone(); double[] modelPixelScale = ModelPixelScaleTag.valueD.clone(); return modelTiepoint[3] + (getNumberColumns() - modelTiepoint[0]) * modelPixelScale[0]; } else { return getNumberColumns(); } } public double getNoData() { if (findTag(Tag.GDALNoData) != null) { if (findTag(Tag.GDALNoData).valueS != null) { String retStr = findTag(Tag.GDALNoData).valueS; if (retStr.trim().toLowerCase().equals("nan")) { return Double.NaN; } else if (retStr.trim().toLowerCase().contains("inf")) { if (retStr.trim().toLowerCase().contains("n")) { return Double.NEGATIVE_INFINITY; } else { return Double.POSITIVE_INFINITY; } } else { return Double.parseDouble(retStr); } } else { return -32768; } } else { return -32768; } } public boolean hasNoDataTag() { return (findTag(Tag.GDALNoData) != null); } public int getPhotometricInterpretation() { if (findTag(Tag.PhotometricInterpretation) != null) { return findTag(Tag.PhotometricInterpretation).value[0]; } else { return -9999; } } public int getCompressionType() { return findTag(Tag.Compression).value[0]; } double[] tileOrStripData; boolean tiledFormat = false; int currentTile = -1; int currentStrip = -1; int nRows = -1; int nCols = -1; int rowsPerStrip = -1; int tileWidth = -1; int tileLength = -1; int nTilesX = -1; int nTilesY = -1; double nodata = -32768; public double getValue(int row, int col) throws Exception { if (!tiledFormat) { // strip oriented // figure out which strip the pixel is in int stripNum = row / rowsPerStrip; if (currentStrip != stripNum) { tileOrStripData = getStripData(stripNum); if (tileOrStripData == null) { throw new Exception("Error reading data. It is likely that the TIFF file is of an unsupported type, possibly due to data compression."); } currentStrip = stripNum; } int stripRow = row % rowsPerStrip; int stripPixelNum = stripRow * nCols + col; return tileOrStripData[stripPixelNum]; } else { // tile oriented // figure out which tile the pixel is in int tileNum = row / tileLength * nTilesX + col / tileWidth; if (currentTile != tileNum) { tileOrStripData = getTileData(tileNum); if (tileOrStripData == null) { throw new Exception("Error reading data. It is likely that the TIFF file is of an unsupported type, possibly due to data compression."); } currentTile = tileNum; } int tileRow = row % tileLength; int tileCol = col % tileWidth; int tilePixelNum = tileRow * tileLength + tileCol; return tileOrStripData[tilePixelNum]; } } private double[] getTileData(int tile) { try { if (!tiledFormat) { return null; } double[] data; int compressionType = findTag(Tag.Compression).value[0]; int sampleFormat = 1; //findTag(Tag.SampleFormat).value[0]; if (findTag(Tag.SampleFormat) != null) { sampleFormat = findTag(Tag.SampleFormat).value[0]; } if (getPhotometricInterpretation() != 2) { int tileOffset = findTag(Tag.TileOffsets).value[tile]; int tileByteCount = findTag(Tag.TileByteCounts).value[tile]; int[] bitsPerSample = findTag(Tag.BitsPerSample).value; int numPixelsInTile = tileByteCount / (bitsPerSample[0] / 8); data = new double[numPixelsInTile]; channel.position(tileOffset); int size = tileByteCount / 8; ByteBuffer buffer = ByteBuffer.allocate(tileByteCount); buffer.order(byteOrder); if (compressionType == 1) { channel.read(buffer); buffer.flip(); if (sampleFormat == 1 && bitsPerSample[0] == 8) { // unsigned byte byte b; for (int i = 0; i < size; i++) { b = buffer.get(); data[i] = (short) (0x000000FF & ((int) b)); } } else if (sampleFormat == 2 && bitsPerSample[0] == 8) { // signed byte byte b; for (int i = 0; i < size; i++) { b = buffer.get(); data[i] = b; } } else if (sampleFormat == 1 && bitsPerSample[0] == 16) { // unsigned 16-bit short int b1; int b2; for (int i = 0; i < numPixelsInTile; i++) { b1 = (0x000000FF & ((int) buffer.get())); b2 = (0x000000FF & ((int) buffer.get())); data[i] = (b2 << 8 | b1); } } else if (sampleFormat == 2 && bitsPerSample[0] == 16) { // signed 16-bit short ShortBuffer sb = buffer.asShortBuffer(); short[] sa = new short[numPixelsInTile]; sb.get(sa); sb = null; for (int j = 0; j < numPixelsInTile; j++) { data[j] = sa[j]; } sa = null; } else if (sampleFormat == 1 && bitsPerSample[0] == 32) { // unsigned 32-bit int int b1, b2, b3, b4; for (int i = 0; i < numPixelsInTile; i++) { b1 = (0x000000FF & ((int) buffer.get())); b2 = (0x000000FF & ((int) buffer.get())); b3 = (0x000000FF & ((int) buffer.get())); b4 = (0x000000FF & ((int) buffer.get())); data[i] = ((long) (b1 << 24 | b2 << 16 | b3 << 8 | b4)) & 0xFFFFFFFFL; } } else if (sampleFormat == 2 && bitsPerSample[0] == 32) { // signed 32-bit int IntBuffer ib = buffer.asIntBuffer(); int[] ia = new int[numPixelsInTile]; ib.get(ia); ib = null; for (int j = 0; j < numPixelsInTile; j++) { data[j] = ia[j]; } ia = null; } else if (sampleFormat == 1 && bitsPerSample[0] == 64) { // unsigned 64-bit long // I don't know what data type you could cast an unsigned long into. Perhaps a BigInteger. return null; } else if (sampleFormat == 2 && bitsPerSample[0] == 64) { // signed 64-bit long LongBuffer lb = buffer.asLongBuffer(); long[] la = new long[numPixelsInTile]; lb.get(la); lb = null; for (int j = 0; j < numPixelsInTile; j++) { data[j] = la[j]; } la = null; } else if (sampleFormat == 3 && bitsPerSample[0] == 32) { // 32-bit single-precision float FloatBuffer fb = buffer.asFloatBuffer(); float[] fa = new float[numPixelsInTile]; fb.get(fa); fb = null; for (int j = 0; j < numPixelsInTile; j++) { data[j] = fa[j]; } fa = null; } else if (sampleFormat == 3 && bitsPerSample[0] == 64) { // 64-bit double-precision float DoubleBuffer db = buffer.asDoubleBuffer(); double[] da = new double[numPixelsInTile]; db.get(da); db = null; data = da.clone(); da = null; } else { return null; } } else if (compressionType == 32773) { return null; } else { return null; } } else { int tileOffset = findTag(Tag.TileOffsets).value[tile]; int tileByteCount = findTag(Tag.TileByteCounts).value[tile]; int[] bitsPerSample = findTag(Tag.BitsPerSample).value; int totalBitsPerSample = 0; for (int a = 0; a < bitsPerSample.length; a++) { totalBitsPerSample += bitsPerSample[a]; } int numPixelsInTile = tileByteCount / (totalBitsPerSample / 8); data = new double[numPixelsInTile]; channel.position(tileOffset); ByteBuffer buffer = ByteBuffer.allocate(tileByteCount); buffer.order(byteOrder); try { if (compressionType == 1) { channel.read(buffer); buffer.flip(); if (totalBitsPerSample == 24 && bitsPerSample.length == 3) { int r, g, b; for (int i = 0; i < nCols; i++) { r = (0x000000FF & ((int) buffer.get())); g = (0x000000FF & ((int) buffer.get())); b = (0x000000FF & ((int) buffer.get())); data[i] = (double) ((255 << 24) | (b << 16) | (g << 8) | r); } } else if (totalBitsPerSample == 32 && bitsPerSample.length == 4) { int r, g, b, a; for (int i = 0; i < nCols; i++) { r = (0x000000FF & ((int) buffer.get())); g = (0x000000FF & ((int) buffer.get())); b = (0x000000FF & ((int) buffer.get())); a = (0x000000FF & ((int) buffer.get())); data[i] = (double) ((a << 24) | (b << 16) | (g << 8) | r); } } else { return null; } } else if (compressionType == 32773) { return null; } else { return null; } } catch (IOException e) { // do nothing } } return data; } catch (Exception e) { return null; } } private double[] getStripData(int stripNum) { double[] data; int compressionType = findTag(Tag.Compression).value[0]; int[] bitsPerSample = findTag(Tag.BitsPerSample).value; int sampleFormat = 1; //findTag(Tag.SampleFormat).value[0]; if (findTag(Tag.SampleFormat) != null) { sampleFormat = findTag(Tag.SampleFormat).value[0]; } if (tiledFormat) { return null; } int stripOffset = findTag(Tag.StripOffsets).value[stripNum]; int stripByteSize = findTag(Tag.StripByteCounts).value[stripNum]; if (getPhotometricInterpretation() != 2) { int numPixelsInStrip = stripByteSize / (bitsPerSample[0] / 8); data = new double[numPixelsInStrip]; try { channel.position(stripOffset); ByteBuffer buffer = ByteBuffer.allocate(stripByteSize); buffer.order(byteOrder); if (compressionType == 1) { channel.read(buffer); buffer.flip(); if (sampleFormat == 1 && bitsPerSample[0] == 8) { // unsigned byte byte b; for (int i = 0; i < numPixelsInStrip; i++) { b = buffer.get(); data[i] = (short) (0x000000FF & ((int) b)); } } else if (sampleFormat == 2 && bitsPerSample[0] == 8) { // signed byte byte b; for (int i = 0; i < numPixelsInStrip; i++) { b = buffer.get(); data[i] = b; } } else if (sampleFormat == 1 && bitsPerSample[0] == 16) { // unsigned 16-bit short int b1; int b2; for (int i = 0; i < numPixelsInStrip; i++) { b1 = (0x000000FF & ((int) buffer.get())); b2 = (0x000000FF & ((int) buffer.get())); data[i] = (b2 << 8 | b1); } } else if (sampleFormat == 2 && bitsPerSample[0] == 16) { // signed 16-bit short ShortBuffer sb = buffer.asShortBuffer(); short[] sa = new short[numPixelsInStrip]; sb.get(sa); sb = null; for (int j = 0; j < numPixelsInStrip; j++) { data[j] = sa[j]; } sa = null; } else if (sampleFormat == 1 && bitsPerSample[0] == 32) { // unsigned 32-bit int int b1, b2, b3, b4; for (int i = 0; i < numPixelsInStrip; i++) { b1 = (0x000000FF & ((int) buffer.get())); b2 = (0x000000FF & ((int) buffer.get())); b3 = (0x000000FF & ((int) buffer.get())); b4 = (0x000000FF & ((int) buffer.get())); data[i] = ((long) (b1 << 24 | b2 << 16 | b3 << 8 | b4)) & 0xFFFFFFFFL; } } else if (sampleFormat == 2 && bitsPerSample[0] == 32) { // signed 32-bit int IntBuffer ib = buffer.asIntBuffer(); int[] ia = new int[numPixelsInStrip]; ib.get(ia); ib = null; for (int j = 0; j < numPixelsInStrip; j++) { data[j] = ia[j]; } ia = null; } else if (sampleFormat == 1 && bitsPerSample[0] == 64) { // unsigned 64-bit long // I don't know what data type you could cast an unsigned long into. Perhaps a BigInteger. return null; } else if (sampleFormat == 2 && bitsPerSample[0] == 64) { // signed 64-bit long LongBuffer lb = buffer.asLongBuffer(); long[] la = new long[numPixelsInStrip]; lb.get(la); lb = null; for (int j = 0; j < numPixelsInStrip; j++) { data[j] = la[j]; } la = null; } else if (sampleFormat == 3 && bitsPerSample[0] == 32) { // 32-bit single-precision float FloatBuffer fb = buffer.asFloatBuffer(); float[] fa = new float[numPixelsInStrip]; fb.get(fa); fb = null; for (int j = 0; j < numPixelsInStrip; j++) { data[j] = fa[j]; } fa = null; } else if (sampleFormat == 3 && bitsPerSample[0] == 64) { // 64-bit double-precision float DoubleBuffer db = buffer.asDoubleBuffer(); double[] da = new double[numPixelsInStrip]; db.get(da); db = null; data = da.clone(); da = null; } else { return null; } } else if (compressionType == 32773) { return null; } else { return null; } } catch (IOException e) { // do nothing } } else { int totalBitsPerSample = 0; for (int a = 0; a < bitsPerSample.length; a++) { totalBitsPerSample += bitsPerSample[a]; } int numPixelsInStrip = stripByteSize / (totalBitsPerSample / 8); data = new double[numPixelsInStrip]; try { channel.position(stripOffset); ByteBuffer buffer = ByteBuffer.allocate(stripByteSize); buffer.order(byteOrder); if (compressionType == 1) { channel.read(buffer); buffer.flip(); if (totalBitsPerSample == 24 && bitsPerSample.length == 3) { int r, g, b; for (int i = 0; i < nCols; i++) { r = (0x000000FF & ((int) buffer.get())); g = (0x000000FF & ((int) buffer.get())); b = (0x000000FF & ((int) buffer.get())); data[i] = (double) ((255 << 24) | (b << 16) | (g << 8) | r); } } else if (totalBitsPerSample == 32 && bitsPerSample.length == 4) { int r, g, b, a; for (int i = 0; i < nCols; i++) { r = (0x000000FF & ((int) buffer.get())); g = (0x000000FF & ((int) buffer.get())); b = (0x000000FF & ((int) buffer.get())); a = (0x000000FF & ((int) buffer.get())); data[i] = (double) ((a << 24) | (b << 16) | (g << 8) | r); } } else { return null; } } else if (compressionType == 32773) { return null; } else { return null; } } catch (IOException e) { return null; } } return data; } // public double[] getRowData(int row) { // double[] data = new double[nCols]; // int offset = 0; // int size = 0; // int compressionType = findTag(Tag.Compression).value[0]; // int[] bitsPerSample = findTag(Tag.BitsPerSample).value; // int sampleFormat = 1; //findTag(Tag.SampleFormat).value[0]; // // if (findTag(Tag.SampleFormat) != null) { // sampleFormat = findTag(Tag.SampleFormat).value[0]; // } // if (tiledFormat) { // return null; // can't handle this type of tiff. // } // // if (getPhotometricInterpretation() != 2) { // // // how many rows per strip? //// rowsPerStrip = findTag(Tag.RowsPerStrip).value[0]; // // which strip will contain row? // int stripNum = (int) row / rowsPerStrip; // // int stripOffset = findTag(Tag.StripOffsets).value[stripNum]; // //int stripSize = findTag(Tag.StripByteCounts).value[0]; // offset = stripOffset + (row % rowsPerStrip * nCols * (bitsPerSample[0] / 8)); // size = nCols * (bitsPerSample[0] / 8); //stripSize; // // try { // channel.position(offset); // ByteBuffer buffer = ByteBuffer.allocate(size); // buffer.order(byteOrder); // // if (compressionType == 1) { // // channel.read(buffer); // buffer.flip(); // // if (sampleFormat == 1 && bitsPerSample[0] == 8) { // unsigned byte // byte b; // for (int i = 0; i < size; i++) { // b = buffer.get(); // data[i] = (short) (0x000000FF & ((int) b)); // } // } else if (sampleFormat == 2 && bitsPerSample[0] == 8) { // signed byte // byte b; // for (int i = 0; i < size; i++) { // b = buffer.get(); // data[i] = b; // } // } else if (sampleFormat == 1 && bitsPerSample[0] == 16) { // unsigned 16-bit short // int b1; // int b2; // for (int i = 0; i < nCols; i++) { // b1 = (0x000000FF & ((int) buffer.get())); // b2 = (0x000000FF & ((int) buffer.get())); // data[i] = (b2 << 8 | b1); // } // } else if (sampleFormat == 2 && bitsPerSample[0] == 16) { // signed 16-bit short // ShortBuffer sb = buffer.asShortBuffer(); // short[] sa = new short[nCols]; // sb.get(sa); // sb = null; // for (int j = 0; j < nCols; j++) { // data[j] = sa[j]; // } // sa = null; // } else if (sampleFormat == 1 && bitsPerSample[0] == 32) { // unsigned 32-bit int // int b1, b2, b3, b4; // for (int i = 0; i < nCols; i++) { // b1 = (0x000000FF & ((int) buffer.get())); // b2 = (0x000000FF & ((int) buffer.get())); // b3 = (0x000000FF & ((int) buffer.get())); // b4 = (0x000000FF & ((int) buffer.get())); // data[i] = ((long) (b1 << 24 | b2 << 16 | b3 << 8 | b4)) & 0xFFFFFFFFL; // } // // } else if (sampleFormat == 2 && bitsPerSample[0] == 32) { // signed 32-bit int // IntBuffer ib = buffer.asIntBuffer(); // int[] ia = new int[nCols]; // ib.get(ia); // ib = null; // for (int j = 0; j < nCols; j++) { // data[j] = ia[j]; // } // ia = null; // } else if (sampleFormat == 1 && bitsPerSample[0] == 64) { // unsigned 64-bit long // // I don't know what data type you could cast an unsigned long into. Perhaps a BigInteger. // return null; // } else if (sampleFormat == 2 && bitsPerSample[0] == 64) { // signed 64-bit long // LongBuffer lb = buffer.asLongBuffer(); // long[] la = new long[nCols]; // lb.get(la); // lb = null; // for (int j = 0; j < nCols; j++) { // data[j] = la[j]; // } // la = null; // } else if (sampleFormat == 3 && bitsPerSample[0] == 32) { // 32-bit single-precision float // FloatBuffer fb = buffer.asFloatBuffer(); // float[] fa = new float[nCols]; // fb.get(fa); // fb = null; // for (int j = 0; j < nCols; j++) { // data[j] = fa[j]; // } // fa = null; // } else if (sampleFormat == 3 && bitsPerSample[0] == 64) { // 64-bit double-precision float // DoubleBuffer db = buffer.asDoubleBuffer(); // double[] da = new double[nCols]; // db.get(da); // db = null; // data = da.clone(); // da = null; // } else { // return null; // } // } else if (compressionType == 32773) { // return null; // } else { // return null; // } // // } catch (IOException e) { // // do nothing // } // // return data; // } else { // // how many rows per strip? // int rowsPerStrip = findTag(Tag.RowsPerStrip).value[0]; // // which strip will contain row? // int stripNum = (int) row / rowsPerStrip; // // int stripOffset = findTag(Tag.StripOffsets).value[stripNum]; // //int stripSize = findTag(Tag.StripByteCounts).value[0]; // // int totalBitsPerSample = 0; // for (int a = 0; a < bitsPerSample.length; a++) { // totalBitsPerSample += bitsPerSample[a]; // } // offset = stripOffset + (row % rowsPerStrip * nCols * (totalBitsPerSample / 8)); // size = nCols * (totalBitsPerSample / 8); //stripSize; // // try { // channel.position(offset); // ByteBuffer buffer = ByteBuffer.allocate(size); // buffer.order(byteOrder); // // if (compressionType == 1) { // // channel.read(buffer); // buffer.flip(); // // if (totalBitsPerSample == 24 && bitsPerSample.length == 3) { // int r, g, b; // for (int i = 0; i < nCols; i++) { // r = (0x000000FF & ((int) buffer.get())); // g = (0x000000FF & ((int) buffer.get())); // b = (0x000000FF & ((int) buffer.get())); // data[i] = (double) ((255 << 24) | (b << 16) | (g << 8) | r); // } // } else if (totalBitsPerSample == 32 && bitsPerSample.length == 4) { // int r, g, b, a; // for (int i = 0; i < nCols; i++) { // r = (0x000000FF & ((int) buffer.get())); // g = (0x000000FF & ((int) buffer.get())); // b = (0x000000FF & ((int) buffer.get())); // a = (0x000000FF & ((int) buffer.get())); // data[i] = (double) ((a << 24) | (b << 16) | (g << 8) | r); // } // } else { // return null; // } // // } else if (compressionType == 32773) { // return null; // } else { // return null; // } // // } catch (IOException e) { // // do nothing // } // return data; //// return null; // } // } private int readHeader(FileChannel channel) throws IOException { channel.position(0); ByteBuffer buffer = ByteBuffer.allocate(8); channel.read(buffer); buffer.flip(); if (showHeaderBytes) { printBytes(System.out, "header", buffer, 4); buffer.rewind(); } byte b = buffer.get(); //byte b2 = buffer.get(); if (b == 73) { // || (b == 80 & b2 == 67)) { byteOrder = ByteOrder.LITTLE_ENDIAN; } buffer.order(byteOrder); buffer.position(4); int firstIFD = buffer.getInt(); if (debugRead) { System.out.println(" firstIFD == " + firstIFD); } return firstIFD; } public ByteOrder getByteOrder() { return byteOrder; } private int readIFD(FileChannel channel, int start) throws IOException { channel.position(start); ByteBuffer buffer = ByteBuffer.allocate(2); buffer.order(byteOrder); int n = channel.read(buffer); buffer.flip(); if (showBytes) { printBytes(System.out, "IFD", buffer, 2); buffer.rewind(); } short nentries = buffer.getShort(); if (debugRead) { System.out.println(" nentries = " + nentries); } start += 2; for (int i = 0; i < nentries; i++) { IFDEntry ifd = readIFDEntry(channel, start); if (debugRead) { System.out.println(i + " == " + ifd); } tags.add(ifd); start += 12; } if (debugRead) { System.out.println(" looking for nextIFD at pos == " + channel.position() + " start = " + start); } channel.position(start); buffer = ByteBuffer.allocate(4); buffer.order(byteOrder); n = channel.read(buffer); buffer.flip(); int nextIFD = buffer.getInt(); if (debugRead) { System.out.println(" nextIFD == " + nextIFD); } return nextIFD; } private IFDEntry readIFDEntry(FileChannel channel, int start) throws IOException { if (debugRead) { System.out.println("readIFDEntry starting position to " + start); } channel.position(start); ByteBuffer buffer = ByteBuffer.allocate(12); buffer.order(byteOrder); channel.read(buffer); buffer.flip(); if (showBytes) { printBytes(System.out, "IFDEntry bytes", buffer, 12); } IFDEntry ifd; buffer.position(0); int code = readUShortValue(buffer); Tag tag = Tag.get(code); if (tag == null) { tag = new Tag(code); } FieldType type = FieldType.get(readUShortValue(buffer)); int count = buffer.getInt(); ifd = new IFDEntry(tag, type, count); if (ifd.count * ifd.type.size <= 4) { readValues(buffer, ifd); } else { int offset = buffer.getInt(); if (debugRead) { System.out.println("position to " + offset); } channel.position(offset); ByteBuffer vbuffer = ByteBuffer.allocate(ifd.count * ifd.type.size); vbuffer.order(byteOrder); channel.read(vbuffer); vbuffer.flip(); readValues(vbuffer, ifd); } return ifd; } /* * Construct a GeoKey from an IFDEntry. * @param id GeoKey.Tag number * @param v value * GeoKey(int id, IFDEntry data, int vcount, int offset) { this.id = id; this.geoTag = GeoKey.Tag.get(id); this.count = vcount; if (data.type == FieldType.SHORT) { if (vcount == 1) geoValue = TagValue.get(geoTag, offset); else { value = new int[vcount]; for (int i=0; i<vcount; i++) value[i] = data.value[offset + i]; } if (geoValue == null) { if (data.type == FieldType.ASCII) valueS = data.valueS.substring( offset, offset+vcount); else { value = new int[vcount]; for (int i=0; i<vcount; i++) value[i] = data.value[offset + i]; } } } */ private void readValues(ByteBuffer buffer, IFDEntry ifd) { if (ifd.type == FieldType.ASCII) { ifd.valueS = readSValue(buffer, ifd); } else if (ifd.type == FieldType.RATIONAL) { ifd.value = new int[ifd.count * 2]; for (int i = 0; i < ifd.count * 2; i++) { ifd.value[i] = readIntValue(buffer, ifd); } } else if (ifd.type == FieldType.FLOAT) { ifd.valueD = new double[ifd.count]; for (int i = 0; i < ifd.count; i++) { ifd.valueD[i] = (double) buffer.getFloat(); } } else if (ifd.type == FieldType.DOUBLE) { ifd.valueD = new double[ifd.count]; for (int i = 0; i < ifd.count; i++) { ifd.valueD[i] = buffer.getDouble(); } } else { ifd.value = new int[ifd.count]; for (int i = 0; i < ifd.count; i++) { ifd.value[i] = readIntValue(buffer, ifd); } } } private int readIntValue(ByteBuffer buffer, IFDEntry ifd) { switch (ifd.type.code) { case 1: return (int) buffer.get(); case 2: return (int) buffer.get(); case 3: return readUShortValue(buffer); case 4: return buffer.getInt(); case 5: return buffer.getInt(); } return 0; } private int readUShortValue(ByteBuffer buffer) { return buffer.getShort() & 0xffff; } private String readSValue(ByteBuffer buffer, IFDEntry ifd) { byte[] dst = new byte[ifd.count]; buffer.get(dst); return new String(dst); } private void printBytes(PrintStream ps, String head, ByteBuffer buffer, int n) { ps.print(head + " == "); for (int i = 0; i < n; i++) { byte b = buffer.get(); int ub = (b < 0) ? b + 256 : b; ps.print(ub + "("); ps.write(b); ps.print(") "); } ps.println(); } ///////////////////////////////////////////////////////////////////////////// // geotiff stuff private void parseGeoInfo() { IFDEntry keyDir = findTag(Tag.GeoKeyDirectoryTag); IFDEntry dparms = findTag(Tag.GeoDoubleParamsTag); IFDEntry aparams = findTag(Tag.GeoAsciiParamsTag); if (null == keyDir) { return; } int nkeys = keyDir.value[3]; if (debugReadGeoKey) { System.out.println("parseGeoInfo nkeys = " + nkeys + " keyDir= " + keyDir); } int pos = 4; for (int i = 0; i < nkeys; i++) { int id = keyDir.value[pos++]; int location = keyDir.value[pos++]; int vcount = keyDir.value[pos++]; int offset = keyDir.value[pos++]; GeoKey.Tag tag = GeoKey.Tag.getOrMake(id); GeoKey key = null; if (location == 0) { // simple case key = new GeoKey(id, offset); } else { // more than one, or non short value IFDEntry data = findTag(Tag.get(location)); if (data == null) { System.out.println("********ERROR parseGeoInfo: cant find Tag code = " + location); } else if (data.tag == Tag.GeoDoubleParamsTag) { // double params double[] dvalue = new double[vcount]; for (int k = 0; k < vcount; k++) { dvalue[k] = data.valueD[offset + k]; } key = new GeoKey(tag, dvalue); } else if (data.tag == Tag.GeoKeyDirectoryTag) { // int params int[] value = new int[vcount]; for (int k = 0; k < vcount; k++) { value[k] = data.value[offset + k]; } key = new GeoKey(tag, value); } else if (data.tag == Tag.GeoAsciiParamsTag) { // ascii params String value; if ((offset + vcount) < data.valueS.length()) { value = data.valueS.substring(offset, offset + vcount); } else { value = data.valueS.substring(offset, data.valueS.length() - 1); } key = new GeoKey(tag, value); } } if (key != null) { keyDir.addGeoKey(key); if (debugReadGeoKey) { System.out.println(" yyy add geokey=" + key); } } } } /** * Write the geotiff Tag information to out. */ public void showInfo(PrintStream out) { out.println("Geotiff file= " + filename); for (int i = 0; i < tags.size(); i++) { IFDEntry ifd = tags.get(i); out.println(i + " IFDEntry == " + ifd); } } /** * Write the geotiff Tag information to a String. */ public String[] showInfo() { String[] ret = new String[tags.size() + 1]; ret[0] = "Geotiff file= " + filename; for (int i = 0; i < tags.size(); i++) { IFDEntry ifd = tags.get(i); ret[i + 1] = ifd.toString(); } return ret; } // this is only used for debugging the tool public static void main(String[] args) { try { //String fileName = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/HW_DEM_UTM11.tif"; //String fileName = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/DEM.tif"; String fileName = "/Users/johnlindsay/Documents/Data/GeoTIFFs/RGB/75GC.tif"; GeoTiff gt = new GeoTiff(fileName); gt.read(); int nRows = gt.getNumberRows(); int nCols = gt.getNumberColumns(); double inNodata = -3.4028234663852886E38; double outNoData = -3.4028234663852886E38; // String whiteboxHeaderFile = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/HW_DEM_UTM11.dep"; // String whiteboxDataFile = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/HW_DEM_UTM11.tas"; //String whiteboxHeaderFile = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/HW_DEM_UTM11 2.dep"; //String whiteboxDataFile = "/Users/johnlindsay/Documents/Data/Highwood_Wetlands/HW_DEM_UTM11 2.tas"; String whiteboxHeaderFile = "/Users/johnlindsay/Documents/Data/GeoTIFFs/RGB/75GC.dep"; String whiteboxDataFile = "/Users/johnlindsay/Documents/Data/GeoTIFFs/RGB/75GC.tas"; // see if they exist, and if so, delete them. (new File(whiteboxHeaderFile)).delete(); (new File(whiteboxDataFile)).delete(); ByteOrder byteOrder = gt.getByteOrder(); WhiteboxRasterBase.DataScale myDataScale; if (gt.getPhotometricInterpretation() != 2) { myDataScale = WhiteboxRasterBase.DataScale.CONTINUOUS; } else { myDataScale = WhiteboxRasterBase.DataScale.RGB; } WhiteboxRaster wbr = new WhiteboxRaster(whiteboxHeaderFile, gt.getNorth(), gt.getSouth(), gt.getEast(), gt.getWest(), nRows, nCols, myDataScale, WhiteboxRasterBase.DataType.FLOAT, outNoData, outNoData); wbr.setByteOrder(byteOrder.toString()); double z; for (int row = 0; row < nRows; row++) { for (int col = 0; col < nCols; col++) { z = gt.getValue(row, col); if (z != inNodata) { wbr.setValue(row, col, z); } } // int progress = (int)(100f * row / (nRows - 1)); } wbr.close(); System.out.println("done"); } catch (Exception e) { System.out.println("Error"); } } }