vtm/src/org/oscim/database/pbmap/MapDatabase.java
Hannes Janetzek 9efe46e3ba cleanups
2013-10-09 01:39:51 +02:00

1273 lines
32 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.pbmap;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.RequestAddCookies;
import org.apache.http.client.protocol.RequestProxyAuthentication;
import org.apache.http.client.protocol.RequestTargetAuthentication;
import org.apache.http.client.protocol.ResponseProcessCookies;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestUserAgent;
import org.oscim.core.BoundingBox;
import org.oscim.core.GeoPoint;
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.OpenResult;
import org.oscim.database.QueryResult;
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";
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 boolean mOpenFile = false;
private static final boolean USE_CACHE = false;
private static final boolean USE_APACHE_HTTP = false;
private static final boolean USE_LW_HTTP = true;
private static final String CACHE_DIRECTORY = "/Android/data/org.oscim.app/cache/";
private static final String CACHE_FILE = "%d-%d-%d.tile";
private static final String SERVER_ADDR = "city.informatik.uni-bremen.de";
// private static final String URL =
// "http://city.informatik.uni-bremen.de:8020/test/%d/%d/%d.osmtile";
private static final String URL = "http://city.informatik.uni-bremen.de/osmstache/test/%d/%d/%d.osmtile";
// private static final String URL =
// "http://city.informatik.uni-bremen.de/tiles/tiles.py///test/%d/%d/%d.osmtile";
// private static final String URL =
// "http://city.informatik.uni-bremen.de/osmstache/gis2/%d/%d/%d.osmtile";
private final static float REF_TILE_SIZE = 4096.0f;
private int MAX_TILE_TAGS = 100;
private Tag[] curTags = new Tag[MAX_TILE_TAGS];
private int mCurTagCnt;
private HttpClient mClient;
private HttpGet mRequest = null;
private IMapDatabaseCallback mMapGenerator;
private float mScaleFactor;
private JobTile mTile;
private FileOutputStream mCacheFile;
private long mContentLenth;
private InputStream mInputStream;
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;
}
});
@Override
public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) {
QueryResult result = QueryResult.SUCCESS;
mCacheFile = null;
mTile = tile;
mMapGenerator = mapDatabaseCallback;
mCurTagCnt = 0;
// scale coordinates to tile size
mScaleFactor = REF_TILE_SIZE / Tile.TILE_SIZE;
File f = null;
mBufferSize = 0;
mBufferPos = 0;
mReadPos = 0;
if (USE_CACHE) {
f = new File(cacheDir, String.format(CACHE_FILE,
Integer.valueOf(tile.zoomLevel),
Integer.valueOf(tile.tileX),
Integer.valueOf(tile.tileY)));
if (cacheRead(tile, f))
return QueryResult.SUCCESS;
}
String url = null;
HttpGet getRequest;
if (!USE_LW_HTTP) {
url = String.format(URL,
Integer.valueOf(tile.zoomLevel),
Integer.valueOf(tile.tileX),
Integer.valueOf(tile.tileY));
}
if (USE_APACHE_HTTP) {
getRequest = new HttpGet(url);
mRequest = getRequest;
}
try {
if (USE_LW_HTTP) {
if (lwHttpSendRequest(tile)) {
if (lwHttpReadHeader() > 0) {
cacheBegin(tile, f);
decode();
}
} else {
result = QueryResult.FAILED;
}
} else if (USE_APACHE_HTTP) {
HttpResponse response = mClient.execute(getRequest);
final int statusCode = response.getStatusLine().getStatusCode();
final HttpEntity entity = response.getEntity();
if (statusCode != HttpStatus.SC_OK) {
Log.d(TAG, "Http response " + statusCode);
entity.consumeContent();
return QueryResult.FAILED;
}
if (!mTile.isLoading) {
Log.d(TAG, "1 loading canceled " + mTile);
entity.consumeContent();
return QueryResult.FAILED;
}
InputStream is = null;
// GZIPInputStream zis = null;
try {
is = entity.getContent();
mContentLenth = entity.getContentLength();
mInputStream = is;
cacheBegin(tile, f);
// zis = new GZIPInputStream(is);
decode();
} finally {
// if (zis != null)
// zis.close();
if (is != null)
is.close();
entity.consumeContent();
}
} else {
HttpURLConnection urlConn =
(HttpURLConnection) new URL(url).openConnection();
InputStream in = urlConn.getInputStream();
try {
decode();
} finally {
urlConn.disconnect();
}
}
} catch (SocketException ex) {
Log.d(TAG, "Socket exception: " + ex.getMessage());
result = QueryResult.FAILED;
} catch (SocketTimeoutException ex) {
Log.d(TAG, "Socket Timeout exception: " + ex.getMessage());
result = QueryResult.FAILED;
} catch (UnknownHostException ex) {
Log.d(TAG, "no network");
result = QueryResult.FAILED;
} catch (Exception ex) {
ex.printStackTrace();
result = QueryResult.FAILED;
}
mLastRequest = SystemClock.elapsedRealtime();
if (USE_APACHE_HTTP)
mRequest = null;
// FIXME remove this stuff
if (!mTile.isLoading) {
Log.d(TAG, "loading canceled " + mTile);
result = QueryResult.FAILED;
}
cacheFinish(tile, f, result == QueryResult.SUCCESS);
return result;
}
private static File cacheDir;
@Override
public String getMapProjection() {
return null;
}
@Override
public MapInfo getMapInfo() {
return mMapInfo;
}
@Override
public boolean isOpen() {
return mOpenFile;
}
private void createClient() {
mOpenFile = true;
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setStaleCheckingEnabled(params, false);
HttpConnectionParams.setTcpNoDelay(params, true);
HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
HttpConnectionParams.setSoTimeout(params, 60 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 32768);
HttpClientParams.setRedirecting(params, false);
DefaultHttpClient client = new DefaultHttpClient(params);
client.removeRequestInterceptorByClass(RequestAddCookies.class);
client.removeResponseInterceptorByClass(ResponseProcessCookies.class);
client.removeRequestInterceptorByClass(RequestUserAgent.class);
client.removeRequestInterceptorByClass(RequestExpectContinue.class);
client.removeRequestInterceptorByClass(RequestTargetAuthentication.class);
client.removeRequestInterceptorByClass(RequestProxyAuthentication.class);
mClient = client;
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
}
@Override
public OpenResult open(Map<String, String> options) {
if (USE_APACHE_HTTP)
createClient();
if (USE_CACHE) {
if (cacheDir == null) {
String externalStorageDirectory = Environment
.getExternalStorageDirectory()
.getAbsolutePath();
String cacheDirectoryPath = externalStorageDirectory + CACHE_DIRECTORY;
cacheDir = createDirectory(cacheDirectoryPath);
}
}
return OpenResult.SUCCESS;
}
@Override
public void close() {
mOpenFile = false;
if (USE_APACHE_HTTP) {
if (mClient != null) {
mClient.getConnectionManager().shutdown();
mClient = null;
}
}
if (USE_LW_HTTP) {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mSocket = null;
}
}
if (USE_CACHE) {
cacheDir = null;
}
}
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 static final int BUFFER_SIZE = 65536;
private final byte[] mReadBuffer = new byte[BUFFER_SIZE];
// position in read buffer
private int mBufferPos;
// bytes available in read buffer
private int mBufferSize;
// overall bytes of content processed
private int mBytesProcessed;
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 boolean decode() throws IOException {
mBytesProcessed = 0;
int val;
while (mBytesProcessed < mContentLenth && (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.d(TAG, "invalid type for tile: " + tag);
return false;
}
}
return true;
}
private boolean decodeTileTags() throws IOException {
String tagString = decodeString();
// Log.d(TAG, "tag>" + tagString + "<");
if (tagString == null || tagString.length() == 0) {
curTags[mCurTagCnt++] = new Tag(Tag.TAG_KEY_NAME, "...");
return false;
}
Tag tag = tagHash.get(tagString);
if (tag == null) {
if (tagString.startsWith(Tag.TAG_KEY_NAME))
tag = new Tag(Tag.TAG_KEY_NAME, tagString.substring(5), false);
else
tag = new Tag(tagString);
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 = mBytesProcessed + bytes;
int indexCnt = 0;
int tagCnt = 0;
int coordCnt = 0;
int layer = 5;
Tag[] tags = null;
short[] index = null;
boolean skip = false;
boolean fail = false;
while (mBytesProcessed < end) {
// read tag and wire type
int val = decodeVarint32();
if (val == 0)
break;
int tag = (val >> 3);
switch (tag) {
case TAG_WAY_TAGS:
tags = decodeWayTags(tagCnt);
break;
case TAG_WAY_INDEX:
index = decodeWayIndices(indexCnt);
break;
case TAG_WAY_COORDS:
if (coordCnt == 0)
skip = true;
int cnt = decodeWayCoordinates(skip, coordCnt);
if (cnt != coordCnt) {
Log.d(TAG, "X wrong number of coordintes");
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.d(TAG, "X invalid type for way: " + tag);
}
}
if (fail || index == null || tags == null || indexCnt == 0 || tagCnt == 0) {
Log.d(TAG, "failed reading way: bytes:" + bytes + " index:"
+ (index == null ? "null" : index.toString()) + " tag:"
+ (tags != null ? tags.toString() : "...") + " "
+ indexCnt + " " + coordCnt + " " + tagCnt);
return false;
}
float[] coords = tmpCoords;
// FIXME, remove all tiles from cache then remove this below
if (layer == 0)
layer = 5;
mMapGenerator.renderWay((byte) layer, tags, coords, index, polygon);
return true;
}
private boolean decodeTileNodes() throws IOException {
int bytes = decodeVarint32();
int end = mBytesProcessed + bytes;
int tagCnt = 0;
int coordCnt = 0;
byte layer = 0;
Tag[] tags = null;
while (mBytesProcessed < end) {
// read tag and wire type
int val = decodeVarint32();
if (val == 0)
break;
int tag = (val >> 3);
switch (tag) {
case TAG_NODE_TAGS:
tags = decodeWayTags(tagCnt);
break;
case TAG_NODE_COORDS:
int cnt = decodeNodeCoordinates(coordCnt, layer, tags);
if (cnt != coordCnt) {
Log.d(TAG, "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.d(TAG, "X invalid type for node: " + tag);
}
}
return true;
}
private int decodeNodeCoordinates(int numNodes, byte layer, Tag[] tags)
throws IOException {
int bytes = decodeVarint32();
readBuffer(bytes);
int cnt = 0;
int end = mBytesProcessed + bytes;
float scale = mScaleFactor;
// read repeated sint32
int lastX = 0;
int lastY = 0;
while (mBytesProcessed < end && cnt < numNodes) {
int lon = decodeZigZag32(decodeVarint32());
int lat = decodeZigZag32(decodeVarint32());
lastX = lon + lastX;
lastY = lat + lastY;
mMapGenerator.renderPointOfInterest(layer,
tags, Tile.TILE_SIZE - lastY / scale, lastX / scale);
cnt += 2;
}
return cnt;
}
private int MAX_WAY_COORDS = 32768;
private float[] tmpCoords = new float[MAX_WAY_COORDS];
private Tag[] decodeWayTags(int tagCnt) throws IOException {
int bytes = decodeVarint32();
Tag[] tags = new Tag[tagCnt];
int cnt = 0;
int end = mBytesProcessed + bytes;
int max = mCurTagCnt;
while (mBytesProcessed < end) {
int tagNum = decodeVarint32();
if (tagNum < 0 || cnt == tagCnt) {
Log.d(TAG, "NULL TAG: " + mTile + " invalid tag:" + tagNum + " "
+ tagCnt + "/" + cnt);
} else {
if (tagNum < Tags.MAX)
tags[cnt++] = Tags.tags[tagNum];
else {
tagNum -= Tags.LIMIT;
if (tagNum >= 0 && tagNum < max) {
// Log.d(TAG, "variable tag: " + curTags[tagNum]);
tags[cnt++] = curTags[tagNum];
} else {
Log.d(TAG, "NULL TAG: " + mTile + " could find tag:"
+ tagNum + " " + tagCnt + "/" + cnt);
}
}
}
}
if (tagCnt != cnt)
Log.d(TAG, "NULL TAG: " + mTile + " ...");
return tags;
}
private short[] mIndices = new short[10];
private short[] decodeWayIndices(int indexCnt) throws IOException {
int bytes = decodeVarint32();
short[] index = mIndices;
if (index.length < indexCnt + 1) {
index = mIndices = new short[indexCnt + 1];
}
readBuffer(bytes);
int cnt = 0;
// int end = bytesRead + bytes;
int pos = mBufferPos;
int end = pos + bytes;
byte[] buf = mReadBuffer;
int result;
while (pos < end) {
// int val = decodeVarint32();
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");
}
index[cnt++] = (short) (result * 2);
// if (cnt < indexCnt)
// index[cnt++] = (short) (val * 2);
// else DEBUG...
}
mBufferPos = pos;
mBytesProcessed += bytes;
index[indexCnt] = -1;
return index;
}
private int decodeWayCoordinates(boolean skip, int nodes) throws IOException {
int bytes = decodeVarint32();
readBuffer(bytes);
if (skip) {
mBufferPos += bytes;
return nodes;
}
int pos = mBufferPos;
int end = pos + bytes;
float[] coords = tmpCoords;
byte[] buf = mReadBuffer;
int cnt = 0;
int result;
int x, lastX = 0;
int y, lastY = 0;
boolean even = true;
float scale = mScaleFactor;
if (nodes * 2 > coords.length) {
Log.d(TAG, "increase way coord buffer " + mTile + " to " + (nodes * 2));
float[] tmp = new float[nodes * 2];
tmpCoords = 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 + 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");
}
if (even) {
x = ((result >>> 1) ^ -(result & 1));
lastX = lastX + x;
coords[cnt++] = lastX / scale;
even = false;
} else {
y = ((result >>> 1) ^ -(result & 1));
lastY = lastY + y;
coords[cnt++] = Tile.TILE_SIZE - lastY / scale;
even = true;
}
}
mBufferPos = pos;
mBytesProcessed += bytes;
return cnt;
}
int mReadPos;
private int readBuffer(int size) throws IOException {
int read = 0;
if (mBufferPos + size < mBufferSize)
return mBufferSize - mBufferPos;
if (mReadPos == mContentLenth)
return mBufferSize - mBufferPos;
if (size > BUFFER_SIZE) {
// FIXME throw exception for now, but frankly better
// sanitize tile data on compilation. this should only
// happen with strings or one ways coordinates are
// larger than 64kb
throw new IOException("X requested size too large " + mTile);
}
if (mBufferSize == mBufferPos) {
mBufferPos = 0;
mBufferSize = 0;
} else if (mBufferPos + (size - mBufferSize) > BUFFER_SIZE) {
Log.d(TAG, "wrap buffer" + (size - mBufferSize) + " " + mBufferPos);
// copy bytes left to read to the beginning of buffer
mBufferSize -= mBufferPos;
System.arraycopy(mReadBuffer, mBufferPos, mReadBuffer, 0, mBufferSize);
mBufferPos = 0;
}
int max = BUFFER_SIZE - mBufferSize;
while ((mBufferSize - mBufferPos) < size && max > 0) {
max = BUFFER_SIZE - mBufferSize;
if (max > mContentLenth - mReadPos)
max = (int) (mContentLenth - mReadPos);
// read until requested size is available in buffer
int len = mInputStream.read(mReadBuffer, mBufferSize, max);
if (len < 0) {
// finished reading, mark end
mReadBuffer[mBufferSize] = 0;
break;
}
read += len;
mReadPos += len;
if (mCacheFile != null)
mCacheFile.write(mReadBuffer, mBufferSize, len);
if (USE_LW_HTTP) {
if (mReadPos == mContentLenth)
break;
}
mBufferSize += len;
}
return read;
}
@Override
public void cancel() {
if (mRequest != null) {
mRequest.abort();
mRequest = null;
}
}
private int decodeVarint32() throws IOException {
int pos = mBufferPos;
if (pos + 10 > mBufferSize) {
readBuffer(4096);
pos = mBufferPos;
}
byte[] buf = mReadBuffer;
if (buf[pos] >= 0) {
mBufferPos += 1;
mBytesProcessed += 1;
return buf[pos];
} else if (buf[pos + 1] >= 0) {
mBufferPos += 2;
mBytesProcessed += 2;
return (buf[pos] & 0x7f)
| (buf[pos + 1]) << 7;
} else if (buf[pos + 2] >= 0) {
mBufferPos += 3;
mBytesProcessed += 3;
return (buf[pos] & 0x7f)
| (buf[pos + 1] & 0x7f) << 7
| (buf[pos + 2]) << 14;
} else if (buf[pos + 3] >= 0) {
mBufferPos += 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");
mBufferPos += read;
mBytesProcessed += read;
return result;
}
// ///////////////////////// Lightweight HttpClient //////////////////////
// would have written simple tcp server/client for this...
private int mMaxReq = 0;
private Socket mSocket;
private OutputStream mCommandStream;
private InputStream mResponseStream;
private long mLastRequest = 0;
private SocketAddress mSockAddr;
private final static byte[] RESPONSE_HTTP_OK = "HTTP/1.1 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 final static byte[] REQUEST_GET_START = "GET /osmstache/test/".getBytes();
private final static byte[] REQUEST_GET_END = (".osmtile HTTP/1.1\n" +
"Host: " + SERVER_ADDR + "\n" +
"Connection: Keep-Alive\n\n").getBytes();
private byte[] mRequestBuffer;
int lwHttpReadHeader() throws IOException {
InputStream is = mResponseStream;
byte[] buf = mReadBuffer;
int read = 0;
int pos = 0;
int end = 0;
// int max_req = 0;
int resp_len = 0;
boolean first = true;
for (int len = 0; 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 for OK
for (int i = 0; i < 15 && pos + i < end; i++)
if (buf[pos + i] != RESPONSE_HTTP_OK[i])
return -1;
first = false;
} 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
resp_len = resp_len * 10 + (buf[pos + i]) - '0';
}
}
// String line = new String(buf, pos, end - pos - 1);
// Log.d(TAG, ">" + line + "< " + resp_len);
pos += (end - pos) + 1;
end = pos;
}
}
mContentLenth = resp_len;
// start of content
mBufferPos = end;
// bytes of content already read into buffer
mReadPos = read - end;
// buffer fill
mBufferSize = read;
mInputStream = mResponseStream;
return resp_len;
}
private boolean lwHttpSendRequest(Tile tile) throws IOException {
if (mSockAddr == null) {
mSockAddr = new InetSocketAddress(SERVER_ADDR, 80);
}
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(mReadBuffer, 0, avail);
}
}
byte[] request = mRequestBuffer;
int pos = REQUEST_GET_START.length;
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;
// 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, "retry - recreate connection");
}
lwHttpConnect();
mCommandStream.write(request, 0, len);
mCommandStream.flush();
return true;
}
private boolean lwHttpConnect() throws IOException {
if (mRequestBuffer == null) {
mRequestBuffer = new byte[1024];
System.arraycopy(REQUEST_GET_START,
0, mRequestBuffer, 0,
REQUEST_GET_START.length);
}
mSocket = new Socket();
mSocket.connect(mSockAddr, 30000);
mSocket.setTcpNoDelay(true);
// mCmdBuffer = new PrintStream(mSocket.getOutputStream());
mCommandStream = new BufferedOutputStream(mSocket.getOutputStream());
mResponseStream = 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;
}
// //////////////////////////// Tile cache ///////////////////////////////
private boolean cacheRead(Tile tile, File f) {
if (f.exists() && f.length() > 0) {
FileInputStream in;
try {
in = new FileInputStream(f);
mContentLenth = f.length();
Log.d(TAG, tile + " using cache: " + mContentLenth);
mInputStream = in;
decode();
in.close();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (Exception ex) {
ex.printStackTrace();
}
f.delete();
return false;
}
return false;
}
private boolean cacheBegin(Tile tile, File f) {
if (USE_CACHE) {
try {
Log.d(TAG, "writing cache: " + tile);
mCacheFile = new FileOutputStream(f);
if (mReadPos > 0) {
try {
mCacheFile.write(mReadBuffer, mBufferPos,
mBufferSize - mBufferPos);
} catch (IOException e) {
e.printStackTrace();
mCacheFile = null;
return false;
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
mCacheFile = null;
return false;
}
}
return true;
}
private void cacheFinish(Tile tile, File file, boolean success) {
if (USE_CACHE) {
if (success) {
try {
mCacheFile.flush();
mCacheFile.close();
Log.d(TAG, tile + " cache written " + file.length());
} catch (IOException e) {
e.printStackTrace();
}
} else {
file.delete();
}
}
mCacheFile = null;
}
/*
* All code below is taken from or based on Google's Protocol Buffers
* implementation:
*/
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// http://code.google.com/p/protobuf/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
private String decodeString() throws IOException {
final int size = decodeVarint32();
readBuffer(size);
final String result = new String(mReadBuffer, mBufferPos, size, "UTF-8");
mBufferPos += size;
mBytesProcessed += size;
return result;
}
private static int decodeZigZag32(final int n) {
return (n >>> 1) ^ -(n & 1);
}
}