/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, 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.coverage.wkb;
import java.awt.Point;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.media.jai.PlanarImage;
import javax.media.jai.RasterFactory;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.grid.GridCoverageBuilder;
import static org.geotoolkit.coverage.wkb.WKBRasterConstants.*;
import org.geotoolkit.io.LEDataInputStream;
import org.apache.sis.referencing.CRS;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.geotoolkit.image.color.ScaledColorSpace;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.util.FactoryException;
/**
* WKB Raster Reader, used in postGIS 2 but can be used elsewhere.
*
* @author Johann Sorel (Geomatys)
*/
public class WKBRasterReader {
private AffineTransform2D gridToCRS = null;
private int srid = 0;
public WKBRasterReader(){
}
/**
* Reset values before new read call.
*/
public void reset(){
gridToCRS = null;
srid = 0;
}
/**
* Get the Grid to CRS transform, can be called after read only.
* @return AffineTransform2D
*/
public AffineTransform2D getGridToCRS() {
return gridToCRS;
}
/**
* Get the postgis srid, can be called after read only.
* @return int, postgid srid
*/
public int getSRID(){
return srid;
}
/**
* Parse given byte[] and rebuild a GridCoverage2D.
*
* @param data
* @return
* @throws IOException
*/
public GridCoverage2D readCoverage(byte[] data, CRSAuthorityFactory authorityFactory)
throws IOException, NoSuchAuthorityCodeException, FactoryException{
final InputStream stream = new ByteArrayInputStream(data);
return readCoverage(stream,authorityFactory);
}
/**
* Parse given InputStream and rebuild a GridCoverage2D.
*
* @param stream
* @return
* @throws IOException
*/
public GridCoverage2D readCoverage(final InputStream stream, CRSAuthorityFactory authorityFactory)
throws IOException, NoSuchAuthorityCodeException, FactoryException{
final BufferedImage image = read(stream);
final GridCoverageBuilder gcb = new GridCoverageBuilder();
final String epsgCode = "EPSG:"+srid;
if(authorityFactory != null){
gcb.setCoordinateReferenceSystem(authorityFactory.createCoordinateReferenceSystem(epsgCode));
}else{
gcb.setCoordinateReferenceSystem(CRS.forCode(epsgCode));
}
gcb.setGridToCRS((MathTransform)getGridToCRS());
gcb.setRenderedImage(image);
return gcb.getGridCoverage2D();
}
/**
* Parse given byte[] and rebuild RenderedImage.
*
* @param data
* @return
* @throws IOException
*/
public BufferedImage read(byte[] data) throws IOException{
final InputStream stream = new ByteArrayInputStream(data);
return read(stream);
}
/**
* Parse given InputStream and rebuild RenderedImage.
*
* @param stream
* @return
* @throws IOException
*/
public BufferedImage read(final InputStream stream) throws IOException{
final DataInput ds;
final boolean littleEndian = stream.read() == 1;
if(littleEndian){
//big endian
ds = new LEDataInputStream(stream);
}else{
//little endian
ds = new DataInputStream(stream);
}
final int version = ds.readUnsignedShort();
final int nbBand = ds.readUnsignedShort();
//grid to crs
final double scaleX = ds.readDouble();
final double scaleY = ds.readDouble();
final double ipX = ds.readDouble();
final double ipY = ds.readDouble();
final double skewX = ds.readDouble();
final double skewY = ds.readDouble();
gridToCRS = new AffineTransform2D(scaleX, skewY, skewX, scaleY, ipX, ipY);
srid = ds.readInt();
final int width = ds.readUnsignedShort();
final int height = ds.readUnsignedShort();
if(nbBand == 0){
//possible for empty raster
return null;
}
final WKBRasterBand[] bands = new WKBRasterBand[nbBand];
for(int i=0;i<nbBand;i++){
final WKBRasterBand band = new WKBRasterBand();
final byte b = ds.readByte();
band.setPixelType(b & BANDTYPE_PIXTYPE_MASK);
band.setOffDatabase( (b & BANDTYPE_FLAG_OFFDB) != 0);
band.setHasNodata( (b & BANDTYPE_FLAG_HASNODATA) != 0);
band.setIsNodata( (b & BANDTYPE_FLAG_ISNODATA) != 0);
band.setReserved( (b & BANDTYPE_FLAG_RESERVED3) != 0);
/* read nodata value */
switch (band.getPixelType()) {
case PT_1BB:
case PT_2BUI:
case PT_4BUI:
case PT_8BUI:
band.setNoDataValue(ds.readUnsignedByte());
break;
case PT_8BSI:
band.setNoDataValue(ds.readByte());
break;
case PT_16BSI:
band.setNoDataValue(ds.readShort());
break;
case PT_16BUI:
band.setNoDataValue(ds.readUnsignedShort());
break;
case PT_32BSI:
band.setNoDataValue(ds.readInt());
break;
case PT_32BUI:
band.setNoDataValue(ds.readInt() & 0x00000000ffffffffL);
break;
case PT_32BF:
band.setNoDataValue(ds.readFloat());
break;
case PT_64BF:
band.setNoDataValue(ds.readDouble());
break;
default:
throw new IOException("unknowned pixel type : "+band.getPixelType());
}
if(band.isOffDatabase()){
throw new IOException("can not access data which are off database");
}else{
//read values
final int nbBytePerPixel = band.getNbBytePerPixel();
final byte[] datas = new byte[width*height*band.getNbBytePerPixel()];
ds.readFully(datas);
if(littleEndian && nbBytePerPixel > 1){
//image databank expect values in big endian so we must flip bytes
byte temp;
for(int k=0;k<datas.length;k+=nbBytePerPixel){
for(int p=0,n=nbBytePerPixel/2; p<n ;p++){
final int index1 = k+p;
final int index2 = k+(nbBytePerPixel-p-1);
temp = datas[index1];
datas[index1] = datas[index2];
datas[index2] = temp;
}
}
}
band.setDatas(datas);
}
bands[i] = band;
}
//we expect all bands to have the same type
final int dataBufferType = bands[0].getDataBufferType();
//rebuild raster
final WritableRaster raster;
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
if(dataBufferType == DataBuffer.TYPE_BYTE){
//more efficient but only works for byte type bands
//check all band have the same sample model and rebuild data buffer
Integer dataType = null;
final byte[][] dataArray = new byte[nbBand][0];
final int[] bankIndices = new int[nbBand];
final int[] bankOffsets = new int[nbBand];
for(int i=0;i<bands.length;i++){
final WKBRasterBand band = bands[i];
if(dataType == null){
dataType = band.getDataBufferType();
}else if(dataType != band.getDataBufferType()){
throw new IOException("Band type differ, can not be mapped to java image.");
}
dataArray[i] = band.getDatas();
bankIndices[i] = i;
bankOffsets[i] = 0;
}
min = -100.0;
max = 100.0;
//rebuild data buffer
final DataBuffer db = new DataBufferByte(dataArray, dataArray[0].length);
final int scanlineStride = width;
raster = RasterFactory.createBandedRaster(
db, width, height, scanlineStride, bankIndices, bankOffsets, new Point(0,0));
}else{
raster = RasterFactory.createBandedRaster(dataBufferType,width,height,nbBand,new Point(0,0));
for(int i=0;i<bands.length;i++){
final byte[] datas = bands[i].getDatas();
final DataInputStream dds = new DataInputStream(new ByteArrayInputStream(datas));
for(int y=0;y<height;y++){
for(int x=0;x<width;x++){
switch (dataBufferType) {
case DataBuffer.TYPE_SHORT:
short d1 = dds.readShort();
raster.setSample(x, y, i, d1);
min = Math.min(min, (double)d1);
max = Math.max(max, (double)d1);
break;
case DataBuffer.TYPE_USHORT:
int d2 = dds.readUnsignedShort();
raster.setSample(x, y, i, d2);
min = Math.min(min, (double)d2);
max = Math.max(max, (double)d2);
break;
case DataBuffer.TYPE_INT:
int d3 = dds.readInt();
raster.setSample(x, y, i, d3);
min = Math.min(min, (double)d3);
max = Math.max(max, (double)d3);
break;
case DataBuffer.TYPE_FLOAT:
float d4 = dds.readFloat();
raster.setSample(x, y, i, d4);
min = Math.min(min, (double)d4);
max = Math.max(max, (double)d4);
break;
case DataBuffer.TYPE_DOUBLE:
double d5 = dds.readDouble();
raster.setSample(x, y, i, d5);
min = Math.min(min, d5);
max = Math.max(max, d5);
break;
default:
throw new IllegalArgumentException("unknowned data buffer type : " + dataBufferType);
}
}
}
}
}
//rebuild image
final SampleModel sm = raster.getSampleModel();
ColorModel cm = PlanarImage.getDefaultColorModel(sm.getDataType(), raster.getNumBands());
if(cm==null){
//fallback
cm = createGrayScaleColorModel(sm.getDataType(), raster.getNumBands(), 0, min, max);
}
return new BufferedImage(cm, raster, false, null);
}
private static ColorModel createGrayScaleColorModel(int dataType, int nbBand, int visibleBand, double min, double max) {
final ColorSpace colors = new ScaledColorSpace(nbBand, visibleBand, min, max);
final ColorModel cm = new ComponentColorModel(colors, false, false, Transparency.OPAQUE, dataType);
return cm;
}
}