/*
* Copyright 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program 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, either version 3 of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.tiling.source;
import java.io.IOException;
import java.io.InputStream;
import org.oscim.core.GeometryBuffer;
import org.oscim.utils.UTF8Decoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class PbfDecoder implements ITileDecoder {
static final Logger log = LoggerFactory.getLogger(PbfDecoder.class);
private final static int S1 = 7;
private final static int S2 = 14;
private final static int S3 = 21;
private final static int S4 = 28;
private final static int M1 = (1 << S1) - 1;
private final static int M2 = (1 << S2) - 1;
private final static int M3 = (1 << S3) - 1;
private final static int M4 = (1 << S4) - 1;
protected static final boolean debug = false;
static class ProtobufException extends IOException {
private static final long serialVersionUID = 1L;
public ProtobufException(String detailMessage) {
super(detailMessage);
}
}
final static ProtobufException TRUNCATED_MSG =
new ProtobufException("truncated msg");
protected final static ProtobufException INVALID_VARINT =
new ProtobufException("invalid varint");
protected final static ProtobufException INVALID_PACKED_SIZE =
new ProtobufException("invalid message size");
protected void error(String msg) throws IOException {
throw new ProtobufException(msg);
}
private final static int BUFFER_SIZE = 1 << 15; // 32kb
protected byte[] buffer = new byte[BUFFER_SIZE];
// position in buffer
protected int bufferPos;
// bytes available in buffer
protected int bufferFill;
// offset of buffer in message
private int mBufferOffset;
// overall bytes of message read
private int mMsgPos;
private InputStream mInputStream;
private final UTF8Decoder mStringDecoder;
public PbfDecoder() {
mStringDecoder = new UTF8Decoder();
}
public void setInputStream(InputStream is) {
mInputStream = is;
bufferFill = 0;
bufferPos = 0;
mBufferOffset = 0;
mMsgPos = 0;
}
protected int decodeVarint32() throws IOException {
int bytesLeft = 0;
int val = 0;
for (int shift = 0; shift < 32; shift += 7) {
if (bytesLeft == 0)
bytesLeft = fillBuffer(1);
byte b = buffer[bufferPos++];
val |= (b & 0x7f) << shift;
if (b >= 0)
return val;
bytesLeft--;
}
throw INVALID_VARINT;
}
protected long decodeVarint64() throws IOException {
int bytesLeft = 0;
long val = 0;
for (int shift = 0; shift < 64; shift += 7) {
if (bytesLeft == 0)
bytesLeft = fillBuffer(1);
byte b = buffer[bufferPos++];
val |= (long) (b & 0x7f) << shift;
if (b >= 0)
return val;
bytesLeft--;
}
throw INVALID_VARINT;
}
protected String decodeString() throws IOException {
String result;
final int size = decodeVarint32();
fillBuffer(size);
if (mStringDecoder == null)
result = new String(buffer, bufferPos, size, "UTF-8");
else
result = mStringDecoder.decode(buffer, bufferPos, size);
bufferPos += size;
return result;
}
protected float decodeFloat() throws IOException {
if (bufferPos + 4 > bufferFill)
fillBuffer(4);
int val = (buffer[bufferPos++] & 0xFF
| (buffer[bufferPos++] & 0xFF) << 8
| (buffer[bufferPos++] & 0xFF) << 16
| (buffer[bufferPos++] & 0xFF) << 24);
return Float.intBitsToFloat(val);
}
protected double decodeDouble() throws IOException {
if (bufferPos + 8 > bufferFill)
fillBuffer(8);
long val = ((long) buffer[bufferPos++] & 0xFF
| ((long) buffer[bufferPos++] & 0xFF) << 8
| ((long) buffer[bufferPos++] & 0xFF) << 16
| ((long) buffer[bufferPos++] & 0xFF) << 24
| ((long) buffer[bufferPos++] & 0xFF) << 32
| ((long) buffer[bufferPos++] & 0xFF) << 40
| ((long) buffer[bufferPos++] & 0xFF) << 48
| ((long) buffer[bufferPos++] & 0xFF) << 56);
return Double.longBitsToDouble(val);
}
protected boolean decodeBool() throws IOException {
if (bufferPos + 1 > bufferFill)
fillBuffer(1);
return buffer[bufferPos++] != 0;
}
protected int decodeInterleavedPoints(GeometryBuffer geom, float scale)
throws IOException {
float[] points = geom.points;
int bytes = decodeVarint32();
fillBuffer(bytes);
int cnt = 0;
int lastX = 0;
int lastY = 0;
boolean even = true;
byte[] buf = buffer;
int pos = bufferPos;
int end = pos + bytes;
while (pos < end) {
byte b = buf[pos++];
int val = b;
if (b < 0) {
b = buf[pos++];
val = (val & M1) | (b << S1);
if (b < 0) {
b = buf[pos++];
val = (val & M2) | (b << S2);
if (b < 0) {
b = buf[pos++];
val = (val & M3) | (b << S3);
if (b < 0) {
b = buf[pos++];
val = (val & M4) | (b << S4);
if (b < 0)
throw INVALID_VARINT;
}
}
}
}
// zigzag decoding
int s = ((val >>> 1) ^ -(val & 1));
if (even) {
lastX = lastX + s;
points[cnt++] = lastX / scale;
even = false;
} else {
lastY = lastY + s;
points[cnt++] = lastY / scale;
even = true;
}
}
if (pos != bufferPos + bytes)
throw INVALID_PACKED_SIZE;
bufferPos = pos;
geom.pointPos = cnt;
// return number of points read
return (cnt >> 1);
}
protected int decodeInterleavedPoints3D(float[] coords, float scale)
throws IOException {
int bytes = decodeVarint32();
fillBuffer(bytes);
int cnt = 0;
int lastX = 0;
int lastY = 0;
int lastZ = 0;
int cur = 0;
byte[] buf = buffer;
int pos = bufferPos;
int end = pos + bytes;
while (pos < end) {
byte b = buf[pos++];
int val = b;
if (b < 0) {
b = buf[pos++];
val = (val & M1) | (b << S1);
if (b < 0) {
b = buf[pos++];
val = (val & M2) | (b << S2);
if (b < 0) {
b = buf[pos++];
val = (val & M3) | (b << S3);
if (b < 0) {
b = buf[pos++];
val = (val & M4) | (b << S4);
if (b < 0)
throw INVALID_VARINT;
}
}
}
}
// zigzag decoding
int s = ((val >>> 1) ^ -(val & 1));
if (cur == 0) {
lastX = lastX + s;
coords[cnt++] = lastX / scale;
} else if (cur == 1) {
lastY = lastY + s;
coords[cnt++] = lastY / scale;
} else {
lastZ = lastZ + s;
coords[cnt++] = lastZ / scale;
}
cur = (cur + 1) % 3;
}
if (pos != bufferPos + bytes)
throw INVALID_PACKED_SIZE;
bufferPos = pos;
// return number of points read
//FIXME inconsitent with 3d version!
return cnt;
}
protected static int deZigZag(int val) {
return ((val >>> 1) ^ -(val & 1));
}
public void decodeVarintArray(int num, int[] array) throws IOException {
int bytes = decodeVarint32();
fillBuffer(bytes);
final byte[] buf = buffer;
int pos = bufferPos;
int cnt = 0;
for (int end = pos + bytes; pos < end; cnt++) {
if (cnt == num)
throw new ProtobufException("invalid array size " + num);
byte b = buf[pos++];
int val = b;
if (b < 0) {
b = buf[pos++];
val = (val & M1) | (b << S1);
if (b < 0) {
b = buf[pos++];
val = (val & M2) | (b << S2);
if (b < 0) {
b = buf[pos++];
val = (val & M3) | (b << S3);
if (b < 0) {
b = buf[pos++];
val = (val & M4) | (b << S4);
if (b < 0)
throw INVALID_VARINT;
}
}
}
}
array[cnt] = val;
}
if (pos != bufferPos + bytes)
throw INVALID_PACKED_SIZE;
bufferPos = pos;
}
/**
* fill short array from packed uint32. Array values must be positive
* as the end will be marked by -1 if the resulting array is larger
* than the input!
*/
protected short[] decodeUnsignedVarintArray(short[] array) throws IOException {
int bytes = decodeVarint32();
int arrayLength = 0;
if (array == null) {
arrayLength = 32;
array = new short[32];
}
fillBuffer(bytes);
int cnt = 0;
final byte[] buf = buffer;
int pos = bufferPos;
for (int end = pos + bytes; pos < end; cnt++) {
byte b = buf[pos++];
int val = b;
if (b < 0) {
b = buf[pos++];
val = (val & M1) | (b << S1);
if (b < 0) {
b = buf[pos++];
val = (val & M2) | (b << S2);
if (b < 0) {
b = buf[pos++];
val = (val & M3) | (b << S3);
if (b < 0) {
b = buf[pos++];
val = (val & M4) | (b << S4);
if (b < 0)
throw INVALID_VARINT;
}
}
}
}
if (arrayLength <= cnt) {
arrayLength = cnt + 16;
short[] tmp = array;
array = new short[arrayLength];
System.arraycopy(tmp, 0, array, 0, cnt);
}
array[cnt] = (short) val;
}
if (pos != bufferPos + bytes)
throw INVALID_PACKED_SIZE;
bufferPos = pos;
if (arrayLength > cnt)
array[cnt] = -1;
return array;
}
// for use int packed varint decoders
protected int decodeVarint32Filled() throws IOException {
byte[] buf = buffer;
int pos = bufferPos;
byte b = buf[pos++];
int val = b;
if (b < 0) {
b = buf[pos++];
val = (val & M1) | (b << S1);
if (b < 0) {
b = buf[pos++];
val = (val & M2) | (b << S2);
if (b < 0) {
b = buf[pos++];
val = (val & M3) | (b << S3);
if (b < 0) {
b = buf[pos++];
val = (val & M4) | (b << S4);
if (b < 0)
throw INVALID_VARINT;
}
}
}
}
bufferPos = pos;
return val;
}
public boolean hasData() throws IOException {
//if (mBufferOffset + bufferPos >= mMsgEnd)
// return false;
return fillBuffer(1) > 0;
}
public int position() {
return mBufferOffset + bufferPos;
}
public int fillBuffer(int size) throws IOException {
int bytesLeft = bufferFill - bufferPos;
// check if buffer already contains the request bytes
if (bytesLeft >= size)
return bytesLeft;
int maxSize = buffer.length;
if (size > maxSize) {
if (debug)
log.debug("increase read buffer to " + size + " bytes");
maxSize = size;
byte[] tmp = buffer;
buffer = new byte[maxSize];
System.arraycopy(tmp, bufferPos, buffer, 0, bytesLeft);
mBufferOffset += bufferPos;
bufferPos = 0;
bufferFill = bytesLeft;
} else if (bytesLeft == 0) {
// just advance buffer offset and reset buffer
mBufferOffset += bufferPos;
bufferPos = 0;
bufferFill = 0;
} else if (bufferPos + size > maxSize) {
// copy bytes left to the beginning of buffer
if (debug)
log.debug("shift " + bufferFill + " " + bufferPos + " " + size);
System.arraycopy(buffer, bufferPos, buffer, 0, bytesLeft);
mBufferOffset += bufferPos;
bufferPos = 0;
bufferFill = bytesLeft;
}
while ((bufferFill - bufferPos) < size) {
int max = maxSize - bufferFill;
if (max <= 0) {
// should not be possible
throw new IOException("burp");
}
// read until requested size is available in buffer
int len = mInputStream.read(buffer, bufferFill, max);
if (len < 0) {
if (debug)
log.debug("finished reading {}", mMsgPos);
// finished reading, mark end
buffer[bufferFill] = 0;
return bufferFill - bufferPos;
}
mMsgPos += len;
bufferFill += len;
}
return bufferFill - bufferPos;
}
protected static int readUnsignedInt(InputStream is, byte[] buf) throws IOException {
// check 4 bytes available..
int read = 0;
int len = 0;
while (read < 4 && (len = is.read(buf, read, 4 - read)) >= 0)
read += len;
if (read < 4)
return read < 0 ? (read * 10) : read;
return decodeInt(buf, 0);
}
static int decodeInt(byte[] buffer, int offset) {
return buffer[offset] << 24 | (buffer[offset + 1] & 0xff) << 16
| (buffer[offset + 2] & 0xff) << 8
| (buffer[offset + 3] & 0xff);
}
}