This is an automated email from the ASF dual-hosted git repository. jsorel pushed a commit to branch feat/postgis-raster in repository https://gitbox.apache.org/repos/asf/sis.git
commit 288c7deab83fb78ff2b123e3733e971f8de2d0a7 Author: jsorel <johann.so...@geomatys.com> AuthorDate: Wed Nov 24 16:49:59 2021 +0100 feat(PostGIS): add WKB Raster reader/writer --- .../internal/sql/postgis/LEDataInputStream.java | 360 +++++++++++++++++++++ .../internal/sql/postgis/LEDataOutputStream.java | 231 +++++++++++++ .../sis/internal/sql/postgis/WKBRasterBand.java | 117 +++++++ .../internal/sql/postgis/WKBRasterConstants.java | 133 ++++++++ .../sis/internal/sql/postgis/WKBRasterReader.java | 331 +++++++++++++++++++ .../sis/internal/sql/postgis/WKBRasterWriter.java | 330 +++++++++++++++++++ .../sis/internal/sql/postgis/LEStreamTest.java | 130 ++++++++ 7 files changed, 1632 insertions(+) diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataInputStream.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataInputStream.java new file mode 100644 index 0000000..fce9903 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataInputStream.java @@ -0,0 +1,360 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * Little endian Data input stream. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class LEDataInputStream extends InputStream implements DataInput { + + private final DataInputStream ds; + private final InputStream in; + private final byte[] buffer = new byte[8]; + private long position; + + public LEDataInputStream(final InputStream in) { + this.in = in; + this.ds = new DataInputStream(in); + } + + /** + * Get current stream position from the first written byte. + * @return stream position + */ + public long getPosition() { + return position; + } + + @Override + public int available() throws IOException { + return ds.available(); + } + + @Override + public final short readShort() throws IOException { + position += 2; + ds.readFully(buffer, 0, 2); + return (short) ((buffer[1] & 0xff) << 8 + | (buffer[0] & 0xff)); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public short[] readShorts(int nbValues) throws IOException { + final short[] array = new short[nbValues]; + for (int i = 0; i < nbValues; i++) { + array[i] = readShort(); + } + return array; + } + + @Override + public final int readUnsignedShort() throws IOException { + position += 2; + ds.readFully(buffer, 0, 2); + return ((buffer[1] & 0xff) << 8 + | (buffer[0] & 0xff)); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public int[] readUnsignedShorts(int nbValues) throws IOException { + final int[] array = new int[nbValues]; + for (int i = 0; i<nbValues; i++) { + array[i] = readUnsignedShort(); + } + return array; + } + + @Override + public final char readChar() throws IOException { + position += 2; + ds.readFully(buffer, 0, 2); + return (char) ((buffer[1] & 0xff) << 8 + | (buffer[0] & 0xff)); + } + + @Override + public final int readInt() throws IOException { + position += 4; + ds.readFully(buffer, 0, 4); + return (buffer[3]) << 24 + | (buffer[2] & 0xff) << 16 + | (buffer[1] & 0xff) << 8 + | (buffer[0] & 0xff); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public int[] readInts(int nbValues) throws IOException { + final int[] array = new int[nbValues]; + for (int i = 0; i < nbValues; i++) { + array[i] = readInt(); + } + return array; + } + + @Override + public final long readLong() throws IOException { + position += 8; + ds.readFully(buffer, 0, 8); + return (long) (buffer[7]) << 56 + | (long) (buffer[6] & 0xff) << 48 + | (long) (buffer[5] & 0xff) << 40 + | (long) (buffer[4] & 0xff) << 32 + | (long) (buffer[3] & 0xff) << 24 + | (long) (buffer[2] & 0xff) << 16 + | (long) (buffer[1] & 0xff) << 8 + | (long) (buffer[0] & 0xff); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public long[] readLongs(int nbValues) throws IOException { + final long[] array = new long[nbValues]; + for (int i = 0; i < nbValues; i++) { + array[i] = readLong(); + } + return array; + } + + @Override + public final float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public float[] readFloats(int nbValues) throws IOException { + final float[] array = new float[nbValues]; + for (int i = 0; i < nbValues; i++) { + array[i] = readFloat(); + } + return array; + } + + @Override + public final double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + + /** + * Read multiple values in one call. + * + * @param nbValues number of valeus to read + * @return array of values + * @throws java.io.IOException + */ + public double[] readDoubles(int nbValues) throws IOException { + final double[] array = new double[nbValues]; + for (int i = 0; i < nbValues; i++) { + array[i] = readDouble(); + } + return array; + } + + @Override + public final int read(byte[] b, int off, int len) throws IOException { + int nb = in.read(b, off, len); + position += nb; + return nb; + } + + @Override + public final void readFully(byte[] b) throws IOException { + ds.readFully(b, 0, b.length); + position += b.length; + } + + @Override + public final void readFully(byte[] b, int off, int len) throws IOException { + ds.readFully(b, off, len); + position += len; + } + + @Override + public final int skipBytes(int n) throws IOException { + int nb = ds.skipBytes(n); + if (nb <= 0) return nb; + position += nb; + return nb; + } + + public final void skipFully(int n) throws IOException { + while (n > 0) { + int nb = skipBytes(n); + if (nb == -1) { + throw new EOFException(); + } else if (nb == 0) { + nb = read(); + if (nb == -1) { + throw new EOFException(); + } + nb = 1; + } + n -= nb; + } + } + + @Override + public final boolean readBoolean() throws IOException { + position++; + return ds.readBoolean(); + } + + @Override + public final byte readByte() throws IOException { + position++; + return ds.readByte(); + } + + @Override + public int read() throws IOException { + position++; + return in.read(); + } + + @Override + public final int readUnsignedByte() throws IOException { + position++; + return ds.readUnsignedByte(); + } + + @Override + public final String readLine() throws IOException { + return ds.readLine(); + } + + @Override + public final String readUTF() throws IOException { + return ds.readUTF(); + } + + /** + * Ralign stream position, skipping any remaining byte to match given block size. + * Older formats or javascript buffer requiere to be aligned on 2, 4 or 8 bytes + * (short,int,float,double) to read datas. + * + * @param blockSize + * @return number of bytes skipped + */ + public int realign(int blockSize) throws IOException { + final long position = getPosition(); + final long res = position % blockSize; + if (res == 0) return 0; + try { + skipFully((int) (blockSize-res)); + } catch (EOFException ex) { + return -1; + } + return (int) res; + } + + @Override + public final void close() throws IOException { + ds.close(); + } + + + public static short readUnsignedByte(final byte[] buffer, final int offset){ + return (short) (buffer[offset] & 0xff); + } + + public static short readShort(final byte[] buffer, final int offset){ + return (short) ((buffer[offset+1] & 0xff) << 8 + | (buffer[offset+0] & 0xff)); + } + + public static int readUnsignedShort(final byte[] buffer, final int offset){ + return ((buffer[offset+1] & 0xff) << 8 | (buffer[offset+0] & 0xff)); + } + + public static char readChar(final byte[] buffer, final int offset){ + return (char) ((buffer[offset+1] & 0xff) << 8 + | (buffer[offset+0] & 0xff)); + } + + public static int readInt(final byte[] buffer, final int offset){ + return (buffer[offset+3]) << 24 + | (buffer[offset+2] & 0xff) << 16 + | (buffer[offset+1] & 0xff) << 8 + | (buffer[offset+0] & 0xff); + } + + public static long readUnsignedInt(final byte[] buffer, final int offset){ + return (long) (buffer[offset+3] & 0xff) << 24 + | (long) (buffer[offset+2] & 0xff) << 16 + | (long) (buffer[offset+1] & 0xff) << 8 + | (long) (buffer[offset+0] & 0xff); + } + + public static long readLong(final byte[] buffer, final int offset){ + return (long) (buffer[offset+7]) << 56 + | (long) (buffer[offset+6] & 0xff) << 48 + | (long) (buffer[offset+5] & 0xff) << 40 + | (long) (buffer[offset+4] & 0xff) << 32 + | (long) (buffer[offset+3] & 0xff) << 24 + | (long) (buffer[offset+2] & 0xff) << 16 + | (long) (buffer[offset+1] & 0xff) << 8 + | (long) (buffer[offset+0] & 0xff); + } + + public static float readFloat(final byte[] buffer, final int offset){ + return Float.intBitsToFloat(readInt(buffer,offset)); + } + + public static double readDouble(final byte[] buffer, final int offset){ + return Double.longBitsToDouble(readLong(buffer,offset)); + } + +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataOutputStream.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataOutputStream.java new file mode 100644 index 0000000..a26b560 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/LEDataOutputStream.java @@ -0,0 +1,231 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.io.DataOutput; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Little endian Data output stream. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class LEDataOutputStream extends FilterOutputStream implements DataOutput { + + private long position; + + public LEDataOutputStream(OutputStream out) { + super(out); + } + + /** + * Get current stream position from the first written byte. + * @return stream position + */ + public long getPosition() { + return position; + } + + @Override + public void write(int b) throws IOException { + out.write(b); + position++; + } + + @Override + public void write(byte[] data, int offset, int length) throws IOException { + out.write(data, offset, length); + position += length; + } + + @Override + public void writeBoolean(boolean b) throws IOException { + this.write( b ? 1 : 0 ); + } + + @Override + public void writeByte(int b) throws IOException { + out.write(b); + position++; + } + + @Override + public void writeShort(int s) throws IOException { + out.write((s ) & 0xFF); + out.write((s >>> 8) & 0xFF); + position += 2; + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeShorts(short[] values) throws IOException { + for (short value : values) { + writeShort(value); + } + } + + public void writeUnsignedShort(int s) throws IOException { + writeShort(s); + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeUnsignedShorts(int[] values) throws IOException { + for (int value : values) { + writeUnsignedShort(value); + } + } + + @Override + public void writeChar(int c) throws IOException { + out.write((c ) & 0xFF); + out.write((c >>> 8) & 0xFF); + position += 2; + } + + @Override + public void writeInt(int i) throws IOException { + out.write((i ) & 0xFF); + out.write((i >>> 8) & 0xFF); + out.write((i >>> 16) & 0xFF); + out.write((i >>> 24) & 0xFF); + position += 4; + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeInts(int[] values) throws IOException { + for (int value : values) { + writeInt(value); + } + } + + @Override + public void writeLong(long l) throws IOException { + out.write((int) (l ) & 0xFF); + out.write((int) (l >>> 8 ) & 0xFF); + out.write((int) (l >>> 16) & 0xFF); + out.write((int) (l >>> 24) & 0xFF); + out.write((int) (l >>> 32) & 0xFF); + out.write((int) (l >>> 40) & 0xFF); + out.write((int) (l >>> 48) & 0xFF); + out.write((int) (l >>> 56) & 0xFF); + position += 8; + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeLongs(long[] values) throws IOException { + for (long value : values) { + writeLong(value); + } + } + + @Override + public final void writeFloat(float f) throws IOException { + this.writeInt(Float.floatToIntBits(f)); + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeFloats(float[] values) throws IOException { + for (float value : values) { + writeFloat(value); + } + } + + @Override + public final void writeDouble(double d) throws IOException { + this.writeLong(Double.doubleToLongBits(d)); + } + + /** + * Write multiple values in one call. + * + * @param values values to write + * @throws IOException + */ + public final void writeDoubles(double[] values) throws IOException { + for (double value : values) { + writeDouble(value); + } + } + + @Override + public void writeBytes(String s) throws IOException { + for (int i = 0, n = s.length(); i < n; i++) { + out.write((byte) s.charAt(i)); + } + position += s.length(); + } + + @Override + public void writeChars(String s) throws IOException { + for (int i = 0, n = s.length(); i < n; i++) { + final char c = s.charAt(i); + out.write((c ) & 0xFF); + out.write((c >>> 8) & 0xFF); + } + position += s.length()*2; + } + + @Override + public void writeUTF(String s) throws IOException { + throw new IOException("Not supported"); + } + + /** + * Align byte stream to a padding value, 0 bytes will be added until padding + * value is reached. + * Padding values are usually very small, 2, 4 or 8 bytes to ensure primitive + * types such as Short, Integer, Float are aligned in memory. + * + * @param padding value to realign. + * @throws IOException + */ + public void realign(int padding) throws IOException{ + while ((position % padding) != 0) { + write(0); + } + } + +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterBand.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterBand.java new file mode 100644 index 0000000..4e916c4 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterBand.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +/** + * WKB Raster band, used in postGIS 2. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class WKBRasterBand { + + private int pixelType; + private boolean offdatabase; + private boolean hasnodata; + private boolean isnodata; + private boolean reserved; + private Number noDataValue; + private byte[] datas; + + public WKBRasterBand() { + } + + public int getPixelType() { + return pixelType; + } + + public void setPixelType(int pixelType) { + this.pixelType = pixelType; + } + + public boolean isOffDatabase() { + return offdatabase; + } + + public void setOffDatabase(boolean offdatabase) { + this.offdatabase = offdatabase; + } + + public boolean hasNodata() { + return hasnodata; + } + + public void setHasNodata(boolean hasnodata) { + this.hasnodata = hasnodata; + } + + public boolean isNodata() { + return isnodata; + } + + public void setIsNodata(boolean isnodata) { + this.isnodata = isnodata; + } + + public boolean getReserved() { + return reserved; + } + + public void setReserved(boolean reserved) { + this.reserved = reserved; + } + + public void setNoDataValue(Number noDataValue) { + this.noDataValue = noDataValue; + } + + public Number getNoDataValue() { + return noDataValue; + } + + public void setDatas(byte[] datas) { + this.datas = datas; + } + + public byte[] getDatas() { + return datas; + } + + public int getNbBytePerPixel() { + return WKBRasterConstants.getNbBytePerPixel(pixelType); + } + + public int getDataBufferType() { + return WKBRasterConstants.getDataBufferType(pixelType); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("WKB Raster Band :"); + sb.append("\n- pixel type : ").append(pixelType); + sb.append("\n- offdatabase : ").append(offdatabase); + sb.append("\n- hasnodata : ").append(hasnodata); + sb.append("\n- is no data : ").append(isnodata); + sb.append("\n- reserved : ").append(reserved); + sb.append("\n- no data value : ").append(noDataValue); + return sb.toString(); + } + + +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterConstants.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterConstants.java new file mode 100644 index 0000000..bccc29d --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterConstants.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.awt.image.DataBuffer; + +/** + * WKB raster constants, used in postGIS 2. + * + * Specification : + * https://postgis.net/docs/RT_reference.html + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class WKBRasterConstants { + + /** 1-bit boolean */ + public static final int PT_1BB = 0; + /** 2-bit unsigned integer */ + public static final int PT_2BUI = 1; + /** 4-bit unsigned integer */ + public static final int PT_4BUI = 2; + /** 8-bit signed integer */ + public static final int PT_8BSI = 3; + /** 8-bit unsigned integer */ + public static final int PT_8BUI = 4; + /** 16-bit signed integer */ + public static final int PT_16BSI = 5; + /** 16-bit unsigned integer */ + public static final int PT_16BUI = 6; + /** 32-bit signed integer */ + public static final int PT_32BSI = 7; + /** 32-bit unsigned integer */ + public static final int PT_32BUI = 8; + /** 32-bit float */ + public static final int PT_32BF = 10; + /** 64-bit float */ + public static final int PT_64BF = 11; + public static final int PT_END = 13; + + public static final int BANDTYPE_FLAGS_MASK = 0xF0; + public static final int BANDTYPE_PIXTYPE_MASK = 0x0F; + public static final int BANDTYPE_FLAG_OFFDB = 1 << 7; + public static final int BANDTYPE_FLAG_HASNODATA = 1 << 6; + public static final int BANDTYPE_FLAG_ISNODATA = 1 << 5; + public static final int BANDTYPE_FLAG_RESERVED3 =1 << 4; + + private WKBRasterConstants() {} + + public static int getNbBytePerPixel(int pixelType) { + switch (pixelType) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: + case PT_8BSI: + return 1; + case PT_16BSI: + case PT_16BUI: + return 2; + case PT_32BSI: + case PT_32BUI: + case PT_32BF: + return 4; + case PT_64BF: + return 8; + default: + throw new IllegalArgumentException("unknowned pixel type : " + pixelType); + } + } + + public static int getDataBufferType(int pixelType) { + switch (pixelType) { + case PT_1BB: + case PT_2BUI: + case PT_4BUI: + case PT_8BUI: + case PT_8BSI: + return DataBuffer.TYPE_BYTE; + case PT_16BSI: + return DataBuffer.TYPE_SHORT; + case PT_16BUI: + return DataBuffer.TYPE_USHORT; + case PT_32BSI: + return DataBuffer.TYPE_INT; + case PT_32BUI: + return DataBuffer.TYPE_INT; + case PT_32BF: + return DataBuffer.TYPE_FLOAT; + case PT_64BF: + return DataBuffer.TYPE_DOUBLE; + default: + throw new IllegalArgumentException("unknowned pixel type : " + pixelType); + } + } + + public static int getPixelType(int dataBufferType) { + switch (dataBufferType) { + case DataBuffer.TYPE_BYTE: + return PT_8BUI; + case DataBuffer.TYPE_SHORT: + return PT_16BSI; + case DataBuffer.TYPE_USHORT: + return PT_16BUI; + case DataBuffer.TYPE_INT: + return PT_32BSI; + case DataBuffer.TYPE_FLOAT: + return PT_32BF; + case DataBuffer.TYPE_DOUBLE: + return PT_64BF; + default: + throw new IllegalArgumentException("unknowned data buffer type : " + dataBufferType); + } + } + +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterReader.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterReader.java new file mode 100644 index 0000000..c0c970a --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterReader.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.awt.Point; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +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 org.apache.sis.coverage.grid.GridCoverage; +import org.apache.sis.coverage.grid.GridCoverageBuilder; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.internal.coverage.j2d.ColorModelFactory; +import org.apache.sis.internal.coverage.j2d.RasterFactory; +import org.apache.sis.internal.referencing.j2d.AffineTransform2D; +import org.apache.sis.referencing.CRS; +import static org.apache.sis.internal.sql.postgis.WKBRasterConstants.*; +import org.opengis.referencing.NoSuchAuthorityCodeException; +import org.opengis.referencing.crs.CRSAuthorityFactory; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.util.FactoryException; + +/** + * WKB Raster Reader, used in postGIS 2 but can be used elsewhere. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class WKBRasterReader { + + private AffineTransform2D gridToCRS; + private int srid; + + 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 GridCoverage 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 GridCoverage 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; + final CoordinateReferenceSystem crs; + if (authorityFactory != null) { + crs = authorityFactory.createCoordinateReferenceSystem(epsgCode); + } else { + crs = CRS.forCode(epsgCode); + } + gcb.setDomain(new GridGeometry(null, PixelInCell.CELL_CENTER, getGridToCRS(), crs)); + gcb.setValues(image); + return gcb.build(); + } + + /** + * 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.createRaster( + db, width, height, 1, scanlineStride, bankIndices, bankOffsets, new Point(0,0)); + + } else { + raster = RasterFactory.createGrayScaleImage(dataBufferType, width, height, bands.length, 0, 0, 100).getRaster(); + 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 = ColorModelFactory.createGrayScale(sm.getDataType(), raster.getNumBands(), 0, min, max); + return new BufferedImage(cm, raster, false, null); + } +} diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterWriter.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterWriter.java new file mode 100644 index 0000000..a27fcc2 --- /dev/null +++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/postgis/WKBRasterWriter.java @@ -0,0 +1,330 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.awt.geom.AffineTransform; +import java.awt.image.ComponentSampleModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferDouble; +import java.awt.image.DataBufferFloat; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBufferShort; +import java.awt.image.DataBufferUShort; +import java.awt.image.Raster; +import java.awt.image.RenderedImage; +import java.awt.image.SampleModel; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.sis.coverage.grid.GridCoverage; +import org.apache.sis.coverage.grid.GridGeometry; +import org.apache.sis.referencing.IdentifiedObjects; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.datum.PixelInCell; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.util.FactoryException; + +/** + * WKB Raster Writer, used in postGIS 2 but can be used elsewhere. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +final class WKBRasterWriter { + + public WKBRasterWriter() { + } + + /** + * Reset values before new write call. + */ + public void reset() { + } + + /** + * Encode given coverage in Postgis WKB. + * + * @param coverage : grid coverage 2d , not null + * @return byte[] encoded image + * @throws IOException + */ + public byte[] write(final GridCoverage coverage) throws IOException, FactoryException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(coverage, out); + return out.toByteArray(); + } + + /** + * Encode given coverage in Postgis WKB. + * + * @param coverage : grid coverage 2d , not null + * @param stream : output stream to write in + * @throws IOException + */ + public void write(final GridCoverage coverage, final OutputStream stream) throws IOException, FactoryException { + write(coverage, stream, true); + } + + /** + * Encode given coverage in Postgis WKB. + * + * @param coverage : grid coverage 2d , not null + * @param stream : output stream to write in + * @param littleEndian : wanted value encoding + * @throws IOException + */ + public void write(final GridCoverage coverage, final OutputStream stream, final boolean littleEndian) + throws IOException, FactoryException { + final GridGeometry gridGeometry = coverage.getGridGeometry(); + final CoordinateReferenceSystem crs = gridGeometry.getCoordinateReferenceSystem(); + final Integer srid = IdentifiedObjects.lookupEPSG(crs); + if (srid == null) { + throw new IOException("CoordinateReferenceSystem does not have an EPSG code."); + } + final MathTransform gridToCRS = coverage.getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER); + if (!(gridToCRS instanceof AffineTransform)) { + throw new IOException("Coverage GridToCRS transform is not affine."); + } + final RenderedImage image = coverage.render(null); + + write(image, (AffineTransform) gridToCRS, srid, stream); + } + + /** + * Encode given image in Postgis WKB. + * + * @param image : image , not null + * @param gridToCRS : image grid to crs, can be null + * @param srid : image srid + * @return byte[] encoded image + * @throws IOException + */ + public byte[] write(final RenderedImage image, final AffineTransform gridToCRS, + final int srid) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(image, gridToCRS, srid, out); + return out.toByteArray(); + } + + /** + * Encode given image in Postgis WKB. + * + * @param image : image , not null + * @param gridToCRS : image grid to crs, can be null + * @param srid : image srid + * @return byte[] encoded image + * @throws IOException + */ + public byte[] write(final Raster image, final AffineTransform gridToCRS, + final int srid) throws IOException { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(image, gridToCRS, srid, out); + return out.toByteArray(); + } + + /** + * Encode given image in Postgis WKB. + * + * @param image : image , not null + * @param gridToCRS : image grid to crs, can be null + * @param srid : image srid + * @param stream : output stream to write in + * @throws IOException + */ + public void write(final RenderedImage image, AffineTransform gridToCRS, + final int srid, final OutputStream stream) throws IOException { + Raster raster; + if (image.getNumXTiles() == 1 && image.getNumYTiles() == 1) { + raster = image.getTile(image.getMinTileX(), image.getMinTileY()); + } else { + raster = image.getData(); + } + write(raster, gridToCRS, srid, stream, false); + } + + /** + * Encode given image in Postgis WKB. + * + * @param image : image , not null + * @param gridToCRS : image grid to crs, can be null + * @param srid : image srid + * @param stream : output stream to write in + * @throws IOException + */ + public void write(final Raster image, AffineTransform gridToCRS, + final int srid, final OutputStream stream) throws IOException { + write(image, gridToCRS, srid, stream, false); + } + + /** + * Encode given image in Postgis WKB. + * + * @param image : image , not null + * @param gridToCRS : image grid to crs, can be null + * @param srid : image srid + * @param stream : output stream to write in + * @param littleEndian : wanted value encoding + * @throws IOException + */ + public void write(final Raster image, AffineTransform gridToCRS, + final int srid, OutputStream stream, final boolean littleEndian) throws IOException { + if (gridToCRS == null) { + gridToCRS = new AffineTransform(); + } + + if (!(stream instanceof BufferedOutputStream)) { + stream = new BufferedOutputStream(stream); + } + + final DataOutput ds; + if (littleEndian) { + ds = new LEDataOutputStream(stream); + } else { + ds = new DataOutputStream(stream); + } + + final SampleModel sm = image.getSampleModel(); + final Raster raster = image; + final int nbBand = sm.getNumBands(); + final int width = image.getWidth(); + final int height = image.getHeight(); + int databufferType = sm.getDataType(); + if (databufferType == DataBuffer.TYPE_INT) { + //special case, most image compression bands on single int when it would be byte + if (sm.getSampleSize()[0] <= 8) { + databufferType = DataBuffer.TYPE_BYTE; + } + } + + final int pixelType = WKBRasterConstants.getPixelType(databufferType); + final int bytePerpixel = WKBRasterConstants.getNbBytePerPixel(pixelType); + + //endianess + ds.write( littleEndian ? 1 : 0 ); + //version, 0 for now + ds.writeShort(0); + //number of bands + ds.writeShort(nbBand); + //grid to crs + ds.writeDouble(gridToCRS.getScaleX()); + ds.writeDouble(gridToCRS.getScaleY()); + ds.writeDouble(gridToCRS.getTranslateX()); + ds.writeDouble(gridToCRS.getTranslateY()); + ds.writeDouble(gridToCRS.getShearX()); + ds.writeDouble(gridToCRS.getShearY()); + //write srid + ds.writeInt(srid); + //width and height + ds.writeShort(width); + ds.writeShort(height); + + + if (!littleEndian && nbBand == 1 && sm instanceof ComponentSampleModel) { + DataBuffer dataBuffer = raster.getDataBuffer(); + final byte flags = (byte) pixelType; + + if (dataBuffer instanceof DataBufferByte) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + byte[] pixelData = ((DataBufferByte) dataBuffer).getData(); + ds.write(pixelData); + stream.flush(); + return; + + } else if (dataBuffer instanceof DataBufferUShort) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + short[] pixelData = ((DataBufferUShort) dataBuffer).getData(); + for (short p : pixelData) ds.writeShort(p); + stream.flush(); + return; + + } else if (dataBuffer instanceof DataBufferShort) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + short[] pixelData = ((DataBufferShort) dataBuffer).getData(); + for (short p : pixelData) ds.writeShort(p); + stream.flush(); + return; + + } else if (dataBuffer instanceof DataBufferInt) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + int[] pixelData = ((DataBufferInt) dataBuffer).getData(); + for (int p : pixelData) ds.writeInt(p); + stream.flush(); + return; + + } else if (dataBuffer instanceof DataBufferFloat) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + float[] pixelData = ((DataBufferFloat) dataBuffer).getData(); + for (float p : pixelData) ds.writeFloat(p); + stream.flush(); + return; + + } else if (dataBuffer instanceof DataBufferDouble) { + ds.write(flags); + ds.write(new byte[bytePerpixel]); + double[] pixelData = ((DataBufferDouble) dataBuffer).getData(); + for (double p : pixelData) ds.writeDouble(p); + stream.flush(); + return; + } + // fallback on pixel by pixel writing + } + + //write each band + for (int b = 0; b < nbBand; b++) { + + // band description + final byte flags = (byte) pixelType; + // OffDatabase = false + // TODO HasNodata : we don't have informations for no data + // this would requiere a SampleDimension object + // IsNodata = false + // Reserved = false + ds.write(flags); + + // TODO no data value + ds.write(new byte[bytePerpixel]); + + //write values + for (int y = raster.getMinY(), maxy = raster.getMinY() + height; y < maxy; y++) { + for (int x = raster.getMinX(), maxx = raster.getMinX() + width; x < maxx; x++) { + switch (databufferType) { + case DataBuffer.TYPE_BYTE : ds.writeByte( (byte) raster.getSample(x, y, b)); break; + case DataBuffer.TYPE_SHORT : ds.writeShort( (short) raster.getSample(x, y, b)); break; + case DataBuffer.TYPE_USHORT : ds.writeShort( (short) raster.getSample(x, y, b)); break; + case DataBuffer.TYPE_INT : ds.writeInt( raster.getSample(x, y, b)); break; + case DataBuffer.TYPE_FLOAT : ds.writeFloat( raster.getSampleFloat(x, y, b)); break; + case DataBuffer.TYPE_DOUBLE : ds.writeDouble( raster.getSampleDouble(x, y, b)); break; + } + } + } + + } + stream.flush(); + } + +} diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/LEStreamTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/LEStreamTest.java new file mode 100644 index 0000000..a6b1ac9 --- /dev/null +++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/internal/sql/postgis/LEStreamTest.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sis.internal.sql.postgis; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import org.apache.sis.test.TestCase; +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * Little Endian data input and output stream tests. + * + * @author Johann Sorel (Geomatys) + * @version 2.0 + * @since 2.0 + * @module + */ +public class LEStreamTest extends TestCase { + + private static final float DELTA = 0.0000001f; + private static final byte[] DATA = new byte[]{ + (byte)0x00, //boolean false + (byte)0x01, //boolean true + (byte)0xF2, //ubyte 242 + (byte)0xFA, //byte -6 + (byte)0x01,(byte)0x02,(byte)0x03,(byte)0x04,(byte)0x05, //byte array 1,2,3,4,5 + (byte)0x70,(byte)0xE0, //ushort 57456 + (byte)0x05,(byte)0xC1, //short -16123 + (byte)0x79,(byte)0x00, //char 'y' + (byte)0xFB,(byte)0x24,(byte)0x0E,(byte)0x2F, //int 789456123 + (byte)0x15,(byte)0xCD,(byte)0x5B,(byte)0x07,(byte)0x00,(byte)0x00,(byte)0x00,(byte)0x00, //long 123456789 + (byte)0xB6,(byte)0xD3,(byte)0xA0,(byte)0x43, //float 321.654 + (byte)0x21,(byte)0xB0,(byte)0x72,(byte)0x68,(byte)0x91,(byte)0xDA,(byte)0x8E,(byte)0x40 //double 987.321 + }; + + @Test + public void readTest() throws IOException{ + + final LEDataInputStream ds = new LEDataInputStream(new ByteArrayInputStream(DATA)); + assertEquals(0, ds.getPosition()); + + //byte types + assertEquals(false, ds.readBoolean()); + assertEquals(1, ds.getPosition()); + assertEquals(true, ds.readBoolean()); + assertEquals(2, ds.getPosition()); + assertEquals(242, ds.readUnsignedByte()); + assertEquals(3, ds.getPosition()); + assertEquals(-6, ds.readByte()); + assertEquals(4, ds.getPosition()); + final byte[] array = new byte[5]; + ds.readFully(array); + assertArrayEquals(new byte[]{1,2,3,4,5}, array); + assertEquals(9, ds.getPosition()); + //primitive types + assertEquals(57456, ds.readUnsignedShort()); + assertEquals(11, ds.getPosition()); + assertEquals(-16123, ds.readShort()); + assertEquals(13, ds.getPosition()); + assertEquals('y', ds.readChar()); + assertEquals(15, ds.getPosition()); + assertEquals(789456123, ds.readInt()); + assertEquals(19, ds.getPosition()); + assertEquals(123456789, ds.readLong()); + assertEquals(27, ds.getPosition()); + assertEquals(321.654f, ds.readFloat(),DELTA); + assertEquals(31, ds.getPosition()); + assertEquals(987.321, ds.readDouble(), DELTA); + assertEquals(39, ds.getPosition()); + + } + + @Test + public void writeTest() throws IOException{ + + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + final LEDataOutputStream ds = new LEDataOutputStream(out); + assertEquals(0, ds.getPosition()); + + //byte types + ds.writeBoolean(false); + assertEquals(1, ds.getPosition()); + ds.writeBoolean(true); + assertEquals(2, ds.getPosition()); + ds.writeByte(242); + assertEquals(3, ds.getPosition()); + ds.writeByte(-6); + assertEquals(4, ds.getPosition()); + ds.write(new byte[]{1,2,3,4,5}); + assertEquals(9, ds.getPosition()); + //primitive types + ds.writeShort(57456); + assertEquals(11, ds.getPosition()); + ds.writeShort(-16123); + assertEquals(13, ds.getPosition()); + ds.writeChar('y'); + assertEquals(15, ds.getPosition()); + ds.writeInt(789456123); + assertEquals(19, ds.getPosition()); + ds.writeLong(123456789); + assertEquals(27, ds.getPosition()); + ds.writeFloat(321.654f); + assertEquals(31, ds.getPosition()); + ds.writeDouble(987.321); + assertEquals(39, ds.getPosition()); + ds.flush(); + + final byte[] res = out.toByteArray(); + assertArrayEquals(DATA, res); + + } + + +}