/* * Copyright 2012 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.oscimap; import java.io.File; import java.io.IOException; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.Arrays; import org.oscim.core.BoundingBox; import org.oscim.core.GeoPoint; 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.generator.JobTile; import android.os.Environment; import android.os.SystemClock; import android.util.Log; /** * * */ public class MapDatabase implements IMapDatabase { private static final String TAG = MapDatabase.class.getName(); static final boolean USE_CACHE = false; 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", null); private static final String CACHE_DIRECTORY = "/Android/data/org.oscim.app/cache/"; private static final String CACHE_FILE = "%d-%d-%d.tile"; private final static float REF_TILE_SIZE = 4096.0f; // 'open' state private boolean mOpen = false; private static File cacheDir; private final int MAX_TILE_TAGS = 100; private Tag[] curTags = new Tag[MAX_TILE_TAGS]; private int mCurTagCnt; private IMapDatabaseCallback mMapGenerator; private float mScaleFactor; private JobTile mTile; private long mContentLenth; private final boolean debug = false; private LwHttp lwHttp; //private final WayData mWay = new WayData(); private final MapElement mElem = new MapElement(); @Override public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) { QueryResult result = QueryResult.SUCCESS; mTile = tile; mMapGenerator = mapDatabaseCallback; // scale coordinates to tile size mScaleFactor = REF_TILE_SIZE / Tile.SIZE; File f = null; if (USE_CACHE) { f = new File(cacheDir, String.format(CACHE_FILE, Integer.valueOf(tile.zoomLevel), Integer.valueOf(tile.tileX), Integer.valueOf(tile.tileY))); if (lwHttp.cacheRead(tile, f)) return QueryResult.SUCCESS; } try { if (lwHttp.sendRequest(tile) && (mContentLenth = lwHttp.readHeader()) >= 0) { lwHttp.cacheBegin(tile, f); 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.cacheFinish(tile, f, true); } else { lwHttp.cacheFinish(tile, f, false); lwHttp.close(); } return result; } @Override public String getMapProjection() { return null; } @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")); } if (USE_CACHE) { if (cacheDir == null) { String externalStorageDirectory = Environment .getExternalStorageDirectory() .getAbsolutePath(); String cacheDirectoryPath = externalStorageDirectory + CACHE_DIRECTORY; cacheDir = createDirectory(cacheDirectoryPath); } } mOpen = true; initDecorder(); return OpenResult.SUCCESS; } @Override public void close() { mOpen = false; lwHttp.close(); if (USE_CACHE) { cacheDir = null; } } @Override public void cancel() { } private static File createDirectory(String pathName) { File file = new File(pathName); if (!file.exists() && !file.mkdirs()) { throw new IllegalArgumentException("could not create directory: " + file); } else if (!file.isDirectory()) { throw new IllegalArgumentException("not a directory: " + file); } else if (!file.canRead()) { throw new IllegalArgumentException("cannot read directory: " + file); } else if (!file.canWrite()) { throw new IllegalArgumentException("cannot write directory: " + file); } return file; } // /////////////// hand sewed tile protocol buffers decoder /////////////// //private final int MAX_WAY_COORDS = 1 << 14; // overall bytes of content processed private int mBytesProcessed; private static final int TAG_TILE_NUM_TAGS = 1; private static final int TAG_TILE_TAG_KEYS = 2; private static final int TAG_TILE_TAG_VALUES = 3; private static final int TAG_TILE_LINE = 11; private static final int TAG_TILE_POLY = 12; private static final int TAG_TILE_POINT = 13; // private static final int TAG_TILE_LABEL = 21; // private static final int TAG_TILE_WATER = 31; private static final int TAG_ELEM_NUM_INDICES = 1; private static final int TAG_ELEM_TAGS = 11; private static final int TAG_ELEM_INDEX = 12; private static final int TAG_ELEM_COORDS = 13; private static final int TAG_ELEM_LAYER = 21; private static final int TAG_ELEM_HEIGHT = 31; private static final int TAG_ELEM_MIN_HEIGHT = 32; private static final int TAG_ELEM_PRIORITY = 41; private short[] mTmpKeys = new short[100]; private final Tag[] mTmpTags = new Tag[20]; //private float[] mTmpCoords; //private short[] mIndices = new short[10]; //private final GeometryBuffer mElem = new GeometryBuffer(MAX_WAY_COORDS, 128); private Tag[][] mElementTags; 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 { mCurTagCnt = 0; if (debug) Log.d(TAG, mTile + " Content length " + mContentLenth); mBytesProcessed = 0; int val; int numTags = 0; while (mBytesProcessed < mContentLenth && (val = decodeVarint32()) > 0) { // read tag and wire type int tag = (val >> 3); switch (tag) { case TAG_TILE_NUM_TAGS: numTags = decodeVarint32(); if (numTags > curTags.length) curTags = new Tag[numTags]; break; case TAG_TILE_TAG_KEYS: mTmpKeys = decodeShortArray(numTags, mTmpKeys); break; case TAG_TILE_TAG_VALUES: // this wastes one byte, as there is no packed string... decodeTileTags(mCurTagCnt++); break; case TAG_TILE_LINE: case TAG_TILE_POLY: case TAG_TILE_POINT: decodeTileElement(tag); break; default: Log.d(TAG, mTile + " invalid type for tile: " + tag); return false; } } return true; } private boolean decodeTileTags(int curTag) throws IOException { String tagString = decodeString(); String key = Tags.keys[mTmpKeys[curTag]]; Tag tag; if (key == Tag.TAG_KEY_NAME) tag = new Tag(key, tagString, false); else tag = new Tag(key, tagString, true); if (debug) Log.d(TAG, mTile + " add tag: " + curTag + " " + tag); curTags[curTag] = tag; return true; } private int decodeWayIndices(int indexCnt) throws IOException { mElem.index = decodeShortArray(indexCnt, mElem.index); short[] index = mElem.index; int coordCnt = 0; for (int i = 0; i < indexCnt; i++) { int len = index[i] * 2; coordCnt += len; index[i] = (short) len; } // set end marker if (indexCnt < index.length) index[indexCnt] = -1; return coordCnt; } private boolean decodeTileElement(int type) throws IOException { int bytes = decodeVarint32(); Tag[] tags = null; short[] index = null; int end = mBytesProcessed + bytes; int indexCnt = 1; //int layer = 5; boolean skip = false; boolean fail = false; int coordCnt = 0; if (type == TAG_TILE_POINT){ coordCnt = 2; mElem.index[0] = 2; } mElem.layer = 5; mElem.priority = 0; mElem.height = 0; mElem.minHeight = 0; while (mBytesProcessed < end) { // read tag and wire type int val = decodeVarint32(); if (val == 0) break; int tag = (val >> 3); switch (tag) { case TAG_ELEM_TAGS: tags = decodeWayTags(); break; case TAG_ELEM_NUM_INDICES: indexCnt = decodeVarint32(); break; case TAG_ELEM_INDEX: coordCnt = decodeWayIndices(indexCnt); break; case TAG_ELEM_COORDS: if (coordCnt == 0) { Log.d(TAG, mTile + " skipping way"); skip = true; } int cnt = decodeWayCoordinates(skip, coordCnt); if (cnt != coordCnt) { Log.d(TAG, mTile + " wrong number of coordintes"); fail = true; } break; case TAG_ELEM_LAYER: mElem.layer = decodeVarint32(); break; case TAG_ELEM_HEIGHT: mElem.height= decodeVarint32(); break; case TAG_ELEM_MIN_HEIGHT: mElem.minHeight = decodeVarint32(); break; case TAG_ELEM_PRIORITY: mElem.priority = decodeVarint32(); break; default: Log.d(TAG, mTile + " invalid type for way: " + tag); } } if (fail || tags == null || indexCnt == 0) { Log.d(TAG, mTile + " failed reading way: bytes:" + bytes + " index:" + (Arrays.toString(index)) + " tag:" + (tags != null ? Arrays.deepToString(tags) : "null") + " " + indexCnt + " " + coordCnt); return false; } mElem.tags = tags; switch (type) { case TAG_TILE_LINE: mElem.geometryType= MapElement.GEOM_LINE; break; case TAG_TILE_POLY: mElem.geometryType= MapElement.GEOM_POLY; break; case TAG_TILE_POINT: mElem.geometryType= MapElement.GEOM_POINT; break; } mMapGenerator.renderElement(mElem); return true; } private Tag[] decodeWayTags() throws IOException { int bytes = decodeVarint32(); Tag[] tmp = mTmpTags; int cnt = 0; int end = mBytesProcessed + bytes; int max = mCurTagCnt; while (mBytesProcessed < end) { int tagNum = decodeVarint32(); if (tagNum < 0) { Log.d(TAG, "NULL TAG: " + mTile + " invalid tag:" + tagNum + " " + cnt); } else if (tagNum < Tags.MAX) { tmp[cnt++] = Tags.tags[tagNum]; } else { tagNum -= Tags.LIMIT; if (tagNum >= 0 && tagNum < max) { // Log.d(TAG, "variable tag: " + curTags[tagNum]); tmp[cnt++] = curTags[tagNum]; } else { Log.d(TAG, "NULL TAG: " + mTile + " could not find tag:" + tagNum + " " + cnt); } } } if (cnt == 0) { Log.d(TAG, "got no TAG!"); } Tag[] tags; if (cnt < 11) tags = mElementTags[cnt - 1]; else tags = new Tag[cnt]; for (int i = 0; i < cnt; i++) tags[i] = tmp[i]; return tags; } private int decodeWayCoordinates(boolean skip, int nodes) throws IOException { int bytes = decodeVarint32(); lwHttp.readBuffer(bytes); if (skip) { lwHttp.bufferPos += bytes; return nodes; } int pos = lwHttp.bufferPos; int end = pos + bytes; byte[] buf = lwHttp.buffer; int cnt = 0; int result; int lastX = 0; int lastY = 0; boolean even = true; float scale = mScaleFactor; float[] coords = mElem.ensurePointSize(nodes, false); // if (nodes * 2 > coords.length) { // Log.d(TAG, mTile + " increase way coord buffer to " + (nodes * 2)); // float[] tmp = new float[nodes * 2]; // mTmpCoords = coords = tmp; // } // read repeated sint32 while (pos < end) { if (buf[pos] >= 0) { result = buf[pos++]; } else if (buf[pos + 1] >= 0) { result = (buf[pos++] & 0x7f) | buf[pos++] << 7; } else if (buf[pos + 2] >= 0) { result = (buf[pos++] & 0x7f) | (buf[pos++] & 0x7f) << 7 | (buf[pos++]) << 14; } else if (buf[pos + 3] >= 0) { result = (buf[pos++] & 0x7f) | (buf[pos++] & 0x7f) << 7 | (buf[pos++] & 0x7f) << 14 | (buf[pos++]) << 21; } else { result = (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2] & 0x7f) << 14 | (buf[pos + 3] & 0x7f) << 21 | (buf[pos + 4]) << 28; pos += 4; int i = 0; while (buf[pos++] < 0 && i < 10) i++; if (i == 10) throw new IOException("X malformed VarInt32 in " + mTile); } // zigzag decoding int s = ((result >>> 1) ^ -(result & 1)); if (even) { lastX = lastX + s; coords[cnt++] = lastX / scale; even = false; } else { lastY = lastY + s; coords[cnt++] = lastY / scale; even = true; } } lwHttp.bufferPos = pos; mBytesProcessed += bytes; return cnt; } private short[] decodeShortArray(int num, short[] array) throws IOException { int bytes = decodeVarint32(); if (array.length < num) array = new short[num]; lwHttp.readBuffer(bytes); int cnt = 0; int pos = lwHttp.bufferPos; int end = pos + bytes; byte[] buf = lwHttp.buffer; int result; while (pos < end) { if (buf[pos] >= 0) { result = buf[pos++]; } else if (buf[pos + 1] >= 0) { result = (buf[pos] & 0x7f) | buf[pos + 1] << 7; pos += 2; } else if (buf[pos + 2] >= 0) { result = (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2]) << 14; pos += 3; } else if (buf[pos + 3] >= 0) { result = (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2] & 0x7f) << 14 | (buf[pos + 3]) << 21; pos += 4; } else { result = (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2] & 0x7f) << 14 | (buf[pos + 3] & 0x7f) << 21 | (buf[pos + 4]) << 28; pos += 4; int i = 0; while (buf[pos++] < 0 && i < 10) i++; if (i == 10) throw new IOException("X malformed VarInt32 in " + mTile); } array[cnt++] = (short) result; } lwHttp.bufferPos = pos; mBytesProcessed += bytes; return array; } private int decodeVarint32() throws IOException { int pos = lwHttp.bufferPos; if (pos + 10 > lwHttp.bufferFill) { lwHttp.readBuffer(4096); pos = lwHttp.bufferPos; } byte[] buf = lwHttp.buffer; if (buf[pos] >= 0) { lwHttp.bufferPos += 1; mBytesProcessed += 1; return buf[pos]; } else if (buf[pos + 1] >= 0) { lwHttp.bufferPos += 2; mBytesProcessed += 2; return (buf[pos] & 0x7f) | (buf[pos + 1]) << 7; } else if (buf[pos + 2] >= 0) { lwHttp.bufferPos += 3; mBytesProcessed += 3; return (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2]) << 14; } else if (buf[pos + 3] >= 0) { lwHttp.bufferPos += 4; mBytesProcessed += 4; return (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2] & 0x7f) << 14 | (buf[pos + 3]) << 21; } int result = (buf[pos] & 0x7f) | (buf[pos + 1] & 0x7f) << 7 | (buf[pos + 2] & 0x7f) << 14 | (buf[pos + 3] & 0x7f) << 21 | (buf[pos + 4]) << 28; int read = 5; pos += 4; // 'Discard upper 32 bits' - the original comment. // havent found this in any document but the code provided by google. while (buf[pos++] < 0 && read < 10) read++; if (read == 10) throw new IOException("X malformed VarInt32 in " + mTile); lwHttp.bufferPos += read; mBytesProcessed += read; return result; } private String decodeString() throws IOException { final int size = decodeVarint32(); lwHttp.readBuffer(size); final String result = new String(lwHttp.buffer, lwHttp.bufferPos, size, "UTF-8"); lwHttp.bufferPos += size; mBytesProcessed += size; return result; } 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); } }