diff --git a/res/menu/options_menu.xml b/res/menu/options_menu.xml index 965415e5..c525f985 100644 --- a/res/menu/options_menu.xml +++ b/res/menu/options_menu.xml @@ -22,6 +22,10 @@ + + + + - + OpenScienceMap @@ -51,6 +51,7 @@ About this software Map file Position + Enable rotation Enter coordinates Last known location Map file center diff --git a/src/org/mapsforge/android/MapView.java b/src/org/mapsforge/android/MapView.java index 621fba71..11c71f00 100644 --- a/src/org/mapsforge/android/MapView.java +++ b/src/org/mapsforge/android/MapView.java @@ -24,11 +24,11 @@ import javax.xml.parsers.ParserConfigurationException; import org.mapsforge.android.mapgenerator.IMapGenerator; import org.mapsforge.android.mapgenerator.JobQueue; +import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.android.mapgenerator.MapDatabaseFactory; import org.mapsforge.android.mapgenerator.MapDatabases; import org.mapsforge.android.mapgenerator.MapRendererFactory; import org.mapsforge.android.mapgenerator.MapRenderers; -import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.android.mapgenerator.MapWorker; import org.mapsforge.android.mapgenerator.Theme; import org.mapsforge.android.rendertheme.ExternalRenderTheme; @@ -78,6 +78,7 @@ public class MapView extends GLSurfaceView { private static final Byte DEFAULT_START_ZOOM_LEVEL = Byte.valueOf((byte) 16); public final static boolean debugFrameTime = false; + public boolean enableRotation = false; private final MapController mMapController; private final MapViewPosition mMapViewPosition; @@ -160,7 +161,7 @@ public class MapView extends GLSurfaceView { IMapDatabase mapDatabase; if (mDebugDatabase) { mapDatabase = MapDatabaseFactory - .createMapDatabase(MapDatabases.JSON_READER); + .createMapDatabase(MapDatabases.TEST_READER); } else { mapDatabase = MapDatabaseFactory.createMapDatabase(mapDatabaseType); } @@ -684,6 +685,10 @@ public class MapView extends GLSurfaceView { mapWorker.proceed(); } + public void enableRotation(boolean enable) { + enableRotation = enable; + } + // public final int // public Handler messageHandler = new Handler() { // diff --git a/src/org/mapsforge/android/MapViewPosition.java b/src/org/mapsforge/android/MapViewPosition.java index 4c76701e..e8f650eb 100644 --- a/src/org/mapsforge/android/MapViewPosition.java +++ b/src/org/mapsforge/android/MapViewPosition.java @@ -19,7 +19,6 @@ import org.mapsforge.core.MapPosition; import org.mapsforge.core.MercatorProjection; import android.util.FloatMath; -import android.util.Log; /** * A MapPosition stores the latitude and longitude coordinate of a MapView together with its zoom level. @@ -112,41 +111,35 @@ public class MapViewPosition { public synchronized void moveMap(float mx, float my) { double pixelX = MercatorProjection.longitudeToPixelX(mLongitude, mZoomLevel); double pixelY = MercatorProjection.latitudeToPixelY(mLatitude, mZoomLevel); + double dx, dy; - // float rad = (float) Math.toRadians(mRotation); - // mx /= mScale; - // my /= mScale; - // - // double x = mx * FloatMath.cos(rad) + my * -FloatMath.sin(rad); - // double y = mx * FloatMath.sin(rad) + my * FloatMath.cos(rad); - // - // double dx = pixelX - x; - // double dy = pixelY - y; + if (mMapView.enableRotation) { + float rad = (float) Math.toRadians(mRotation); + dx = mx / mScale; + dy = my / mScale; - double dx = pixelX - mx / mScale; - double dy = pixelY - my / mScale; + double x = dx * FloatMath.cos(rad) + dy * -FloatMath.sin(rad); + double y = dx * FloatMath.sin(rad) + dy * FloatMath.cos(rad); + dx = pixelX - x; + dy = pixelY - y; + } + else { + dx = pixelX - mx / mScale; + dy = pixelY - my / mScale; + } mLatitude = MercatorProjection.pixelYToLatitude(dy, mZoomLevel); mLatitude = MercatorProjection.limitLatitude(mLatitude); mLongitude = MercatorProjection.pixelXToLongitude(dx, mZoomLevel); - // - // mLatitude = MercatorProjection.pixelYToLatitude(pixelY - moveVertical / mScale, - // mZoomLevel); - // mLatitude = MercatorProjection.limitLatitude(mLatitude); - // - // mLongitude = MercatorProjection.pixelXToLongitude(pixelX - moveHorizontal - // / mScale, mZoomLevel); - mLongitude = MercatorProjection.wrapLongitude(mLongitude); // mLongitude = MercatorProjection.limitLongitude(mLongitude); } - public synchronized void rotateMap(float angle) { + public synchronized void rotateMap(float angle, float cx, float cy) { + moveMap(cx, cy); mRotation -= angle; - Log.d("...", "angle:" + mRotation); - // mRotation %= 360; } synchronized void setMapCenter(GeoPoint geoPoint) { diff --git a/src/org/mapsforge/android/TouchHandler.java b/src/org/mapsforge/android/TouchHandler.java index ce40e210..53b869a3 100644 --- a/src/org/mapsforge/android/TouchHandler.java +++ b/src/org/mapsforge/android/TouchHandler.java @@ -112,16 +112,33 @@ public class TouchHandler { return true; } - // if (multi > 0) { - // double dx = event.getX(0) - event.getX(1); - // double dy = event.getY(0) - event.getY(1); - // double rad = Math.atan2(dy, dx); - // float angle = (float) Math.toDegrees(rad); - // mMapPosition.rotateMap(angle - mAngle); - // mAngle = angle; - // mMapView.redrawTiles(); - // } + if (mMapView.enableRotation) { + if (multi > 0) { + double x1 = event.getX(0); + double x2 = event.getX(1); + double y1 = event.getY(0); + double y2 = event.getY(1); + double dx = x1 - x2; + double dy = y1 - y2; + + double rad = Math.atan2(dy, dx); + float angle = (float) Math.toDegrees(rad); + + // focus point relative to center + double cx = (mMapView.getWidth() >> 1) - (x1 + x2) / 2; + double cy = (mMapView.getHeight() >> 1) - (y1 + y2) / 2; + double r = Math.toRadians(angle - mAngle); + + double x = cx * Math.cos(r) + cy * -Math.sin(r) - cx; + double y = cx * Math.sin(r) + cy * Math.cos(r) - cy; + // Log.d("...", "move " + x + " " + y + " " + cx + " " + cy); + + mMapPosition.rotateMap(angle - mAngle, (float) x, (float) y); + mAngle = angle; + mMapView.redrawTiles(); + } + } // save the position of the event mPosX = event.getX(pointerIndex); mPosY = event.getY(pointerIndex); @@ -182,7 +199,7 @@ public class TouchHandler { private boolean onActionUp(MotionEvent motionEvent) { mActivePointerId = INVALID_POINTER_ID; mScaling = false; - + multi = 0; return true; } @@ -400,7 +417,11 @@ public class TouchHandler { mCenterX = mMapView.getWidth() >> 1; mCenterY = mMapView.getHeight() >> 1; mScale = 1; - // mMapPosition = mMapView.getMapPosition(); + + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } return true; } diff --git a/src/org/mapsforge/android/glrenderer/MapGenerator.java b/src/org/mapsforge/android/glrenderer/MapGenerator.java index b448c69a..ff9cdcf7 100644 --- a/src/org/mapsforge/android/glrenderer/MapGenerator.java +++ b/src/org/mapsforge/android/glrenderer/MapGenerator.java @@ -428,12 +428,11 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas mDebugDrawUnmatched = debugSettings.mDrawUnmatchted; // fixed now.... - if (tile.newData || tile.isReady || tile.isCanceled) { + if (tile.newData || tile.isReady) { Log.d(TAG, "XXX tile already loaded " + tile + " " + tile.newData + " " - + tile.isReady + " " - + tile.isCanceled); + + tile.isReady + " "); return false; } diff --git a/src/org/mapsforge/android/glrenderer/MapRenderer.java b/src/org/mapsforge/android/glrenderer/MapRenderer.java index d877a706..073dadfe 100644 --- a/src/org/mapsforge/android/glrenderer/MapRenderer.java +++ b/src/org/mapsforge/android/glrenderer/MapRenderer.java @@ -36,7 +36,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.ArrayList; -import java.util.Collections; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; @@ -47,7 +46,6 @@ import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.android.rendertheme.RenderTheme; import org.mapsforge.android.utils.GlUtils; import org.mapsforge.core.MapPosition; -import org.mapsforge.core.MercatorProjection; import org.mapsforge.core.Tile; import android.opengl.GLES20; @@ -57,45 +55,11 @@ import android.util.FloatMath; import android.util.Log; public class MapRenderer implements org.mapsforge.android.IMapRenderer { - private static final String TAG = "MapRenderer"; - private static final int MB = 1024 * 1024; - private static final int SHORT_BYTES = 2; - static final float COORD_MULTIPLIER = 8.0f; - - private static final int MAX_TILES_IN_QUEUE = 40; - private static final int CACHE_TILES_MAX = 250; - private static final int LIMIT_BUFFERS = 16 * MB; - - private static int CACHE_TILES = CACHE_TILES_MAX; - - private final MapView mMapView; - private static ArrayList mJobList; - private static ArrayList mVBOs; - - // all tiles currently referenced - private static ArrayList mTiles; - - // tiles that have new data to upload, see passTile() - private static ArrayList mTilesLoaded; - - private static int mWidth, mHeight; - private static float mAspect; - - // current center tile, values used to check if position has - // changed for updating current tile list - private static long mTileX, mTileY; - private static float mPrevScale; - private static byte mPrevZoom; - - private static int rotateBuffers = 2; - private static ShortBuffer shortBuffer[]; - private static short[] mFillCoords; - - // bytes currently loaded in VBOs - private static int mBufferMemoryUsage; - - class TilesData { + /** + * used for passing tiles to be rendered from TileLoader(Main) to GLThread + */ + static class TilesData { int cnt = 0; final MapTile[] tiles; @@ -104,14 +68,39 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } } + private static final String TAG = "MapRenderer"; + + private static final int MB = 1024 * 1024; + private static final int SHORT_BYTES = 2; + static final float COORD_MULTIPLIER = 8.0f; + + private static final int CACHE_TILES_MAX = 200; + private static final int LIMIT_BUFFERS = 16 * MB; + + private static int CACHE_TILES = CACHE_TILES_MAX; + + private final MapView mMapView; + private static ArrayList mVBOs; + + private static int mWidth, mHeight; + private static float mAspect; + + private static int rotateBuffers = 2; + private static ShortBuffer shortBuffer[]; + private static short[] mFillCoords; + + // bytes currently loaded in VBOs + private static int mBufferMemoryUsage; + private static float[] mMVPMatrix = new float[16]; - // private static float[] mRotateMatrix = new float[16]; - // private static float[] mProjMatrix = new float[16]; + private static float[] mRotateMatrix = new float[16]; + private static float[] mProjMatrix = new float[16]; + private static float[] mRotTMatrix = new float[16]; // newTiles is set in updateVisibleList and swapped // with curTiles on main thread. curTiles is swapped // with drawTiles in onDrawFrame in GL thread. - private static TilesData newTiles, curTiles, drawTiles; + private static TilesData curTiles, drawTiles; // draw position is updated from current position in onDrawFrame // keeping the position and active tiles consistent while drawing @@ -121,8 +110,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { // changed. used in onDrawFrame to flip curTiles/drawTiles private static boolean mUpdateTiles; - private static boolean mInitial; - private float[] mClearColor = null; // number of tiles drawn in one frame @@ -130,6 +117,8 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { private static boolean mUpdateColor = false; + static Object lock = new Object(); + /** * @param mapView * the MapView @@ -138,475 +127,52 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { Log.d(TAG, "init MapRenderer"); mMapView = mapView; - - if (mInitial) - return; - - mJobList = new ArrayList(); - mTiles = new ArrayList(); - mTilesLoaded = new ArrayList(30); - Matrix.setIdentityM(mMVPMatrix, 0); - mInitial = true; mUpdateTiles = false; - - QuadTree.init(); - } - - private static int updateTileDistances(ArrayList tiles, - MapPosition mapPosition) { - int h = (Tile.TILE_SIZE >> 1); - byte zoom = mapPosition.zoomLevel; - long x = (long) mapPosition.x; - long y = (long) mapPosition.y; - - int diff; - long dx, dy; - int cnt = 0; - - // TODO this could need some fixing, and optimization - // to consider move/zoom direction - - for (int i = 0, n = tiles.size(); i < n; i++) { - JobTile t = (JobTile) tiles.get(i); - diff = (t.zoomLevel - zoom); - if (t.isActive) - cnt++; - - if (diff == 0) { - dx = (t.pixelX + h) - x; - dy = (t.pixelY + h) - y; - // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * 0.25f; - t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f; - } else if (diff > 0) { - // tile zoom level is child of current - - if (diff < 3) { - dx = ((t.pixelX + h) >> diff) - x; - dy = ((t.pixelY + h) >> diff) - y; - } - else { - dx = ((t.pixelX + h) >> (diff >> 1)) - x; - dy = ((t.pixelY + h) >> (diff >> 1)) - y; - } - // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)); - t.distance = FloatMath.sqrt((dx * dx + dy * dy)); - - } else { - // tile zoom level is parent of current - dx = ((t.pixelX + h) << -diff) - x; - dy = ((t.pixelY + h) << -diff) - y; - - // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * (-diff * 0.5f); - t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f); - } - } - return cnt; - } - - private static boolean childIsActive(MapTile t) { - MapTile c = null; - - for (int i = 0; i < 4; i++) { - if (t.rel.child[i] == null) - continue; - - c = t.rel.child[i].tile; - if (c != null && c.isActive && !(c.isReady || c.newData)) - return true; - } - - return false; - } - - // FIXME still the chance that one jumped two zoomlevels between - // cur and draw. should use reference counter instead - private static boolean tileInUse(MapTile t) { - byte z = mPrevZoom; - - if (t.isActive) { - return true; - - } else if (t.zoomLevel == z + 1) { - MapTile p = t.rel.parent.tile; - - if (p != null && p.isActive && !(p.isReady || p.newData)) - return true; - - } else if (t.zoomLevel == z + 2) { - MapTile p = t.rel.parent.parent.tile; - - if (p != null && p.isActive && !(p.isReady || p.newData)) - return true; - - } else if (t.zoomLevel == z - 1) { - if (childIsActive(t)) - return true; - - } else if (t.zoomLevel == z - 2) { - for (QuadTree c : t.rel.child) { - if (c == null) - continue; - - if (c.tile != null && childIsActive(c.tile)) - return true; - } - } else if (t.zoomLevel == z - 3) { - for (QuadTree c : t.rel.child) { - if (c == null) - continue; - - for (QuadTree c2 : c.child) { - if (c2 == null) - continue; - - if (c2.tile != null && childIsActive(c2.tile)) - return true; - } - } - } - return false; - } - - private static void limitCache(MapPosition mapPosition, int remove) { - int removes = remove; - - int size = mTiles.size(); - - // remove orphaned tiles - for (int i = 0; i < size;) { - MapTile cur = mTiles.get(i); - // make sure tile cannot be used by GL or MapWorker Thread - if ((!cur.isActive) && (!cur.isLoading) && (!cur.newData) - && (!cur.isReady) && (!tileInUse(cur))) { - - clearTile(cur); - mTiles.remove(i); - removes--; - size--; - // Log.d(TAG, "remove empty tile" + cur); - continue; - } - i++; - } - - if (removes <= 0) - return; - - updateTileDistances(mTiles, mapPosition); - - Log.d(TAG, "remove tiles: " + removes); - - // boolean printAll = false; - - for (int i = 1; i <= removes; i++) { - - MapTile t = mTiles.get(0); - int pos = 0; - - for (int j = 1; j < size; j++) { - MapTile t2 = mTiles.get(j); - if (t2.distance > t.distance) { - t = t2; - pos = j; - } - } - - synchronized (t) { - if (t.isActive) { - // dont remove tile used by renderthread or mapgenerator - Log.d(TAG, "X not removing active " + t + " " + t.distance); - - // if (printAll) { - // printAll = false; - // for (GLMapTile tt : mTiles) - // Log.d(TAG, ">>>" + tt + " " + tt.distance); - // } - } else if ((t.isReady || t.newData) && tileInUse(t)) { - // check if this tile could be used as proxy - // for not yet drawn active tile - Log.d(TAG, "X not removing proxy: " + t + " " + t.distance); - } else { - if (t.isLoading) { - Log.d(TAG, ">>> cancel loading " + t + " " + t.distance); - t.isCanceled = true; - } - mTiles.remove(pos); - clearTile(t); - size--; - } - } - } - } - - private static void limitLoadQueue(int remove) { - int size = remove; - - synchronized (mTilesLoaded) { - - // remove uploaded tiles - for (int i = 0; i < size;) { - MapTile t = mTilesLoaded.get(i); - // rel == null means tile is already removed by limitCache - if (!t.newData || t.rel == null) { - mTilesLoaded.remove(i); - size--; - continue; - } - i++; - } - - // clear loaded but not used tiles - if (size < MAX_TILES_IN_QUEUE) - return; - - while (size-- > MAX_TILES_IN_QUEUE - 20) { - MapTile t = mTilesLoaded.get(size); - - synchronized (t) { - if (t.rel == null) { - mTilesLoaded.remove(size); - continue; - } - - if (tileInUse(t)) { - // Log.d(TAG, "keep unused tile data: " + t + " " + t.isActive); - continue; - } - - mTilesLoaded.remove(size); - mTiles.remove(t); - // Log.d(TAG, "remove unused tile data: " + t); - clearTile(t); - } - } - } - } - - private boolean updateVisibleList(MapPosition mapPosition, int zdir) { - double x = mapPosition.x; - double y = mapPosition.y; - byte zoomLevel = mapPosition.zoomLevel; - float scale = mapPosition.scale; - - double add = 1.0f / scale; - int offsetX = (int) ((mWidth >> 1) * add) + Tile.TILE_SIZE; - int offsetY = (int) ((mHeight >> 1) * add) + Tile.TILE_SIZE; - - long pixelRight = (long) x + offsetX; - long pixelBottom = (long) y + offsetY; - long pixelLeft = (long) x - offsetX; - long pixelTop = (long) y - offsetY; - - int tileLeft = MercatorProjection.pixelXToTileX(pixelLeft, zoomLevel); - int tileTop = MercatorProjection.pixelYToTileY(pixelTop, zoomLevel); - int tileRight = MercatorProjection.pixelXToTileX(pixelRight, zoomLevel); - int tileBottom = MercatorProjection.pixelYToTileY(pixelBottom, zoomLevel); - - mJobList.clear(); - mMapView.addJobs(null); - - int tiles = 0; - if (newTiles == null) - return false; - - int max = newTiles.tiles.length - 1; - - for (int yy = tileTop; yy <= tileBottom; yy++) { - for (int xx = tileLeft; xx <= tileRight; xx++) { - - if (tiles == max) - break; - - MapTile tile = QuadTree.getTile(xx, yy, zoomLevel); - - if (tile == null) { - tile = new MapTile(xx, yy, zoomLevel); - - QuadTree.add(tile); - mTiles.add(tile); - } - - newTiles.tiles[tiles++] = tile; - - if (!(tile.isLoading || tile.newData || tile.isReady)) { - mJobList.add(tile); - } - - if (zdir > 0 && zoomLevel > 0) { - // prefetch parent - MapTile parent = tile.rel.parent.tile; - - if (parent == null) { - parent = new MapTile(xx >> 1, yy >> 1, (byte) (zoomLevel - 1)); - - QuadTree.add(parent); - mTiles.add(parent); - } - - if (!(parent.isLoading || parent.isReady || parent.newData)) { - if (!mJobList.contains(parent)) - mJobList.add(parent); - } - } - } - } - - newTiles.cnt = tiles; - - // pass new tile list to glThread - synchronized (this) { - for (int i = 0; i < curTiles.cnt; i++) { - boolean found = false; - - for (int j = 0; j < drawTiles.cnt && !found; j++) - if (curTiles.tiles[i] == drawTiles.tiles[j]) - found = true; - - for (int j = 0; j < newTiles.cnt && !found; j++) - if (curTiles.tiles[i] == newTiles.tiles[j]) - found = true; - - if (!found) - curTiles.tiles[i].isActive = false; - } - - for (int i = 0; i < tiles; i++) - newTiles.tiles[i].isActive = true; - - TilesData tmp = curTiles; - curTiles = newTiles; - curTiles.cnt = tiles; - newTiles = tmp; - - mCurPosition = mapPosition; - - mUpdateTiles = true; - } - - if (mJobList.size() > 0) { - updateTileDistances(mJobList, mapPosition); - Collections.sort(mJobList); - mMapView.addJobs(mJobList); - } - - return true; - } - - private static void clearTile(MapTile t) { - t.newData = false; - t.isLoading = false; - t.isReady = false; - - LineLayers.clear(t.lineLayers); - PolygonLayers.clear(t.polygonLayers); - - t.labels = null; - t.lineLayers = null; - t.polygonLayers = null; - - if (t.vbo != null) { - synchronized (mVBOs) { - mVBOs.add(t.vbo); - } - t.vbo = null; - } - - QuadTree.remove(t); } /** * called by MapView when position or map settings changes */ @Override - public synchronized void redrawTiles(boolean clear) { - boolean changedPos = false; - boolean changedZoom = false; - MapPosition mapPosition = mMapView.getMapPosition().getMapPosition(); + public void redrawTiles(boolean clear) { + TileLoader.redrawTiles(clear); + } - if (mapPosition == null) { - Log.d(TAG, ">>> no map position"); - return; - } + static void updatePosition(MapPosition mapPosition) { + // synchronized (MapRenderer.lock) { + mCurPosition = mapPosition; + // } + } - if (clear) { - mInitial = true; - synchronized (this) { + static TilesData updateTiles(MapPosition mapPosition, TilesData tiles) { + synchronized (MapRenderer.lock) { - for (MapTile t : mTiles) - clearTile(t); - - mTiles.clear(); - mTilesLoaded.clear(); - QuadTree.init(); - curTiles.cnt = 0; - mBufferMemoryUsage = 0; - } - } - - byte zoomLevel = mapPosition.zoomLevel; - float scale = mapPosition.scale; - - long tileX = MercatorProjection.pixelXToTileX(mapPosition.x, zoomLevel) - * Tile.TILE_SIZE; - long tileY = MercatorProjection.pixelYToTileY(mapPosition.y, zoomLevel) - * Tile.TILE_SIZE; - - int zdir = 0; - if (mInitial || mPrevZoom != zoomLevel) { - changedZoom = true; - mPrevScale = scale; - } - else if (tileX != mTileX || tileY != mTileY) { - if (mPrevScale - scale > 0 && scale > 1.2) - zdir = 1; - mPrevScale = scale; - changedPos = true; - } - else if (mPrevScale - scale > 0.2 || mPrevScale - scale < -0.2) { - if (mPrevScale - scale > 0 && scale > 1.2) - zdir = 1; - mPrevScale = scale; - changedPos = true; - } - - if (mInitial) { mCurPosition = mapPosition; - mInitial = false; + + for (int i = 0; i < curTiles.cnt; i++) + curTiles.tiles[i].isActive = false; + + TilesData tmp = curTiles; + curTiles = tiles; + + for (int i = 0; i < curTiles.cnt; i++) + curTiles.tiles[i].isActive = true; + + for (int j = 0; j < drawTiles.cnt; j++) + drawTiles.tiles[j].isActive = true; + + mUpdateTiles = true; + + return tmp; } - mTileX = tileX; - mTileY = tileY; - mPrevZoom = zoomLevel; + } - if (changedZoom) { - // need to update visible list first when zoom level changes - // as scaling is relative to the tiles of current zoom-level - updateVisibleList(mapPosition, 0); - } else { - // pass new position to glThread - synchronized (this) { - // do not change position while drawing - mCurPosition = mapPosition; - } + static void addVBO(VertexBufferObject vbo) { + synchronized (mVBOs) { + mVBOs.add(vbo); } - - if (!MapView.debugFrameTime) - mMapView.requestRender(); - - if (changedPos) - updateVisibleList(mapPosition, zdir); - - if (changedPos || changedZoom) { - int remove = mTiles.size() - CACHE_TILES; - if (remove > 10) - limitCache(mapPosition, remove); - } - - int size = mTilesLoaded.size(); - if (size > MAX_TILES_IN_QUEUE) - limitLoadQueue(size); - } /** @@ -616,11 +182,10 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { public synchronized boolean passTile(JobTile jobTile) { MapTile tile = (MapTile) jobTile; - if (tile.isCanceled) { + if (!tile.isLoading) { // no one should be able to use this tile now, mapgenerator passed it, // glthread does nothing until newdata is set. Log.d(TAG, "passTile: canceled " + tile); - tile.isLoading = false; return true; } @@ -644,9 +209,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (!MapView.debugFrameTime) mMapView.requestRender(); - synchronized (mTilesLoaded) { - mTilesLoaded.add(0, tile); - } + TileLoader.addTileLoaded(tile); return true; } @@ -655,37 +218,46 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { // ... asus has just 16 bit?! // private static final float depthStep = 0.00000011920928955078125f; + private static boolean mRotate = false; + private static void setMatrix(MapTile tile, float div, int offset) { float x, y, scale; - scale = (float) (2.0 * mDrawPosition.scale / (mHeight * div)); - x = (float) (tile.pixelX - mDrawPosition.x * div); - y = (float) (tile.pixelY - mDrawPosition.y * div); + if (mRotate) { + scale = (float) (1.0 * mDrawPosition.scale / (mHeight * div)); + x = (float) (tile.pixelX - mDrawPosition.x * div); + y = (float) (tile.pixelY - mDrawPosition.y * div); - mMVPMatrix[12] = x * scale * mAspect; - mMVPMatrix[13] = -(y + Tile.TILE_SIZE) * scale; - // increase the 'distance' with each tile drawn. - mMVPMatrix[14] = -1 + offset * 0.01f; // depthStep; // 0.01f; - mMVPMatrix[0] = scale * mAspect / COORD_MULTIPLIER; - mMVPMatrix[5] = scale / COORD_MULTIPLIER; + Matrix.setIdentityM(mMVPMatrix, 0); - // Matrix.setIdentityM(mMVPMatrix, 0); - // - // Matrix.scaleM(mMVPMatrix, 0, scale / COORD_MULTIPLIER, - // scale / COORD_MULTIPLIER, 1); - // - // Matrix.translateM(mMVPMatrix, 0, x * COORD_MULTIPLIER, -(y + Tile.TILE_SIZE) - // * COORD_MULTIPLIER, 0); - // - // Matrix.setRotateM(mRotateMatrix, 0, mDrawPosition.angle, 0, 0, 1); - // - // Matrix.multiplyMM(mMVPMatrix, 0, mRotateMatrix, 0, mMVPMatrix, 0); - // - // Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0); + Matrix.scaleM(mMVPMatrix, 0, scale / COORD_MULTIPLIER, + scale / COORD_MULTIPLIER, 1); + + Matrix.translateM(mMVPMatrix, 0, + x * COORD_MULTIPLIER, + -(y + Tile.TILE_SIZE) * COORD_MULTIPLIER, + -1 + offset * 0.01f); + + Matrix.multiplyMM(mMVPMatrix, 0, mRotateMatrix, 0, mMVPMatrix, 0); + + Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0); + } + else { + scale = (float) (2.0 * mDrawPosition.scale / (mHeight * div)); + x = (float) (tile.pixelX - mDrawPosition.x * div); + y = (float) (tile.pixelY - mDrawPosition.y * div); + + mMVPMatrix[12] = x * scale * mAspect; + mMVPMatrix[13] = -(y + Tile.TILE_SIZE) * scale; + // increase the 'distance' with each tile drawn. + mMVPMatrix[14] = -1 + offset * 0.01f; // depthStep; // 0.01f; + mMVPMatrix[0] = scale * mAspect / COORD_MULTIPLIER; + mMVPMatrix[5] = scale / COORD_MULTIPLIER; + } } - private static boolean setTileScissor(MapTile tile, float div) { + private static boolean isVisible(MapTile tile, float div) { double dx, dy, scale; if (div == 0) { @@ -773,8 +345,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { + sbuf.limit() + " " + sbuf.remaining() + " " + PolygonLayers.sizeOf(tile.polygonLayers) + " " - + tile.isCanceled + " " - + tile.isLoading + " " + tile.rel); tile.lineOffset *= SHORT_BYTES; @@ -790,7 +360,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { + sbuf.limit() + " " + sbuf.remaining() + " " + LineLayers.sizeOf(tile.lineLayers) - + tile.isCanceled + " " + tile.isLoading + " " + tile.rel); @@ -864,6 +433,8 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (MapView.debugFrameTime) start = SystemClock.uptimeMillis(); + mRotate = mMapView.enableRotation; + if (mUpdateColor && mClearColor != null) { glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], mClearColor[3]); mUpdateColor = false; @@ -871,7 +442,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { GLES20.glDepthMask(true); - // Note: i have the impression it is faster to also clear the + // Note: having the impression it is faster to also clear the // stencil buffer even when not needed. probaly otherwise it // is masked out from the depth buffer as they share the same // memory region afaik @@ -879,11 +450,8 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { | GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT); - if (mInitial) - return; - // get position and current tiles to draw - synchronized (this) { + synchronized (MapRenderer.lock) { mDrawPosition = mCurPosition; if (mUpdateTiles) { @@ -904,7 +472,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { for (int i = 0; i < tileCnt; i++) { MapTile tile = tiles[i]; - if (!setTileScissor(tile, 1)) + if (!isVisible(tile, 1)) continue; if (tile.texture == null && TextRenderer.drawToTexture(tile)) @@ -939,6 +507,11 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (updateTextures > 0) TextRenderer.compileTextures(); + if (mRotate) { + Matrix.setRotateM(mRotateMatrix, 0, mDrawPosition.angle, 0, 0, 1); + Matrix.transposeM(mRotTMatrix, 0, mRotateMatrix, 0); + } + GLES20.glEnable(GLES20.GL_DEPTH_TEST); for (int i = 0; i < tileCnt; i++) { @@ -970,10 +543,13 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (scale < 1) scale = 1; + // scale = (1.0f * mDrawPosition.scale / mHeight); + // TextRenderer.beginDraw(scale); + if (z >= MapGenerator.STROKE_MAX_ZOOM_LEVEL) - TextRenderer.beginDraw(FloatMath.sqrt(s) / scale); + TextRenderer.beginDraw(FloatMath.sqrt(s) / scale, mRotTMatrix); else - TextRenderer.beginDraw(s); + TextRenderer.beginDraw(s, mRotTMatrix); for (int i = 0; i < tileCnt; i++) { if (!tiles[i].isVisible || tiles[i].texture == null) @@ -1055,7 +631,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (c == null) continue; - if (!setTileScissor(c, 2)) { + if (!isVisible(c, 2)) { drawn++; continue; } @@ -1107,16 +683,13 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { @Override public void onSurfaceChanged(GL10 glUnused, int width, int height) { Log.d(TAG, "SurfaceChanged:" + width + " " + height); - mTilesLoaded.clear(); - mTiles.clear(); - - ShortPool.init(); - QuadTree.init(); + // mTilesLoaded.clear(); + // mTiles.clear(); // LineLayers.finish(); - drawTiles = newTiles = curTiles = null; + drawTiles = curTiles = null; mBufferMemoryUsage = 0; - mInitial = true; + // mInitial = true; if (width <= 0 || height <= 0) return; @@ -1127,22 +700,20 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mHeight = height; mAspect = (float) height / width; - // Matrix.orthoM(mProjMatrix, 0, -0.5f / mAspect, 0.5f / mAspect, -0.5f, 0.5f, -1, 1); + Matrix.orthoM(mProjMatrix, 0, -0.5f / mAspect, 0.5f / mAspect, -0.5f, 0.5f, -1, 1); glViewport(0, 0, width, height); int numTiles = (mWidth / (Tile.TILE_SIZE / 2) + 2) * (mHeight / (Tile.TILE_SIZE / 2) + 2); + TileLoader.init(mMapView, width, height, numTiles); + drawTiles = new TilesData(numTiles); - newTiles = new TilesData(numTiles); curTiles = new TilesData(numTiles); - // Log.d(TAG, "using: " + numTiles + " + cache: " + CACHE_TILES); - GlUtils.checkGlError("pre glGenBuffers"); - // Set up vertex buffer objects - int numVBO = (CACHE_TILES + numTiles); + int numVBO = (CACHE_TILES + (numTiles * 2)); int[] mVboIds = new int[numVBO]; glGenBuffers(numVBO, mVboIds, 0); GlUtils.checkGlError("glGenBuffers"); @@ -1155,8 +726,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { // Set up textures TextRenderer.init(numTiles); - // glDisable(GL_DITHER); - if (mClearColor != null) { glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], mClearColor[3]); @@ -1199,10 +768,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mFillCoords[6] = max; mFillCoords[7] = min; - // int i[] = new int[1]; - // GLES20.glGetIntegerv(GLES20.GL_, i, 0); - // Log.d(TAG, " >>>> " + i[0]); - LineLayers.init(); PolygonLayers.init(); } diff --git a/src/org/mapsforge/android/glrenderer/MapTile.java b/src/org/mapsforge/android/glrenderer/MapTile.java index 172fe64f..2d56f676 100644 --- a/src/org/mapsforge/android/glrenderer/MapTile.java +++ b/src/org/mapsforge/android/glrenderer/MapTile.java @@ -19,25 +19,51 @@ import org.mapsforge.android.mapgenerator.JobTile; class MapTile extends JobTile { byte lastDraw = 0; - // VBO layout: - // 16 bytes fill coordinates - // n bytes polygon vertices - // m bytes lines vertices + /** + * VBO layout: + * - 16 bytes fill coordinates + * - n bytes polygon vertices + * - m bytes lines vertices + */ VertexBufferObject vbo; - // polygonOffset in vbo is always 16 bytes, + /** + * polygonOffset in vbo is always 16 bytes, + */ int lineOffset; TextTexture texture; - // Tile data set by MapGenerator: + /** + * Tile data set by MapGenerator: + */ LineLayer lineLayers; PolygonLayer polygonLayers; TextItem labels; + /** + * tile has new data to upload to gl + */ boolean newData; - // pointer to access relatives in TileTree + /** + * tile is loaded and ready for drawing. + */ + boolean isReady; + + /** + * tile is in view region. + */ + boolean isVisible; + + /** + * tile is used by render thread. set by updateVisibleList (main thread). + */ + boolean isActive; + + /** + * pointer to access relatives in TileTree + */ QuadTree rel; MapTile(int tileX, int tileY, byte zoomLevel) { diff --git a/src/org/mapsforge/android/glrenderer/Shaders.java b/src/org/mapsforge/android/glrenderer/Shaders.java index 8c4743dd..565988f7 100644 --- a/src/org/mapsforge/android/glrenderer/Shaders.java +++ b/src/org/mapsforge/android/glrenderer/Shaders.java @@ -111,14 +111,39 @@ class Shaders { + "attribute vec4 vertex;" + "attribute vec2 tex_coord;" + "uniform mat4 mvp;" + + "uniform mat4 rotation;" + "uniform float scale;" + "varying vec2 tex_c;" + "const vec2 div = vec2(1.0/4096.0,1.0/2048.0);" + "void main() {" - + " gl_Position = mvp * vec4(vertex.xy + (vertex.zw / scale), 0.0, 1.0);" - + " tex_c = tex_coord * div;" + + " if (mod(vertex.x, 2.0) == 0.0){" + + " gl_Position = mvp * vec4(vertex.xy + vertex.zw / scale, 0.0, 1.0);" + + " } else {" + + " vec4 dir = rotation * vec4(vertex.zw / scale, 0.0, 1.0);" + + " gl_Position = mvp * vec4(vertex.xy + dir.xy, 0.0, 1.0);" + + " }" + + " tex_c = tex_coord * div;" + "}"; + // final static String textVertexShader = "" + // + "precision highp float; " + // + "attribute vec4 vertex;" + // + "attribute vec2 tex_coord;" + // + "uniform mat4 mvp;" + // + "uniform mat4 rotation;" + // + "uniform float scale;" + // + "varying vec2 tex_c;" + // + "const vec2 div = vec2(1.0/4096.0,1.0/2048.0);" + // + "void main() {" + // + " if (mod(vertex.x, 2.0) == 0.0){" + // + " gl_Position = mvp * vec4(vertex.xy + vertex.zw / scale, 0.0, 1.0);" + // + " } else {" + // + " vec4 dir = rotation * vec4(vertex.zw / scale, 0.0, 1.0);" + // + " gl_Position = mvp * vec4(vertex.xy + dir.xy, 0.0, 1.0);" + // + " }" + // + " tex_c = tex_coord * div;" + // + "}"; + final static String textFragmentShader = "" + "precision highp float;" + "uniform sampler2D tex;" diff --git a/src/org/mapsforge/android/glrenderer/TextRenderer.java b/src/org/mapsforge/android/glrenderer/TextRenderer.java index e17242f1..cfada51a 100644 --- a/src/org/mapsforge/android/glrenderer/TextRenderer.java +++ b/src/org/mapsforge/android/glrenderer/TextRenderer.java @@ -32,7 +32,9 @@ import android.util.Log; public class TextRenderer { private final static int TEXTURE_WIDTH = 512; private final static int TEXTURE_HEIGHT = 256; - private final static float SCALE_FACTOR = 8.0f; + private final static float SCALE = 8.0f; + private final static int LBIT_MASK = 0xfffffffe; + private final static int L2BIT_MASK = 0xfffffffc; final static int INDICES_PER_SPRITE = 6; final static int VERTICES_PER_SPRITE = 4; @@ -53,6 +55,7 @@ public class TextRenderer { private static int mTextProgram; private static int hTextUVPMatrix; + private static int hTextRotationMatrix; private static int hTextVertex; private static int hTextScale; private static int hTextTextureCoord; @@ -99,6 +102,8 @@ public class TextRenderer { Shaders.textFragmentShader); hTextUVPMatrix = GLES20.glGetUniformLocation(mTextProgram, "mvp"); + hTextRotationMatrix = GLES20.glGetUniformLocation(mTextProgram, "rotation"); + hTextVertex = GLES20.glGetAttribLocation(mTextProgram, "vertex"); hTextScale = GLES20.glGetUniformLocation(mTextProgram, "scale"); hTextTextureCoord = GLES20.glGetAttribLocation(mTextProgram, "tex_coord"); @@ -283,10 +288,10 @@ public class TextRenderer { float hh = height / 2.0f; if (t.caption != null) { - x1 = x3 = (short) (SCALE_FACTOR * (-hw)); - y1 = y3 = (short) (SCALE_FACTOR * (-hh)); - x2 = x4 = (short) (SCALE_FACTOR * (hw)); - y2 = y4 = (short) (SCALE_FACTOR * (hh)); + x1 = x3 = (short) (SCALE * (-hw)); + y1 = y3 = (short) (SCALE * (-hh)); + x2 = x4 = (short) (SCALE * (hw)); + y2 = y4 = (short) (SCALE * (hh)); } else { float vx = t.x1 - t.x2; @@ -298,26 +303,45 @@ public class TextRenderer { float ux = -vy; float uy = vx; - x1 = (short) (SCALE_FACTOR * (vx * hw + ux * hh)); - y1 = (short) (SCALE_FACTOR * (vy * hw + uy * hh)); + // int dx = (int) (vx * SCALE) & L2BIT_MASK; + // int dy = (int) (vy * SCALE) & L2BIT_MASK; + // + // x1 = (short) dx; + // y1 = (short) dy; + // + // x2 = (short) (dx | 1); + // y3 = (short) (dy | 1); + // + // x4 = (short) (dx | 3); + // y4 = (short) (dy | 3); + // + // x3 = (short) (dx | 2); + // y2 = (short) (dy | 2); - x2 = (short) (SCALE_FACTOR * (-vx * hw + ux * hh)); - y3 = (short) (SCALE_FACTOR * (-vy * hw + uy * hh)); - - x4 = (short) (SCALE_FACTOR * (-vx * hw - ux * hh)); - y4 = (short) (SCALE_FACTOR * (-vy * hw - uy * hh)); - - x3 = (short) (SCALE_FACTOR * (vx * hw - ux * hh)); - y2 = (short) (SCALE_FACTOR * (vy * hw - uy * hh)); + x1 = (short) (SCALE * (vx * hw + ux * hh)); + y1 = (short) (SCALE * (vy * hw + uy * hh)); + x2 = (short) (SCALE * (-vx * hw + ux * hh)); + y3 = (short) (SCALE * (-vy * hw + uy * hh)); + x4 = (short) (SCALE * (-vx * hw - ux * hh)); + y4 = (short) (SCALE * (-vy * hw - uy * hh)); + x3 = (short) (SCALE * (vx * hw - ux * hh)); + y2 = (short) (SCALE * (vy * hw - uy * hh)); } - short u1 = (short) (SCALE_FACTOR * x); - short v1 = (short) (SCALE_FACTOR * y); - short u2 = (short) (SCALE_FACTOR * (x + width)); - short v2 = (short) (SCALE_FACTOR * (y + height)); + short u1 = (short) (SCALE * x); + short v1 = (short) (SCALE * y); + short u2 = (short) (SCALE * (x + width)); + short v2 = (short) (SCALE * (y + height)); + + // pack caption/way-text info in lowest bit + short tx; + if (t.caption == null) + tx = (short) ((int) (SCALE * t.x) & LBIT_MASK | 0); + else + tx = (short) ((int) (SCALE * t.x) & LBIT_MASK | 1); + + short ty = (short) (SCALE * t.y); - short tx = (short) (SCALE_FACTOR * t.x); - short ty = (short) (SCALE_FACTOR * t.y); // top-left buf[pos++] = tx; buf[pos++] = ty; @@ -366,7 +390,7 @@ public class TextRenderer { GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, mBitmap, mBitmapFormat, mBitmapType); - GLES20.glFlush(); + // GLES20.glFlush(); return true; } @@ -397,13 +421,14 @@ public class TextRenderer { mShortBuffer); } - static void beginDraw(float scale) { + static void beginDraw(float scale, float[] rotation) { GLES20.glUseProgram(mTextProgram); GLES20.glEnableVertexAttribArray(hTextTextureCoord); GLES20.glEnableVertexAttribArray(hTextVertex); GLES20.glUniform1f(hTextScale, scale); + GLES20.glUniformMatrix4fv(hTextRotationMatrix, 1, false, rotation, 0); if (debug) { GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); diff --git a/src/org/mapsforge/android/glrenderer/TileLoader.java b/src/org/mapsforge/android/glrenderer/TileLoader.java new file mode 100644 index 00000000..0c5cded2 --- /dev/null +++ b/src/org/mapsforge/android/glrenderer/TileLoader.java @@ -0,0 +1,530 @@ +/* + * 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 . + */ +package org.mapsforge.android.glrenderer; + +import java.util.ArrayList; +import java.util.Collections; + +import org.mapsforge.android.MapView; +import org.mapsforge.android.glrenderer.MapRenderer.TilesData; +import org.mapsforge.android.mapgenerator.JobTile; +import org.mapsforge.core.MapPosition; +import org.mapsforge.core.MercatorProjection; +import org.mapsforge.core.Tile; + +import android.util.FloatMath; +import android.util.Log; + +// for lack of a better name... maybe TileManager? + +/** + * Update list of visible tiles and passes them to MapRenderer, when not available tiles are created and added to + * JobQueue (mapView.addJobs) for loading by MapGenerator class + */ + +public class TileLoader { + private static final String TAG = "TileLoader"; + + private static final int MAX_TILES_IN_QUEUE = 40; + private static final int CACHE_TILES_MAX = 200; + // private static final int MB = 1024 * 1024; + // private static final int LIMIT_BUFFERS = 16 * MB; + + private static int CACHE_TILES = CACHE_TILES_MAX; + + private static MapView mMapView; + + private static ArrayList mJobList; + // private static ArrayList mVBOs; + + // all tiles currently referenced + private static ArrayList mTiles; + // private static ArrayList mTilesActive; + // tiles that have new data to upload, see passTile() + private static ArrayList mTilesLoaded; + + // current center tile, values used to check if position has + // changed for updating current tile list + private static long mTileX, mTileY; + private static float mPrevScale; + private static byte mPrevZoom; + private static boolean mInitial; + + // private static MapPosition mCurPosition, mDrawPosition; + private static int mWidth, mHeight; + + private static TilesData newTiles; // , curTiles, drawTiles; + + static void init(MapView mapView, int w, int h, int numTiles) { + mMapView = mapView; + + ShortPool.init(); + QuadTree.init(); + + mJobList = new ArrayList(); + mTiles = new ArrayList(); + mTilesLoaded = new ArrayList(30); + + mInitial = true; + mWidth = w; + mHeight = h; + + newTiles = new TilesData(numTiles); + } + + static void redrawTiles(boolean clear) { + + boolean changedPos = false; + boolean changedZoom = false; + MapPosition mapPosition = mMapView.getMapPosition().getMapPosition(); + + if (mapPosition == null) { + Log.d(TAG, ">>> no map position"); + return; + } + + if (clear) { + mInitial = true; + synchronized (MapRenderer.lock) { + + for (MapTile t : mTiles) + clearTile(t); + + mTiles.clear(); + mTilesLoaded.clear(); + QuadTree.init(); + // curTiles.cnt = 0; + // mBufferMemoryUsage = 0; + } + } + + byte zoomLevel = mapPosition.zoomLevel; + float scale = mapPosition.scale; + + long tileX = MercatorProjection.pixelXToTileX(mapPosition.x, zoomLevel) + * Tile.TILE_SIZE; + long tileY = MercatorProjection.pixelYToTileY(mapPosition.y, zoomLevel) + * Tile.TILE_SIZE; + + int zdir = 0; + if (mInitial || mPrevZoom != zoomLevel) { + changedZoom = true; + mPrevScale = scale; + } + else if (tileX != mTileX || tileY != mTileY) { + if (mPrevScale - scale > 0 && scale > 1.2) + zdir = 1; + mPrevScale = scale; + changedPos = true; + } + else if (mPrevScale - scale > 0.2 || mPrevScale - scale < -0.2) { + if (mPrevScale - scale > 0 && scale > 1.2) + zdir = 1; + mPrevScale = scale; + changedPos = true; + } + + if (mInitial) { + // mCurPosition = mapPosition; + mInitial = false; + } + + mTileX = tileX; + mTileY = tileY; + mPrevZoom = zoomLevel; + + if (changedZoom) { + // need to update visible list first when zoom level changes + // as scaling is relative to the tiles of current zoom-level + updateVisibleList(mapPosition, 0); + } else { + // pass new position to glThread + + MapRenderer.updatePosition(mapPosition); + + // synchronized () { + // // do not change position while drawing + // mCurPosition = mapPosition; + // } + } + + if (!MapView.debugFrameTime) + mMapView.requestRender(); + + if (changedPos) + updateVisibleList(mapPosition, zdir); + + if (changedPos || changedZoom) { + int remove = mTiles.size() - CACHE_TILES; + if (remove > 50) + limitCache(mapPosition, remove); + } + + int size = mTilesLoaded.size(); + if (size > MAX_TILES_IN_QUEUE) + limitLoadQueue(size); + + } + + private static boolean updateVisibleList(MapPosition mapPosition, int zdir) { + double x = mapPosition.x; + double y = mapPosition.y; + byte zoomLevel = mapPosition.zoomLevel; + float scale = mapPosition.scale; + + double add = 1.0f / scale; + int offsetX = (int) ((mWidth >> 1) * add) + Tile.TILE_SIZE; + int offsetY = (int) ((mHeight >> 1) * add) + Tile.TILE_SIZE; + + long pixelRight = (long) x + offsetX; + long pixelBottom = (long) y + offsetY; + long pixelLeft = (long) x - offsetX; + long pixelTop = (long) y - offsetY; + + int tileLeft = MercatorProjection.pixelXToTileX(pixelLeft, zoomLevel); + int tileTop = MercatorProjection.pixelYToTileY(pixelTop, zoomLevel); + int tileRight = MercatorProjection.pixelXToTileX(pixelRight, zoomLevel); + int tileBottom = MercatorProjection.pixelYToTileY(pixelBottom, zoomLevel); + + mJobList.clear(); + + // set non processed tiles to isLoading == false + mMapView.addJobs(null); + + int tiles = 0; + if (newTiles == null) + return false; + + int max = newTiles.tiles.length - 1; + + for (int yy = tileTop; yy <= tileBottom; yy++) { + for (int xx = tileLeft; xx <= tileRight; xx++) { + + if (tiles == max) + break; + + MapTile tile = QuadTree.getTile(xx, yy, zoomLevel); + + if (tile == null) { + tile = new MapTile(xx, yy, zoomLevel); + + QuadTree.add(tile); + mTiles.add(tile); + } + + newTiles.tiles[tiles++] = tile; + + if (!(tile.isLoading || tile.newData || tile.isReady)) { + mJobList.add(tile); + } + + if (zdir > 0 && zoomLevel > 0) { + // prefetch parent + MapTile parent = tile.rel.parent.tile; + + if (parent == null) { + parent = new MapTile(xx >> 1, yy >> 1, (byte) (zoomLevel - 1)); + + QuadTree.add(parent); + mTiles.add(parent); + } + + if (!(parent.isLoading || parent.isReady || parent.newData)) { + if (!mJobList.contains(parent)) + mJobList.add(parent); + } + } + } + } + + // pass new tile list to glThread + newTiles.cnt = tiles; + newTiles = MapRenderer.updateTiles(mapPosition, newTiles); + + // pass new tile list to glThread + // synchronized (MapRenderer.lock) { + // + // for (int i = 0; i < curTiles.cnt; i++) + // curTiles.tiles[i].isActive = false; + // + // for (int j = 0; j < drawTiles.cnt; j++) + // drawTiles.tiles[j].isActive = true; + // + // for (int i = 0; i < tiles; i++) + // newTiles.tiles[i].isActive = true; + // + // TilesData tmp = curTiles; + // curTiles = newTiles; + // curTiles.cnt = tiles; + // newTiles = tmp; + // + // mCurPosition = mapPosition; + // + // mUpdateTiles = true; + // } + + if (mJobList.size() > 0) { + updateTileDistances(mJobList, mapPosition); + Collections.sort(mJobList); + mMapView.addJobs(mJobList); + } + + return true; + } + + private static void clearTile(MapTile t) { + t.newData = false; + t.isLoading = false; + t.isReady = false; + + LineLayers.clear(t.lineLayers); + PolygonLayers.clear(t.polygonLayers); + + t.labels = null; + t.lineLayers = null; + t.polygonLayers = null; + + if (t.vbo != null) { + MapRenderer.addVBO(t.vbo); + t.vbo = null; + } + + QuadTree.remove(t); + } + + private static boolean childIsActive(MapTile t) { + MapTile c = null; + + for (int i = 0; i < 4; i++) { + if (t.rel.child[i] == null) + continue; + + c = t.rel.child[i].tile; + if (c != null && c.isActive && !(c.isReady || c.newData)) + return true; + } + + return false; + } + + // FIXME still the chance that one jumped two zoomlevels between + // cur and draw. should use reference counter instead + private static boolean tileInUse(MapTile t) { + byte z = mPrevZoom; + + if (t.isActive) { + return true; + + } else if (t.zoomLevel == z + 1) { + MapTile p = t.rel.parent.tile; + + if (p != null && p.isActive && !(p.isReady || p.newData)) + return true; + + } else if (t.zoomLevel == z + 2) { + MapTile p = t.rel.parent.parent.tile; + + if (p != null && p.isActive && !(p.isReady || p.newData)) + return true; + + } else if (t.zoomLevel == z - 1) { + if (childIsActive(t)) + return true; + + } else if (t.zoomLevel == z - 2) { + for (QuadTree c : t.rel.child) { + if (c == null) + continue; + + if (c.tile != null && childIsActive(c.tile)) + return true; + } + } else if (t.zoomLevel == z - 3) { + for (QuadTree c : t.rel.child) { + if (c == null) + continue; + + for (QuadTree c2 : c.child) { + if (c2 == null) + continue; + + if (c2.tile != null && childIsActive(c2.tile)) + return true; + } + } + } + return false; + } + + private static void updateTileDistances(ArrayList tiles, + MapPosition mapPosition) { + int h = (Tile.TILE_SIZE >> 1); + byte zoom = mapPosition.zoomLevel; + long x = (long) mapPosition.x; + long y = (long) mapPosition.y; + + int diff; + long dx, dy; + + // TODO this could need some fixing, and optimization + // to consider move/zoom direction + + for (int i = 0, n = tiles.size(); i < n; i++) { + JobTile t = (JobTile) tiles.get(i); + diff = (t.zoomLevel - zoom); + + if (diff == 0) { + dx = (t.pixelX + h) - x; + dy = (t.pixelY + h) - y; + // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * 0.25f; + t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f; + } else if (diff > 0) { + // tile zoom level is child of current + + if (diff < 3) { + dx = ((t.pixelX + h) >> diff) - x; + dy = ((t.pixelY + h) >> diff) - y; + } + else { + dx = ((t.pixelX + h) >> (diff >> 1)) - x; + dy = ((t.pixelY + h) >> (diff >> 1)) - y; + } + // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)); + t.distance = FloatMath.sqrt((dx * dx + dy * dy)); + + } else { + // tile zoom level is parent of current + dx = ((t.pixelX + h) << -diff) - x; + dy = ((t.pixelY + h) << -diff) - y; + + // t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * (-diff * 0.5f); + t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f); + } + } + } + + private static void limitCache(MapPosition mapPosition, int remove) { + int removes = remove; + + int size = mTiles.size(); + int tmp = size; + + // remove orphaned tiles + for (int i = 0; i < size;) { + MapTile cur = mTiles.get(i); + // make sure tile cannot be used by GL or MapWorker Thread + if ((!cur.isActive) && (!cur.isLoading) && (!cur.newData) + && (!cur.isReady) && (!tileInUse(cur))) { + + clearTile(cur); + mTiles.remove(i); + removes--; + size--; + // Log.d(TAG, "remove empty tile" + cur); + continue; + } + i++; + } + + Log.d(TAG, "remove tiles: " + removes + " " + size + " " + tmp); + + if (removes <= 0) + return; + + updateTileDistances(mTiles, mapPosition); + Collections.sort(mTiles); + + // boolean printAll = false; + + for (int i = 1; i <= removes; i++) { + + MapTile t = mTiles.remove(size - i); + + synchronized (t) { + if (t.isActive) { + // dont remove tile used by renderthread + Log.d(TAG, "X not removing active " + t + " " + t.distance); + + // if (printAll) { + // printAll = false; + // for (GLMapTile tt : mTiles) + // Log.d(TAG, ">>>" + tt + " " + tt.distance); + // } + mTiles.add(t); + } else if ((t.isReady || t.newData) && tileInUse(t)) { + // check if this tile could be used as proxy + Log.d(TAG, "X not removing proxy: " + t + " " + t.distance); + mTiles.add(t); + } else { + if (t.isLoading) { + Log.d(TAG, "X cancel loading " + t + " " + t.distance); + t.isLoading = false; + } + clearTile(t); + } + } + } + } + + private static void limitLoadQueue(int remove) { + int size = remove; + + synchronized (mTilesLoaded) { + + // remove uploaded tiles + for (int i = 0; i < size;) { + MapTile t = mTilesLoaded.get(i); + // rel == null means tile is already removed by limitCache + if (!t.newData || t.rel == null) { + mTilesLoaded.remove(i); + size--; + continue; + } + i++; + } + + // clear loaded but not used tiles + if (size < MAX_TILES_IN_QUEUE) + return; + + while (size-- > MAX_TILES_IN_QUEUE - 20) { + MapTile t = mTilesLoaded.get(size); + + synchronized (t) { + if (t.rel == null) { + mTilesLoaded.remove(size); + continue; + } + + if (tileInUse(t)) { + // Log.d(TAG, "keep unused tile data: " + t + " " + t.isActive); + continue; + } + + mTilesLoaded.remove(size); + mTiles.remove(t); + // Log.d(TAG, "remove unused tile data: " + t); + clearTile(t); + } + } + } + } + + static void addTileLoaded(MapTile tile) { + synchronized (mTilesLoaded) { + mTilesLoaded.add(0, tile); + } + } +} diff --git a/src/org/mapsforge/android/mapgenerator/JobTile.java b/src/org/mapsforge/android/mapgenerator/JobTile.java index d1bc845c..7bb89495 100644 --- a/src/org/mapsforge/android/mapgenerator/JobTile.java +++ b/src/org/mapsforge/android/mapgenerator/JobTile.java @@ -20,28 +20,20 @@ import org.mapsforge.core.Tile; * */ public class JobTile extends Tile implements Comparable { - /** - * tile is loaded and ready for drawing. (set and used by render thread after uploading data to gl). - */ - public boolean isReady; + // public final static int LOADING = 1; + // public final static int NEWDATA = 1 << 1; + // public final static int READY = 1 << 2; + // public final static int AVAILABLE = 1 << 1 | 1 << 2; + // public final static int CANCELED = 1 << 3; + // public int state; /** - * tile is removed from JobQueue and loading in DatabaseRenderer. set by MapWorker. + * tile is in JobQueue */ public boolean isLoading; /** - * tile is in view region. (set and used by render thread) - */ - public boolean isVisible; - - /** - * tile is used by render thread. set by updateVisibleList (main thread). - */ - public boolean isActive; - - /** - * distance from center, used in TileScheduler set by updateVisibleList. + * distance from map center. */ public float distance; @@ -61,7 +53,8 @@ public class JobTile extends Tile implements Comparable { public int compareTo(JobTile o) { if (this.distance < o.distance) { return -1; - } else if (this.distance > o.distance) { + } + if (this.distance > o.distance) { return 1; } return 0; diff --git a/src/org/mapsforge/android/mapgenerator/MapDatabaseFactory.java b/src/org/mapsforge/android/mapgenerator/MapDatabaseFactory.java index cb45de54..3eb4476d 100644 --- a/src/org/mapsforge/android/mapgenerator/MapDatabaseFactory.java +++ b/src/org/mapsforge/android/mapgenerator/MapDatabaseFactory.java @@ -61,8 +61,8 @@ public final class MapDatabaseFactory { switch (mapDatabase) { case MAP_READER: return new org.mapsforge.database.mapfile.MapDatabase(); - case JSON_READER: - return new org.mapsforge.database.json.MapDatabase(); + case TEST_READER: + return new org.mapsforge.database.test.MapDatabase(); case POSTGIS_READER: return new org.mapsforge.database.postgis.MapDatabase(); case PBMAP_READER: diff --git a/src/org/mapsforge/android/mapgenerator/MapDatabases.java b/src/org/mapsforge/android/mapgenerator/MapDatabases.java index 370f0347..fa21fc58 100644 --- a/src/org/mapsforge/android/mapgenerator/MapDatabases.java +++ b/src/org/mapsforge/android/mapgenerator/MapDatabases.java @@ -26,7 +26,7 @@ public enum MapDatabases { /** * ... */ - JSON_READER, + TEST_READER, /** * ... diff --git a/src/org/mapsforge/android/swrenderer/MapGenerator.java b/src/org/mapsforge/android/swrenderer/MapGenerator.java index f9223451..0f1d689d 100644 --- a/src/org/mapsforge/android/swrenderer/MapGenerator.java +++ b/src/org/mapsforge/android/swrenderer/MapGenerator.java @@ -91,7 +91,7 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, private static float PI180 = (float) (Math.PI / 180) / 1000000.0f; private static float PIx4 = (float) Math.PI * 4; - private Tile mCurrentTile; + private JobTile mCurrentTile; private static long mCurrentTileY; private static long mCurrentTileX; private static long mCurrentTileZoom; diff --git a/src/org/mapsforge/android/swrenderer/MapTile.java b/src/org/mapsforge/android/swrenderer/MapTile.java index d6ddd684..bffca4c4 100644 --- a/src/org/mapsforge/android/swrenderer/MapTile.java +++ b/src/org/mapsforge/android/swrenderer/MapTile.java @@ -28,6 +28,8 @@ public class MapTile extends JobTile { // private long mLoadTime; private int mTextureID; + boolean isVisible; + /** * @param tileX * ... diff --git a/src/org/mapsforge/app/TileMap.java b/src/org/mapsforge/app/TileMap.java index f2c85662..897b44fe 100755 --- a/src/org/mapsforge/app/TileMap.java +++ b/src/org/mapsforge/app/TileMap.java @@ -104,6 +104,10 @@ public class TileMap extends MapActivity { return true; case R.id.menu_position: + mMapView.enableRotation(true); + return true; + + case R.id.menu_rotation_enable: return true; case R.id.menu_position_my_location_enable: diff --git a/src/org/mapsforge/core/Tile.java b/src/org/mapsforge/core/Tile.java index ae98c5aa..7e744890 100644 --- a/src/org/mapsforge/core/Tile.java +++ b/src/org/mapsforge/core/Tile.java @@ -49,8 +49,6 @@ public class Tile { */ public final long pixelY; - public volatile boolean isCanceled; - /** * @param tileX * the X number of the tile. diff --git a/src/org/mapsforge/database/IMapDatabase.java b/src/org/mapsforge/database/IMapDatabase.java index 2b106fd0..53adaa91 100644 --- a/src/org/mapsforge/database/IMapDatabase.java +++ b/src/org/mapsforge/database/IMapDatabase.java @@ -16,7 +16,7 @@ package org.mapsforge.database; import java.io.File; -import org.mapsforge.core.Tile; +import org.mapsforge.android.mapgenerator.JobTile; /** * @@ -38,7 +38,7 @@ public interface IMapDatabase { * the callback which handles the extracted map elements. * @return true if successful */ - public abstract QueryResult executeQuery(Tile tile, + public abstract QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback); /** diff --git a/src/org/mapsforge/database/mapfile/MapDatabase.java b/src/org/mapsforge/database/mapfile/MapDatabase.java index 1dc11917..335fba34 100644 --- a/src/org/mapsforge/database/mapfile/MapDatabase.java +++ b/src/org/mapsforge/database/mapfile/MapDatabase.java @@ -20,9 +20,9 @@ import java.io.RandomAccessFile; import java.util.logging.Level; import java.util.logging.Logger; +import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.core.MercatorProjection; import org.mapsforge.core.Tag; -import org.mapsforge.core.Tile; import org.mapsforge.database.FileOpenResult; import org.mapsforge.database.IMapDatabase; import org.mapsforge.database.IMapDatabaseCallback; @@ -204,7 +204,7 @@ public class MapDatabase implements IMapDatabase { * org.mapsforge.map.reader.MapDatabaseCallback) */ @Override - public QueryResult executeQuery(Tile tile, IMapDatabaseCallback mapDatabaseCallback) { + public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) { if (sMapFileHeader == null) return QueryResult.FAILED; diff --git a/src/org/mapsforge/database/pbmap/MapDatabase.java b/src/org/mapsforge/database/pbmap/MapDatabase.java index c571c81b..7625d0c8 100644 --- a/src/org/mapsforge/database/pbmap/MapDatabase.java +++ b/src/org/mapsforge/database/pbmap/MapDatabase.java @@ -53,6 +53,7 @@ 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.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.core.BoundingBox; import org.mapsforge.core.GeoPoint; import org.mapsforge.core.Tag; @@ -106,7 +107,7 @@ public class MapDatabase implements IMapDatabase { private IMapDatabaseCallback mMapGenerator; private float mScaleFactor; - private Tile mTile; + private JobTile mTile; private FileOutputStream mCacheFile; private long mContentLenth; @@ -129,7 +130,7 @@ public class MapDatabase implements IMapDatabase { }); @Override - public QueryResult executeQuery(Tile tile, IMapDatabaseCallback mapDatabaseCallback) { + public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) { QueryResult result = QueryResult.SUCCESS; mCacheFile = null; @@ -194,7 +195,7 @@ public class MapDatabase implements IMapDatabase { entity.consumeContent(); return QueryResult.FAILED; } - if (mTile.isCanceled) { + if (!mTile.isLoading) { Log.d(TAG, "1 loading canceled " + mTile); entity.consumeContent(); @@ -249,7 +250,7 @@ public class MapDatabase implements IMapDatabase { mRequest = null; // FIXME remove this stuff - if (mTile.isCanceled) { + if (!mTile.isLoading) { Log.d(TAG, "loading canceled " + mTile); result = QueryResult.FAILED; } diff --git a/src/org/mapsforge/database/postgis/MapDatabase.java b/src/org/mapsforge/database/postgis/MapDatabase.java index 25469bc2..7ce17e93 100644 --- a/src/org/mapsforge/database/postgis/MapDatabase.java +++ b/src/org/mapsforge/database/postgis/MapDatabase.java @@ -24,10 +24,10 @@ import java.util.HashMap; import java.util.Map.Entry; import java.util.Properties; +import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.core.BoundingBox; import org.mapsforge.core.GeoPoint; import org.mapsforge.core.Tag; -import org.mapsforge.core.Tile; import org.mapsforge.core.WebMercator; import org.mapsforge.database.FileOpenResult; import org.mapsforge.database.IMapDatabase; @@ -45,7 +45,7 @@ import android.util.Log; public class MapDatabase implements IMapDatabase { private final static String TAG = "MapDatabase"; - private static final String QUERY = "SELECT tags, geom FROM __get_tile(?,?,?,false)"; + private static final String QUERY = "SELECT tags, geom FROM __get_tile(?,?,?)"; private final float mScale = 1; @@ -71,7 +71,7 @@ public class MapDatabase implements IMapDatabase { private boolean connect() { Connection conn = null; - String dburl = "jdbc:postgresql://city.informatik.uni-bremen.de:5432/gis"; + String dburl = "jdbc:postgresql://city.informatik.uni-bremen.de:5432/gis-2.0"; Properties dbOpts = new Properties(); dbOpts.setProperty("user", "osm"); @@ -102,7 +102,7 @@ public class MapDatabase implements IMapDatabase { } @Override - public QueryResult executeQuery(Tile tile, IMapDatabaseCallback mapDatabaseCallback) { + public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) { if (connection == null) { if (!connect()) return QueryResult.FAILED; diff --git a/src/org/mapsforge/database/json/MapDatabase.java b/src/org/mapsforge/database/test/MapDatabase.java similarity index 96% rename from src/org/mapsforge/database/json/MapDatabase.java rename to src/org/mapsforge/database/test/MapDatabase.java index 164ba2eb..c62afb0d 100644 --- a/src/org/mapsforge/database/json/MapDatabase.java +++ b/src/org/mapsforge/database/test/MapDatabase.java @@ -12,10 +12,11 @@ * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see . */ -package org.mapsforge.database.json; +package org.mapsforge.database.test; import java.io.File; +import org.mapsforge.android.mapgenerator.JobTile; import org.mapsforge.core.BoundingBox; import org.mapsforge.core.MercatorProjection; import org.mapsforge.core.Tag; @@ -51,7 +52,7 @@ public class MapDatabase implements IMapDatabase { // private static double HALF_PI = Math.PI / 2; @Override - public QueryResult executeQuery(Tile tile, IMapDatabaseCallback mapDatabaseCallback) { + public QueryResult executeQuery(JobTile tile, IMapDatabaseCallback mapDatabaseCallback) { long cx = tile.pixelX + (Tile.TILE_SIZE >> 1); long cy = tile.pixelY + (Tile.TILE_SIZE >> 1);