/* * 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.InetSocketAddress; import java.net.MalformedURLException; 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.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.MapOptions; 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/osmstache/gis-live/%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 String mHost; private int mPort; 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) && 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; 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(MapOptions options) { //if (USE_APACHE_HTTP) // createClient(); if (mOpenFile) return OpenResult.SUCCESS; if (options == null || !options.containsKey("url")) return new OpenResult("options missing"); URL url; try { url = new URL(options.get("url")); } catch (MalformedURLException e) { e.printStackTrace(); 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 oscim database: " + host + " " + port + " " + path); REQUEST_GET_START = ("GET " + path).getBytes(); REQUEST_GET_END = (".osmtile HTTP/1.1\n" + "Host: " + host + "\n" + "Connection: Keep-Alive\n\n").getBytes(); mHost = host; mPort = port; //mSockAddr = new InetSocketAddress(host, port); mRequestBuffer = new byte[1024]; System.arraycopy(REQUEST_GET_START, 0, mRequestBuffer, 0, REQUEST_GET_START.length); if (USE_CACHE) { if (cacheDir == null) { String externalStorageDirectory = Environment .getExternalStorageDirectory() .getAbsolutePath(); String cacheDirectoryPath = externalStorageDirectory + CACHE_DIRECTORY; cacheDir = createDirectory(cacheDirectoryPath); } } mOpenFile = true; 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) { mSockAddr = null; 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, 0); 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 > 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 byte[] REQUEST_GET_START;// = "GET /osmstache/test/".getBytes(); private 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(mHost, mPort); } 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); } }