diff --git a/vtm-android-example/src/org/oscim/android/test/BaseMapActivity.java b/vtm-android-example/src/org/oscim/android/test/BaseMapActivity.java
index e2387bcf..ee7ba3f4 100644
--- a/vtm-android-example/src/org/oscim/android/test/BaseMapActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/BaseMapActivity.java
@@ -2,6 +2,7 @@ package org.oscim.android.test;
import org.oscim.android.MapActivity;
import org.oscim.android.MapView;
+import org.oscim.android.cache.TileCache;
import org.oscim.layers.tile.vector.VectorTileLayer;
import org.oscim.tiling.source.TileSource;
import org.oscim.tiling.source.oscimap4.OSciMap4TileSource;
@@ -13,6 +14,9 @@ public class BaseMapActivity extends MapActivity {
MapView mMapView;
VectorTileLayer mBaseLayer;
+ TileSource mTileSource;
+
+ private TileCache mCache;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -21,10 +25,21 @@ public class BaseMapActivity extends MapActivity {
mMapView = (MapView) findViewById(R.id.mapView);
- TileSource tileSource = new OSciMap4TileSource();
- tileSource.setOption("url", "http://opensciencemap.org/tiles/vtm");
+ mTileSource = new OSciMap4TileSource();
+ mTileSource.setOption("url", "http://opensciencemap.org/tiles/vtm");
- mBaseLayer = mMap.setBaseMap(tileSource);
+
+ mCache = new TileCache(this, "cachedir", "testdb");
+ mCache.setCacheSize(512 * (1 << 10));
+ mTileSource.setCache(mCache);
+
+ mBaseLayer = mMap.setBaseMap(mTileSource);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mCache.dispose();
}
@Override
diff --git a/vtm-android/src/org/oscim/android/cache/TileCache.java b/vtm-android/src/org/oscim/android/cache/TileCache.java
new file mode 100644
index 00000000..076af367
--- /dev/null
+++ b/vtm-android/src/org/oscim/android/cache/TileCache.java
@@ -0,0 +1,277 @@
+/*
+ * 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.android.cache;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+import org.oscim.core.Tile;
+import org.oscim.tiling.source.ITileCache;
+import org.slf4j.LoggerFactory;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDoneException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+
+public class TileCache implements ITileCache {
+
+ final static org.slf4j.Logger log = LoggerFactory.getLogger(TileCache.class);
+
+ class CacheTileReader implements TileReader {
+ final InputStream mInputStream;
+ final Tile mTile;
+ final int mSize;
+
+ public CacheTileReader(Tile tile, InputStream is, int size) {
+ mTile = tile;
+ mInputStream = is;
+ mSize = size;
+ }
+
+ @Override
+ public Tile getTile() {
+ return mTile;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return mInputStream;
+ }
+
+ @Override
+ public int getBytes() {
+ return mSize;
+ }
+ }
+
+ class CacheTileWriter implements TileWriter {
+ final ByteArrayOutputStream mOutputStream;
+ final Tile mTile;
+
+ CacheTileWriter(Tile tile, ByteArrayOutputStream os) {
+ mTile = tile;
+ mOutputStream = os;
+ }
+
+ @Override
+ public Tile getTile() {
+ return mTile;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return mOutputStream;
+ }
+
+ @Override
+ public void complete(boolean success) {
+ saveTile(mTile, mOutputStream, success);
+ }
+ }
+
+ private final ArrayList mCacheBuffers;
+
+ private final SQLiteHelper dbHelper;
+
+ private final SQLiteDatabase mDatabase;
+ private final SQLiteStatement mStmtGetTile;
+ private final SQLiteStatement mStmtPutTile;
+ private final SQLiteStatement mStmtUpdateTile;
+
+ public void dispose() {
+ if (mDatabase.isOpen())
+ mDatabase.close();
+ }
+
+ public TileCache(Context context, String cacheDirectory, String dbName) {
+
+ dbHelper = new SQLiteHelper(context);
+ mDatabase = dbHelper.getWritableDatabase();
+
+ mStmtGetTile = mDatabase.compileStatement("" +
+ "SELECT " + COLUMN_DATA +
+ " FROM " + TABLE_NAME +
+ " WHERE x=? AND y=? AND z = ?");
+
+ mStmtPutTile = mDatabase.compileStatement("" +
+ "INSERT INTO " + TABLE_NAME +
+ " (x, y, z, time, last_access, data)" +
+ " VALUES(?,?,?,?,?,?)");
+
+ mStmtUpdateTile = mDatabase.compileStatement("" +
+ "UPDATE " + TABLE_NAME +
+ " SET last_access=?" +
+ " WHERE x=? AND y=? AND z=?");
+
+ mCacheBuffers = new ArrayList();
+ }
+
+ @Override
+ public TileWriter writeTile(Tile tile) {
+ ByteArrayOutputStream os;
+
+ synchronized (mCacheBuffers) {
+ if (mCacheBuffers.size() == 0)
+ os = new ByteArrayOutputStream(32 * 1024);
+ else
+ os = mCacheBuffers.remove(mCacheBuffers.size() - 1);
+ }
+ return new CacheTileWriter(tile, os);
+ }
+
+ static final String TABLE_NAME = "tiles";
+ static final String COLUMN_TIME = "time";
+ static final String COLUMN_ACCESS = "last_access";
+ static final String COLUMN_DATA = "data";
+
+ //static final String COLUMN_SIZE = "size";
+
+ class SQLiteHelper extends SQLiteOpenHelper {
+
+ private static final String DATABASE_NAME = "tile.db";
+ private static final int DATABASE_VERSION = 3;
+
+ private static final String DATABASE_CREATE =
+ "CREATE TABLE "
+ + TABLE_NAME + "("
+ + "x INTEGER NOT NULL,"
+ + "y INTEGER NOT NULL,"
+ + "z INTEGER NOT NULL,"
+ + COLUMN_TIME + " LONG NOT NULL,"
+ //+ COLUMN_SIZE + " LONG NOT NULL,"
+ + COLUMN_ACCESS + " LONG NOT NULL,"
+ + COLUMN_DATA + " BLOB,"
+ + "PRIMARY KEY(x,y,z));";
+
+ public SQLiteHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(DATABASE_CREATE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
+ }
+
+ public void saveTile(Tile tile, ByteArrayOutputStream data, boolean success) {
+ byte[] bytes = null;
+
+ if (success)
+ bytes = data.toByteArray();
+
+ synchronized (mCacheBuffers) {
+ data.reset();
+ mCacheBuffers.add(data);
+ }
+
+ log.debug("store tile {} {}", tile, Boolean.valueOf(success));
+
+ if (!success)
+ return;
+
+ synchronized (mStmtPutTile) {
+ mStmtPutTile.bindLong(1, tile.tileX);
+ mStmtPutTile.bindLong(2, tile.tileY);
+ mStmtPutTile.bindLong(3, tile.zoomLevel);
+ mStmtPutTile.bindLong(4, 0);
+ mStmtPutTile.bindLong(5, 0);
+ mStmtPutTile.bindBlob(6, bytes);
+
+ mStmtPutTile.execute();
+
+ mStmtPutTile.clearBindings();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public synchronized TileReader getTileApi11(Tile tile) {
+ InputStream in = null;
+
+ mStmtGetTile.bindLong(1, tile.tileX);
+ mStmtGetTile.bindLong(2, tile.tileY);
+ mStmtGetTile.bindLong(3, tile.zoomLevel);
+
+ try {
+ ParcelFileDescriptor result = mStmtGetTile.simpleQueryForBlobFileDescriptor();
+ in = new FileInputStream(result.getFileDescriptor());
+ } catch (SQLiteDoneException e) {
+ log.debug("not in cache {}", tile);
+ return null;
+ } finally {
+ mStmtGetTile.clearBindings();
+ }
+
+ log.debug("load tile {}", tile);
+
+ return new CacheTileReader(tile, in, Integer.MAX_VALUE);
+ }
+
+ private final String[] mQueryVals = new String[3];
+
+ @Override
+ public synchronized TileReader getTile(Tile tile) {
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB)
+ return getTileApi11(tile);
+
+ mQueryVals[0] = String.valueOf(tile.zoomLevel);
+ mQueryVals[1] = String.valueOf(tile.tileX);
+ mQueryVals[2] = String.valueOf(tile.tileY);
+
+ Cursor cursor = mDatabase.rawQuery("SELECT " + COLUMN_DATA +
+ " FROM " + TABLE_NAME +
+ " WHERE z=? AND x=? AND y=?", mQueryVals);
+
+ if (!cursor.moveToFirst()) {
+ log.debug("not in cache {}", tile);
+ return null;
+ }
+
+ InputStream in = new ByteArrayInputStream(cursor.getBlob(0));
+
+ if (!cursor.isClosed())
+ cursor.close();
+
+ log.debug("load tile {}", tile);
+
+ return new CacheTileReader(tile, in, Integer.MAX_VALUE);
+ }
+
+ public SQLiteDatabase open() throws SQLException {
+ return dbHelper.getWritableDatabase();
+ }
+
+ @Override
+ public void setCacheSize(long size) {
+ // TODO Auto-generated method stub
+ }
+}
diff --git a/vtm/src/org/oscim/tiling/source/TileSource.java b/vtm/src/org/oscim/tiling/source/TileSource.java
index ef9c985d..129ae8e2 100644
--- a/vtm/src/org/oscim/tiling/source/TileSource.java
+++ b/vtm/src/org/oscim/tiling/source/TileSource.java
@@ -26,6 +26,12 @@ public abstract class TileSource {
protected final Options options = new Options();
+ public ITileCache tileCache;
+
+ public void setCache(ITileCache cache) {
+ tileCache = cache;
+ }
+
public TileSource setOption(String key, String value) {
options.put(key, value);
return this;
diff --git a/vtm/src/org/oscim/tiling/source/common/LwHttp.java b/vtm/src/org/oscim/tiling/source/common/LwHttp.java
index 46e92355..258e1747 100644
--- a/vtm/src/org/oscim/tiling/source/common/LwHttp.java
+++ b/vtm/src/org/oscim/tiling/source/common/LwHttp.java
@@ -104,22 +104,40 @@ public class LwHttp {
}
static class Buffer extends BufferedInputStream {
- public Buffer(InputStream is) {
+ final OutputStream mCache;
+
+ public Buffer(InputStream is, OutputStream cache) {
super(is, 4096);
+ mCache = cache;
}
@Override
public synchronized int read() throws IOException {
- return super.read();
+ int data = super.read();
+ if (data >= 0)
+ mCache.write(data);
+
+ return data;
}
@Override
public synchronized int read(byte[] buffer, int offset, int byteCount)
throws IOException {
- return super.read(buffer, offset, byteCount);
+ int len = super.read(buffer, offset, byteCount);
+
+ if (len >= 0)
+ mCache.write(buffer, offset, len);
+
+ return len;
}
}
+ OutputStream mCacheOutputStream;
+
+ public void setOutputStream(OutputStream outputStream) {
+ mCacheOutputStream = outputStream;
+ }
+
public void close() {
if (mSocket != null) {
try {
@@ -204,6 +222,10 @@ public class LwHttp {
is.mark(0);
is.skip(end);
+ if (mCacheOutputStream != null) {
+ is = new Buffer(is, mCacheOutputStream);
+ }
+
if (mInflateContent)
return new InflaterInputStream(is);
diff --git a/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java b/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java
index 37e420fd..aeec26d5 100644
--- a/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java
+++ b/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java
@@ -14,14 +14,17 @@
*/
package org.oscim.tiling.source.common;
+import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.oscim.tiling.MapTile;
+import org.oscim.tiling.source.ITileCache;
import org.oscim.tiling.source.ITileDataSink;
import org.oscim.tiling.source.ITileDataSource;
+import org.oscim.utils.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,48 +37,76 @@ public abstract class PbfTileDataSource implements ITileDataSource {
protected LwHttp mConn;
protected final PbfDecoder mTileDecoder;
+ protected final ITileCache mTileCache;
- public PbfTileDataSource(PbfDecoder tileDecoder) {
+ public PbfTileDataSource(PbfDecoder tileDecoder, ITileCache tileCache) {
mTileDecoder = tileDecoder;
+ mTileCache = tileCache;
}
@Override
public QueryResult executeQuery(MapTile tile, ITileDataSink sink) {
- QueryResult result = QueryResult.SUCCESS;
+ boolean success = true;
+
+ ITileCache.TileWriter cacheWriter = null;
+
+ if (mTileCache != null) {
+ ITileCache.TileReader c = mTileCache.getTile(tile);
+ if (c == null) {
+ // create new cache entry
+ cacheWriter = mTileCache.writeTile(tile);
+ mConn.setOutputStream(cacheWriter.getOutputStream());
+ } else {
+ try {
+ InputStream is = c.getInputStream();
+ if (mTileDecoder.decode(tile, sink, is, c.getBytes())) {
+ IOUtils.closeQuietly(is);
+ return QueryResult.SUCCESS;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ log.debug(tile + " Cache read failed");
+ }
+ }
try {
InputStream is;
if (!mConn.sendRequest(tile)) {
- log.debug(tile + " Request Failed");
- result = QueryResult.FAILED;
+ log.debug(tile + " Request failed");
+ success = false;
} else if ((is = mConn.readHeader()) != null) {
- boolean win = mTileDecoder.decode(tile, sink, is, mConn.getContentLength());
- if (!win)
- log.debug(tile + " failed");
+ int bytes = mConn.getContentLength();
+ success = mTileDecoder.decode(tile, sink, is, bytes);
+ if (!success)
+ log.debug(tile + " Decoding failed");
} else {
log.debug(tile + " Network Error");
- result = QueryResult.FAILED;
+ success = false;
}
} catch (SocketException e) {
log.debug(tile + " Socket exception: " + e.getMessage());
- result = QueryResult.FAILED;
+ success = false;
} catch (SocketTimeoutException e) {
log.debug(tile + " Socket Timeout");
- result = QueryResult.FAILED;
+ success = false;
} catch (UnknownHostException e) {
log.debug(tile + " No Network");
- result = QueryResult.FAILED;
+ success = false;
} catch (Exception e) {
e.printStackTrace();
- result = QueryResult.FAILED;
+ success = false;
}
mConn.requestCompleted();
- if (result != QueryResult.SUCCESS)
+ if (cacheWriter != null)
+ cacheWriter.complete(success);
+
+ if (success)
mConn.close();
- return result;
+ return success ? QueryResult.SUCCESS : QueryResult.FAILED;
}
@Override
diff --git a/vtm/src/org/oscim/tiling/source/mapnik/MapnikVectorTileSource.java b/vtm/src/org/oscim/tiling/source/mapnik/MapnikVectorTileSource.java
index 482f1e84..520c257a 100644
--- a/vtm/src/org/oscim/tiling/source/mapnik/MapnikVectorTileSource.java
+++ b/vtm/src/org/oscim/tiling/source/mapnik/MapnikVectorTileSource.java
@@ -18,6 +18,7 @@ import java.net.URL;
import org.oscim.core.Tile;
import org.oscim.tiling.source.ITileDataSource;
+import org.oscim.tiling.source.TileSource;
import org.oscim.tiling.source.common.LwHttp;
import org.oscim.tiling.source.common.PbfTileDataSource;
import org.oscim.tiling.source.common.UrlTileSource;
@@ -26,13 +27,13 @@ public class MapnikVectorTileSource extends UrlTileSource {
@Override
public ITileDataSource getDataSource() {
- return new TileDataSource(mUrl);
+ return new TileDataSource(this, mUrl);
}
static class TileDataSource extends PbfTileDataSource {
- public TileDataSource(URL url) {
- super(new TileDecoder());
+ public TileDataSource(TileSource tileSource, URL url) {
+ super(new TileDecoder(), tileSource.tileCache);
mConn = new LwHttp(url, "image/png", "vector.pbf", true) {
@Override
diff --git a/vtm/src/org/oscim/tiling/source/oscimap/OSciMap1TileSource.java b/vtm/src/org/oscim/tiling/source/oscimap/OSciMap1TileSource.java
index d51573fc..ad46a744 100644
--- a/vtm/src/org/oscim/tiling/source/oscimap/OSciMap1TileSource.java
+++ b/vtm/src/org/oscim/tiling/source/oscimap/OSciMap1TileSource.java
@@ -17,6 +17,7 @@ package org.oscim.tiling.source.oscimap;
import java.net.URL;
import org.oscim.tiling.source.ITileDataSource;
+import org.oscim.tiling.source.TileSource;
import org.oscim.tiling.source.common.LwHttp;
import org.oscim.tiling.source.common.PbfTileDataSource;
import org.oscim.tiling.source.common.UrlTileSource;
@@ -29,12 +30,12 @@ public class OSciMap1TileSource extends UrlTileSource {
@Override
public ITileDataSource getDataSource() {
- return new TileDataSource(mUrl);
+ return new TileDataSource(this, mUrl);
}
class TileDataSource extends PbfTileDataSource {
- public TileDataSource(URL url) {
- super(new TileDecoder());
+ public TileDataSource(TileSource tileSource, URL url) {
+ super(new TileDecoder(), tileSource.tileCache);
mConn = new LwHttp(url, "application/osmtile", "osmtile", false);
}
}
diff --git a/vtm/src/org/oscim/tiling/source/oscimap2/OSciMap2TileSource.java b/vtm/src/org/oscim/tiling/source/oscimap2/OSciMap2TileSource.java
index 37094fa2..e3dbd245 100644
--- a/vtm/src/org/oscim/tiling/source/oscimap2/OSciMap2TileSource.java
+++ b/vtm/src/org/oscim/tiling/source/oscimap2/OSciMap2TileSource.java
@@ -26,6 +26,7 @@ import org.oscim.core.TagSet;
import org.oscim.core.Tile;
import org.oscim.tiling.source.ITileDataSink;
import org.oscim.tiling.source.ITileDataSource;
+import org.oscim.tiling.source.TileSource;
import org.oscim.tiling.source.common.LwHttp;
import org.oscim.tiling.source.common.PbfDecoder;
import org.oscim.tiling.source.common.PbfTileDataSource;
@@ -37,12 +38,12 @@ public class OSciMap2TileSource extends UrlTileSource {
@Override
public ITileDataSource getDataSource() {
- return new TileDataSource(mUrl);
+ return new TileDataSource(this, mUrl);
}
class TileDataSource extends PbfTileDataSource {
- public TileDataSource(URL url) {
- super(new TileDecoder());
+ public TileDataSource(TileSource tileSource, URL url) {
+ super(new TileDecoder(), tileSource.tileCache);
mConn = new LwHttp(url, "application/osmtile", "osmtile", false);
}
}
diff --git a/vtm/src/org/oscim/tiling/source/oscimap4/OSciMap4TileSource.java b/vtm/src/org/oscim/tiling/source/oscimap4/OSciMap4TileSource.java
index 3d9db73e..2757d552 100644
--- a/vtm/src/org/oscim/tiling/source/oscimap4/OSciMap4TileSource.java
+++ b/vtm/src/org/oscim/tiling/source/oscimap4/OSciMap4TileSource.java
@@ -17,6 +17,7 @@ package org.oscim.tiling.source.oscimap4;
import java.net.URL;
import org.oscim.tiling.source.ITileDataSource;
+import org.oscim.tiling.source.TileSource;
import org.oscim.tiling.source.common.LwHttp;
import org.oscim.tiling.source.common.PbfTileDataSource;
import org.oscim.tiling.source.common.UrlTileSource;
@@ -25,12 +26,12 @@ public class OSciMap4TileSource extends UrlTileSource {
@Override
public ITileDataSource getDataSource() {
- return new TileDataSource(mUrl);
+ return new TileDataSource(this, mUrl);
}
class TileDataSource extends PbfTileDataSource {
- public TileDataSource(URL url) {
- super(new TileDecoder());
+ public TileDataSource(TileSource tileSource, URL url) {
+ super(new TileDecoder(), tileSource.tileCache);
//mConn = new LwHttp(url, "application/x-protobuf", "vtm", false);
mConn = new LwHttp(url, "image/png", "vtm", false);
}