/** * Rectify elevation raster. For best performance each elevation raster must have correct * parameters and values set. The <code>rectify()</code> operation validates that correct * Elevation min and max values are set or calculated. All values that beyond min/max and voids, * must be marked with "Missing Signal" (aka "nodata" value). * * @param raster A DataRaster to rectify * @throws IllegalArgumentException if <code>raster</code> is <code>null</code> */ public static void rectify(ByteBufferRaster raster) throws IllegalArgumentException { if (null == raster) { String msg = Logging.getMessage("nullValue.RasterIsNull"); Logging.logger().finest(msg); throw new IllegalArgumentException(msg); } int width = raster.getWidth(); int height = raster.getHeight(); if (width == 0 || height == 0) { // nothing to do return; } double[] minmax = raster.getExtremes(); if (null == minmax) { // nothing to do return; } Double minValue = minmax[0]; Double maxValue = minmax[1]; Double missingDataSignal = AVListImpl.getDoubleValue(raster, AVKey.MISSING_DATA_SIGNAL, null); // check if the minimum value is one of the well known NODATA values if (ElevationsUtil.isKnownMissingSignal(minValue) || (missingDataSignal != null && missingDataSignal.equals(minValue))) { missingDataSignal = minValue; raster.setTransparentValue(missingDataSignal); minmax = raster.getExtremes(); if (null != minmax) { minValue = minmax[0]; maxValue = minmax[1]; } } BufferWrapper bufferWrapper = raster.getBuffer(); // Allocate a buffer to hold one row of scalar values. double[] array = new double[width]; boolean needsConversion = false; double conversionValue = 1d; if (raster.hasKey(AVKey.ELEVATION_UNIT)) { String unit = raster.getStringValue(AVKey.ELEVATION_UNIT); if (AVKey.UNIT_METER.equalsIgnoreCase(unit)) { needsConversion = false; } else if (AVKey.UNIT_FOOT.equalsIgnoreCase(unit)) { needsConversion = true; conversionValue = WWMath.convertFeetToMeters(1); minValue = WWMath.convertFeetToMeters(minValue); maxValue = WWMath.convertFeetToMeters(maxValue); raster.setValue(AVKey.ELEVATION_UNIT, AVKey.UNIT_METER); } else { needsConversion = false; String msg = Logging.getMessage("generic.UnrecognizedElevationUnit", unit); Logging.logger().warning(msg); } } boolean rasterHasVoids = false; for (int j = 0; j < height; j++) { bufferWrapper.getDouble(j * width, array, 0, width); boolean commitChanges = false; for (int i = 0; i < width; i++) { double value = array[i]; if (null != missingDataSignal && value == missingDataSignal) { rasterHasVoids = true; } else { if (needsConversion) { value *= conversionValue; commitChanges = true; array[i] = value; } if (value < minValue || value > maxValue) { rasterHasVoids = true; if (null != missingDataSignal) { array[i] = missingDataSignal; commitChanges = true; } } } } if (commitChanges) bufferWrapper.putDouble(j * width, array, 0, width); } if (rasterHasVoids) { if (missingDataSignal != null) raster.setValue(AVKey.MISSING_DATA_SIGNAL, missingDataSignal); } else { raster.removeKey(AVKey.MISSING_DATA_SIGNAL); } raster.setValue(AVKey.ELEVATION_MIN, minValue); raster.setValue(AVKey.ELEVATION_MAX, maxValue); }
public void writeRaster(BufferWrapperRaster raster) throws IOException, IllegalArgumentException { if (raster == null) { String msg = Logging.getMessage("nullValue.RasterIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (0 == raster.getWidth() || 0 == raster.getHeight()) { String msg = Logging.getMessage("generic.InvalidImageSize", raster.getWidth(), raster.getHeight()); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.validateParameters(raster, raster.getWidth(), raster.getHeight()); int bitsPerSample, samplesPerPixel, sampleFormat, photometric, numBands; if (AVKey.ELEVATION.equals(raster.getValue(AVKey.PIXEL_FORMAT))) { if (AVKey.FLOAT32.equals(raster.getValue(AVKey.DATA_TYPE))) { numBands = 1; samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME; sampleFormat = Tiff.SampleFormat.IEEEFLOAT; photometric = Tiff.Photometric.Grayscale_BlackIsZero; bitsPerSample = Tiff.BitsPerSample.ELEVATIONS_FLOAT32; } else if (AVKey.INT16.equals(raster.getValue(AVKey.DATA_TYPE))) { numBands = 1; samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME; sampleFormat = Tiff.SampleFormat.SIGNED; photometric = Tiff.Photometric.Grayscale_BlackIsZero; bitsPerSample = Tiff.BitsPerSample.ELEVATIONS_INT16; } else { String msg = Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.DATA_TYPE)); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } } else if (AVKey.IMAGE.equals(raster.getValue(AVKey.PIXEL_FORMAT))) { if (AVKey.INT8.equals(raster.getValue(AVKey.DATA_TYPE))) { numBands = 1; samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME; sampleFormat = Tiff.SampleFormat.UNSIGNED; photometric = Tiff.Photometric.Grayscale_BlackIsZero; bitsPerSample = Tiff.BitsPerSample.MONOCHROME_UINT8; } else if (AVKey.INT16.equals(raster.getValue(AVKey.DATA_TYPE))) { numBands = 1; samplesPerPixel = Tiff.SamplesPerPixel.MONOCHROME; sampleFormat = Tiff.SampleFormat.UNSIGNED; photometric = Tiff.Photometric.Grayscale_BlackIsZero; bitsPerSample = Tiff.BitsPerSample.MONOCHROME_UINT16; } else if (AVKey.INT32.equals(raster.getValue(AVKey.DATA_TYPE))) { numBands = 3; // TODO check ALPHA / Transparency samplesPerPixel = Tiff.SamplesPerPixel.RGB; sampleFormat = Tiff.SampleFormat.UNSIGNED; photometric = Tiff.Photometric.Color_RGB; bitsPerSample = Tiff.BitsPerSample.RGB; } else { String msg = Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.DATA_TYPE)); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } } else { String msg = Logging.getMessage("GeotiffWriter.UnsupportedType", raster.getValue(AVKey.PIXEL_FORMAT)); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } int bytesPerSample = numBands * bitsPerSample / Byte.SIZE; this.writeTiffHeader(); // write the image data... int numRows = raster.getHeight(); int numCols = raster.getWidth(); int[] stripCounts = new int[numRows]; int[] stripOffsets = new int[numRows]; BufferWrapper srcBuffer = raster.getBuffer(); ByteBuffer dataBuff = ByteBuffer.allocateDirect(numCols * bytesPerSample); switch (bitsPerSample) { // case Tiff.BitsPerSample.MONOCHROME_BYTE: case Tiff.BitsPerSample.MONOCHROME_UINT8: { for (int y = 0; y < numRows; y++) { stripOffsets[y] = (int) this.theChannel.position(); stripCounts[y] = numCols * bytesPerSample; dataBuff.clear(); for (int x = 0; x < numCols * numBands; x++) { dataBuff.put(srcBuffer.getByte(x + y * numCols)); } dataBuff.flip(); this.theChannel.write(dataBuff); } } break; // case Tiff.BitsPerSample.MONOCHROME_UINT16: case Tiff.BitsPerSample.ELEVATIONS_INT16: { for (int y = 0; y < numRows; y++) { stripOffsets[y] = (int) this.theChannel.position(); stripCounts[y] = numCols * bytesPerSample; dataBuff.clear(); for (int x = 0; x < numCols * numBands; x++) { dataBuff.putShort(srcBuffer.getShort(x + y * numCols)); } dataBuff.flip(); this.theChannel.write(dataBuff); } } break; case Tiff.BitsPerSample.ELEVATIONS_FLOAT32: { for (int y = 0; y < numRows; y++) { stripOffsets[y] = (int) this.theChannel.position(); stripCounts[y] = numCols * bytesPerSample; dataBuff.clear(); for (int x = 0; x < numCols * numBands; x++) { dataBuff.putFloat(srcBuffer.getFloat(x + y * numCols)); } dataBuff.flip(); this.theChannel.write(dataBuff); } } break; case Tiff.BitsPerSample.RGB: { for (int y = 0; y < numRows; y++) { stripOffsets[y] = (int) this.theChannel.position(); stripCounts[y] = numCols * bytesPerSample; dataBuff.clear(); for (int x = 0; x < numCols; x++) { int color = srcBuffer.getInt(x + y * numCols); byte red = (byte) (0xFF & (color >> 16)); byte green = (byte) (0xFF & (color >> 8)); byte blue = (byte) (0xFF & color); // dataBuff.put(0xFF & (color >> 24)); // alpha dataBuff.put(red).put(green).put(blue); } dataBuff.flip(); this.theChannel.write(dataBuff); } } break; } // write out values for the tiff tags and build up the IFD. These are supposed to be sorted; for // now // do this manually here. ArrayList<TiffIFDEntry> ifds = new ArrayList<TiffIFDEntry>(10); ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_WIDTH, Tiff.Type.LONG, 1, numCols)); ifds.add(new TiffIFDEntry(Tiff.Tag.IMAGE_LENGTH, Tiff.Type.LONG, 1, numRows)); long offset = this.theChannel.position(); if (Tiff.BitsPerSample.RGB == bitsPerSample) { short[] bps = new short[numBands]; for (int i = 0; i < numBands; i++) { bps[i] = Tiff.BitsPerSample.MONOCHROME_BYTE; } this.theChannel.write(ByteBuffer.wrap(this.getBytes(bps))); ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, numBands, offset)); } else ifds.add(new TiffIFDEntry(Tiff.Tag.BITS_PER_SAMPLE, Tiff.Type.SHORT, 1, bitsPerSample)); ifds.add(new TiffIFDEntry(Tiff.Tag.COMPRESSION, Tiff.Type.LONG, 1, Tiff.Compression.NONE)); ifds.add(new TiffIFDEntry(Tiff.Tag.PHOTO_INTERPRETATION, Tiff.Type.SHORT, 1, photometric)); ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLES_PER_PIXEL, Tiff.Type.SHORT, 1, samplesPerPixel)); ifds.add(new TiffIFDEntry(Tiff.Tag.ORIENTATION, Tiff.Type.SHORT, 1, Tiff.Orientation.DEFAULT)); ifds.add( new TiffIFDEntry( Tiff.Tag.PLANAR_CONFIGURATION, Tiff.Type.SHORT, 1, Tiff.PlanarConfiguration.CHUNKY)); ifds.add(new TiffIFDEntry(Tiff.Tag.SAMPLE_FORMAT, Tiff.Type.SHORT, 1, sampleFormat)); offset = this.theChannel.position(); dataBuff = ByteBuffer.allocateDirect(stripOffsets.length * INTEGER_SIZEOF); for (int stripOffset : stripOffsets) { dataBuff.putInt(stripOffset); } dataBuff.flip(); this.theChannel.write(dataBuff); ifds.add(new TiffIFDEntry(Tiff.Tag.STRIP_OFFSETS, Tiff.Type.LONG, stripOffsets.length, offset)); ifds.add(new TiffIFDEntry(Tiff.Tag.ROWS_PER_STRIP, Tiff.Type.LONG, 1, 1)); offset = this.theChannel.position(); dataBuff.clear(); // stripOffsets and stripCounts are same length by design; can reuse the // ByteBuffer... for (int stripCount : stripCounts) { dataBuff.putInt(stripCount); } dataBuff.flip(); this.theChannel.write(dataBuff); ifds.add( new TiffIFDEntry(Tiff.Tag.STRIP_BYTE_COUNTS, Tiff.Type.LONG, stripCounts.length, offset)); this.appendGeoTiff(ifds, raster); this.writeIFDs(ifds); }