diff --git a/vtm/src/org/oscim/layers/tile/JobQueue.java b/vtm/src/org/oscim/layers/tile/JobQueue.java index 05d1be0e3..a68c9c71a 100644 --- a/vtm/src/org/oscim/layers/tile/JobQueue.java +++ b/vtm/src/org/oscim/layers/tile/JobQueue.java @@ -1,5 +1,4 @@ /* - * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2012, 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). @@ -20,11 +19,16 @@ import static org.oscim.layers.tile.MapTile.State.LOADING; import static org.oscim.layers.tile.MapTile.State.NONE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A JobQueue keeps the list of pending jobs for a MapView and prioritizes them. */ public class JobQueue { + static final Logger log = LoggerFactory.getLogger(JobQueue.class); + private int mCurrentJob = 0; private MapTile[] mJobs; @@ -51,7 +55,12 @@ public synchronized void clear() { MapTile[] tiles = mJobs; for (int i = mCurrentJob, n = mJobs.length; i < n; i++) { - tiles[i].state = NONE; + + if (tiles[i].state == LOADING) + tiles[i].state = NONE; + else + log.debug("wrong tile in queue {} {}", tiles[i], tiles[i].state); + tiles[i] = null; } mCurrentJob = 0; diff --git a/vtm/src/org/oscim/layers/tile/TileManager.java b/vtm/src/org/oscim/layers/tile/TileManager.java index fad743886..d198a496d 100644 --- a/vtm/src/org/oscim/layers/tile/TileManager.java +++ b/vtm/src/org/oscim/layers/tile/TileManager.java @@ -51,8 +51,8 @@ public class TileManager { private final int mCacheLimit; private int mCacheReduce; - private final int mMinZoom; - private final int mMaxZoom; + private int mMinZoom; + private int mMaxZoom; private int[] mZoomTable; @@ -60,11 +60,12 @@ public class TileManager { * limit number tiles with new data not uploaded to GL * TODO this should depend on the number of tiles displayed */ - private static final int MAX_TILES_IN_QUEUE = 40; + private static final int MAX_TILES_IN_QUEUE = 20; /** cache limit threshold */ - private static final int CACHE_THRESHOLD = 30; + private static final int CACHE_THRESHOLD = 25; private static final int CACHE_CLEAR_THRESHOLD = 10; + private static final int QUEUE_CLEAR_THRESHOLD = 10; private final Map mMap; private final Viewport mViewport; @@ -81,49 +82,55 @@ public class TileManager { /** counter for tiles with new data not yet loaded to GL */ private int mTilesForUpload; - /* new tile jobs for MapWorkers */ + /** new tile jobs for MapWorkers */ private final ArrayList mJobs; - /* counter to check whether current TileSet has changed */ + /** counter to check whether current TileSet has changed */ private int mUpdateSerial; - /* lock for TileSets while updating MapTile locks - still needed? */ + /** lock for TileSets while updating MapTile locks - still needed? */ private final Object mTilelock = new Object(); private TileSet mCurrentTiles; /* package */TileSet mNewTiles; - /* job queue filled in TileManager and polled by TileLoaders */ + /** job queue filled in TileManager and polled by TileLoaders */ private final JobQueue jobQueue; private final float[] mMapPlane = new float[8]; - private final TileIndex mIndex = new TileIndex() { - - @Override - public void removeItem(MapTile t) { - if (t.node == null) { - log.error("already removed {}", t); - return; - } - - super.remove(t.node); - t.node.item = null; - } - - @Override - public TileNode create() { - return new TileNode(); - } - }; - - public final EventDispatcher events = new EventDispatcher() { - - @Override - public void tell(Listener l, Event event, MapTile tile) { - l.onTileManagerEvent(event, tile); - } - }; + private boolean mLoadParent = false; + private int mPrevZoomlevel; + + //private final double mLevelUpThreshold = 1.15; + //private final double mLevelDownThreshold = 1.95; + private final double mLevelUpThreshold = 1; + private final double mLevelDownThreshold = 2; + + private final TileIndex mIndex = + new TileIndex() { + @Override + public void removeItem(MapTile t) { + if (t.node == null) + throw new IllegalStateException("already removed: " + t); + + super.remove(t.node); + t.node.item = null; + } + + @Override + public TileNode create() { + return new TileNode(); + } + }; + + public final EventDispatcher events = + new EventDispatcher() { + @Override + public void tell(Listener l, Event event, MapTile tile) { + l.onTileManagerEvent(event, tile); + } + }; public interface Listener extends EventListener { void onTileManagerEvent(Event event, MapTile tile); @@ -149,7 +156,12 @@ public TileManager(Map map, int minZoom, int maxZoom, int cacheLimit) { public void setZoomTable(int[] zoomLevel) { mZoomTable = zoomLevel; + } + public MapTile getTile(int x, int y, int z) { + synchronized (mTilelock) { + return mIndex.getTile(x, y, z); + } } public void init() { @@ -186,9 +198,10 @@ public void init() { public boolean update(MapPosition pos) { // FIXME cant expect init to be called otherwise - if (mNewTiles == null || mNewTiles.tiles.length == 0) + if (mNewTiles == null || mNewTiles.tiles.length == 0) { + mPrevZoomlevel = pos.zoomLevel; init(); - + } /* clear JobQueue and set tiles to state == NONE. * one could also append new tiles and sort in JobQueue * but this has the nice side-effect that MapWorkers dont @@ -208,7 +221,27 @@ public boolean update(MapPosition pos) { int tileZoom = FastMath.clamp(pos.zoomLevel, mMinZoom, mMaxZoom); - if (mZoomTable != null) { + //mLoadParent = pos.getZoomScale() < 1.5; + + /* greater 1 when zoomed in further than current zoomlevel */ + double scaleDiv = pos.scale / (1 << tileZoom); + + mLoadParent = false; + + if (mZoomTable == null) { + /* positive when current zoomlevel is greater than previous */ + int zoomDiff = tileZoom - mPrevZoomlevel; + if (zoomDiff == 1) { + /* dont switch zoomlevel if */ + if (scaleDiv < mLevelUpThreshold) + tileZoom = mPrevZoomlevel; + } else if (zoomDiff == -1) { + mLoadParent = true; + /* dont switch zoomlevel if */ + if (scaleDiv > mLevelDownThreshold) + tileZoom = mPrevZoomlevel; + } + } else { int match = 0; for (int z : mZoomTable) { if (z <= tileZoom && z > match) @@ -219,11 +252,13 @@ public boolean update(MapPosition pos) { tileZoom = match; } + //log.debug("{}|{}|{}|{}", mLoadParent, mPrevZoomlevel, pos.zoomLevel, scaleDiv); + mPrevZoomlevel = tileZoom; mViewport.getMapExtents(mMapPlane, Tile.SIZE / 2); - // scan visible tiles. callback function calls 'addTile' - // which updates mNewTiles + /* scan visible tiles. callback function calls 'addTile' + * which updates mNewTiles */ mNewTiles.cnt = 0; mScanBox.scan(pos.x, pos.y, pos.scale, tileZoom, mMapPlane); @@ -255,7 +290,7 @@ public boolean update(MapPosition pos) { /* unlock previous tiles */ mCurrentTiles.releaseTiles(); - /* make new tiles current */ + /* swap newTiles with currentTiles */ TileSet tmp = mCurrentTiles; mCurrentTiles = mNewTiles; mNewTiles = tmp; @@ -277,7 +312,6 @@ public boolean update(MapPosition pos) { /* sets tiles to state == LOADING */ jobQueue.setJobs(jobs); - mJobs.clear(); if (mCacheReduce < mCacheLimit / 2) { @@ -294,13 +328,11 @@ public boolean update(MapPosition pos) { if (remove > CACHE_THRESHOLD || mTilesForUpload > MAX_TILES_IN_QUEUE) - limitCache(pos, remove); return true; } - /** only used in setmapDatabase -- deprecate? */ public void clearJobs() { jobQueue.clear(); } @@ -316,8 +348,9 @@ public MapTile getTileJob() { /** * Retrive a TileSet of current tiles. Tiles remain locked in cache until * the set is unlocked by either passing it again to this function or to - * releaseTiles. If passed TileSet is null it will be allocated. + * releaseTiles. * + * @threadsafe * @param tileSet * to be updated * @return true if TileSet has changed @@ -340,12 +373,6 @@ public boolean getActiveTiles(TileSet tileSet) { return true; } - /** - * Unlock TileSet and clear all item references. - */ - public void releaseTiles(TileSet tileSet) { - tileSet.releaseTiles(); - } MapTile addTile(int x, int y, int zoomLevel) { MapTile tile = mIndex.getTile(x, y, zoomLevel); @@ -359,14 +386,14 @@ MapTile addTile(int x, int y, int zoomLevel) { mJobs.add(tile); } - if ((zoomLevel > mMinZoom) && (mZoomTable == null)) { + if (mLoadParent && (zoomLevel > mMinZoom) && (mZoomTable == null)) { /* prefetch parent */ MapTile p = tile.node.parent.item; if (p == null) { TileNode n = mIndex.add(x >> 1, y >> 1, zoomLevel - 1); p = n.item = new MapTile(n, x >> 1, y >> 1, zoomLevel - 1); addToCache(p); - // hack to not add tile twice to queue + /* hack to not add tile twice to queue */ p.state = LOADING; mJobs.add(p); } else if (!p.isActive()) { @@ -400,21 +427,19 @@ private void addToCache(MapTile tile) { } private void removeFromCache(MapTile t) { - - if (t.state == NEW_DATA || t.state == READY) + if (t.state(NEW_DATA | READY)) events.fire(TILE_REMOVED, t); - synchronized (t) { - /* still belongs to TileLoader thread, defer clearing to - * jobCompleted() */ - if (t.state != CANCEL) - t.clear(); + if (dbg) + log.debug("remove from cache {} {} {}", t, t.state, t.isLocked()); - // needed? - t.state = CANCEL; + //synchronized (t) { + /* When in CANCEL state tile belongs to TileLoader thread, + * defer clearing to jobCompleted() */ + if (t.state(NEW_DATA | READY)) + t.clear(); - mIndex.removeItem(t); - } + mIndex.removeItem(t); mTilesCount--; } @@ -490,18 +515,15 @@ private void limitCache(MapPosition pos, int remove) { remove--; } - remove = (newTileCnt - MAX_TILES_IN_QUEUE) + 10; + remove = (newTileCnt - MAX_TILES_IN_QUEUE) + QUEUE_CLEAR_THRESHOLD; //int r = remove; for (int i = size - 1; i >= 0 && remove > 0; i--) { MapTile t = tiles[i]; - if (t != null && t.state == NEW_DATA) { - if (!t.isLocked()) { - newTileCnt--; - - removeFromCache(t); - tiles[i] = null; - remove--; - } + if (t != null && !t.isLocked() && t.state == NEW_DATA) { + newTileCnt--; + removeFromCache(t); + tiles[i] = null; + remove--; } } @@ -512,37 +534,55 @@ private void limitCache(MapPosition pos, int remove) { } /** - * called by TileLoader thread when tile is loaded. + * Called by TileLoader thread when tile is loaded. * + * @threadsafe * @param tile * Tile ready for upload in TileRenderLayer */ - public void jobCompleted(final MapTile tile, final boolean success) { - mMap.post(new Runnable() { - - @Override - public void run() { - if (!success || tile.state == CANCEL) { - - log.debug("loading {}: {}", - (success ? "canceled" : "failed"), - tile); - tile.clear(); - return; - } + public void jobCompleted(MapTile tile, boolean success) { + + /* send TILE_LOADED event on main-loop */ + mMap.post(new JobCompletedEvent(tile, success)); + + /* locked means the tile is visible or referenced by + * a tile that might be visible. */ + if (tile.isLocked()) + mMap.render(); + } + + class JobCompletedEvent implements Runnable { + final MapTile tile; + final boolean success; + + public JobCompletedEvent(MapTile tile, boolean success) { + this.tile = tile; + this.success = success; + } + + @Override + public void run() { + if (success && tile.state != CANCEL) { tile.state = NEW_DATA; events.fire(TILE_LOADED, tile); - mTilesForUpload += 1; + mTilesForUpload++; + + log.debug("loading {}: ready", tile); - /* locked means the tile is visible or referenced by - * a tile that might be visible. */ - if (tile.isLocked()) - mMap.render(); + return; } - }); + log.debug("loading {}: {}", tile, + success ? "canceled" + : "failed"); + + tile.clear(); + + if (tile.state == LOADING) + mIndex.removeItem(tile); + } } private static void updateDistances(MapTile[] tiles, int size, MapPosition pos) { @@ -629,4 +669,9 @@ protected void setVisible(int y, int x1, int x2) { public MapTile getTile(int tileX, int tileY, byte zoomLevel) { return mIndex.getTile(tileX, tileY, zoomLevel); } + + public void setZoomLevel(int zoomLevelMin, int zoomLevelMax) { + mMinZoom = zoomLevelMin; + mMaxZoom = zoomLevelMax; + } } diff --git a/vtm/src/org/oscim/layers/tile/TileRenderer.java b/vtm/src/org/oscim/layers/tile/TileRenderer.java index d1cc8ee37..2687c6e24 100644 --- a/vtm/src/org/oscim/layers/tile/TileRenderer.java +++ b/vtm/src/org/oscim/layers/tile/TileRenderer.java @@ -81,7 +81,7 @@ public synchronized void setBitmapAlpha(float alpha) { public synchronized void update(GLViewport v) { if (mAlpha == 0) { - mTileManager.releaseTiles(mDrawTiles); + mDrawTiles.releaseTiles(); return; } diff --git a/vtm/src/org/oscim/layers/tile/vector/VectorTileLayer.java b/vtm/src/org/oscim/layers/tile/vector/VectorTileLayer.java index 327e8d6d5..0f4b6419d 100644 --- a/vtm/src/org/oscim/layers/tile/vector/VectorTileLayer.java +++ b/vtm/src/org/oscim/layers/tile/vector/VectorTileLayer.java @@ -78,7 +78,7 @@ protected TileLoader createLoader() { public boolean setTileSource(TileSource tileSource) { pauseLoaders(true); mTileManager.clearJobs(); - + mTileManager.setZoomLevel(tileSource.getZoomLevelMin(), tileSource.getZoomLevelMax()); if (mTileSource != null) { mTileSource.close(); mTileSource = null;