diff --git a/src/org/oscim/database/MapDatabaseFactory.java b/src/org/oscim/database/MapDatabaseFactory.java
index 604ee4d7..d081c5a7 100644
--- a/src/org/oscim/database/MapDatabaseFactory.java
+++ b/src/org/oscim/database/MapDatabaseFactory.java
@@ -45,7 +45,8 @@ public final class MapDatabaseFactory {
case TEST_READER:
return new org.oscim.database.test.MapDatabase();
case PBMAP_READER:
- return new org.oscim.database.pbmap.MapDatabase();
+ //return new org.oscim.database.pbmap.MapDatabase();
+ return new org.oscim.database.mapnik.MapDatabase();
case OSCIMAP_READER:
return new org.oscim.database.oscimap.MapDatabase();
default:
diff --git a/src/org/oscim/database/mapnik/LwHttp.java b/src/org/oscim/database/mapnik/LwHttp.java
new file mode 100644
index 00000000..03facef8
--- /dev/null
+++ b/src/org/oscim/database/mapnik/LwHttp.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2013 Hannes Janetzek
+ *
+ * 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 .
+ */
+package org.oscim.database.mapnik;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.URL;
+import java.util.zip.InflaterInputStream;
+
+import org.oscim.core.Tile;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+public class LwHttp {
+ private static final String TAG = LwHttp.class.getName();
+ private final static int BUFFER_SIZE = 65536;
+
+ //
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ // position in buffer
+ int bufferPos;
+
+ // bytes available in buffer
+ int bufferFill;
+
+ // offset of buffer in message
+ private int mBufferOffset;
+
+ private String mHost;
+ private int mPort;
+ private InputStream mInputStream;
+
+ private int mMaxReq = 0;
+ private Socket mSocket;
+ private OutputStream mCommandStream;
+ private BufferedInputStream mResponseStream;
+ long mLastRequest = 0;
+ private SocketAddress mSockAddr;
+
+ //private final static byte[] RESPONSE_HTTP_OK = "HTTP/1.1 200 OK".getBytes();
+ private final static byte[] RESPONSE_HTTP_OK = "200 OK".getBytes();
+ private final static byte[] RESPONSE_CONTENT_LEN = "Content-Length: ".getBytes();
+ private final static int RESPONSE_EXPECTED_LIVES = 100;
+ private final static int RESPONSE_EXPECTED_TIMEOUT = 10000;
+
+ private byte[] REQUEST_GET_START;
+ private byte[] REQUEST_GET_END;
+
+ private byte[] mRequestBuffer;
+
+ boolean setServer(String urlString) {
+ urlString = "http://d1s11ojcu7opje.cloudfront.net/dev/764e0b8d";
+
+ URL url;
+ try {
+ url = new URL(urlString);
+ } catch (MalformedURLException e) {
+
+ e.printStackTrace();
+ return false;
+ //return new OpenResult("invalid url: " + options.get("url"));
+ }
+
+ int port = url.getPort();
+ if (port < 0)
+ port = 80;
+
+ String host = url.getHost();
+ String path = url.getPath();
+ Log.d(TAG, "open database: " + host + " " + port + " " + path);
+
+ REQUEST_GET_START = ("GET " + path).getBytes();
+ REQUEST_GET_END = (".vector.pbf HTTP/1.1\n" +
+ "User-Agent: Wget/1.13.4 (linux-gnu)\n" +
+ "Accept: */*\n" +
+ "Host: " + host + "\n" +
+ "Connection: Keep-Alive\n\n").getBytes();
+
+ mHost = host;
+ mPort = port;
+
+ mRequestBuffer = new byte[1024];
+ System.arraycopy(REQUEST_GET_START, 0,
+ mRequestBuffer, 0, REQUEST_GET_START.length);
+
+ return true;
+ }
+
+ void close() {
+ if (mSocket != null) {
+ try {
+ mSocket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ mSocket = null;
+ }
+ }
+ }
+
+ int readHeader() throws IOException {
+
+ InputStream is = mResponseStream;
+ mResponseStream.mark(1 << 16);
+
+ byte[] buf = buffer;
+ boolean first = true;
+ int read = 0;
+ int pos = 0;
+ int end = 0;
+ int len = 0;
+
+ int contentLength = 0;
+
+ // header cannot be larger than BUFFER_SIZE for this to work
+ for (; pos < read || (len = is.read(buf, read, BUFFER_SIZE - read)) >= 0; len = 0) {
+ read += len;
+ while (end < read && (buf[end] != '\n'))
+ end++;
+
+ if (buf[end] == '\n') {
+ if (first) {
+ // check only for OK
+ first = false;
+ if (!compareBytes(buf, pos + 9, end, RESPONSE_HTTP_OK, 6)){
+ String line = new String(buf, pos, end - pos - 1);
+ Log.d(TAG, ">" + line + "< ");
+ return -1;
+ }
+ } else if (end - pos == 1) {
+ // check empty line (header end)
+ end += 1;
+ break;
+ }
+ else {
+ // parse Content-Length, TODO just encode this with message
+ for (int i = 0; pos + i < end - 1; i++) {
+ if (i < 16) {
+ if (buf[pos + i] == RESPONSE_CONTENT_LEN[i])
+ continue;
+
+ break;
+ }
+
+ // read int value
+ contentLength = contentLength * 10 + (buf[pos + i]) - '0';
+ }
+ }
+ //String line = new String(buf, pos, end - pos - 1);
+ //Log.d(TAG, ">" + line + "< ");
+
+ pos += (end - pos) + 1;
+ end = pos;
+ }
+ }
+
+ // back to start of content
+ mResponseStream.reset();
+ mResponseStream.mark(0);
+ mResponseStream.skip(end);
+
+ // start of content
+ bufferPos = 0;
+ mBufferOffset = 0;
+
+ // buffer fill
+ bufferFill = 0;
+
+ // decode zlib compressed content
+ mInputStream = new InflaterInputStream(mResponseStream);
+
+ return 1;
+ }
+
+ boolean sendRequest(Tile tile) throws IOException {
+
+ bufferFill = 0;
+ bufferPos = 0;
+ //mReadPos = 0;
+
+ if (mSocket != null && ((mMaxReq-- <= 0)
+ || (SystemClock.elapsedRealtime() - mLastRequest
+ > RESPONSE_EXPECTED_TIMEOUT))) {
+ try {
+ mSocket.close();
+ } catch (IOException e) {
+
+ }
+
+ // Log.d(TAG, "not alive - recreate connection " + mMaxReq);
+ mSocket = null;
+ }
+
+ if (mSocket == null) {
+ lwHttpConnect();
+ // we know our server
+ mMaxReq = RESPONSE_EXPECTED_LIVES;
+ // Log.d(TAG, "create connection");
+ } else {
+ // should not be needed
+ int avail = mResponseStream.available();
+ if (avail > 0) {
+ Log.d(TAG, "Consume left-over bytes: " + avail);
+ mResponseStream.read(buffer, 0, avail);
+ }
+ }
+
+ byte[] request = mRequestBuffer;
+ int pos = REQUEST_GET_START.length;
+
+ request[pos++] = '/';
+ request[pos++] = pos2hex(tile.tileX);
+ request[pos++] = pos2hex(tile.tileY);
+ request[pos++] = '/';
+ pos = writeInt(tile.zoomLevel, pos, request);
+ request[pos++] = '/';
+ pos = writeInt(tile.tileX, pos, request);
+ request[pos++] = '/';
+ pos = writeInt(tile.tileY, pos, request);
+
+ int len = REQUEST_GET_END.length;
+ System.arraycopy(REQUEST_GET_END, 0, request, pos, len);
+ len += pos;
+
+ //Log.d(TAG, "request " + new String(request,0,len));
+ // this does the same but with a few more allocations:
+ // byte[] request = String.format(REQUEST,
+ // Integer.valueOf(tile.zoomLevel),
+ // Integer.valueOf(tile.tileX), Integer.valueOf(tile.tileY)).getBytes();
+
+ try {
+ mCommandStream.write(request, 0, len);
+ mCommandStream.flush();
+ return true;
+ } catch (IOException e) {
+ Log.d(TAG, "recreate connection");
+ }
+
+ lwHttpConnect();
+
+ mCommandStream.write(request, 0, len);
+ mCommandStream.flush();
+
+ return true;
+ }
+
+ private final static byte[] hexTable = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ private static byte pos2hex(int pos){
+ return hexTable[(pos % 16)];
+ }
+
+ private boolean lwHttpConnect() throws IOException {
+ if (mSockAddr == null)
+ mSockAddr = new InetSocketAddress(mHost, mPort);
+
+ mSocket = new Socket();
+ mSocket.connect(mSockAddr, 30000);
+ mSocket.setTcpNoDelay(true);
+
+ mCommandStream = mSocket.getOutputStream();
+ mResponseStream = new BufferedInputStream(mSocket.getInputStream());
+
+ return true;
+ }
+
+ // write (positive) integer as char sequence to buffer
+ private static int writeInt(int val, int pos, byte[] buf) {
+ if (val == 0) {
+ buf[pos] = '0';
+ return pos + 1;
+ }
+
+ int i = 0;
+ for (int n = val; n > 0; n = n / 10, i++)
+ buf[pos + i] = (byte) ('0' + n % 10);
+
+ // reverse bytes
+ for (int j = pos, end = pos + i - 1, mid = pos + i / 2; j < mid; j++, end--) {
+ byte tmp = buf[j];
+ buf[j] = buf[end];
+ buf[end] = tmp;
+ }
+
+ return pos + i;
+ }
+
+ private static boolean compareBytes(byte[] buffer, int position, int available,
+ byte[] string, int length) {
+
+ if (available - position < length)
+ return false;
+
+ for (int i = 0; i < length; i++)
+ if (buffer[position + i] != string[i])
+ return false;
+
+ return true;
+ }
+
+ 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);
+ }
+
+ public boolean hasData() {
+ try {
+ return readBuffer(1);
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public int position() {
+ return mBufferOffset + bufferPos;
+ }
+
+ public boolean readBuffer(int size) throws IOException {
+ // check if buffer already contains the request bytes
+ if (bufferPos + size < bufferFill)
+ return true;
+
+ // check if inputstream is read to the end
+ //if (mReadPos == mReadEnd)
+ // return;
+
+ int maxSize = buffer.length;
+
+ if (size > maxSize) {
+ Log.d(TAG, "increase read buffer to " + size + " bytes");
+ maxSize = size;
+ byte[] tmp = new byte[maxSize];
+ bufferFill -= bufferPos;
+ System.arraycopy(buffer, bufferPos, tmp, 0, bufferFill);
+ mBufferOffset += bufferPos;
+ bufferPos = 0;
+ buffer = tmp;
+ }
+
+ if (bufferFill == bufferPos) {
+ mBufferOffset += bufferPos;
+ bufferPos = 0;
+ bufferFill = 0;
+ } else if (bufferPos + size > maxSize) {
+ // copy bytes left to the beginning of buffer
+ bufferFill -= bufferPos;
+ System.arraycopy(buffer, bufferPos, buffer, 0, bufferFill);
+ mBufferOffset += bufferPos;
+ bufferPos = 0;
+ }
+
+ int max = maxSize - bufferFill;
+
+ while ((bufferFill - bufferPos) < size && max > 0) {
+
+ max = maxSize - bufferFill;
+
+ // read until requested size is available in buffer
+ int len = mInputStream.read(buffer, bufferFill, max);
+
+ if (len < 0) {
+ // finished reading, mark end
+ buffer[bufferFill] = 0;
+ return false;
+ }
+
+ bufferFill += len;
+ }
+ return true;
+ }
+
+}
diff --git a/src/org/oscim/database/mapnik/MapDatabase.java b/src/org/oscim/database/mapnik/MapDatabase.java
new file mode 100644
index 00000000..365a529a
--- /dev/null
+++ b/src/org/oscim/database/mapnik/MapDatabase.java
@@ -0,0 +1,899 @@
+/*
+ * Copyright 2013
+ *
+ * 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 .
+ */
+package org.oscim.database.mapnik;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+
+import org.oscim.core.BoundingBox;
+import org.oscim.core.GeoPoint;
+import org.oscim.core.GeometryBuffer.GeometryType;
+import org.oscim.core.MapElement;
+import org.oscim.core.Tag;
+import org.oscim.core.Tile;
+import org.oscim.database.IMapDatabase;
+import org.oscim.database.IMapDatabaseCallback;
+import org.oscim.database.MapInfo;
+import org.oscim.database.MapOptions;
+import org.oscim.layers.tile.MapTile;
+import org.oscim.utils.UTF8Decoder;
+import org.oscim.utils.pool.Inlist;
+import org.oscim.utils.pool.Pool;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+public class MapDatabase implements IMapDatabase {
+ private static final String TAG = MapDatabase.class.getName();
+
+ private static final MapInfo mMapInfo =
+ new MapInfo(new BoundingBox(-180, -90, 180, 90),
+ new Byte((byte) 4), new GeoPoint(53.11, 8.85),
+ null, 0, 0, 0, "de", "comment", "author",
+ new int[] { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }
+ );
+
+ private final static float REF_TILE_SIZE = 4096.0f;
+
+ // 'open' state
+ private boolean mOpen = false;
+
+ private IMapDatabaseCallback mMapGenerator;
+ private float mScaleFactor;
+ private MapTile mTile;
+
+ private LwHttp lwHttp;
+
+ private final UTF8Decoder mStringDecoder;
+
+ //private final MapElement mElem;
+
+ public MapDatabase() {
+ mStringDecoder = new UTF8Decoder();
+ //mElem = new MapElement();
+ }
+
+ @Override
+ public QueryResult executeQuery(MapTile tile, IMapDatabaseCallback mapDatabaseCallback) {
+ QueryResult result = QueryResult.SUCCESS;
+
+ mTile = tile;
+
+ mMapGenerator = mapDatabaseCallback;
+
+ // scale coordinates to tile size
+ mScaleFactor = REF_TILE_SIZE / Tile.SIZE;
+
+ try {
+
+ if (lwHttp.sendRequest(tile) && lwHttp.readHeader() >= 0) {
+ decode();
+ } else {
+ Log.d(TAG, tile + " Network Error");
+ result = QueryResult.FAILED;
+ }
+ } catch (SocketException ex) {
+ Log.d(TAG, tile + " Socket exception: " + ex.getMessage());
+ result = QueryResult.FAILED;
+ } catch (SocketTimeoutException ex) {
+ Log.d(TAG, tile + " Socket Timeout exception: " + ex.getMessage());
+ result = QueryResult.FAILED;
+ } catch (UnknownHostException ex) {
+ Log.d(TAG, tile + " no network");
+ result = QueryResult.FAILED;
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ result = QueryResult.FAILED;
+ }
+
+ lwHttp.mLastRequest = SystemClock.elapsedRealtime();
+
+ if (result != QueryResult.SUCCESS) {
+ lwHttp.close();
+ }
+ Log.d(TAG, ">>> " + result + " >>> " + mTile);
+ return result;
+ }
+
+ @Override
+ public MapInfo getMapInfo() {
+ return mMapInfo;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return mOpen;
+ }
+
+ @Override
+ public OpenResult open(MapOptions options) {
+ if (mOpen)
+ return OpenResult.SUCCESS;
+
+ if (options == null || !options.containsKey("url"))
+ return new OpenResult("options missing");
+
+ lwHttp = new LwHttp();
+
+ if (!lwHttp.setServer(options.get("url"))) {
+ return new OpenResult("invalid url: " + options.get("url"));
+ }
+
+ mOpen = true;
+ initDecorder();
+
+ return OpenResult.SUCCESS;
+ }
+
+ @Override
+ public void close() {
+ mOpen = false;
+ lwHttp.close();
+ }
+
+ @Override
+ public String getMapProjection() {
+ return null;
+ }
+
+ @Override
+ public void cancel() {
+ }
+
+ private static final int TAG_TILE_LAYERS = 3;
+
+ private static final int TAG_LAYER_VERSION = 15;
+ private static final int TAG_LAYER_NAME = 1;
+ private static final int TAG_LAYER_FEATURES = 2;
+ private static final int TAG_LAYER_KEYS = 3;
+ private static final int TAG_LAYER_VALUES = 4;
+ private static final int TAG_LAYER_EXTENT = 5;
+
+ private static final int TAG_FEATURE_ID = 1;
+ private static final int TAG_FEATURE_TAGS = 2;
+ private static final int TAG_FEATURE_TYPE = 3;
+ private static final int TAG_FEATURE_GEOMETRY = 4;
+
+ private static final int TAG_VALUE_STRING = 1;
+ private static final int TAG_VALUE_FLOAT = 2;
+ private static final int TAG_VALUE_DOUBLE = 3;
+ private static final int TAG_VALUE_LONG = 4;
+ private static final int TAG_VALUE_UINT = 5;
+ private static final int TAG_VALUE_SINT = 6;
+ private static final int TAG_VALUE_BOOL = 7;
+
+ private static final int TAG_GEOM_UNKNOWN = 0;
+ private static final int TAG_GEOM_POINT = 1;
+ private static final int TAG_GEOM_LINE = 2;
+ private static final int TAG_GEOM_POLYGON = 3;
+
+ //rivate final short[] mTmpKeys = new short[100];
+ //private final Tag[] mTmpTags = new Tag[20];
+ //private Tag[][] mElementTags;
+
+ private short[] mTmpTags = new short[1024];
+
+ //private final short[] mPrevTags = new short[1024];
+
+ private void initDecorder() {
+ // reusable tag set
+ // Tag[][] tags = new Tag[10][];
+ // for (int i = 0; i < 10; i++)
+ // tags[i] = new Tag[i + 1];
+ //mElementTags = tags;
+ }
+
+ private boolean decode() throws IOException {
+
+ int val;
+
+ while (lwHttp.hasData() && (val = decodeVarint32()) > 0) {
+ // read tag and wire type
+ int tag = (val >> 3);
+
+ switch (tag) {
+ case TAG_TILE_LAYERS:
+ decodeLayer();
+ break;
+
+ default:
+ Log.d(TAG, mTile + " invalid type for tile: " + tag);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean decodeLayer() throws IOException {
+
+ int version = 0;
+ int extent = 4096;
+
+ int bytes = decodeVarint32();
+
+ ArrayList keys = new ArrayList();
+ ArrayList values = new ArrayList();
+
+ String name = null;
+ int numFeatures = 0;
+ ArrayList features = new ArrayList();
+
+ int end = lwHttp.position() + bytes;
+ while (lwHttp.position() < end) {
+ // read tag and wire type
+ int val = decodeVarint32();
+ if (val == 0)
+ break;
+
+ int tag = (val >> 3);
+
+ switch (tag) {
+ case TAG_LAYER_KEYS:
+ keys.add(decodeString());
+ break;
+
+ case TAG_LAYER_VALUES:
+ values.add(decodeValue());
+ break;
+
+ case TAG_LAYER_FEATURES:
+ numFeatures++;
+ decodeFeature(features);
+ break;
+
+ case TAG_LAYER_VERSION:
+ version = decodeVarint32();
+ break;
+
+ case TAG_LAYER_NAME:
+ name = decodeString();
+ break;
+
+ case TAG_LAYER_EXTENT:
+ extent = decodeVarint32();
+ break;
+
+ default:
+ Log.d(TAG, mTile + " invalid type for layer: " + tag);
+ break;
+ }
+
+ }
+ boolean isRoad = "road".equals(name);
+ boolean isBridge = "bridge".equals(name);
+ boolean isTunnel = "tunnel".equals(name);
+ boolean isBuilding = "building".equals(name);
+ boolean isLanduse = "landuse".equals(name);
+ boolean isWater = "water".equals(name);
+
+ Tag layerTag = new Tag(name, Tag.VALUE_YES);
+
+ if (numFeatures == 0)
+ return true;
+
+ for (Feature f : features) {
+ int addTags = 0;
+ if (isBuilding || isWater)
+ addTags = 1;
+
+ Tag[] tags = new Tag[f.numTags + addTags];
+
+ if (isBuilding)
+ tags[tags.length - 1] = BUILDING_TAG;
+ if (isWater)
+ tags[tags.length - 1] = WATER_TAG;
+
+ if (tags.length == 0)
+ continue;
+
+ for (int j = 0; j < (f.numTags << 1); j += 2) {
+
+ String key = keys.get(f.tags[j]);
+ String val = values.get(f.tags[j + 1]);
+
+ Tag tag = null;
+ if ("class".equals(key)) {
+ if (isRoad || isTunnel || isBridge) {
+ if ("street".equals(val))
+ tag = HIGHWAY_STREET_TAG;
+ else if ("main".equals(val))
+ tag = HIGHWAY_MAIN_TAG;
+ else if ("major".equals(val))
+ tag = HIGHWAY_MAJOR_TAG;
+ else if ("major_rail".equals(val))
+ tag = HIGHWAY_RAIL_TAG;
+ else
+ tag = new Tag(Tag.TAG_KEY_HIGHWAY, val);
+
+ } else if (isLanduse)
+ tag = new Tag(Tag.TAG_KEY_LANDUSE, val);
+ }
+ if (tag == null)
+ tag = new Tag(key, val);
+
+ tags[j >> 1] = tag;
+ }
+
+ f.elem.set(tags, 5);
+ mMapGenerator.renderElement(f.elem);
+ mFeaturePool.release(f);
+ }
+
+ return true;
+ }
+
+ private final Pool mFeaturePool = new Pool() {
+ int count;
+
+ @Override
+ protected Feature createItem() {
+ count++;
+ return new Feature();
+ }
+
+ @Override
+ protected boolean clearItem(Feature item) {
+ if (count > 50) {
+ count--;
+ return false;
+ }
+
+ item.elem.tags = null;
+ item.elem.clear();
+ item.tags = null;
+ item.type = 0;
+ item.numTags = 0;
+
+ return true;
+ }
+ };
+
+ private final static Tag WATER_TAG = new Tag("natural", "water");
+ private final static Tag BUILDING_TAG = new Tag("building", "yes");
+ private final static Tag HIGHWAY_MAIN_TAG = new Tag("highway", "secondary");
+ private final static Tag HIGHWAY_MAJOR_TAG = new Tag("highway", "primary");
+ private final static Tag HIGHWAY_STREET_TAG = new Tag("highway", "residential");
+ private final static Tag HIGHWAY_RAIL_TAG = new Tag("railway", "rail");
+
+ //private final Tag[] mFallbackTag = new Tag[] { new Tag("debug", "way") };
+
+ // private int mClipped;
+
+ static class Feature extends Inlist {
+ short[] tags;
+ int numTags;
+ int type;
+
+ final MapElement elem;
+
+ Feature() {
+ elem = new MapElement();
+ }
+
+ boolean match(short otherTags[], int otherNumTags, int otherType) {
+ if (numTags != otherNumTags)
+ return false;
+
+ if (type != otherType)
+ return false;
+
+ for (int i = 0; i < numTags << 1; i++) {
+ if (tags[i] != otherTags[i])
+ return false;
+ }
+ return true;
+ }
+
+ }
+
+ private void decodeFeature(ArrayList features) throws IOException {
+ int bytes = decodeVarint32();
+ int end = lwHttp.position() + bytes;
+
+ int type = 0;
+ long id;
+
+ lastX = 0;
+ lastY = 0;
+
+ mTmpTags[0] = -1;
+
+ Feature curFeature = null;
+ int numTags = 0;
+
+ //Log.d(TAG, "start feature");
+ while (lwHttp.position() < end) {
+ // read tag and wire type
+ int val = decodeVarint32();
+ if (val == 0)
+ break;
+
+ int tag = (val >>> 3);
+
+ switch (tag) {
+ case TAG_FEATURE_ID:
+ id = decodeVarint32();
+ break;
+
+ case TAG_FEATURE_TAGS:
+ mTmpTags = decodeShortArray(mTmpTags);
+
+ for (; numTags < mTmpTags.length && mTmpTags[numTags] >= 0;)
+ numTags += 2;
+
+ numTags >>= 1;
+
+ break;
+
+ case TAG_FEATURE_TYPE:
+ type = decodeVarint32();
+
+ //Log.d(TAG, "got type " + type);
+
+ break;
+
+ case TAG_FEATURE_GEOMETRY:
+
+ for (Feature f : features) {
+ if (f.match(mTmpTags, numTags, type)) {
+ curFeature = f;
+ break;
+ }
+ }
+
+ if (curFeature == null) {
+ curFeature = mFeaturePool.get();
+ curFeature.tags = new short[numTags << 1];
+ System.arraycopy(mTmpTags, 0, curFeature.tags, 0, numTags << 1);
+ curFeature.numTags = numTags;
+ curFeature.type = type;
+
+ features.add(curFeature);
+ }
+
+ decodeCoordinates(type, curFeature);
+ break;
+
+ default:
+ Log.d(TAG, mTile + " invalid type for feature: " + tag);
+ break;
+ }
+ }
+ }
+
+ private final static int CLOSE_PATH = 0x07;
+ private final static int MOVE_TO = 0x01;
+ //private final static int LINE_TO = 0x02;
+
+ private int lastX, lastY;
+ private final int pixel = 7;
+
+ private int decodeCoordinates(int type, Feature feature) throws IOException {
+ int bytes = decodeVarint32();
+ lwHttp.readBuffer(bytes);
+
+ if (feature == null) {
+ lwHttp.bufferPos += bytes;
+ return 0;
+ }
+
+ MapElement elem = feature.elem;
+
+ boolean isPoint = false;
+ boolean isPoly = false;
+ boolean isLine = false;
+
+ if (type == TAG_GEOM_LINE) {
+ elem.startLine();
+ isLine = true;
+ }
+ else if (type == TAG_GEOM_POLYGON) {
+ elem.startPolygon();
+ isPoly = true;
+ } else if (type == TAG_GEOM_POINT) {
+ isPoint = true;
+ elem.startPoints();
+ } else if (type == TAG_GEOM_UNKNOWN)
+ elem.startPoints();
+
+ int cnt = 0;
+
+ boolean even = true;
+
+ float scale = mScaleFactor;
+
+ byte[] buf = lwHttp.buffer;
+ int pos = lwHttp.bufferPos;
+ int end = pos + bytes;
+ int val;
+ int curX = 0;
+ int curY = 0;
+ int prevX = 0;
+ int prevY = 0;
+
+ int cmd = 0;
+ int num = 0;
+ boolean first = true;
+ boolean lastClip = false;
+
+ boolean isOuter = true;
+
+ int xmin = Integer.MAX_VALUE, xmax = Integer.MIN_VALUE;
+ int ymin = Integer.MAX_VALUE, ymax = Integer.MIN_VALUE;
+
+ 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;
+
+ int max = pos + VARINT_LIMIT;
+ while (pos < max)
+ if (buf[pos++] >= 0)
+ break;
+
+ if (pos == max)
+ throw new IOException("malformed VarInt32 in " + mTile);
+ }
+
+ if (num == 0) {
+ num = val >>> 3;
+ cmd = val & 0x07;
+
+ if (isLine && lastClip) {
+ elem.addPoint(curX / scale, curY / scale);
+ lastClip = false;
+ }
+
+ if (cmd == CLOSE_PATH) {
+ num = 0;
+ continue;
+ }
+ if (first) {
+ first = false;
+ continue;
+ }
+ if (cmd == MOVE_TO) {
+ if (type == TAG_GEOM_LINE)
+ elem.startLine();
+ else if (type == TAG_GEOM_POLYGON) {
+ isOuter = false;
+ elem.startHole();
+ }
+ }
+ } else {
+ // zigzag decoding
+ int s = ((val >>> 1) ^ -(val & 1));
+
+ if (even) {
+ even = false;
+ curX = lastX = lastX + s;
+ } else {
+ even = true;
+
+ curY = lastY = lastY + s;
+
+ int dx = (curX - prevX);
+ int dy = (curY - prevY);
+
+ if ((isPoint || cmd == MOVE_TO)
+ || (dx > pixel || dx < -pixel)
+ || (dy > pixel || dy < -pixel)
+ // dont clip at tile boundaries
+ || (curX <= 0 || curX >= 4095)
+ || (curY <= 0 || curY >= 4095)) {
+ prevX = curX;
+ prevY = curY;
+ elem.addPoint(curX / scale, curY / scale);
+ lastClip = false;
+
+ if (isPoly) {
+ if (curX < xmin)
+ xmin = curX;
+ if (curX > xmax)
+ xmax = curX;
+
+ if (curY < ymin)
+ ymin = curY;
+ if (curY > ymax)
+ ymax = curY;
+
+ }
+
+ } else {
+ lastClip = true;
+ }
+
+ num--;
+ cnt++;
+ }
+ }
+ }
+
+ if (isPoly && isOuter && !testBBox(xmax - xmin, ymax - ymin)) {
+ //Log.d(TAG, "skip small poly "+ elem.indexPos + " > "
+ // + (xmax - xmin) * (ymax - ymin));
+ elem.pointPos -= elem.index[elem.indexPos];
+ if (elem.indexPos > 0) {
+ elem.indexPos -= 3;
+ elem.index[elem.indexPos + 1] = -1;
+ } else {
+ elem.type = GeometryType.NONE;
+ }
+
+ lwHttp.bufferPos += bytes;
+
+ return 0;
+ }
+
+ if (isLine && lastClip)
+ elem.addPoint(curX / scale, curY / scale);
+
+ lwHttp.bufferPos = pos;
+
+ return cnt;
+ }
+
+ private static boolean testBBox(int dx, int dy) {
+ return dx * dy > 64 * 64;
+ }
+
+ private String decodeValue() throws IOException {
+ int bytes = decodeVarint32();
+
+ String value = null;
+
+ int end = lwHttp.position() + bytes;
+
+ while (lwHttp.position() < end) {
+ // read tag and wire type
+ int val = decodeVarint32();
+ if (val == 0)
+ break;
+
+ int tag = (val >> 3);
+
+ switch (tag) {
+ case TAG_VALUE_STRING:
+ value = decodeString();
+ break;
+
+ case TAG_VALUE_UINT:
+ value = String.valueOf(decodeVarint32());
+ break;
+
+ case TAG_VALUE_SINT:
+ value = String.valueOf(decodeVarint32());
+ break;
+
+ case TAG_VALUE_LONG:
+ value = String.valueOf(decodeVarint32());
+ break;
+
+ case TAG_VALUE_FLOAT:
+ value = String.valueOf(decodeFloat());
+ break;
+
+ case TAG_VALUE_DOUBLE:
+ value = String.valueOf(decodeDouble());
+ break;
+
+ case TAG_VALUE_BOOL:
+ value = decodeBool() ? "yes" : "no";
+ break;
+ default:
+ break;
+ }
+
+ }
+ return value;
+ }
+
+ private final static int VARINT_LIMIT = 7;
+ private final static int VARINT_MAX = 10;
+
+ private short[] decodeShortArray(short[] array) throws IOException {
+ int bytes = decodeVarint32();
+ int arrayLength = array.length;
+
+ lwHttp.readBuffer(bytes);
+ int cnt = 0;
+
+ byte[] buf = lwHttp.buffer;
+ int pos = lwHttp.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;
+
+ int max = pos + VARINT_LIMIT;
+ while (pos < max)
+ if (buf[pos++] >= 0)
+ break;
+
+ if (pos == max)
+ throw new IOException("malformed VarInt32 in " + mTile);
+ }
+
+ if (arrayLength <= cnt) {
+ arrayLength = cnt + 16;
+ short[] tmp = array;
+ array = new short[arrayLength];
+ System.arraycopy(tmp, 0, array, 0, cnt);
+ }
+
+ array[cnt++] = (short) val;
+ }
+
+ lwHttp.bufferPos = pos;
+
+ if (arrayLength > cnt)
+ array[cnt] = -1;
+
+ return array;
+ }
+
+ private int decodeVarint32() throws IOException {
+ if (lwHttp.bufferPos + VARINT_MAX > lwHttp.bufferFill)
+ lwHttp.readBuffer(4096);
+
+ byte[] buf = lwHttp.buffer;
+ int pos = lwHttp.bufferPos;
+ int val;
+
+ 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;
+
+ // 'Discard upper 32 bits'
+ int max = pos + VARINT_LIMIT;
+ while (pos < max)
+ if (buf[pos++] >= 0)
+ break;
+
+ if (pos == max)
+ throw new IOException("malformed VarInt32 in " + mTile);
+ }
+ }
+
+ lwHttp.bufferPos = pos;
+
+ return val;
+ }
+
+ private float decodeFloat() throws IOException {
+ if (lwHttp.bufferPos + 4 > lwHttp.bufferFill)
+ lwHttp.readBuffer(4096);
+
+ byte[] buf = lwHttp.buffer;
+ int pos = lwHttp.bufferPos;
+
+ int val = (buf[pos++] & 0xFF
+ | (buf[pos++] & 0xFF) << 8
+ | (buf[pos++] & 0xFF) << 16
+ | (buf[pos++] & 0xFF) << 24);
+
+ lwHttp.bufferPos += 4;
+
+ return Float.intBitsToFloat(val);
+ }
+
+ private double decodeDouble() throws IOException {
+ if (lwHttp.bufferPos + 8 > lwHttp.bufferFill)
+ lwHttp.readBuffer(4096);
+
+ byte[] buf = lwHttp.buffer;
+ int pos = lwHttp.bufferPos;
+
+ long val = (buf[pos++] & 0xFF
+ | (buf[pos++] & 0xFF) << 8
+ | (buf[pos++] & 0xFF) << 16
+ | (buf[pos++] & 0xFF) << 24
+ | (buf[pos++] & 0xFF) << 32
+ | (buf[pos++] & 0xFF) << 40
+ | (buf[pos++] & 0xFF) << 48
+ | (buf[pos++] & 0xFF) << 56);
+
+ lwHttp.bufferPos += 8;
+
+ return Double.longBitsToDouble(val);
+ }
+
+ private boolean decodeBool() throws IOException {
+ if (lwHttp.bufferPos + 1 > lwHttp.bufferFill)
+ lwHttp.readBuffer(4096);
+
+ boolean val = lwHttp.buffer[lwHttp.bufferPos++] != 0;
+
+ return val;
+ }
+
+ private String decodeString() throws IOException {
+ final int size = decodeVarint32();
+ lwHttp.readBuffer(size);
+ final String result = mStringDecoder.decode(lwHttp.buffer, lwHttp.bufferPos, size);
+
+ //Log.d(TAG, "string: " + result);
+
+ lwHttp.bufferPos += size;
+
+ return result;
+
+ }
+}