/*
* 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.oscimap;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement;
import org.oscim.core.Tag;
import org.oscim.core.Tile;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.source.PbfDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TileDecoder extends PbfDecoder {
static final Logger log = LoggerFactory.getLogger(TileDecoder.class);
private final static float REF_TILE_SIZE = 4096.0f;
private static final int TAG_TILE_TAGS = 1;
private static final int TAG_TILE_WAYS = 2;
private static final int TAG_TILE_POLY = 3;
private static final int TAG_TILE_NODES = 4;
private static final int TAG_WAY_TAGS = 11;
private static final int TAG_WAY_INDEX = 12;
private static final int TAG_WAY_COORDS = 13;
private static final int TAG_WAY_LAYER = 21;
private static final int TAG_WAY_NUM_TAGS = 1;
private static final int TAG_WAY_NUM_INDICES = 2;
private static final int TAG_WAY_NUM_COORDS = 3;
private static final int TAG_NODE_TAGS = 11;
private static final int TAG_NODE_COORDS = 12;
private static final int TAG_NODE_LAYER = 21;
private static final int TAG_NODE_NUM_TAGS = 1;
private static final int TAG_NODE_NUM_COORDS = 2;
private int MAX_TILE_TAGS = 100;
private Tag[] curTags = new Tag[MAX_TILE_TAGS];
private int mCurTagCnt;
private ITileDataSink mSink;
private float mScale;
private Tile mTile;
private final MapElement mElem;
TileDecoder() {
mElem = new MapElement();
}
@Override
public boolean decode(Tile tile, ITileDataSink sink, InputStream is)
throws IOException {
setInputStream(is);
mTile = tile;
mSink = sink;
mScale = REF_TILE_SIZE / Tile.SIZE;
return decode();
}
private static final int MAX_TAGS_CACHE = 100;
private static Map<String, Tag> tagHash =
Collections.synchronizedMap(new LinkedHashMap<String, Tag>(MAX_TAGS_CACHE,
0.75f,
true) {
private static final long serialVersionUID = 1L;
//@Override
//protected boolean removeEldestEntry(Entry<String, Tag> e) {
//if (size() < MAX_TAGS_CACHE)
//return false;
//return true;
//}
});
private boolean decode() throws IOException {
int val;
mCurTagCnt = 0;
while (hasData() && (val = decodeVarint32()) > 0) {
// read tag and wire type
int tag = (val >> 3);
switch (tag) {
case TAG_TILE_TAGS:
decodeTileTags();
break;
case TAG_TILE_WAYS:
decodeTileWays(false);
break;
case TAG_TILE_POLY:
decodeTileWays(true);
break;
case TAG_TILE_NODES:
decodeTileNodes();
break;
default:
log.debug("invalid type for tile: " + tag);
return false;
}
}
return true;
}
private boolean decodeTileTags() throws IOException {
String tagString = decodeString();
if (tagString == null || tagString.length() == 0) {
curTags[mCurTagCnt++] = new Tag(Tag.KEY_NAME, "...");
return false;
}
Tag tag = tagHash.get(tagString);
if (tag == null) {
if (tagString.startsWith(Tag.KEY_NAME))
tag = new Tag(Tag.KEY_NAME, tagString.substring(5), false);
else
tag = Tag.parse(tagString);
if (tag != null)
tagHash.put(tagString, tag);
}
if (mCurTagCnt >= MAX_TILE_TAGS) {
MAX_TILE_TAGS = mCurTagCnt + 10;
Tag[] tmp = new Tag[MAX_TILE_TAGS];
System.arraycopy(curTags, 0, tmp, 0, mCurTagCnt);
curTags = tmp;
}
curTags[mCurTagCnt++] = tag;
return true;
}
private boolean decodeTileWays(boolean polygon) throws IOException {
int bytes = decodeVarint32();
int end = position() + bytes;
int indexCnt = 0;
int tagCnt = 0;
int coordCnt = 0;
int layer = 5;
boolean fail = false;
while (position() < end) {
// read tag and wire type
int val = decodeVarint32();
if (val == 0)
break;
int tag = (val >> 3);
switch (tag) {
case TAG_WAY_TAGS:
if (!decodeWayTags(tagCnt))
return false;
break;
case TAG_WAY_INDEX:
decodeWayIndices(indexCnt);
break;
case TAG_WAY_COORDS:
if (coordCnt == 0) {
log.debug(mTile + " no coordinates");
}
mElem.ensurePointSize(coordCnt, false);
int cnt = decodeInterleavedPoints(mElem.points, mScale);
if (cnt != coordCnt) {
log.debug(mTile + " wrong number of coordintes "
+ coordCnt + "/" + cnt);
fail = true;
}
break;
case TAG_WAY_LAYER:
layer = decodeVarint32();
break;
case TAG_WAY_NUM_TAGS:
tagCnt = decodeVarint32();
break;
case TAG_WAY_NUM_INDICES:
indexCnt = decodeVarint32();
break;
case TAG_WAY_NUM_COORDS:
coordCnt = decodeVarint32();
break;
default:
log.debug("X invalid type for way: " + tag);
}
}
if (fail || indexCnt == 0 || tagCnt == 0) {
log.debug("failed reading way: bytes:" + bytes + " index:"
//+ (tags != null ? tags.toString() : "...") + " "
+ indexCnt + " " + coordCnt + " " + tagCnt);
return false;
}
// FIXME, remove all tiles from cache then remove this below
//if (layer == 0)
// layer = 5;
mElem.type = polygon ? GeometryType.POLY : GeometryType.LINE;
mElem.setLayer(layer);
mSink.process(mElem);
return true;
}
private boolean decodeTileNodes() throws IOException {
int bytes = decodeVarint32();
int end = position() + bytes;
int tagCnt = 0;
int coordCnt = 0;
byte layer = 0;
while (position() < end) {
// read tag and wire type
int val = decodeVarint32();
if (val == 0)
break;
int tag = (val >> 3);
switch (tag) {
case TAG_NODE_TAGS:
if (!decodeWayTags(tagCnt))
return false;
break;
case TAG_NODE_COORDS:
int cnt = decodeNodeCoordinates(coordCnt, layer);
if (cnt != coordCnt) {
log.debug("X wrong number of coordintes");
return false;
}
break;
case TAG_NODE_LAYER:
layer = (byte) decodeVarint32();
break;
case TAG_NODE_NUM_TAGS:
tagCnt = decodeVarint32();
break;
case TAG_NODE_NUM_COORDS:
coordCnt = decodeVarint32();
break;
default:
log.debug("X invalid type for node: " + tag);
}
}
return true;
}
private int decodeNodeCoordinates(int numNodes, byte layer)
throws IOException {
int bytes = decodeVarint32();
fillBuffer(bytes);
int cnt = 0;
int end = position() + bytes;
// read repeated sint32
int lastX = 0;
int lastY = 0;
float[] coords = mElem.ensurePointSize(numNodes, false);
while (position() < end && cnt < numNodes) {
int lon = deZigZag(decodeVarint32());
int lat = deZigZag(decodeVarint32());
lastX = lon + lastX;
lastY = lat + lastY;
coords[cnt++] = lastX / mScale;
coords[cnt++] = Tile.SIZE - lastY / mScale;
}
mElem.index[0] = (short) numNodes;
mElem.type = GeometryType.POINT;
mElem.setLayer(layer);
mSink.process(mElem);
return cnt;
}
private boolean decodeWayTags(int tagCnt) throws IOException {
int bytes = decodeVarint32();
mElem.tags.clear();
int cnt = 0;
int end = position() + bytes;
int max = mCurTagCnt;
for (; position() < end; cnt++) {
int tagNum = decodeVarint32();
if (tagNum < 0 || cnt == tagCnt) {
log.debug("NULL TAG: " + mTile
+ " invalid tag:" + tagNum
+ " " + tagCnt + "/" + cnt);
continue;
}
if (tagNum < Tags.MAX) {
mElem.tags.add(Tags.tags[tagNum]);
continue;
}
tagNum -= Tags.LIMIT;
if (tagNum >= 0 && tagNum < max) {
mElem.tags.add(curTags[tagNum]);
} else {
log.debug("NULL TAG: " + mTile
+ " could find tag:"
+ tagNum + " " + tagCnt
+ "/" + cnt);
}
}
if (tagCnt != cnt) {
log.debug("NULL TAG: " + mTile);
return false;
}
return true;
}
private int decodeWayIndices(int indexCnt) throws IOException {
mElem.ensureIndexSize(indexCnt, false);
decodeVarintArray(indexCnt, mElem.index);
int[] index = mElem.index;
int coordCnt = 0;
for (int i = 0; i < indexCnt; i++) {
coordCnt += index[i];
index[i] *= 2;
}
// set end marker
if (indexCnt < index.length)
index[indexCnt] = -1;
return coordCnt;
}
//@Override
protected int decodeInterleavedPoints(float[] coords, float scale)
throws IOException {
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;
int val;
while (pos < end) {
if (buf[pos] >= 0) {
val = buf[pos++];
} else if (buf[pos + 1] >= 0) {
val = (buf[pos++] & 0x7f)
| buf[pos++] << 7;
} else if (buf[pos + 2] >= 0) {
val = (buf[pos++] & 0x7f)
| (buf[pos++] & 0x7f) << 7
| (buf[pos++]) << 14;
} else if (buf[pos + 3] >= 0) {
val = (buf[pos++] & 0x7f)
| (buf[pos++] & 0x7f) << 7
| (buf[pos++] & 0x7f) << 14
| (buf[pos++]) << 21;
} else {
val = (buf[pos++] & 0x7f)
| (buf[pos++] & 0x7f) << 7
| (buf[pos++] & 0x7f) << 14
| (buf[pos++] & 0x7f) << 21
| (buf[pos]) << 28;
if (buf[pos++] < 0)
throw INVALID_VARINT;
}
// zigzag decoding
int s = ((val >>> 1) ^ -(val & 1));
if (even) {
lastX = lastX + s;
coords[cnt++] = lastX / scale;
even = false;
} else {
lastY = lastY + s;
coords[cnt++] = Tile.SIZE - lastY / scale;
even = true;
}
}
if (pos != bufferPos + bytes)
throw INVALID_PACKED_SIZE;
bufferPos = pos;
// return number of points read
return cnt;
}
}