/* * 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; } }