vtm/src/org/oscim/database/oscimap/MapDatabase.java
2013-10-09 01:56:00 +02:00

704 lines
16 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}