diff --git a/src/org/mapsforge/android/IMapRenderer.java b/src/org/mapsforge/android/IMapRenderer.java index b5249d32..9ed921da 100644 --- a/src/org/mapsforge/android/IMapRenderer.java +++ b/src/org/mapsforge/android/IMapRenderer.java @@ -16,6 +16,7 @@ package org.mapsforge.android; import org.mapsforge.android.mapgenerator.IMapGenerator; import org.mapsforge.android.mapgenerator.MapGeneratorJob; +import org.mapsforge.android.rendertheme.RenderTheme; import android.opengl.GLSurfaceView; @@ -46,4 +47,6 @@ public interface IMapRenderer extends GLSurfaceView.Renderer { public void redrawTiles(boolean clear); public IMapGenerator createMapGenerator(); + + public void setRenderTheme(RenderTheme t); } diff --git a/src/org/mapsforge/android/MapView.java b/src/org/mapsforge/android/MapView.java index c04e7c32..af376cf4 100644 --- a/src/org/mapsforge/android/MapView.java +++ b/src/org/mapsforge/android/MapView.java @@ -496,6 +496,7 @@ public class MapView extends GLSurfaceView { * * @param internalRenderTheme * the internal rendering theme. + * @return ... * @throws IllegalArgumentException * if the supplied internalRenderTheme is null. */ @@ -538,6 +539,7 @@ public class MapView extends GLSurfaceView { try { inputStream = theme.getRenderThemeAsStream(); RenderTheme t = RenderThemeHandler.getRenderTheme(inputStream); + mMapRenderer.setRenderTheme(t); mMapWorkers[0].getMapGenerator().setRenderTheme(t); return true; } catch (ParserConfigurationException e) { diff --git a/src/org/mapsforge/android/glrenderer/GLMapTile.java b/src/org/mapsforge/android/glrenderer/GLMapTile.java index 9abc5783..5c352747 100644 --- a/src/org/mapsforge/android/glrenderer/GLMapTile.java +++ b/src/org/mapsforge/android/glrenderer/GLMapTile.java @@ -14,21 +14,23 @@ */ package org.mapsforge.android.glrenderer; -import java.util.ArrayList; - import org.mapsforge.android.mapgenerator.MapTile; import org.mapsforge.core.Tile; class GLMapTile extends MapTile { + long lastDraw = 0; + + VertexBufferObject vbo; + + // polygonOffset is always 8 + int lineOffset; - VertexBufferObject lineVBO; - VertexBufferObject polygonVBO; TextTexture texture; LineLayer lineLayers; PolygonLayer polygonLayers; - ArrayList labels; + TextItem labels; boolean newData; boolean loading; @@ -37,9 +39,6 @@ class GLMapTile extends MapTile { final long x; final long y; - // scissor coordinates - int sx, sy, sw, sh; - final GLMapTile[] child = { null, null, null, null }; GLMapTile parent; diff --git a/src/org/mapsforge/android/glrenderer/LineLayer.java b/src/org/mapsforge/android/glrenderer/LineLayer.java index 3e7264d0..b0bf8d73 100644 --- a/src/org/mapsforge/android/glrenderer/LineLayer.java +++ b/src/org/mapsforge/android/glrenderer/LineLayer.java @@ -15,38 +15,35 @@ package org.mapsforge.android.glrenderer; import org.mapsforge.android.rendertheme.renderinstruction.Line; +import org.mapsforge.core.Tile; +import android.graphics.Paint.Cap; import android.util.FloatMath; class LineLayer { - private static final float SCALE_FACTOR = 16; - - Line line; + private static final float S = MapRenderer.COORD_MULTIPLIER; + private static final float S1000 = 1000; LineLayer next; LineLayer outlines; + Line line; float width; boolean isOutline; + int layer; ShortItem pool; protected ShortItem curItem; int verticesCnt; int offset; + short[] mVertex; - final int layer; - - LineLayer(int layer, Line line, boolean outline) { + LineLayer(int layer, Line line, float width, boolean outline) { this.layer = layer; - + this.width = width; this.line = line; this.isOutline = outline; - - if (!outline) { - curItem = ShortPool.get(); - pool = curItem; - } } void addOutline(LineLayer link) { @@ -58,45 +55,82 @@ class LineLayer { outlines = link; } - short[] getNextItem() { - curItem.used = ShortItem.SIZE; + private static ShortItem addTwoVertex(short[] vertex, ShortItem item) { + ShortItem it = item; - curItem.next = ShortPool.get(); - curItem = curItem.next; + if (it.used + 6 >= ShortItem.SIZE) { - return curItem.vertices; + if (it.used == ShortItem.SIZE) { + it.next = ShortPool.get(); + it = it.next; + + } else { + System.arraycopy(vertex, 0, it.vertices, it.used, 6); + it.used += 6; + + it.next = ShortPool.get(); + it = it.next; + + System.arraycopy(vertex, 6, it.vertices, it.used, 6); + it.used += 6; + + return it; + } + } + + System.arraycopy(vertex, 0, it.vertices, it.used, 12); + it.used += 12; + + return it; + } + + private static ShortItem addVertex(short[] vertex, ShortItem item) { + ShortItem it = item; + + if (it.used == ShortItem.SIZE) { + it.next = ShortPool.get(); + it = it.next; + } + + System.arraycopy(vertex, 0, it.vertices, it.used, 6); + it.used += 6; + + return it; } /* - * line extrusion is based on code from GLMap (https://github.com/olofsj/GLMap/) by olofsj + * line extrusion is based on code from GLMap (https://github.com/olofsj/GLMap/) by olofsj -- need some way to know + * how the road connects to set the ending angles */ - void addLine(float[] pointArray, int pos, int length, float w, boolean capRound) { + void addLine(float[] pointArray, int pos, int length) { float x, y, nextX, nextY, prevX, prevY, ux, uy, vx, vy, wx, wy; float a; int pointPos = pos; - boolean rounded = capRound; - width = w;// * SCALE_FACTOR; - if (w < 0.5) - rounded = false; + boolean rounded = false; + boolean squared = false; + + if (line.cap == Cap.ROUND) + rounded = true; + else if (line.cap == Cap.SQUARE) + squared = true; + + if (pool == null) { + curItem = ShortPool.get(); + pool = curItem; + + mVertex = new short[12]; + } // amount of vertices used verticesCnt += length + (rounded ? 6 : 2); - int MAX = PoolItem.SIZE; + ShortItem si = curItem; - short[] curVertices = curItem.vertices; - int vertexPos = curItem.used; + x = pointArray[pointPos++]; + y = pointArray[pointPos++]; - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - x = pointArray[pointPos++];// * SCALE_FACTOR; - y = pointArray[pointPos++];// * SCALE_FACTOR; - - nextX = pointArray[pointPos++];// * SCALE_FACTOR; - nextY = pointArray[pointPos++];// * SCALE_FACTOR; + nextX = pointArray[pointPos++]; + nextY = pointArray[pointPos++]; // Calculate triangle corners for the given width vx = nextX - x; @@ -110,109 +144,83 @@ class LineLayer { ux = -vy; uy = vx; - float uxw = ux * w; - float uyw = uy * w; + float uxw = ux; + float uyw = uy; - float vxw = vx * w; - float vyw = vy * w; + float vxw = vx; + float vyw = vy; + int tsize = Tile.TILE_SIZE; - // boolean outside = (x <= 0 || x >= Tile.TILE_SIZE || y <= 0 || y >= Tile.TILE_SIZE) - // && (x - vxw <= 0 || x - vxw >= Tile.TILE_SIZE || y - vyw <= 0 || y - vyw >= Tile.TILE_SIZE); + short v[] = mVertex; + + v[0] = (short) (x * S); + v[1] = (short) (y * S); + + boolean outside = (x <= 0 || x >= tsize || y <= 0 || y >= tsize) + && (x - vxw <= 0 || x - vxw >= tsize || y - vyw <= 0 || y - vyw >= tsize); - boolean outside = false; if (rounded && !outside) { + v[2] = (short) ((uxw - vxw) * S1000); + v[3] = (short) ((uyw - vyw) * S1000); + v[4] = -1; + v[5] = 1; + si = addVertex(v, si); + si = addVertex(v, si); - // Add the first point twice to be able to draw with GL_TRIANGLE_STRIP - - curVertices[vertexPos++] = (short) ((x + uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 1; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x + uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 1; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 1; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } + v[2] = (short) (-(uxw + vxw) * S1000); + v[3] = (short) (-(uyw + vyw) * S1000); + v[4] = 1; + v[5] = 1; + si = addVertex(v, si); // Start of line - curVertices[vertexPos++] = (short) ((x + uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; + v[2] = (short) ((uxw) * S1000); + v[3] = (short) ((uyw) * S1000); + v[4] = -1; + v[5] = 0; + si = addVertex(v, si); - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; + v[2] = (short) ((-uxw) * S1000); + v[3] = (short) ((-uyw) * S1000); + v[4] = 1; + v[5] = 0; + si = addVertex(v, si); } else { // outside means line is probably clipped // TODO should align ending with tile boundary // for now, just extend the line a little - if (!outside) { + + if (squared) { + vxw = 0; + vyw = 0; + } else if (!outside) { vxw *= 0.5; vyw *= 0.5; } - if (rounded) { + + if (rounded) verticesCnt -= 2; - } + // Add the first point twice to be able to draw with GL_TRIANGLE_STRIP - curVertices[vertexPos++] = (short) ((x + uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; + v[2] = (short) ((uxw - vxw) * S1000); + v[3] = (short) ((uyw - vyw) * S1000); + v[4] = -1; + v[5] = 0; + si = addVertex(v, si); + si = addVertex(v, si); - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x + uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; + v[2] = (short) (-(uxw + vxw) * S1000); + v[3] = (short) (-(uyw + vyw) * S1000); + v[4] = 1; + v[5] = 0; + si = addVertex(v, si); } prevX = x; prevY = y; x = nextX; y = nextY; - // boolean flipped = false; for (; pointPos < pos + length;) { nextX = pointArray[pointPos++]; @@ -235,58 +243,40 @@ class LineLayer { // Sum of these two vectors points ux = vx + wx; uy = vy + wy; + a = -wy * ux + wx * uy; - if ((a < 0.1 && a > -0.1)) { - // Almost straight, use normal vector + // boolean split = false; + if (a < 0.1f && a > -0.1f) { + // Almost straight or miter goes to infinity, use normal vector ux = -wy; uy = wx; } else { ux = (ux / a); uy = (uy / a); - if (ux > 2 || uy > 2 || ux < -2 || uy < -2) { + if (ux > 2.0f || ux < -2.0f || uy > 2.0f || uy < -2.0f) { ux = -wy; uy = wx; - - // ux = vx + wx; - // uy = vy + wy; - // // Normalize u, and project normal vector onto this - // double c = Math.sqrt(ux * ux + uy * uy); - // if (a < 0) { - // ux = (float) -(ux / c); - // uy = (float) -(uy / c); - // } - // else { - // ux = (float) (ux / c); - // uy = (float) (uy / c); - // } - // flipped = flipped ? false : true; } } - uxw = ux * w; - uyw = uy * w; + uxw = ux * S1000; + uyw = uy * S1000; - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } + v[6] = v[0] = (short) (x * S); + v[7] = v[1] = (short) (y * S); - curVertices[vertexPos++] = (short) ((x + uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; + v[2] = (short) uxw; + v[3] = (short) uyw; + v[4] = -1; + v[5] = 0; - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; + v[8] = (short) -uxw; + v[9] = (short) -uyw; + v[10] = 1; + v[11] = 0; + si = addTwoVertex(v, si); prevX = x; prevY = y; @@ -305,105 +295,71 @@ class LineLayer { ux = vy; uy = -vx; - uxw = ux * w; - uyw = uy * w; + uxw = ux; + uyw = uy; - vxw = vx * w; - vyw = vy * w; + vxw = vx; + vyw = vy; - // outside = (x <= 0 || x >= Tile.TILE_SIZE || y <= 0 || y >= Tile.TILE_SIZE) - // && (x - vxw <= 0 || x - vxw >= Tile.TILE_SIZE || y - vyw <= 0 || y - vyw >= Tile.TILE_SIZE); + outside = (x <= 0 || x >= tsize || y <= 0 || y >= tsize) + && (x - vxw <= 0 || x - vxw >= tsize || y - vyw <= 0 || y - vyw >= tsize); - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } + v[0] = (short) (x * S); + v[1] = (short) (y * S); if (rounded && !outside) { - curVertices[vertexPos++] = (short) ((x + uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; + v[2] = (short) ((uxw) * S1000); + v[3] = (short) ((uyw) * S1000); + v[4] = -1; + v[5] = 0; + si = addVertex(v, si); - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } + v[2] = (short) ((-uxw) * S1000); + v[3] = (short) ((-uyw) * S1000); + v[4] = 1; + v[5] = 0; + si = addVertex(v, si); // For rounded line edges - curVertices[vertexPos++] = (short) ((x + uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = -1; + v[2] = (short) ((uxw - vxw) * S1000); + v[3] = (short) ((uyw - vyw) * S1000); + v[4] = -1; + v[5] = -1; + si = addVertex(v, si); - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - // Add the last vertex twice to be able to draw with GL_TRIANGLE_STRIP - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = -1; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = -1; + v[2] = (short) (-(uxw + vxw) * S1000); + v[3] = (short) (-(uyw + vyw) * S1000); + v[4] = 1; + v[5] = -1; + si = addVertex(v, si); + si = addVertex(v, si); } else { - if (!outside) { + if (squared) { + vxw = 0; + vyw = 0; + } else if (!outside) { vxw *= 0.5; vyw *= 0.5; } - if (rounded) { + + if (rounded) verticesCnt -= 2; - } - curVertices[vertexPos++] = (short) ((x + uxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y + uyw) * SCALE_FACTOR); - curVertices[vertexPos++] = -1; - curVertices[vertexPos++] = 0; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - // Add the last vertex twice to be able to draw with GL_TRIANGLE_STRIP - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; - - if (vertexPos == MAX) { - curVertices = getNextItem(); - vertexPos = 0; - } - - curVertices[vertexPos++] = (short) ((x - uxw - vxw) * SCALE_FACTOR); - curVertices[vertexPos++] = (short) ((y - uyw - vyw) * SCALE_FACTOR); - curVertices[vertexPos++] = 1; - curVertices[vertexPos++] = 0; + v[2] = (short) ((uxw) * S1000); + v[3] = (short) ((uyw) * S1000); + v[4] = -1; + v[5] = 0; + si = addVertex(v, si); + v[2] = (short) (-(uxw + vxw) * S1000); + v[3] = (short) (-(uyw + vyw) * S1000); + v[4] = 1; + v[5] = 0; + si = addVertex(v, si); + si = addVertex(v, si); } - curItem.used = vertexPos; + curItem = si; } } diff --git a/src/org/mapsforge/android/glrenderer/LineLayers.java b/src/org/mapsforge/android/glrenderer/LineLayers.java index 6c1bd674..825bf5b6 100644 --- a/src/org/mapsforge/android/glrenderer/LineLayers.java +++ b/src/org/mapsforge/android/glrenderer/LineLayers.java @@ -14,136 +14,167 @@ */ package org.mapsforge.android.glrenderer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import static android.opengl.GLES20.GL_TRIANGLE_STRIP; +import static android.opengl.GLES20.glDisableVertexAttribArray; +import static android.opengl.GLES20.glDrawArrays; +import static android.opengl.GLES20.glEnableVertexAttribArray; +import static android.opengl.GLES20.glGetAttribLocation; +import static android.opengl.GLES20.glGetUniformLocation; +import static android.opengl.GLES20.glUniform4f; +import static android.opengl.GLES20.glUniform4fv; +import static android.opengl.GLES20.glUniformMatrix4fv; +import static android.opengl.GLES20.glUseProgram; +import static android.opengl.GLES20.glVertexAttribPointer; + import java.nio.ShortBuffer; +import org.mapsforge.android.rendertheme.renderinstruction.Line; +import org.mapsforge.android.utils.GlUtils; + +import android.opengl.GLES20; +import android.util.FloatMath; + class LineLayers { - private static int NUM_VERTEX_FLOATS = 4; + private static int NUM_VERTEX_SHORTS = 6; - // static FloatBuffer compileLayerData(LineLayer layers, FloatBuffer buf) { - // FloatBuffer fbuf = buf; - // int size = 0; - // - // for (LineLayer l = layers; l != null; l = l.next) - // size += l.verticesCnt; - // - // size *= NUM_VERTEX_FLOATS; - // - // if (buf == null || buf.capacity() < size) { - // ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 4).order( - // ByteOrder.nativeOrder()); - // fbuf = bbuf.asFloatBuffer(); - // } else { - // fbuf.clear(); - // } - // int pos = 0; - // - // PoolItem last = null, items = null; - // - // for (LineLayer l = layers; l != null; l = l.next) { - // if (l.isOutline) - // continue; - // - // for (PoolItem item = l.pool; item != null; item = item.next) { - // fbuf.put(item.vertices, 0, item.used); - // last = item; - // } - // l.offset = pos; - // pos += l.verticesCnt; - // - // if (last != null) { - // last.next = items; - // items = l.pool; - // } - // - // l.pool = null; - // } - // - // VertexPool.add(items); - // - // fbuf.flip(); - // - // return fbuf; - // } - // - // static ShortBuffer compileLayerData(LineLayer layers, ShortBuffer buf) { - // int size = 0; - // ShortBuffer sbuf = buf; - // - // for (LineLayer l = layers; l != null; l = l.next) - // size += l.verticesCnt; - // - // size *= NUM_VERTEX_FLOATS; - // - // if (buf == null || buf.capacity() < size) { - // ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 2).order( - // ByteOrder.nativeOrder()); - // sbuf = bbuf.asShortBuffer(); - // } else { - // sbuf.clear(); - // } - // int pos = 0; - // - // short[] data = new short[PoolItem.SIZE]; - // - // PoolItem last = null, items = null; - // - // for (LineLayer l = layers; l != null; l = l.next) { - // if (l.isOutline) - // continue; - // - // for (PoolItem item = l.pool; item != null; item = item.next) { - // PoolItem.toHalfFloat(item, data); - // sbuf.put(data, 0, item.used); - // last = item; - // } - // - // l.offset = pos; - // pos += l.verticesCnt; - // - // if (last != null) { - // last.next = items; - // items = l.pool; - // } - // - // l.pool = null; - // } - // - // VertexPool.add(items); - // - // sbuf.flip(); - // - // return sbuf; - // } - // - // static void clear(LineLayer layer) { - // for (LineLayer l = layer; l != null; l = l.next) { - // if (l.pool != null) - // VertexPool.add(l.pool); - // } - // } + private static final int LINE_VERTICES_DATA_POS_OFFSET = 0; + private static final int LINE_VERTICES_DATA_TEX_OFFSET = 8; - static ShortBuffer compileLayerData(LineLayer layers, ShortBuffer buf) { + // shader handles + private static int lineProgram; + private static int hLineVertexPosition; + private static int hLineTexturePosition; + private static int hLineColor; + private static int hLineMatrix; + private static int hLineScale; + private static int hLineWidth; + + static boolean init() { + lineProgram = GlUtils.createProgram(Shaders.lineVertexShader, + Shaders.lineFragmentShader); + if (lineProgram == 0) { + // Log.e(TAG, "Could not create line program."); + return false; + } + + hLineMatrix = glGetUniformLocation(lineProgram, "mvp"); + hLineScale = glGetUniformLocation(lineProgram, "u_wscale"); + hLineWidth = glGetUniformLocation(lineProgram, "u_width"); + hLineColor = glGetUniformLocation(lineProgram, "u_color"); + hLineVertexPosition = GLES20.glGetAttribLocation(lineProgram, "a_position"); + hLineTexturePosition = glGetAttribLocation(lineProgram, "a_st"); + + return true; + } + + static LineLayer drawLines(GLMapTile tile, LineLayer layer, int next, float[] matrix, + float div, double zoom, float scale) { + + float z = 1 / div; + + if (layer == null) + return null; + + glUseProgram(lineProgram); + + glEnableVertexAttribArray(hLineVertexPosition); + glEnableVertexAttribArray(hLineTexturePosition); + + glVertexAttribPointer(hLineVertexPosition, 4, GLES20.GL_SHORT, + false, 12, tile.lineOffset + LINE_VERTICES_DATA_POS_OFFSET); + + glVertexAttribPointer(hLineTexturePosition, 2, GLES20.GL_SHORT, + false, 12, tile.lineOffset + LINE_VERTICES_DATA_TEX_OFFSET); + + glUniformMatrix4fv(hLineMatrix, 1, false, matrix, 0); + + // if (diff != 0) + // // diff < 0 means tile is parent + // z = (diff > 0) ? 1.0f / (1 << diff) : (1 << -diff); + + // scale factor to map one pixel on tile to one pixel on screen: + // float pixel = 2.0f / (scale * z); + // GLES20.glUniform1f(hLineScale, pixel); + + // line scale factor (for non fixed lines) + float s = FloatMath.sqrt(scale * z); + boolean blur = false; + GLES20.glUniform1f(hLineScale, 0); + + LineLayer l = layer; + for (; l != null && l.layer < next; l = l.next) { + Line line = l.line; + if (line.fade != -1 && line.fade > zoom) + continue; + + if (line.fade >= zoom) { + float alpha = 1.0f; + + alpha = (scale > 1.2f ? scale : 1.2f) - alpha; + if (alpha > 1.0f) + alpha = 1.0f; + glUniform4f(hLineColor, + line.color[0], line.color[1], line.color[2], alpha); + } else { + glUniform4fv(hLineColor, 1, line.color, 0); + } + + if (blur) { + GLES20.glUniform1f(hLineScale, 0); + blur = false; + } + if (l.isOutline) { + for (LineLayer o = l.outlines; o != null; o = o.outlines) { + if (line.blur != 0) { + GLES20.glUniform1f(hLineScale, (l.width + o.width) / (scale * z) + - (line.blur / (scale * z))); + blur = true; + } + + if (zoom > MapGenerator.STROKE_MAX_ZOOM_LEVEL) + GLES20.glUniform1f(hLineWidth, + (l.width + o.width) / (scale * z)); + else + GLES20.glUniform1f(hLineWidth, l.width / (scale * z) + + o.width / s); + + glDrawArrays(GL_TRIANGLE_STRIP, o.offset, o.verticesCnt); + } + } + else { + if (line.blur != 0) { + GLES20.glUniform1f(hLineScale, (l.width / s) * line.blur); + blur = true; + } + + if (line.fixed || zoom > MapGenerator.STROKE_MAX_ZOOM_LEVEL) { + // invert scaling of extrusion vectors so that line width stays the same + GLES20.glUniform1f(hLineWidth, (l.width / (scale * z))); + } else { + GLES20.glUniform1f(hLineWidth, (l.width / s)); + } + + glDrawArrays(GL_TRIANGLE_STRIP, l.offset, l.verticesCnt); + } + } + + glDisableVertexAttribArray(hLineVertexPosition); + glDisableVertexAttribArray(hLineTexturePosition); + + return l; + } + + static int sizeOf(LineLayer layers) { int size = 0; - ShortBuffer sbuf = buf; - for (LineLayer l = layers; l != null; l = l.next) size += l.verticesCnt; - size *= NUM_VERTEX_FLOATS; + size *= NUM_VERTEX_SHORTS; + return size; + } - if (buf == null || buf.capacity() < size) { - ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 2).order( - ByteOrder.nativeOrder()); - sbuf = bbuf.asShortBuffer(); - } else { - sbuf.clear(); - } + static void compileLayerData(LineLayer layers, ShortBuffer sbuf) { int pos = 0; - - // short[] data = new short[PoolItem.SIZE]; - ShortItem last = null, items = null; for (LineLayer l = layers; l != null; l = l.next) { @@ -151,8 +182,6 @@ class LineLayers { continue; for (ShortItem item = l.pool; item != null; item = item.next) { - // PoolItem.toHalfFloat(item, data); - // sbuf.put(data, 0, item.used); sbuf.put(item.vertices, 0, item.used); last = item; } @@ -166,19 +195,110 @@ class LineLayers { } l.pool = null; + l.curItem = null; } ShortPool.add(items); - - sbuf.flip(); - - return sbuf; } + // @SuppressLint("UseValueOf") + // private static final Boolean lock = new Boolean(true); + // private static final int POOL_LIMIT = 1500; + // + // static private LineLayer pool = null; + // static private int count = 0; + // static private int countAll = 0; + // + // static void finish() { + // synchronized (lock) { + // count = 0; + // countAll = 0; + // pool = null; + // } + // } + // + // static LineLayer get(int layer, Line line, float width, boolean outline) { + // synchronized (lock) { + // + // if (count == 0 && pool == null) { + // countAll++; + // return new LineLayer(layer, line, width, outline); + // } + // if (count > 0) { + // count--; + // } else { + // int c = 0; + // LineLayer tmp = pool; + // + // while (tmp != null) { + // c++; + // tmp = tmp.next; + // } + // + // Log.d("LineLayersl", "eek wrong count: " + c + " left"); + // } + // + // LineLayer it = pool; + // pool = pool.next; + // it.next = null; + // it.layer = layer; + // it.line = line; + // it.isOutline = outline; + // it.width = width; + // return it; + // } + // } + // + // static void add(LineLayer layers) { + // if (layers == null) + // return; + // + // synchronized (lock) { + // + // // limit pool items + // if (countAll < POOL_LIMIT) { + // LineLayer last = layers; + // + // while (true) { + // count++; + // + // if (last.next == null) + // break; + // + // last = last.next; + // } + // + // last.next = pool; + // pool = layers; + // + // } else { + // int cleared = 0; + // LineLayer prev, tmp = layers; + // while (tmp != null) { + // prev = tmp; + // tmp = tmp.next; + // + // countAll--; + // cleared++; + // + // prev.next = null; + // + // } + // Log.d("LineLayers", "sum: " + countAll + " free: " + count + " freed " + // + cleared); + // } + // + // } + // } + // static void clear(LineLayer layer) { for (LineLayer l = layer; l != null; l = l.next) { - if (l.pool != null) + if (l.pool != null) { ShortPool.add(l.pool); + l.pool = null; + l.curItem = null; + } } + // LineLayers.add(layer); } } diff --git a/src/org/mapsforge/android/glrenderer/MapGenerator.java b/src/org/mapsforge/android/glrenderer/MapGenerator.java index 952dd687..602c518a 100644 --- a/src/org/mapsforge/android/glrenderer/MapGenerator.java +++ b/src/org/mapsforge/android/glrenderer/MapGenerator.java @@ -14,8 +14,6 @@ */ package org.mapsforge.android.glrenderer; -import java.util.ArrayList; - import org.mapsforge.android.mapgenerator.IMapGenerator; import org.mapsforge.android.mapgenerator.MapGeneratorJob; import org.mapsforge.android.rendertheme.IRenderCallback; @@ -23,6 +21,7 @@ import org.mapsforge.android.rendertheme.RenderTheme; import org.mapsforge.android.rendertheme.renderinstruction.Area; import org.mapsforge.android.rendertheme.renderinstruction.Caption; import org.mapsforge.android.rendertheme.renderinstruction.Line; +import org.mapsforge.android.rendertheme.renderinstruction.PathText; import org.mapsforge.android.rendertheme.renderinstruction.RenderInstruction; import org.mapsforge.core.MercatorProjection; import org.mapsforge.core.Tag; @@ -46,11 +45,11 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas private static final double PI180 = (Math.PI / 180) / 1000000.0; private static final double PIx4 = Math.PI * 4; private static final double STROKE_INCREASE = Math.sqrt(2); - private static final byte STROKE_MIN_ZOOM_LEVEL = 12; private static final byte LAYERS = 11; private static final double f900913 = 20037508.342789244; - // 134217728 - // 2147483648.000 + + static final byte STROKE_MIN_ZOOM_LEVEL = 12; + static final byte STROKE_MAX_ZOOM_LEVEL = 17; private static RenderTheme renderTheme; @@ -66,14 +65,23 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas private LineLayer mCurLineLayer; private PolygonLayer mCurPolyLayer; - private ArrayList mLabels; + private TextItem mLabels; private int mDrawingLayer; private int mLevels; - private boolean useSphericalMercator = false; private float mStrokeScale = 1.0f; + private boolean mProjected; + // private boolean mProjectedResult; + private float mSimplify; + // private boolean firstMatch; + // private boolean prevClosed; + + private RenderInstruction[] mRenderInstructions = null; + + private final String TAG_WATER = "water".intern(); + /** * */ @@ -84,53 +92,62 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas private float mPoiX = 256; private float mPoiY = 256; - private Tag mTagEmptyName = new Tag("name", ""); + private Tag mTagEmptyName = new Tag(Tag.TAG_KEY_NAME, null, false); private Tag mTagName; private void filterTags(Tag[] tags) { for (int i = 0; i < tags.length; i++) { // Log.d(TAG, "check tag: " + tags[i]); - if (tags[i].key == mTagEmptyName.key && tags[i].value != null) { + if (tags[i].key == Tag.TAG_KEY_NAME && tags[i].value != null) { mTagName = tags[i]; tags[i] = mTagEmptyName; } } } + // private RenderInstruction[] mNodeRenderInstructions; + @Override public void renderPointOfInterest(byte layer, float latitude, float longitude, Tag[] tags) { mTagName = null; - long x = mCurrentTile.x; - long y = mCurrentTile.y; - long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel; + if (mMapProjection != null) + { + long x = mCurrentTile.x; + long y = mCurrentTile.y; + long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel; - double divx, divy; - long dx = (x - (z >> 1)); - long dy = (y - (z >> 1)); + double divx, divy; + long dx = (x - (z >> 1)); + long dy = (y - (z >> 1)); - if (useSphericalMercator) { - divx = f900913 / (z >> 1); - divy = f900913 / (z >> 1); - mPoiX = (float) (longitude / divx - dx); - mPoiY = (float) (latitude / divy + dy); + if (mMapProjection == WebMercator.NAME) { + double div = f900913 / (z >> 1); + // divy = f900913 / (z >> 1); + mPoiX = (float) (longitude / div - dx); + mPoiY = (float) (latitude / div + dy); + } else { + divx = 180000000.0 / (z >> 1); + divy = z / PIx4; + mPoiX = (float) (longitude / divx - dx); + double sinLat = Math.sin(latitude * PI180); + mPoiY = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy); + if (mPoiX < -10 || mPoiX > Tile.TILE_SIZE + 10 || mPoiY < -10 + || mPoiY > Tile.TILE_SIZE + 10) + return; + } } else { - divx = 180000000.0 / (z >> 1); - divy = z / PIx4; - mPoiX = (float) (longitude / divx - dx); - double sinLat = Math.sin(latitude * PI180); - mPoiY = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy); - if (mPoiX < -10 || mPoiX > Tile.TILE_SIZE + 10 || mPoiY < -10 - || mPoiY > Tile.TILE_SIZE + 10) - return; + mPoiX = longitude; + mPoiY = latitude; } // remove tags that should not be cached in Rendertheme filterTags(tags); // Log.d(TAG, "renderPointOfInterest: " + mTagName); + // mNodeRenderInstructions = MapGenerator.renderTheme.matchNode(this, tags, mCurrentTile.zoomLevel); } @@ -140,82 +157,6 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas } - private boolean mProjected; - private boolean mProjectedResult; - private float mSimplify; - - private boolean projectToTile() { - if (mProjected) - return mProjectedResult; - - float[] coords = mWayNodes; - - long x = mCurrentTile.x; - long y = mCurrentTile.y; - long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel; - float min = mSimplify; - - double divx, divy; - long dx = (x - (z >> 1)); - long dy = (y - (z >> 1)); - - if (useSphericalMercator) { - divx = f900913 / (z >> 1); - divy = f900913 / (z >> 1); - } else { - divx = 180000000.0 / (z >> 1); - divy = z / PIx4; - } - - for (int pos = 0, outPos = 0, i = 0, m = mWays.length; i < m; i++) { - int len = mWays[i]; - if (len == 0) - continue; - int cnt = 0; - float lat, lon, prevLon = 0, prevLat = 0; - - for (int end = pos + len; pos < end; pos += 2) { - - if (useSphericalMercator) { - lon = (float) (coords[pos] / divx - dx); - lat = (float) (coords[pos + 1] / divy + dy); - } else { - lon = (float) ((coords[pos]) / divx - dx); - double sinLat = Math.sin(coords[pos + 1] * PI180); - lat = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy); - } - - if (cnt != 0) { - // drop small distance intermediate nodes - - if (lat == prevLat && lon == prevLon) - continue; - - if ((pos != end - 2) && - !((lat > prevLat + min || lat < prevLat - min) || - (lon > prevLon + min || lon < prevLon - min))) - continue; - } - coords[outPos++] = prevLon = lon; - coords[outPos++] = prevLat = lat; - - cnt += 2; - } - - mWays[i] = (short) cnt; - } - mProjected = true; - mProjectedResult = true; - return true; - } - - // private boolean firstMatch; - // private boolean prevClosed; - - private RenderInstruction[] mRenderInstructions = null; - - private final String TAG_WATER = "water".intern(); - @Override public void renderWay(byte layer, Tag[] tags, float[] wayNodes, short[] wayLength, boolean closed) { @@ -239,6 +180,9 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas mWayNodes = wayNodes; mWays = wayLength; + // remove tags that should not be cached in Rendertheme + filterTags(tags); + // if (mRenderInstructions != null) { // for (int i = 0, n = mRenderInstructions.length; i < n; i++) // mRenderInstructions[i].renderWay(this, tags); @@ -266,7 +210,6 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas mRenderInstructions = MapGenerator.renderTheme.matchWay(this, debugTagWay, (byte) 0, true, true); } - } @Override @@ -277,9 +220,10 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas return; if (caption.textKey == mTagEmptyName.key) { - if (mLabels == null) - mLabels = new ArrayList(); - mLabels.add(new TextItem(mWayNodes[0], mWayNodes[1], mTagName.value, caption)); + + TextItem t = new TextItem(mWayNodes[0], mWayNodes[1], mTagName.value, caption); + t.next = mLabels; + mLabels = t; } } @@ -292,9 +236,23 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas return; if (caption.textKey == mTagEmptyName.key) { - if (mLabels == null) - mLabels = new ArrayList(); - mLabels.add(new TextItem(mPoiX, mPoiY, mTagName.value, caption)); + TextItem t = new TextItem(mPoiX, mPoiY, mTagName.value, caption); + t.next = mLabels; + mLabels = t; + } + } + + @Override + public void renderWayText(PathText pathText) { + // Log.d(TAG, "renderWayText: " + mTagName); + + if (mTagName == null) + return; + + if (pathText.textKey == mTagEmptyName.key) { + + mLabels = WayDecorator.renderText(mWayNodes, mTagName.value, pathText, 0, + mWays[0], mLabels); } } @@ -320,13 +278,23 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas private int countNodes; @Override - public void renderWay(Line line) { + public void renderWay(Line line, int level) { + projectToTile(); - LineLayer outlineLayer = null; + if (line.outline && mCurLineLayer == null) + return; + + float w = line.width; + + if (!line.fixed) { + w *= mStrokeScale; + w *= mProjectionScaleFactor; + } + LineLayer lineLayer = null; - int numLayer = mDrawingLayer + line.level; + int numLayer = mDrawingLayer + level; LineLayer l = mLineLayers; @@ -334,7 +302,9 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas lineLayer = mCurLineLayer; } else if (l == null || l.layer > numLayer) { // insert new layer at start - lineLayer = new LineLayer(numLayer, line, false); + lineLayer = new LineLayer(numLayer, line, w, line.outline); + // lineLayer = LineLayers.get(numLayer, line, w, false); + lineLayer.next = l; mLineLayers = lineLayer; } else { @@ -346,7 +316,8 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas } // insert new layer between current and next layer if (l.next == null || l.next.layer > numLayer) { - lineLayer = new LineLayer(numLayer, line, false); + lineLayer = new LineLayer(numLayer, line, w, line.outline); + // lineLayer = LineLayers.get(numLayer, line, w, false); lineLayer.next = l.next; l.next = lineLayer; } @@ -357,77 +328,83 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas if (lineLayer == null) return; + if (line.outline) { + lineLayer.addOutline(mCurLineLayer); + return; + } + mCurLineLayer = lineLayer; - float w = line.strokeWidth; - - if (!line.fixed) { - w *= mStrokeScale; - w *= mProjectionScaleFactor; - } - else { - w *= 1.2; // TODO make this dependent on dpi - } + boolean round = line.round; for (int i = 0, pos = 0, n = mWays.length; i < n; i++) { int length = mWays[i]; + if (length < 0) + break; + // save some vertices + if (round && i > 200) { + // Log.d(TAG, "WAY TOO MANY LINES!!!"); + round = false; + } // need at least two points if (length >= 4) { - lineLayer.addLine(mWayNodes, pos, length, w, line.round); + lineLayer.addLine(mWayNodes, pos, length); countLines++; countNodes += length; } pos += length; } - if (line.outline < 0) - return; - - Line outline = MapGenerator.renderTheme.getOutline(line.outline); - - if (outline == null) - return; - - numLayer = mDrawingLayer + outline.level; - - l = mLineLayers; - - if (l == null || l.layer > numLayer) { - // insert new layer at start - outlineLayer = new LineLayer(numLayer, outline, true); - outlineLayer.next = l; - mLineLayers = outlineLayer; - } else { - while (l != null) { - if (l.layer == numLayer) { - outlineLayer = l; - break; - } - // insert new layer between current and next layer - if (l.next == null || l.next.layer > numLayer) { - outlineLayer = new LineLayer(numLayer, outline, true); - outlineLayer.next = l.next; - l.next = outlineLayer; - } - l = l.next; - } - } - - if (outlineLayer != null) - outlineLayer.addOutline(lineLayer); + // if (line.outline < 0) + // return; + // + // Line outline = MapGenerator.renderTheme.getOutline(line.outline); + // + // if (outline == null) + // return; + // + // numLayer = mDrawingLayer + outline.getLevel(); + // + // l = mLineLayers; + // + // if (l == null || l.layer > numLayer) { + // // insert new layer at start + // outlineLayer = new LineLayer(numLayer, outline, w, true); + // // outlineLayer = LineLayers.get(numLayer, outline, w, true); + // outlineLayer.next = l; + // mLineLayers = outlineLayer; + // } else { + // while (l != null) { + // if (l.layer == numLayer) { + // outlineLayer = l; + // break; + // } + // // insert new layer between current and next layer + // if (l.next == null || l.next.layer > numLayer) { + // outlineLayer = new LineLayer(numLayer, outline, w, true); + // // outlineLayer = LineLayers.get(numLayer, outline, w, true); + // outlineLayer.next = l.next; + // l.next = outlineLayer; + // } + // l = l.next; + // } + // } + // + // if (outlineLayer != null) + // outlineLayer.addOutline(lineLayer); } @Override - public void renderArea(Area area) { + public void renderArea(Area area, int level) { if (!mDebugDrawPolygons) return; - if (!projectToTile()) + if (!mProjected && !projectToTile()) return; - int numLayer = mDrawingLayer + area.level; + int numLayer = mDrawingLayer + level; PolygonLayer layer = null; PolygonLayer l = mPolyLayers; @@ -463,6 +440,9 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas for (int i = 0, pos = 0, n = mWays.length; i < n; i++) { int length = mWays[i]; + if (length < 0) + break; + // need at least three points if (length >= 6) layer.addPolygon(mWayNodes, pos, length); @@ -470,6 +450,7 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas pos += length; } + // if (area.line != null) } @Override @@ -478,12 +459,6 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas } - @Override - public void renderWayText(String text, Paint paint, Paint stroke) { - // TODO Auto-generated method stub - - } - @Override public void cleanup() { // TODO Auto-generated method stub @@ -495,23 +470,27 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas @Override public boolean executeJob(MapGeneratorJob mapGeneratorJob) { + GLMapTile tile; if (mMapDatabase == null) return false; - useSphericalMercator = WebMercator.NAME.equals(mMapDatabase.getMapProjection()); - - mCurrentTile = (GLMapTile) mapGeneratorJob.tile; + tile = mCurrentTile = (GLMapTile) mapGeneratorJob.tile; mDebugDrawPolygons = !mapGeneratorJob.debugSettings.mDisablePolygons; mDebugDrawUnmatched = mapGeneratorJob.debugSettings.mDrawUnmatchted; - if (mCurrentTile.isLoading || mCurrentTile.isReady) + + if (tile.isLoading || tile.isReady || tile.isCanceled) return false; - mCurrentTile.isLoading = true; + tile.isLoading = true; mLevels = MapGenerator.renderTheme.getLevels(); - setScaleStrokeWidth(mCurrentTile.zoomLevel); + // limit stroke scale at z=17 + if (tile.zoomLevel < STROKE_MAX_ZOOM_LEVEL) + setScaleStrokeWidth(tile.zoomLevel); + else + setScaleStrokeWidth(STROKE_MAX_ZOOM_LEVEL); mLineLayers = null; mPolyLayers = null; @@ -520,43 +499,54 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas // firstMatch = true; countLines = 0; countNodes = 0; - mProjectionScaleFactor = (float) (1.0 / Math.cos(MercatorProjection - .pixelYToLatitude(mCurrentTile.pixelY, mCurrentTile.zoomLevel) - * (Math.PI / 180))); // / 1.5f; - if (mMapDatabase.executeQuery(mCurrentTile, this) != QueryResult.SUCCESS) { + // acount for area changes with latitude + mProjectionScaleFactor = 0.5f + (float) (0.5 / Math.cos(MercatorProjection + .pixelYToLatitude(tile.pixelY, tile.zoomLevel) + * (Math.PI / 180))); + + if (mMapDatabase.executeQuery(tile, this) != QueryResult.SUCCESS) { + Log.d(TAG, "Failed loading: " + tile); LineLayers.clear(mLineLayers); PolygonLayers.clear(mPolyLayers); mLineLayers = null; mPolyLayers = null; - mCurrentTile.isLoading = false; + tile.isLoading = false; return false; } if (mapGeneratorJob.debugSettings.mDrawTileFrames) { + + final float[] debugBoxCoords = { 0, 0, 0, Tile.TILE_SIZE, + Tile.TILE_SIZE, Tile.TILE_SIZE, Tile.TILE_SIZE, 0, 0, 0 }; + final short[] debugBoxIndex = { 10 }; + mTagName = new Tag("name", countLines + " " + countNodes + " " - + mCurrentTile.toString(), false); - mPoiX = 10; + + tile.toString(), false); + mPoiX = Tile.TILE_SIZE >> 1; mPoiY = 10; MapGenerator.renderTheme.matchNode(this, debugTagWay, (byte) 0); - // float[] coords = { 0, 0, 0, Tile.TILE_SIZE, Tile.TILE_SIZE, Tile.TILE_SIZE, - // Tile.TILE_SIZE, 0, 0, 0 }; - // LineLayer ll = mLineLayers.getLayer(Integer.MAX_VALUE, Color.BLACK, false, - // true, -1); - // ll.addLine(coords, 0, coords.length, 1.5f, false); + + mWays = debugBoxIndex; + mWayNodes = debugBoxCoords; + mDrawingLayer = 10 * mLevels; + MapGenerator.renderTheme.matchWay(this, debugTagBox, (byte) 0, false, true); } - mCurrentTile.lineLayers = mLineLayers; - mCurrentTile.polygonLayers = mPolyLayers; - mCurrentTile.labels = mLabels; + tile.lineLayers = mLineLayers; + tile.polygonLayers = mPolyLayers; + tile.labels = mLabels; mCurPolyLayer = null; mCurLineLayer = null; - mCurrentTile.newData = true; + tile.newData = true; + tile.isLoading = false; + return true; } - private Tag[] debugTagWay = { new Tag("debug", "way") }; - private Tag[] debugTagArea = { new Tag("debug", "area") }; + private final Tag[] debugTagBox = { new Tag("debug", "box") }; + private final Tag[] debugTagWay = { new Tag("debug", "way") }; + private final Tag[] debugTagArea = { new Tag("debug", "area") }; private float mProjectionScaleFactor; @@ -586,9 +576,12 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas mStrokeScale = 1; } + private String mMapProjection; + @Override public void setMapDatabase(IMapDatabase mapDatabase) { mMapDatabase = mapDatabase; + mMapProjection = mMapDatabase.getMapProjection(); } @Override @@ -609,4 +602,76 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, IMapDatabas return mRenderInstructions != null; } + + private boolean projectToTile() { + if (mProjected || mMapProjection == null) + return true; + + boolean useWebMercator = false; + + if (mMapProjection == WebMercator.NAME) + useWebMercator = true; + + float[] coords = mWayNodes; + + long x = mCurrentTile.x; + long y = mCurrentTile.y; + long z = Tile.TILE_SIZE << mCurrentTile.zoomLevel; + float min = mSimplify; + + double divx, divy; + long dx = (x - (z >> 1)); + long dy = (y - (z >> 1)); + + if (useWebMercator) { + divx = f900913 / (z >> 1); + divy = f900913 / (z >> 1); + } else { + divx = 180000000.0 / (z >> 1); + divy = z / PIx4; + } + + for (int pos = 0, outPos = 0, i = 0, m = mWays.length; i < m; i++) { + int len = mWays[i]; + if (len == 0) + continue; + if (len < 0) + break; + + int cnt = 0; + float lat, lon, prevLon = 0, prevLat = 0; + + for (int end = pos + len; pos < end; pos += 2) { + + if (useWebMercator) { + lon = (float) (coords[pos] / divx - dx); + lat = (float) (coords[pos + 1] / divy + dy); + } else { + lon = (float) ((coords[pos]) / divx - dx); + double sinLat = Math.sin(coords[pos + 1] * PI180); + lat = (float) (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy); + } + + if (cnt != 0) { + // drop small distance intermediate nodes + if (lat == prevLat && lon == prevLon) + continue; + + if ((pos != end - 2) && + !((lat > prevLat + min || lat < prevLat - min) || + (lon > prevLon + min || lon < prevLon - min))) + continue; + } + coords[outPos++] = prevLon = lon; + coords[outPos++] = prevLat = lat; + + cnt += 2; + } + + mWays[i] = (short) cnt; + } + mProjected = true; + // mProjectedResult = true; + return true; + } } diff --git a/src/org/mapsforge/android/glrenderer/MapRenderer.java b/src/org/mapsforge/android/glrenderer/MapRenderer.java index e11b9c07..7bbf2195 100644 --- a/src/org/mapsforge/android/glrenderer/MapRenderer.java +++ b/src/org/mapsforge/android/glrenderer/MapRenderer.java @@ -16,52 +16,29 @@ package org.mapsforge.android.glrenderer; import static android.opengl.GLES20.GL_ARRAY_BUFFER; import static android.opengl.GLES20.GL_BLEND; -import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT; -import static android.opengl.GLES20.GL_DEPTH_TEST; import static android.opengl.GLES20.GL_DITHER; import static android.opengl.GLES20.GL_DYNAMIC_DRAW; -import static android.opengl.GLES20.GL_EQUAL; -import static android.opengl.GLES20.GL_INVERT; -import static android.opengl.GLES20.GL_NEVER; import static android.opengl.GLES20.GL_ONE; import static android.opengl.GLES20.GL_ONE_MINUS_SRC_ALPHA; import static android.opengl.GLES20.GL_SCISSOR_TEST; import static android.opengl.GLES20.GL_SRC_ALPHA; import static android.opengl.GLES20.GL_STENCIL_BUFFER_BIT; -import static android.opengl.GLES20.GL_STENCIL_TEST; -import static android.opengl.GLES20.GL_TRIANGLE_FAN; -import static android.opengl.GLES20.GL_TRIANGLE_STRIP; -import static android.opengl.GLES20.GL_ZERO; import static android.opengl.GLES20.glBindBuffer; import static android.opengl.GLES20.glBlendFunc; import static android.opengl.GLES20.glBufferData; import static android.opengl.GLES20.glClear; import static android.opengl.GLES20.glClearColor; import static android.opengl.GLES20.glClearStencil; -import static android.opengl.GLES20.glColorMask; import static android.opengl.GLES20.glDepthMask; import static android.opengl.GLES20.glDisable; -import static android.opengl.GLES20.glDisableVertexAttribArray; -import static android.opengl.GLES20.glDrawArrays; import static android.opengl.GLES20.glEnable; -import static android.opengl.GLES20.glEnableVertexAttribArray; import static android.opengl.GLES20.glFinish; import static android.opengl.GLES20.glGenBuffers; -import static android.opengl.GLES20.glGetAttribLocation; -import static android.opengl.GLES20.glGetUniformLocation; import static android.opengl.GLES20.glScissor; -import static android.opengl.GLES20.glStencilFunc; -import static android.opengl.GLES20.glStencilMask; -import static android.opengl.GLES20.glStencilOp; -import static android.opengl.GLES20.glUniform1f; -import static android.opengl.GLES20.glUniform2f; -import static android.opengl.GLES20.glUniform4f; -import static android.opengl.GLES20.glUniform4fv; -import static android.opengl.GLES20.glUniformMatrix4fv; -import static android.opengl.GLES20.glUseProgram; -import static android.opengl.GLES20.glVertexAttribPointer; import static android.opengl.GLES20.glViewport; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.ShortBuffer; import java.util.ArrayList; import java.util.Collections; @@ -77,8 +54,7 @@ import org.mapsforge.android.mapgenerator.JobParameters; import org.mapsforge.android.mapgenerator.MapGeneratorJob; import org.mapsforge.android.mapgenerator.TileCacheKey; import org.mapsforge.android.mapgenerator.TileDistanceSort; -import org.mapsforge.android.rendertheme.renderinstruction.Line; -import org.mapsforge.android.utils.GlConfigChooser; +import org.mapsforge.android.rendertheme.RenderTheme; import org.mapsforge.android.utils.GlUtils; import org.mapsforge.core.MapPosition; import org.mapsforge.core.MercatorProjection; @@ -102,26 +78,28 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { private static int CACHE_TILES = CACHE_TILES_MAX; private static int LIMIT_BUFFERS = 20 * MB; - // private static final int OES_HALF_FLOAT = 0x8D61; - // private static final int FLOAT_BYTES = 4; private static final int SHORT_BYTES = 2; - private static final int POLYGON_VERTICES_DATA_POS_OFFSET = 0; - private static final int LINE_VERTICES_DATA_POS_OFFSET = 0; - private static final int LINE_VERTICES_DATA_TEX_OFFSET = 8; - private static int STENCIL_BITS = 8; + static final float COORD_MULTIPLIER = 8.0f; private final MapView mMapView; private static ArrayList mJobList; private static ArrayList mVBOs; private static TileCacheKey mTileCacheKey; private static HashMap mTiles; + + // all tiles currently referenced private static ArrayList mTileList; + + // tiles that have new data to upload, see passTile() + private static ArrayList mTilesLoaded; + private static TileDistanceSort mTileDistanceSort; private static DebugSettings mDebugSettings; private static JobParameters mJobParameter; - private static MapPosition mMapPosition, mPrevMapPosition; + + // private static MapPosition mMapPosition; private static int mWidth, mHeight; private static float mAspect; @@ -133,11 +111,12 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { // current center tile private static long mTileX, mTileY; + private static float mLastScale; + private static byte mLastZoom; private static int rotateBuffers = 2; private static ShortBuffer shortBuffer[]; - - static boolean useHalfFloat = false; + private static short[] mFillCoords; // bytes currently loaded in VBOs private static int mBufferMemoryUsage; @@ -156,35 +135,31 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } private static float[] mMVPMatrix = new float[16]; - // private float[] mMMatrix = new float[16]; - // private float[] mRMatrix = new float[16]; - // newTiles is set in updateVisibleList and synchronized swapped - // with nextTiles on main thread. - // nextTiles is swapped with curTiles in onDrawFrame in GL thread. - private static TilesData newTiles, nextTiles, curTiles; + // 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 boolean mInitial; - - // shader handles - private static int lineProgram; - private static int hLineVertexPosition; - private static int hLineTexturePosition; - private static int hLineColor; - private static int hLineMatrix; - private static int hLineMode; - private static int hLineWidth; - - private static int polygonProgram; - private static int hPolygonVertexPosition; - private static int hPolygonMatrix; - private static int hPolygonColor; + private static short mDrawCount = 0; /** * */ public boolean timing = false; + @Override + public void setRenderTheme(RenderTheme t) { + int bg = t.getMapBackground(); + float[] c = new float[4]; + c[0] = (bg >> 16 & 0xff) / 255.0f; + c[1] = (bg >> 8 & 0xff) / 255.0f; + c[2] = (bg >> 0 & 0xff) / 255.0f; + c[3] = (bg >> 24 & 0xff) / 255.0f; + mClearColor = c; + } + /** * @param mapView * the MapView @@ -205,6 +180,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mTiles = new HashMap(CACHE_TILES * 2); mTileList = new ArrayList(); mTileCacheKey = new TileCacheKey(); + mTilesLoaded = new ArrayList(30); mTileDistanceSort = new TileDistanceSort(); Matrix.setIdentityM(mMVPMatrix, 0); @@ -212,62 +188,87 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mUpdateTiles = false; } - private static void updateTileDistances() { + private static int updateTileDistances() { int h = (Tile.TILE_SIZE >> 1); - byte zoom = mMapPosition.zoomLevel; - long x = mTileX * (Tile.TILE_SIZE) + h; - long y = mTileY * (Tile.TILE_SIZE) + h; + byte zoom = mLastZoom; + long x = mTileX + h; + long y = mTileY + h; int diff; long dx, dy; + int cnt = 0; // TODO this could need some fixing.. // and be optimized to consider move/zoom direction for (int i = 0, n = mTileList.size(); i < n; i++) { GLMapTile t = mTileList.get(i); diff = (t.zoomLevel - zoom); + if (t.isActive) + cnt++; if (diff == 0) { dx = (t.pixelX + h) - x; dy = (t.pixelY + h) - y; - dy *= mAspect; - t.distance = (1 + ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy))); + t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * 0.5f; + // t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * 0.25f; } else if (diff > 0) { // tile zoom level is child of current dx = ((t.pixelX + h) >> diff) - x; dy = ((t.pixelY + h) >> diff) - y; - dy *= mAspect; - t.distance = 2 * ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)); + // dy *= mAspect; + t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) * diff; + // t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * diff; } else { // tile zoom level is parent of current dx = ((t.pixelX + h) << -diff) - x; dy = ((t.pixelY + h) << -diff) - y; - dy *= mAspect; - t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)); - // prefer lower zoom level, it covers a larger area - t.distance /= -diff; + t.distance = ((dx > 0 ? dx : -dx) + (dy > 0 ? dy : -dy)) + * (-diff * 0.25f); + // t.distance = FloatMath.sqrt((dx * dx + dy * dy)) * (-diff * 0.5f); } // Log.d(TAG, diff + " " + (float) t.distance / Tile.TILE_SIZE + " " + t); } + return cnt; + } + + private static boolean childIsActive(GLMapTile t) { + GLMapTile c = null; + + for (int i = 0; i < 4; i++) { + c = t.child[i]; + if (c != null && c.isActive && !(c.isReady || c.newData)) + return true; + } + + return false; } private static void limitCache(int remove) { - byte z = mMapPosition.zoomLevel; - - for (int j = mTileList.size() - 1, cnt = 0; cnt < remove && j > 0; j--, cnt++) { + byte z = mLastZoom; + // Log.d(TAG, "--------------------------------"); + for (int j = mTileList.size() - 1, cnt = 0; cnt < remove && j > 0; j--) { GLMapTile t = mTileList.remove(j); - if (t.isActive) { - // Log.d(TAG, "EEEK removing active tile"); + // dont remove tile used by renderthread or mapgenerator + // FIXME set tile loading state in main thread + if (t.isLoading) { + + Log.d(TAG, "cancel loading " + t + " " + (t.zoomLevel - mCurZ) + + " " + (t.zoomLevel - mDrawZ) + " " + t.distance); + t.isCanceled = true; + } + else if (t.isActive) { + Log.d(TAG, "EEEK removing active " + t + " " + (t.zoomLevel - mCurZ) + + " " + (t.zoomLevel - mDrawZ) + " " + t.distance); mTileList.add(t); continue; } // check if this tile is used as proxy for not yet drawn active tile // TODO to be simplified... - if (t.isReady || t.newData || t.isLoading) { + else if (t.isReady || t.newData) { if (t.zoomLevel == z + 1) { if (t.parent != null && t.parent.isActive && !(t.parent.isReady || t.parent.newData)) { @@ -309,13 +310,16 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } if (c != null) { - Log.d(TAG, "EEEK removing active second level proxy parent"); + // Log.d(TAG, "EEEK removing active second level proxy parent"); mTileList.add(t); continue; } } } + // Log.d(TAG, ">>> remove " + t + " " + (t.zoomLevel - mCurZ) + // + " " + t.distance); + cnt++; mTileCacheKey.set(t.tileX, t.tileY, t.zoomLevel); mTiles.remove(mTileCacheKey); @@ -334,25 +338,18 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } } - if (t.lineVBO != null) { - synchronized (mVBOs) { - mVBOs.add(t.lineVBO); - mVBOs.add(t.polygonVBO); - t.lineVBO = null; - t.polygonVBO = null; - } - } - if (t.newData) { - // Log.d(TAG, ">>>> clear unsused data ! " + t + "<<<<"); - LineLayers.clear(t.lineLayers); - PolygonLayers.clear(t.polygonLayers); + synchronized (mVBOs) { + clearTile(t); } } + // Log.d(TAG, "--------------------------------<<"); } - private boolean updateVisibleList(double x, double y) { - byte zoomLevel = mMapPosition.zoomLevel; - float scale = mMapPosition.scale; + private Object lock = new Object(); + + private boolean updateVisibleList(double x, double y, int zdir) { + byte zoomLevel = mLastZoom; + float scale = mLastScale; double add = 1.0f / scale; int offsetX = (int) ((mWidth >> 1) * add); int offsetY = (int) ((mHeight >> 1) * add); @@ -399,9 +396,9 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { TileCacheKey key = new TileCacheKey(mTileCacheKey); // FIXME use sparse matrix or sth. + if (mTiles.put(key, tile) != null) Log.d(TAG, "eeek collision"); - mTileList.add(tile); mTileCacheKey.set((tileX >> 1), (tileY >> 1), (byte) (zoomLevel - 1)); @@ -411,18 +408,17 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { // set this tile to be child of its parent if (tile.parent != null) { tile.parent.child[idx] = tile; - } - else if (zoomLevel > 0) { + } else if (zdir > 0 && zoomLevel > 0) { tile.parent = new GLMapTile(tileX >> 1, tileY >> 1, (byte) (zoomLevel - 1)); key = new TileCacheKey(mTileCacheKey); if (mTiles.put(key, tile.parent) != null) Log.d(TAG, "eeek collision"); mTileList.add(tile.parent); - setChildren(tile.parent); + setTileChildren(tile.parent); } - setChildren(tile); + setTileChildren(tile); } @@ -434,60 +430,96 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mJobList.add(job); } - // prefetch parent - // if (tile.parent != null && !tile.parent.isReady && !tile.parent.newData - // && !tile.parent.isLoading) { - // MapGeneratorJob job = new MapGeneratorJob(tile.parent, mJobParameter, - // mDebugSettings); - // if (!mJobList.contains(job)) - // mJobList.add(job); - // } + if (zdir > 0) { + // prefetch parent + if (tile.parent != null && !tile.parent.isReady + && !tile.parent.newData + && !tile.parent.isLoading) { + MapGeneratorJob job = new MapGeneratorJob(tile.parent, + mJobParameter, + mDebugSettings); + if (!mJobList.contains(job)) + mJobList.add(job); + } + } } } - updateTileDistances(); - // scramble tile draw order: might help to make gl // pipelines more independent... just a guess :) - for (int i = 1; i < tiles / 2; i += 2) { - GLMapTile tmp = newTiles.tiles[i]; - newTiles.tiles[i] = newTiles.tiles[tiles - i]; - newTiles.tiles[tiles - i] = tmp; - } + // for (int i = 1; i < tiles / 2; i += 2) { + // GLMapTile tmp = newTiles.tiles[i]; + // newTiles.tiles[i] = newTiles.tiles[tiles - i]; + // newTiles.tiles[tiles - i] = tmp; + // } + + newTiles.cnt = tiles; // pass new tile list to glThread synchronized (this) { - for (int i = 0; i < nextTiles.cnt; i++) - nextTiles.tiles[i].isActive = false; - for (int i = 0; i < curTiles.cnt; i++) - curTiles.tiles[i].isActive = true; + for (int i = 0; i < curTiles.cnt; i++) { + boolean found = false; - for (int i = 0; i < tiles; i++) - newTiles.tiles[i].isActive = true; + for (int j = 0; j < drawTiles.cnt && !found; j++) + if (curTiles.tiles[i] == drawTiles.tiles[j]) + found = true; - TilesData tmp = nextTiles; - nextTiles = newTiles; - nextTiles.cnt = tiles; + 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; + // activeList.remove(newTiles.tiles[i]); + } + } + + for (int i = 0; i < tiles; i++) { + if (!newTiles.tiles[i].isActive) { + newTiles.tiles[i].isActive = true; + // activeList.add(newTiles.tiles[i]); + } + } + + TilesData tmp = curTiles; + curTiles = newTiles; + curTiles.cnt = tiles; newTiles = tmp; + mCurX = x; + mCurY = y; + mCurZ = mLastZoom; + mCurScale = mLastScale; + mUpdateTiles = true; } - int removes = mTiles.size() - CACHE_TILES; - - if (removes > 0) { - Collections.sort(mTileList, mTileDistanceSort); - limitCache(removes); + int active = 0; + // see FIXME in passTile + synchronized (mTiles) { + active = updateTileDistances(); } if (mJobList.size() > 0) mMapView.addJobs(mJobList); + int removes = mTiles.size() - CACHE_TILES; + + if (removes > 20) { + + Log.d(TAG, "---- remove " + removes + " on " + zoomLevel + " active:" + + active + "------"); + Collections.sort(mTileList, mTileDistanceSort); + limitCache(removes); + } + return true; } - private static void setChildren(GLMapTile tile) { + // private static ArrayList activeList = new ArrayList(100); + + private static void setTileChildren(GLMapTile tile) { long xx = tile.tileX << 1; long yy = tile.tileY << 1; @@ -505,16 +537,31 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } } + private static void clearTile(GLMapTile t) { + LineLayers.clear(t.lineLayers); + PolygonLayers.clear(t.polygonLayers); + + if (t.vbo != null) + mVBOs.add(t.vbo); + + t.labels = null; + t.lineLayers = null; + t.polygonLayers = null; + t.newData = false; + t.isLoading = false; + t.isReady = false; + } + /** - * + * called by MapView when position or map settings changes */ @Override public void redrawTiles(boolean clear) { boolean changedPos = false; boolean changedZoom = false; - mMapPosition = mMapView.getMapPosition().getMapPosition(); + MapPosition mapPosition = mMapView.getMapPosition().getMapPosition(); - if (mMapPosition == null) { + if (mapPosition == null) { Log.d(TAG, ">>> no map position"); return; } @@ -522,326 +569,154 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (clear) { mInitial = true; synchronized (this) { - for (GLMapTile t : mTileList) { - if (t.lineVBO != null) { - mVBOs.add(t.lineVBO); - } - if (t.polygonVBO != null) - mVBOs.add(t.polygonVBO); - } + for (GLMapTile t : mTileList) + clearTile(t); + mTileList.clear(); mTiles.clear(); - nextTiles.cnt = 0; + curTiles.cnt = 0; mBufferMemoryUsage = 0; } } - byte zoomLevel = mMapPosition.zoomLevel; - float scale = mMapPosition.scale; + byte zoomLevel = mapPosition.zoomLevel; + float scale = mapPosition.scale; double x = MercatorProjection.longitudeToPixelX( - mMapPosition.geoPoint.getLongitude(), zoomLevel); + mapPosition.geoPoint.getLongitude(), zoomLevel); double y = MercatorProjection.latitudeToPixelY( - mMapPosition.geoPoint.getLatitude(), zoomLevel); + mapPosition.geoPoint.getLatitude(), zoomLevel); - long tileX = MercatorProjection.pixelXToTileX(x, zoomLevel); - long tileY = MercatorProjection.pixelYToTileY(y, zoomLevel); + long tileX = MercatorProjection.pixelXToTileX(x, zoomLevel) * Tile.TILE_SIZE; + long tileY = MercatorProjection.pixelYToTileY(y, zoomLevel) * Tile.TILE_SIZE; - if (mInitial || mPrevMapPosition.zoomLevel != zoomLevel) + int zdir = 0; + if (mInitial || mLastZoom != zoomLevel) { changedZoom = true; - else if (tileX != mTileX || tileY != mTileY) + mLastScale = scale; + } + else if (tileX != mTileX || tileY != mTileY) { + if (mLastScale - scale > 0 && scale > 1.2) + zdir = 1; + mLastScale = scale; changedPos = true; - + } + else if (mLastScale - scale > 0.2 || mLastScale - scale < -0.2) { + if (mLastScale - scale > 0 && scale > 1.2) + zdir = 1; + mLastScale = scale; + changedPos = true; + } mInitial = false; mTileX = tileX; mTileY = tileY; - mPrevMapPosition = mMapPosition; + mLastZoom = zoomLevel; - if (changedZoom) - updateVisibleList(x, y); + if (zdir > 0) + Log.d(TAG, "prefetch parent"); - // pass new position to glThread - synchronized (this) { - // do not change position while drawing - mCurX = x; - mCurY = y; - mCurZ = zoomLevel; - mCurScale = scale; + if (changedZoom) { + // need to update visible list first when zoom level changes + // as scaling is relative to the tiles of current zoom-level + updateVisibleList(x, y, 0); + } else { + // pass new position to glThread + synchronized (this) { + // do not change position while drawing + mCurX = x; + mCurY = y; + mCurZ = zoomLevel; + mCurScale = scale; + } } if (!timing) mMapView.requestRender(); if (changedPos) - updateVisibleList(x, y); + updateVisibleList(x, y, zdir); } + private static final int MAX_TILES_IN_QUEUE = 40; + + /** + * called by MapWorkers when tile is loaded + */ @Override - public boolean passTile(MapGeneratorJob mapGeneratorJob) { - if (!timing && mapGeneratorJob.tile.isVisible) - mMapView.requestRender(); - return true; - } + public synchronized boolean passTile(MapGeneratorJob mapGeneratorJob) { + GLMapTile tile = (GLMapTile) mapGeneratorJob.tile; - private PolygonLayer[] mFillPolys; - - private void fillPolygons(int count) { - boolean blend = false; - - // draw to framebuffer - glColorMask(true, true, true, true); - - // do not modify stencil buffer - glStencilMask(0); - - for (int c = 0; c < count; c++) { - PolygonLayer l = mFillPolys[c]; - - float alpha = 1.0f; - - if (l.area.fade >= mDrawZ || l.area.color[3] != 1.0) { - - alpha = (mDrawScale > 1.3f ? mDrawScale : 1.3f) - alpha; - if (alpha > 1.0f) - alpha = 1.0f; - - alpha *= l.area.color[3]; - - if (!blend) { - glEnable(GL_BLEND); - blend = true; - } - glUniform4f(hPolygonColor, - l.area.color[0], l.area.color[1], l.area.color[2], alpha); - - } else if (l.area.blend == mDrawZ) { - alpha = mDrawScale - 1.0f; - if (alpha > 1.0f) - alpha = 1.0f; - else if (alpha < 0) - alpha = 0; - - glUniform4f(hPolygonColor, - l.area.color[0] * (1 - alpha) + l.area.blendColor[0] * alpha, - l.area.color[1] * (1 - alpha) + l.area.blendColor[1] * alpha, - l.area.color[2] * (1 - alpha) + l.area.blendColor[2] * alpha, 1); - } else { - if (blend) { - glDisable(GL_BLEND); - blend = false; - } - if (l.area.blend <= mDrawZ && l.area.blend > 0) - glUniform4fv(hPolygonColor, 1, l.area.blendColor, 0); - else - glUniform4fv(hPolygonColor, 1, l.area.color, 0); - } - - // set stencil buffer mask used to draw this layer - glStencilFunc(GL_EQUAL, 0xff, 1 << c); - - // draw tile fill coordinates - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - } - - if (blend) - glDisable(GL_BLEND); - } - - private boolean drawPolygons(GLMapTile tile, int diff) { - int cnt = 0; - - if (tile.polygonLayers == null) + if (tile.isCanceled) { + Log.d(TAG, "passTile: canceld " + tile); + clearTile(tile); return true; - - // Nexus doesnt like using changing scissor in combination with stencil... - // glScissor(tile.sx, tile.sy, tile.sw, tile.sh); - - if (mLastBoundVBO != tile.polygonVBO.id) { - mLastBoundVBO = tile.polygonVBO.id; - glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id); - - glVertexAttribPointer(hPolygonVertexPosition, 2, - GLES20.GL_SHORT, false, 0, - POLYGON_VERTICES_DATA_POS_OFFSET); } - setMatrix(tile, diff); - glUniformMatrix4fv(hPolygonMatrix, 1, false, mMVPMatrix, 0); + if (!timing && tile.isVisible) + mMapView.requestRender(); - // boolean firstPass = true; + int size = mTilesLoaded.size(); + if (size > MAX_TILES_IN_QUEUE) { + // remove uploaded tiles + for (int i = 0; i < size;) { + GLMapTile t = mTilesLoaded.get(i); - for (PolygonLayer l = tile.polygonLayers; l != null; l = l.next) { - // fade out polygon layers (set in RederTheme) - if (l.area.fade > 0 && l.area.fade > mDrawZ) - continue; - - if (cnt == 0) { - // disable drawing to framebuffer - glColorMask(false, false, false, false); - - // never pass the test, i.e. always apply first stencil op (sfail) - glStencilFunc(GL_NEVER, 0, 0xff); - - // if (firstPass) - // firstPass = false; - // else { - // GLES20.glFlush(); - - // clear stencilbuffer - glStencilMask(0xFF); - // glClear(GL_STENCIL_BUFFER_BIT); - - // clear stencilbuffer (tile region) - glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - // } - - // stencil op for stencil method polygon drawing - glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT); + if (!t.newData) { + mTilesLoaded.remove(i); + size--; + continue; + } + i++; } - mFillPolys[cnt] = l; + if (size > MAX_TILES_IN_QUEUE) { + // FIXME pass tile via queue back to mainloop instead... + synchronized (mTiles) { + Collections.sort(mTilesLoaded, mTileDistanceSort); + } + // clear loaded but not used tiles + while (size-- > MAX_TILES_IN_QUEUE) { + GLMapTile t = mTilesLoaded.get(size); + // FIXME race condition: tile could be uploaded as proxy + // therefore sync with upload tile data + synchronized (t) { - // set stencil mask to draw to - glStencilMask(1 << cnt++); - - glDrawArrays(GL_TRIANGLE_FAN, l.offset, l.verticesCnt); - - // draw up to 8 layers into stencil buffer - if (cnt == STENCIL_BITS) { - fillPolygons(cnt); - cnt = 0; + // dont remove tile if currently used or is direct parent + // or child of currently active tile + if (t.isActive || childIsActive(t)) + // (t.parent != null && t.parent.isActive)) + { + // Log.d(TAG, "keep unused tile data: " + t + " " + t.isActive); + continue; + } + mTilesLoaded.remove(size); + // Log.d(TAG, "remove unused tile data: " + t); + clearTile(t); + // TODO could also remove from mTileHash/List ? + } + } } } + mTilesLoaded.add(0, tile); - if (cnt > 0) { - fillPolygons(cnt); - // eeek, nexus! - cant do old-school polygons - // glFinish(); - } - GLES20.glFlush(); return true; } - private static int mLastBoundVBO; - - private boolean drawLines(GLMapTile tile, int diff) { - float z = 1; - - if (tile.lineLayers == null) - return false; - - glScissor(tile.sx, tile.sy, tile.sw, tile.sh); - - if (mLastBoundVBO != tile.lineVBO.id) { - mLastBoundVBO = tile.lineVBO.id; - glBindBuffer(GL_ARRAY_BUFFER, tile.lineVBO.id); - - glVertexAttribPointer(hLineVertexPosition, 2, GLES20.GL_SHORT, - false, 8, LINE_VERTICES_DATA_POS_OFFSET); - - glVertexAttribPointer(hLineTexturePosition, 2, GLES20.GL_SHORT, - false, 8, LINE_VERTICES_DATA_TEX_OFFSET >> 1); - - } - if (diff != 0) - z = (diff > 0) ? (1 << diff) : 1.0f / (1 << -diff); - - setMatrix(tile, diff); - glUniformMatrix4fv(hLineMatrix, 1, false, mMVPMatrix, 0); - - boolean drawOutlines = false; - boolean drawFixed = false; - - // stroke scale factor - float wdiv = 1.0f / FloatMath.sqrt(mDrawScale / z); - // linear scale for fixed lines - float fdiv = 0.9f / (mDrawScale / z); - - boolean first = true; - - for (LineLayer l = tile.lineLayers; l != null; l = l.next) { - Line line = l.line; - if (line.fade > 0 && line.fade > mDrawZ) - continue; - - // set line width and mode - if (first || (l.isOutline != drawOutlines) - || (line.fixed != drawFixed)) { - first = false; - drawOutlines = l.isOutline; - drawFixed = line.fixed; - - if (drawOutlines) { - glUniform2f(hLineMode, 0, wdiv); - } else if (!drawFixed) { - glUniform2f(hLineMode, 0, wdiv * 0.98f); - } - } - - if (drawFixed) { - if (l.width < 1.0) - glUniform2f(hLineMode, 0.4f, fdiv); - else - glUniform2f(hLineMode, 0, fdiv); - } - - if (line.fade >= mDrawZ) { - float alpha = 1.0f; - - alpha = (mDrawScale > 1.3f ? mDrawScale : 1.3f) - alpha; - if (alpha > 1.0f) - alpha = 1.0f; - glUniform4f(hLineColor, - line.color[0], line.color[1], line.color[2], alpha); - } else { - glUniform4fv(hLineColor, 1, line.color, 0); - } - - if (drawOutlines) { - for (LineLayer o = l.outlines; o != null; o = o.outlines) { - - if (mSimpleLines) - glUniform1f(hLineWidth, o.width); - - glDrawArrays(GL_TRIANGLE_STRIP, o.offset, o.verticesCnt); - } - } - else { - if (mSimpleLines) - glUniform1f(hLineWidth, l.width); - - glDrawArrays(GL_TRIANGLE_STRIP, l.offset, l.verticesCnt); - } - } - GLES20.glFlush(); - return true; - } - - private static void setMatrix(GLMapTile tile, int diff) { + private static void setMatrix(GLMapTile tile, float div) { float x, y, scale; - float z = 1; - if (diff == 0) { - scale = (float) (mDrawScale * 2.0 / mHeight); - x = (float) (mDrawX - tile.x); - y = (float) (tile.y - mDrawY); - } else { - z = (diff > 0) ? (1 << diff) : 1.0f / (1 << -diff); - scale = (float) (mDrawScale * 2.0 / mHeight / z); - x = (float) (mDrawX * z - tile.x); - y = (float) (tile.y - mDrawY * z); - } + scale = (float) (2.0 * mDrawScale / (mHeight * div)); + x = (float) (tile.x - mDrawX * div); + y = (float) (tile.y - mDrawY * div); - mMVPMatrix[12] = -x * (scale * mAspect); - mMVPMatrix[13] = -y * (scale); - mMVPMatrix[0] = (scale * mAspect); - mMVPMatrix[5] = (scale); + mMVPMatrix[12] = x * scale * mAspect; + mMVPMatrix[13] = -y * scale; + mMVPMatrix[0] = scale * mAspect / COORD_MULTIPLIER; + mMVPMatrix[5] = scale / COORD_MULTIPLIER; } private static boolean setTileScissor(GLMapTile tile, float div) { - double dx, dy, scale; if (div == 0) { @@ -857,40 +732,14 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { int sx = (int) (dx * scale); int sy = (int) (dy * scale); - int sw = (int) ((dx + size) * scale) - sx; - int sh = (int) ((dy + size) * scale) - sy; - - sx = (mWidth >> 1) + sx; - sy = (mHeight >> 1) - (sy + sh); - - // shrink width/height to screen intersection - if (sx < 0) { - sw += sx; - sx = 0; - } - - if (sy < 0) { - sh += sy; - sy = 0; - } - - if (sw + sx > mWidth) - sw = mWidth - sx; - - if (sh + (sy - sh) > mHeight) - sh = mHeight - sy; - - if (sw <= 0 || sh <= 0) { + if (sy > mHeight / 2 || sx > mWidth / 2 + || sx + size * scale < -mWidth / 2 + || sy + size * scale < -mHeight / 2) { tile.isVisible = false; return false; - } tile.isVisible = true; - tile.sx = sx; - tile.sy = sy; - tile.sw = sw; - tile.sh = sh; return true; } @@ -900,101 +749,112 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { private boolean uploadTileData(GLMapTile tile) { ShortBuffer sbuf = null; - // double start = SystemClock.uptimeMillis(); - // use multiple buffers to avoid overwriting buffer while current - // data is uploaded (or rather the blocking which is required to avoid this) + // data is uploaded (or rather the blocking which is probably done to avoid this) if (uploadCnt >= rotateBuffers) { - // mMapView.requestRender(); - // return false; uploadCnt = 0; - glFinish(); + GLES20.glFlush(); } // Upload line data to vertex buffer object - if (tile.lineVBO == null) { + + // FIXME do this in main thread: + if (tile.vbo == null) { synchronized (mVBOs) { - if (mVBOs.size() < 2) { + if (mVBOs.size() < 1) { Log.d(TAG, "uploadTileData, no VBOs left"); return false; } - tile.lineVBO = mVBOs.remove(mVBOs.size() - 1); - tile.polygonVBO = mVBOs.remove(mVBOs.size() - 1); + tile.vbo = mVBOs.remove(mVBOs.size() - 1); } } - int size = 0; - sbuf = LineLayers.compileLayerData(tile.lineLayers, - shortBuffer[uploadCnt * 2]); - size = sbuf.remaining(); - shortBuffer[uploadCnt * 2] = sbuf; + synchronized (tile) { + if (!tile.newData) + return false; - if (size > 0) { - mBufferMemoryUsage -= tile.lineVBO.size; + tile.isReady = true; + tile.newData = false; + } - glBindBuffer(GL_ARRAY_BUFFER, tile.lineVBO.id); - // Nexus doesnt like this either - // glBufferData(GL_ARRAY_BUFFER, 0, null, GL_DYNAMIC_DRAW); + int lineSize = LineLayers.sizeOf(tile.lineLayers); + int polySize = PolygonLayers.sizeOf(tile.polygonLayers); + int newSize = lineSize + polySize; - tile.lineVBO.size = size * SHORT_BYTES; - glBufferData(GL_ARRAY_BUFFER, tile.lineVBO.size, - sbuf, GL_DYNAMIC_DRAW); - mBufferMemoryUsage += tile.lineVBO.size; - - } else { + if (newSize == 0) { + LineLayers.clear(tile.lineLayers); + PolygonLayers.clear(tile.polygonLayers); tile.lineLayers = null; - } - - sbuf = PolygonLayers.compileLayerData(tile.polygonLayers, - shortBuffer[uploadCnt * 2 + 1]); - size = sbuf.remaining(); - shortBuffer[uploadCnt * 2 + 1] = sbuf; - - // Upload polygon data to vertex buffer object - if (size > 0) { - mBufferMemoryUsage -= tile.polygonVBO.size; - - glBindBuffer(GL_ARRAY_BUFFER, tile.polygonVBO.id); - - tile.polygonVBO.size = size * SHORT_BYTES; - glBufferData(GL_ARRAY_BUFFER, tile.polygonVBO.size, - sbuf, GL_DYNAMIC_DRAW); - - mBufferMemoryUsage += tile.polygonVBO.size; - - } else { tile.polygonLayers = null; + return false; } - tile.newData = false; - tile.isReady = true; - tile.isLoading = false; + // Log.d(TAG, "uploadTileData, " + tile); - // double compile = SystemClock.uptimeMillis(); - // glFinish(); - // double now = SystemClock.uptimeMillis(); - // Log.d(TAG, tile + " - upload took: " + (now - start) + " " - // + (mBufferMemoryUsage / 1024) + "kb"); + glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id); + + sbuf = shortBuffer[uploadCnt]; + + // add fill coordinates + newSize += 8; + + // FIXME probably not a good idea to do this in gl thread... + if (sbuf == null || sbuf.capacity() < newSize) { + ByteBuffer bbuf = ByteBuffer.allocateDirect(newSize * SHORT_BYTES).order( + ByteOrder.nativeOrder()); + sbuf = bbuf.asShortBuffer(); + shortBuffer[uploadCnt] = sbuf; + sbuf.put(mFillCoords, 0, 8); + } + + sbuf.clear(); + sbuf.position(8); + + PolygonLayers.compileLayerData(tile.polygonLayers, sbuf); + LineLayers.compileLayerData(tile.lineLayers, sbuf); + sbuf.flip(); + + newSize *= SHORT_BYTES; + + if (tile.vbo.size > newSize && tile.vbo.size < newSize * 4) { + GLES20.glBufferSubData(GL_ARRAY_BUFFER, 0, newSize, sbuf); + } else { + mBufferMemoryUsage -= tile.vbo.size; + tile.vbo.size = newSize; + glBufferData(GL_ARRAY_BUFFER, tile.vbo.size, sbuf, GL_DYNAMIC_DRAW); + mBufferMemoryUsage += tile.vbo.size; + } + tile.lineOffset = (8 + polySize) * SHORT_BYTES; + + // tile.isLoading = false; uploadCnt++; return true; } + private float[] mClearColor; + private static long mRedrawCnt = 0; + @Override public void onDrawFrame(GL10 glUnused) { long start = 0, poly_time = 0, clear_time = 0; - if (mMapPosition == null) + if (mClearColor != null) { + glClearColor(mClearColor[0], mClearColor[1], mClearColor[2], + mClearColor[3]); + mClearColor = null; + } + + glClear(GLES20.GL_COLOR_BUFFER_BIT);// | GLES20.GL_DEPTH_BUFFER_BIT); + + if (mInitial) return; if (timing) start = SystemClock.uptimeMillis(); - // glStencilMask(0xFF); - // glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - glClear(GL_COLOR_BUFFER_BIT); - + // get position and current tiles to draw synchronized (this) { mDrawX = mCurX; mDrawY = mCurY; @@ -1002,54 +862,52 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mDrawScale = mCurScale; if (mUpdateTiles) { - TilesData tmp = curTiles; - curTiles = nextTiles; - nextTiles = tmp; + TilesData tmp = drawTiles; + drawTiles = curTiles; + curTiles = tmp; mUpdateTiles = false; } } - int tileCnt = curTiles.cnt; - GLMapTile[] tiles = curTiles.tiles; + int tileCnt = drawTiles.cnt; + GLMapTile[] tiles = drawTiles.tiles; + // not really tested, try to clear some vbo when exceding limit if (mBufferMemoryUsage > LIMIT_BUFFERS) { Log.d(TAG, "buffer object usage: " + mBufferMemoryUsage / MB + "MB"); + synchronized (mVBOs) { for (VertexBufferObject vbo : mVBOs) { if (vbo.size == 0) continue; + mBufferMemoryUsage -= vbo.size; glBindBuffer(GL_ARRAY_BUFFER, vbo.id); - glBufferData(GL_ARRAY_BUFFER, 0, null, - GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, 0, null, GL_DYNAMIC_DRAW); vbo.size = 0; - } glBindBuffer(GL_ARRAY_BUFFER, 0); } Log.d(TAG, " > " + mBufferMemoryUsage / MB + "MB"); - if (CACHE_TILES > 50) + if (CACHE_TILES > 100) CACHE_TILES -= 50; } else if (CACHE_TILES < CACHE_TILES_MAX) { CACHE_TILES += 50; } uploadCnt = 0; - mLastBoundVBO = -1; - int updateTextures = 0; - // check visible tiles, set tile clip scissors, upload new vertex data - + // check visible tiles, upload new vertex data for (int i = 0; i < tileCnt; i++) { GLMapTile tile = tiles[i]; if (!setTileScissor(tile, 1)) continue; - if (tile.texture == null && tile.labels != null && - mTextRenderer.drawToTexture(tile)) + if (updateTextures < 4 && tile.texture == null + && TextRenderer.drawToTexture(tile)) updateTextures++; if (tile.newData) { @@ -1058,9 +916,8 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } if (!tile.isReady) { - if (tile.parent != null) { - if (tile.parent.newData) - uploadTileData(tile.parent); + if (tile.parent != null && tile.parent.newData) { + uploadTileData(tile.parent); } else { if (tile.child[0] != null && tile.child[0].newData) uploadTileData(tile.child[0]); @@ -1074,9 +931,11 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } } - if (updateTextures > 0) - mTextRenderer.compileTextures(); - + if (updateTextures > 0) { + TextRenderer.compileTextures(); + if (updateTextures == 4) + mMapView.requestRender(); + } // if (GlUtils.checkGlOutOfMemory("upload: " + mBufferMemoryUsage) // && LIMIT_BUFFERS > MB) // LIMIT_BUFFERS -= MB; @@ -1084,83 +943,159 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { if (timing) clear_time = (SystemClock.uptimeMillis() - start); - // glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, mWidth, mHeight); - glUseProgram(polygonProgram); - glEnableVertexAttribArray(hPolygonVertexPosition); - - glDisable(GL_BLEND); - // Draw Polygons - glEnable(GL_STENCIL_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (int i = 0; i < tileCnt; i++) { - if (tiles[i].isVisible) { - GLMapTile tile = tiles[i]; - - if (tile.isReady) - drawPolygons(tile, 0); - else - drawProxyPolygons(tile); + if (tiles[i].isVisible && !tiles[i].isReady) { + drawProxyTile(tiles[i]); } } - glDisable(GL_STENCIL_TEST); - - // required on GalaxyII, Android 2.3.3 (cant just VAA enable once...) - glDisableVertexAttribArray(hPolygonVertexPosition); - - if (timing) { - glFinish(); - poly_time = (SystemClock.uptimeMillis() - start); + for (int i = 0; i < tileCnt; i++) { + if (tiles[i].isVisible && tiles[i].isReady) { + drawTile(tiles[i], 1); + } } + mDrawCount = 0; - // glEnable(GL_SCISSOR_TEST); - - // Draw lines glEnable(GL_BLEND); - glUseProgram(lineProgram); - - glEnableVertexAttribArray(hLineVertexPosition); - glEnableVertexAttribArray(hLineTexturePosition); - - for (int i = 0; i < tileCnt; i++) { - if (tiles[i].isVisible) { - GLMapTile tile = tiles[i]; - - if (tile.isReady) - drawLines(tile, 0); - else - drawProxyLines(tile); - } - } - - glDisableVertexAttribArray(hLineVertexPosition); - glDisableVertexAttribArray(hLineTexturePosition); - - glScissor(0, 0, mWidth, mHeight); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - mTextRenderer.beginDraw(mDrawScale); + int zoomLevelDiff = Math + .max((int) mDrawZ - MapGenerator.STROKE_MAX_ZOOM_LEVEL, 0); + float scale = (float) Math.pow(1.4, zoomLevelDiff); + if (scale < 1) + scale = 1; + + if (mDrawZ >= MapGenerator.STROKE_MAX_ZOOM_LEVEL) + TextRenderer.beginDraw(FloatMath.sqrt(mDrawScale) / scale); + else + TextRenderer.beginDraw(mDrawScale); + for (int i = 0; i < tileCnt; i++) { if (!tiles[i].isVisible || tiles[i].texture == null) continue; - setMatrix(tiles[i], 0); - - mTextRenderer.drawTile(tiles[i], mMVPMatrix); + setMatrix(tiles[i], 1); + TextRenderer.drawTile(tiles[i], mMVPMatrix); } - mTextRenderer.endDraw(); + TextRenderer.endDraw(); if (timing) { glFinish(); - Log.d(TAG, "draw took " + (SystemClock.uptimeMillis() - start) + " " - + clear_time + " " + poly_time); + Log.d(TAG, "draw took " + (SystemClock.uptimeMillis() - start) + + " " + clear_time + " " + poly_time); } - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + mRedrawCnt++; } - private static TextRenderer mTextRenderer; + private static void drawTile(GLMapTile tile, float div) { + + if (tile.lastDraw == mRedrawCnt) + return; + + tile.lastDraw = mRedrawCnt; + + // if (div != 1) + // Log.d(TAG, "draw proxy " + div + " " + tile); + + setMatrix(tile, div); + + glBindBuffer(GL_ARRAY_BUFFER, tile.vbo.id); + LineLayer ll = tile.lineLayers; + PolygonLayer pl = tile.polygonLayers; + + boolean clipped = false; + + for (; pl != null || ll != null;) { + int lnext = Integer.MAX_VALUE; + int pnext = Integer.MAX_VALUE; + + if (ll != null) + lnext = ll.layer; + + if (pl != null) + pnext = pl.layer; + + if (pl != null && pnext < lnext) { + glDisable(GL_BLEND); + + pl = PolygonLayers.drawPolygons(pl, lnext, mMVPMatrix, mDrawZ, + mDrawScale, mDrawCount, !clipped); + + if (!clipped) { + clipped = true; + glDisable(GLES20.GL_DEPTH_TEST); + } + } else { + // nastay + if (!clipped) + PolygonLayers.drawPolygons(null, 0, mMVPMatrix, mDrawZ, + mDrawScale, mDrawCount, true); + else { + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + glEnable(GLES20.GL_POLYGON_OFFSET_FILL); + GLES20.glPolygonOffset(0, mDrawCount); + } + glEnable(GL_BLEND); + + ll = LineLayers.drawLines(tile, ll, pnext, mMVPMatrix, div, + mDrawZ, mDrawScale); + + glDisable(GLES20.GL_DEPTH_TEST); + glDisable(GLES20.GL_POLYGON_OFFSET_FILL); + } + } + + mDrawCount++; + } + + private static boolean drawProxyChild(GLMapTile tile) { + int drawn = 0; + for (int i = 0; i < 4; i++) { + GLMapTile c = tile.child[i]; + if (c != null) { + if (!setTileScissor(c, 2)) { + drawn++; + continue; + } + + if (c.isReady && c.isVisible) { + drawTile(c, 2); + drawn++; + } + } + } + return drawn == 4; + } + + private static void drawProxyTile(GLMapTile tile) { + if (mDrawScale < 1.5f) { + if (!drawProxyChild(tile)) { + if (tile.parent != null) { + if (tile.parent.isReady) { + drawTile(tile.parent, 0.5f); + } else { + GLMapTile p = tile.parent.parent; + if (p != null && p.isReady) + drawTile(p, 0.25f); + } + } + } + } else { + if (tile.parent != null && tile.parent.isReady) { + drawTile(tile.parent, 0.5f); + } else if (!drawProxyChild(tile)) { + if (tile.parent != null) { + GLMapTile p = tile.parent.parent; + if (p != null && p.isReady) + drawTile(p, 0.25f); + } + } + } + } @Override public void onSurfaceChanged(GL10 glUnused, int width, int height) { @@ -1169,15 +1104,17 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { mVBOs.clear(); mTiles.clear(); mTileList.clear(); + ShortPool.finish(); + // LineLayers.finish(); - curTiles = newTiles = nextTiles = null; + drawTiles = newTiles = curTiles = null; mBufferMemoryUsage = 0; + mInitial = true; if (width <= 0 || height <= 0) return; - STENCIL_BITS = GlConfigChooser.stencilSize; - mFillPolys = new PolygonLayer[STENCIL_BITS]; + // STENCIL_BITS = GlConfigChooser.stencilSize; mWidth = width; mHeight = height; @@ -1186,176 +1123,84 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { glViewport(0, 0, width, height); GlUtils.checkGlError("glViewport"); + glScissor(0, 0, mWidth, mHeight); + int numTiles = (mWidth / (Tile.TILE_SIZE / 2) + 2) * (mHeight / (Tile.TILE_SIZE / 2) + 2); - curTiles = new TilesData(numTiles); + drawTiles = new TilesData(numTiles); newTiles = new TilesData(numTiles); - nextTiles = new TilesData(numTiles); + curTiles = new TilesData(numTiles); + + Log.d(TAG, "using: " + numTiles + " + cache: " + CACHE_TILES); // Set up vertex buffer objects - int numVBO = (CACHE_TILES + numTiles) * 2; + int numVBO = (CACHE_TILES + numTiles); int[] mVboIds = new int[numVBO]; glGenBuffers(numVBO, mVboIds, 0); GlUtils.checkGlError("glGenBuffers"); - for (int i = 0; i < numVBO; i++) + for (int i = 1; i < numVBO; i++) mVBOs.add(new VertexBufferObject(mVboIds[i])); // Set up textures - mTextRenderer = new TextRenderer(numTiles * 2); - - mDebugSettings = mMapView.getDebugSettings(); - mJobParameter = mMapView.getJobParameters(); - - mInitial = true; - mMapView.redrawTiles(); - } - - private boolean mSimpleLines = false; - - @Override - public void onSurfaceCreated(GL10 gl, EGLConfig config) { - // Set up the program for rendering lines - - lineProgram = GlUtils.createProgram(Shaders.gLineVertexShader, - Shaders.gLineFragmentShader); - if (lineProgram == 0) { - mSimpleLines = true; - Log.e(TAG, "trying simple line program."); - lineProgram = GlUtils.createProgram(Shaders.gLineVertexShader, - Shaders.gLineFragmentShaderSimple); - if (lineProgram == 0) { - Log.e(TAG, "Could not create line program."); - return; - } - } - - // String ext = glGetString(GL_EXTENSIONS); - - // if (ext.indexOf("GL_OES_vertex_half_float") >= 0) { - // useHalfFloat = true; - shortBuffer = new ShortBuffer[rotateBuffers * 2]; - // } - // else { - // floatBuffer = new FloatBuffer[rotateBuffers * 2]; - // } - // Log.d(TAG, "Extensions: " + ext); - - hLineMatrix = glGetUniformLocation(lineProgram, "u_center"); - hLineMode = glGetUniformLocation(lineProgram, "u_mode"); - hLineColor = glGetUniformLocation(lineProgram, "u_color"); - hLineVertexPosition = GLES20.glGetAttribLocation(lineProgram, "a_position"); - hLineTexturePosition = glGetAttribLocation(lineProgram, "a_st"); - if (mSimpleLines) - hLineWidth = glGetUniformLocation(lineProgram, "u_width"); - - // Set up the program for rendering polygons - polygonProgram = GlUtils.createProgram(Shaders.gPolygonVertexShader, - Shaders.gPolygonFragmentShader); - if (polygonProgram == 0) { - Log.e(TAG, "Could not create polygon program."); - return; - } - hPolygonMatrix = glGetUniformLocation(polygonProgram, "u_center"); - hPolygonVertexPosition = glGetAttribLocation(polygonProgram, "a_position"); - hPolygonColor = glGetUniformLocation(polygonProgram, "u_color"); - - // glUseProgram(polygonProgram); - // glEnableVertexAttribArray(hPolygonVertexPosition); - // - // glUseProgram(lineProgram); - // glEnableVertexAttribArray(hLineVertexPosition); - // glEnableVertexAttribArray(hLineTexturePosition); + TextRenderer.init(numTiles * 2); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - glDisable(GL_DEPTH_TEST); + glDisable(GLES20.GL_DEPTH_TEST); glDepthMask(false); glDisable(GL_DITHER); glClearColor(0.98f, 0.98f, 0.97f, 1.0f); glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); + + // glClear(GL_STENCIL_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); + // glEnable(GLES20.GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + mDebugSettings = mMapView.getDebugSettings(); + mJobParameter = mMapView.getJobParameters(); + + mMapView.redrawTiles(); + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + + String ext = GLES20.glGetString(GLES20.GL_EXTENSIONS); + Log.d(TAG, "Extensions: " + ext); + + // if (ext.indexOf("GL_OES_vertex_half_float") >= 0) { + // useHalfFloat = true; + shortBuffer = new ShortBuffer[rotateBuffers]; + + // add half pixel to tile clip/fill coordinates + // to avoid rounding issues + short min = -4; + short max = (short) ((Tile.TILE_SIZE << 3) + 4); + mFillCoords = new short[8]; + mFillCoords[0] = min; + mFillCoords[1] = max; + mFillCoords[2] = max; + mFillCoords[3] = max; + mFillCoords[4] = min; + mFillCoords[5] = min; + mFillCoords[6] = max; + mFillCoords[7] = min; + + LineLayers.init(); + PolygonLayers.init(); + GlUtils.checkGlError("onSurfaceCreated"); } - private void drawProxyLines(GLMapTile tile) { - if (tile.parent != null && tile.parent.isReady) { - tile.parent.sx = tile.sx; - tile.parent.sy = tile.sy; - tile.parent.sw = tile.sw; - tile.parent.sh = tile.sh; - drawLines(tile.parent, -1); - } else { - int drawn = 0; - // scissor coordinates already set for polygons - for (int i = 0; i < 4; i++) { - GLMapTile c = tile.child[i]; - if (c != null && c.isReady && c.isVisible) { - drawLines(c, 1); - drawn++; - } - - } - if (drawn < 4 && tile.parent != null) { - GLMapTile p = tile.parent.parent; - if (p != null && p.isReady) { - p.sx = tile.sx; - p.sy = tile.sy; - p.sw = tile.sw; - p.sh = tile.sh; - drawLines(p, -2); - } - } - } - } - - private void drawProxyPolygons(GLMapTile tile) { - if (tile.parent != null && tile.parent.isReady) { - tile.parent.sx = tile.sx; - tile.parent.sy = tile.sy; - tile.parent.sw = tile.sw; - tile.parent.sh = tile.sh; - drawPolygons(tile.parent, -1); - } else { - int drawn = 0; - - for (int i = 0; i < 4; i++) { - GLMapTile c = tile.child[i]; - - if (c != null && c.isReady && setTileScissor(c, 2)) { - drawPolygons(c, 1); - drawn++; - } - } - - if (drawn < 4 && tile.parent != null) { - GLMapTile p = tile.parent.parent; - if (p != null && p.isReady) { - p.sx = tile.sx; - p.sy = tile.sy; - p.sw = tile.sw; - p.sh = tile.sh; - drawPolygons(p, -2); - } - } - } - } - @Override public boolean processedTile() { return true; } - public IMapGenerator getMapGenerator() { - return new MapGenerator(); - } - @Override public IMapGenerator createMapGenerator() { return new MapGenerator(); diff --git a/src/org/mapsforge/android/glrenderer/PolygonLayer.java b/src/org/mapsforge/android/glrenderer/PolygonLayer.java index 77056d53..abe5636c 100644 --- a/src/org/mapsforge/android/glrenderer/PolygonLayer.java +++ b/src/org/mapsforge/android/glrenderer/PolygonLayer.java @@ -15,11 +15,14 @@ package org.mapsforge.android.glrenderer; import org.mapsforge.android.rendertheme.renderinstruction.Area; +import org.mapsforge.core.Tile; class PolygonLayer { + private static final float S = MapRenderer.COORD_MULTIPLIER; + PolygonLayer next; Area area; - private static final float SCALE_FACTOR = 16.0f; + // private static final float MapRenderer.COORD_MULTIPLIER = 8.0f; private boolean first = true; private float originX; private float originY; @@ -53,8 +56,8 @@ class PolygonLayer { if (first) { first = false; - originX = points[pos]; - originY = points[pos + 1]; + originX = Tile.TILE_SIZE >> 1; // points[pos]; + originY = Tile.TILE_SIZE >> 1; // points[pos + 1]; } short[] curVertices = curItem.vertices; @@ -65,24 +68,25 @@ class PolygonLayer { outPos = 0; } - curVertices[outPos++] = (short) (originX * SCALE_FACTOR); - curVertices[outPos++] = (short) (originY * SCALE_FACTOR); + curVertices[outPos++] = (short) (originX * S); + curVertices[outPos++] = (short) (originY * S); + int MAX = ShortItem.SIZE; int remaining = length; int inPos = pos; while (remaining > 0) { - if (outPos == ShortItem.SIZE) { + if (outPos == MAX) { curVertices = getNextItem(); outPos = 0; } int len = remaining; - if (len > (ShortItem.SIZE) - outPos) - len = (ShortItem.SIZE) - outPos; + if (len > MAX - outPos) + len = MAX - outPos; for (int i = 0; i < len; i++) - curVertices[outPos++] = (short) (points[inPos++] * SCALE_FACTOR); + curVertices[outPos++] = (short) (points[inPos++] * S); // System.arraycopy(points, inPos, curVertices, outPos, len); @@ -92,13 +96,13 @@ class PolygonLayer { remaining -= len; } - if (outPos == PoolItem.SIZE) { + if (outPos == MAX) { curVertices = getNextItem(); outPos = 0; } - curVertices[outPos++] = (short) (points[pos + 0] * SCALE_FACTOR); - curVertices[outPos++] = (short) (points[pos + 1] * SCALE_FACTOR); + curVertices[outPos++] = (short) (points[pos + 0] * S); + curVertices[outPos++] = (short) (points[pos + 1] * S); curItem.used = outPos; } diff --git a/src/org/mapsforge/android/glrenderer/PolygonLayers.java b/src/org/mapsforge/android/glrenderer/PolygonLayers.java index 8bf625ee..f20dcd06 100644 --- a/src/org/mapsforge/android/glrenderer/PolygonLayers.java +++ b/src/org/mapsforge/android/glrenderer/PolygonLayers.java @@ -14,172 +14,269 @@ */ package org.mapsforge.android.glrenderer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; +import static android.opengl.GLES20.GL_BLEND; +import static android.opengl.GLES20.GL_EQUAL; +import static android.opengl.GLES20.GL_INVERT; +import static android.opengl.GLES20.GL_KEEP; +import static android.opengl.GLES20.GL_NEVER; +import static android.opengl.GLES20.GL_STENCIL_TEST; +import static android.opengl.GLES20.GL_TRIANGLE_FAN; +import static android.opengl.GLES20.GL_TRIANGLE_STRIP; +import static android.opengl.GLES20.GL_ZERO; +import static android.opengl.GLES20.glColorMask; +import static android.opengl.GLES20.glDisable; +import static android.opengl.GLES20.glDrawArrays; +import static android.opengl.GLES20.glEnable; +import static android.opengl.GLES20.glEnableVertexAttribArray; +import static android.opengl.GLES20.glGetAttribLocation; +import static android.opengl.GLES20.glGetUniformLocation; +import static android.opengl.GLES20.glStencilFunc; +import static android.opengl.GLES20.glStencilMask; +import static android.opengl.GLES20.glStencilOp; +import static android.opengl.GLES20.glUniform4f; +import static android.opengl.GLES20.glUniform4fv; +import static android.opengl.GLES20.glUniformMatrix4fv; +import static android.opengl.GLES20.glUseProgram; +import static android.opengl.GLES20.glVertexAttribPointer; + import java.nio.ShortBuffer; -import org.mapsforge.core.Tile; +import org.mapsforge.android.utils.GlUtils; + +import android.opengl.GLES20; class PolygonLayers { - private static final int NUM_VERTEX_FLOATS = 2; - // static final float[] mFillCoords = { -2, Tile.TILE_SIZE + 1, - // Tile.TILE_SIZE + 1, Tile.TILE_SIZE + 1, -2, - // -2, Tile.TILE_SIZE + 1, -2 }; + private static final int NUM_VERTEX_SHORTS = 2; + private static final int POLYGON_VERTICES_DATA_POS_OFFSET = 0; + private static int STENCIL_BITS = 8; - // private static short[] mByteFillCoords = null; + private static PolygonLayer[] mFillPolys; - // static FloatBuffer compileLayerData(PolygonLayer layers, FloatBuffer buf) { - // FloatBuffer fbuf = buf; - // int size = 4; - // - // for (PolygonLayer l = layers; l != null; l = l.next) - // size += l.verticesCnt; - // - // size *= NUM_VERTEX_FLOATS; - // - // if (buf == null || buf.capacity() < size) { - // ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 4).order( - // ByteOrder.nativeOrder()); - // // Log.d("GLMap", "allocate buffer " + size); - // fbuf = bbuf.asFloatBuffer(); - // } else { - // fbuf.clear(); - // } - // - // fbuf.put(mFillCoords, 0, 8); - // int pos = 4; - // - // PoolItem last = null, items = null; - // - // for (PolygonLayer l = layers; l != null; l = l.next) { - // - // for (PoolItem item = l.pool; item != null; item = item.next) { - // fbuf.put(item.vertices, 0, item.used); - // last = item; - // } - // l.offset = pos; - // pos += l.verticesCnt; - // - // if (last != null) { - // last.next = items; - // items = l.pool; - // } - // - // l.pool = null; - // } - // - // VertexPool.add(items); - // - // fbuf.flip(); - // - // return fbuf; - // } - // - // static final short[] tmpItem = new short[PoolItem.SIZE]; + private static int polygonProgram; + private static int hPolygonVertexPosition; + private static int hPolygonMatrix; + private static int hPolygonColor; - // static ShortBuffer compileLayerData(PolygonLayer layers, ShortBuffer buf) { - // ShortBuffer sbuf = buf; - // int size = 4; + static boolean init() { + + // Set up the program for rendering polygons + polygonProgram = GlUtils.createProgram(Shaders.polygonVertexShader, + Shaders.polygonFragmentShader); + if (polygonProgram == 0) { + // Log.e(TAG, "Could not create polygon program."); + return false; + } + hPolygonMatrix = glGetUniformLocation(polygonProgram, "mvp"); + hPolygonVertexPosition = glGetAttribLocation(polygonProgram, "a_position"); + hPolygonColor = glGetUniformLocation(polygonProgram, "u_color"); + + mFillPolys = new PolygonLayer[STENCIL_BITS]; + + return true; + } + + private static void fillPolygons(int count, double zoom, float scale) { + boolean blend = false; + + // draw to framebuffer + glColorMask(true, true, true, true); + + // do not modify stencil buffer + glStencilMask(0); + + for (int c = 0; c < count; c++) { + PolygonLayer l = mFillPolys[c]; + + float alpha = 1.0f; + + if (l.area.fade >= zoom || l.area.color[3] != 1.0) { + + if (l.area.fade >= zoom) { + alpha = (scale > 1.3f ? scale : 1.3f) - alpha; + if (alpha > 1.0f) + alpha = 1.0f; + } + + alpha *= l.area.color[3]; + + if (!blend) { + glEnable(GL_BLEND); + blend = true; + } + glUniform4f(hPolygonColor, + l.area.color[0], l.area.color[1], l.area.color[2], alpha); + + } else if (l.area.blend == zoom) { + alpha = scale - 1.0f; + if (alpha > 1.0f) + alpha = 1.0f; + else if (alpha < 0) + alpha = 0; + + glUniform4f(hPolygonColor, + l.area.color[0] * (1 - alpha) + l.area.blendColor[0] * alpha, + l.area.color[1] * (1 - alpha) + l.area.blendColor[1] * alpha, + l.area.color[2] * (1 - alpha) + l.area.blendColor[2] * alpha, 1); + } else { + if (blend) { + glDisable(GL_BLEND); + blend = false; + } + if (l.area.blend <= zoom && l.area.blend > 0) + glUniform4fv(hPolygonColor, 1, l.area.blendColor, 0); + else + glUniform4fv(hPolygonColor, 1, l.area.color, 0); + } + + // set stencil buffer mask used to draw this layer + glStencilFunc(GL_EQUAL, 0xff, 1 << c); + + // draw tile fill coordinates + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + if (blend) + glDisable(GL_BLEND); + } + + static PolygonLayer drawPolygons(PolygonLayer layer, int next, + float[] matrix, double zoom, float scale, short drawCount, boolean clip) { + int cnt = 0; + + glUseProgram(polygonProgram); + glEnableVertexAttribArray(hPolygonVertexPosition); + + glVertexAttribPointer(hPolygonVertexPosition, 2, + GLES20.GL_SHORT, false, 0, + POLYGON_VERTICES_DATA_POS_OFFSET); + + glUniformMatrix4fv(hPolygonMatrix, 1, false, matrix, 0); + + glEnable(GL_STENCIL_TEST); + + PolygonLayer l = layer; + + for (; l != null && l.layer < next; l = l.next) { + // fade out polygon layers (set in RederTheme) + if (l.area.fade > 0 && l.area.fade > zoom) + continue; + + if (cnt == 0) { + // disable drawing to framebuffer + glColorMask(false, false, false, false); + + // never pass the test, i.e. always apply first stencil op (sfail) + glStencilFunc(GL_NEVER, 0, 0xff); + + // clear stencilbuffer + glStencilMask(0xFF); + // glClear(GL_STENCIL_BUFFER_BIT); + + // clear stencilbuffer (tile region) + glStencilOp(GL_ZERO, GL_KEEP, GL_KEEP); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + // stencil op for stencil method polygon drawing + glStencilOp(GL_INVERT, GL_KEEP, GL_KEEP); + } + + mFillPolys[cnt] = l; + + // set stencil mask to draw to + glStencilMask(1 << cnt++); + + glDrawArrays(GL_TRIANGLE_FAN, l.offset, l.verticesCnt); + + // draw up to 8 layers into stencil buffer + if (cnt == STENCIL_BITS) { + fillPolygons(cnt, zoom, scale); + cnt = 0; + } + } + + if (cnt > 0) + fillPolygons(cnt, zoom, scale); + + glDisable(GL_STENCIL_TEST); + + if (clip) { + drawDepthClip(drawCount); + } + + // required on GalaxyII, Android 2.3.3 (cant just VAA enable once...) + GLES20.glDisableVertexAttribArray(hPolygonVertexPosition); + + return l; + } + + // static void drawStencilClip(byte drawCount) { + // // set stencil mask for line drawing... HACK!!! + // glColorMask(false, false, false, false); // - // for (PolygonLayer l = layers; l != null; l = l.next) - // size += l.verticesCnt; + // int c = drawCount % 16; + // // never pass the test, i.e. always apply first stencil op (sfail) + // // glStencilFunc(GLES20.GL_NEVER, drawCount, 0); + // glStencilFunc(GLES20.GL_NEVER, flipdabit[c], 0); // - // size *= NUM_VERTEX_FLOATS; + // // replace stencilbuffer + // glStencilMask(0xff); // - // if (buf == null || buf.capacity() < size) { - // ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 2).order( - // ByteOrder.nativeOrder()); - // sbuf = bbuf.asShortBuffer(); - // } else { - // sbuf.clear(); - // } + // // set stencilbuffer for (tile region) + // glStencilOp(GLES20.GL_REPLACE, GLES20.GL_KEEP, GLES20.GL_KEEP); + // glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // - // short[] data = tmpItem; + // glStencilFunc(GL_EQUAL, flipdabit[c], 0xff); + // // do not modify stencil buffer + // glStencilMask(0); // - // if (mByteFillCoords == null) { - // mByteFillCoords = new short[8]; - // FastMath.convertFloatToHalf(mFillCoords[0], mByteFillCoords, 0); - // FastMath.convertFloatToHalf(mFillCoords[1], mByteFillCoords, 1); - // FastMath.convertFloatToHalf(mFillCoords[2], mByteFillCoords, 2); - // FastMath.convertFloatToHalf(mFillCoords[3], mByteFillCoords, 3); - // FastMath.convertFloatToHalf(mFillCoords[4], mByteFillCoords, 4); - // FastMath.convertFloatToHalf(mFillCoords[5], mByteFillCoords, 5); - // FastMath.convertFloatToHalf(mFillCoords[6], mByteFillCoords, 6); - // FastMath.convertFloatToHalf(mFillCoords[7], mByteFillCoords, 7); - // } - // - // sbuf.put(mByteFillCoords, 0, 8); - // int pos = 4; - // - // PoolItem last = null, items = null; - // - // for (PolygonLayer l = layers; l != null; l = l.next) { - // - // for (PoolItem item = l.pool; item != null; item = item.next) { - // PoolItem.toHalfFloat(item, data); - // sbuf.put(data, 0, item.used); - // last = item; - // } - // - // l.offset = pos; - // pos += l.verticesCnt; - // - // if (last != null) { - // last.next = items; - // items = l.pool; - // } - // - // l.pool = null; - // } - // - // VertexPool.add(items); - // - // sbuf.flip(); - // - // return sbuf; - // } - // - // static void clear(PolygonLayer layers) { - // for (PolygonLayer l = layers; l != null; l = l.next) { - // if (l.pool != null) - // VertexPool.add(l.pool); - // } + // glColorMask(true, true, true, true); // } - private static short[] mFillCoords; + // this only increases the chance for the stencil buffer clip to work + // should check for clipping with depth buffer or sth + // private static short[] flipdabit = { + // 255, 254, 253, 251, + // 247, 239, 223, 191, + // 127, 63, 252, 250, + // 249, 243, 231, 207 }; - static ShortBuffer compileLayerData(PolygonLayer layers, ShortBuffer buf) { - ShortBuffer sbuf = buf; - int size = 4; + static void drawDepthClip(short drawCount) { + + glColorMask(false, false, false, false); + glEnable(GLES20.GL_DEPTH_TEST); + glEnable(GLES20.GL_POLYGON_OFFSET_FILL); + // GLES20.GL_PO + GLES20.glPolygonOffset(0, drawCount); + + // int i[] = new int[1]; + // GLES20.glGetIntegerv(GLES20.GL_POLYGON_OFFSET_UNITS, i, 0); + // Log.d("...", "UNITS " + i[0]); + // + // System.out.println("set offset: " + drawCount); + GLES20.glDepthMask(true); + + GLES20.glDepthFunc(GLES20.GL_ALWAYS); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glDepthMask(false); + glColorMask(true, true, true, true); + + GLES20.glDepthFunc(GLES20.GL_EQUAL); + } + + static int sizeOf(PolygonLayer layers) { + int size = 0; for (PolygonLayer l = layers; l != null; l = l.next) size += l.verticesCnt; - size *= NUM_VERTEX_FLOATS; + size *= NUM_VERTEX_SHORTS; - if (buf == null || buf.capacity() < size) { - ByteBuffer bbuf = ByteBuffer.allocateDirect(size * 2).order( - ByteOrder.nativeOrder()); - sbuf = bbuf.asShortBuffer(); - } else { - sbuf.clear(); - } + return size; + } - if (mFillCoords == null) { - short min = (-2) << 4; - short max = (short) ((Tile.TILE_SIZE + 1) << 4); - mFillCoords = new short[8]; - mFillCoords[0] = min; - mFillCoords[1] = max; - mFillCoords[2] = max; - mFillCoords[3] = max; - mFillCoords[4] = min; - mFillCoords[5] = min; - mFillCoords[6] = max; - mFillCoords[7] = min; - } - - sbuf.put(mFillCoords, 0, 8); + static void compileLayerData(PolygonLayer layers, ShortBuffer sbuf) { int pos = 4; ShortItem last = null, items = null; @@ -203,10 +300,6 @@ class PolygonLayers { } ShortPool.add(items); - - sbuf.flip(); - - return sbuf; } static void clear(PolygonLayer layers) { diff --git a/src/org/mapsforge/android/glrenderer/Shaders.java b/src/org/mapsforge/android/glrenderer/Shaders.java index e632b20d..89339b07 100644 --- a/src/org/mapsforge/android/glrenderer/Shaders.java +++ b/src/org/mapsforge/android/glrenderer/Shaders.java @@ -16,166 +16,141 @@ package org.mapsforge.android.glrenderer; class Shaders { - final static String gLineVertexShader = "" + + final static String lineVertexShader = "" + "precision mediump float; \n" - + "uniform mat4 u_center;" + + "uniform mat4 mvp;" + "attribute vec4 a_position;" + "attribute vec2 a_st;" + "varying vec2 v_st;" - + "const vec4 scale = vec4(1.0/16.0, 1.0/16.0, 0.0, 1.0);" + + "uniform float u_width;" + + "uniform float u_offset;" + + "const float dscale = 8.0/1000.0;" + "void main() {" - // + " gl_Position = u_center * vec4(a_position.x, a_position.y, 0.0, 1.0);" - // + " v_st = a_position.zw;" - + " gl_Position = u_center * (scale * a_position);" - // + " gl_Position = u_center * a_position;" - + " v_st = a_st;" + + " vec2 dir = dscale * u_width * a_position.zw;" + + " gl_Position = mvp * vec4(a_position.xy + dir, 0.0,1.0);" + + " v_st = u_width * a_st;" + "}"; - final static String gLineFragmentShader = "" + // final static String lineFragmentShader = "" + // + "precision mediump float;\n" + // + "uniform float u_wscale;" + // + "uniform float u_width;" + // + "uniform vec4 u_color;" + // + "varying vec2 v_st;" + // + "const float zero = 0.0;" + // + "void main() {" + // + " vec4 color = u_color;" + // + " float len;" + // + " if (v_st.t == zero)" + // + " len = abs(v_st.s);" + // + " else " + // + " len = length(v_st);" + // + " color.a *= smoothstep(zero, u_wscale, u_width - len);" + // + " gl_FragColor = color;" + // + "}"; + + final static String lineFragmentShader = "" + "#extension GL_OES_standard_derivatives : enable\n" - + "precision mediump float;" - + "uniform lowp vec2 u_mode;" + + "precision mediump float;\n" + + "uniform float u_wscale;" + + "uniform float u_width;" + "uniform vec4 u_color;" - + "const lowp float zero = 0.0;" - + "const float fuzzf = 1.8;" + "varying vec2 v_st;" + + "const float zero = 0.0;" + "void main() {" - + " lowp vec4 color = u_color;" - + " lowp float len;" - + " lowp float fuzz;" - + " lowp float width = u_mode[1];" - // + " if (v_st.t == zero){ " - // + " fuzz = fwidth(v_st.s) * fuzzf;" - // + " len = width - abs(v_st.s);" - // + " } else {" - + " fuzz = max(fwidth(v_st.s), fwidth(v_st.t)) * fuzzf;" - + " len = width - length(v_st);" - // + " } " - // + " if (len < min_fuzz)" - // + " discard;" - // + " alpha = zero;" - + " if (len < fuzz) {" - + " lowp float min_fuzz = -fuzz * u_mode[0];" - + " color.a *= smoothstep(min_fuzz, fuzz, len);" - // + " if (color.a == 0.0 ) color = vec4(1.0,0.0,0.0,1.0);" - + " }" + + " vec4 color = u_color;" + + " float width = u_width;" + + " float len;" + + " if (v_st.t == zero)" + + " len = abs(v_st.s);" + + " else " + + " len = length(v_st);" + // + " if (u_width - len < 2.0){" + + " vec2 st_width = fwidth(v_st);" + + " float fuzz = max(st_width.s, st_width.t) * 1.5;" + + " color.a *= smoothstep(zero, fuzz + u_wscale, u_width - len);" + // + " }" + " gl_FragColor = color;" + "}"; - // final static String gLineFragmentShader = "" - // + "#extension GL_OES_standard_derivatives : enable\n" - // + "#pragma profilepragma blendoperation(gl_FragColor, GL_FUNC_ADD, GL_ONE, GL_ONE_MINUS_SRC_ALPHA)\n" - // + "precision mediump float;" - // + "uniform vec2 u_mode;" - // + "uniform vec4 u_color;" - // + "const float zero = 0.0;" - // + "const vec4 blank = vec4(0.0,0.0,0.0,0.0);" - // + "varying vec2 v_st;" - // + "void main() {" - // + "lowp color = u_color;" - // + "lowp alpha = 1.0;" - // + "float width = u_mode[1];" - // + " if (v_st.t == zero){ " - // + " float fuzz = fwidth(v_st.s) * 1.5;" - // + " float min_fuzz = -fuzz * u_mode[0];" - // + " float len = width - abs(v_st.s);" - // // + " if (len > fuzz)" - // // + " gl_FragColor = u_color;" - // // + " else if (len < min_fuzz)" - // // + " gl_FragColor = blank;" - // // + " else" - // + " gl_FragColor = u_color * smoothstep(min_fuzz, fuzz, len);" - // + " } else {" - // + " float fuzz = max(fwidth(v_st.s), fwidth(v_st.t)) * 1.5;" - // + " gl_FragColor = u_color * smoothstep(-fuzz * u_mode[0], fuzz, width - length(v_st));" - // + " } " - // + "glFragColor = color" - // + "}"; - - // final static String gLineFragmentShader = "" - // + "#extension GL_OES_standard_derivatives : enable\n" - // + "precision mediump float;" - // + "uniform float u_width;" - // + "uniform vec2 u_mode;" - // + "uniform vec4 u_color;" - // + "const float zero = 0.0;" - // // + "const vec4 blank = vec4(1.0, 0.0, 0.0, 1.0);" - // + "varying vec2 v_st;" - // + "void main() {" - // + "float width = u_mode[1];" - // // + "float alpha = 1.0;" - // + " if (u_mode[0] == zero) {" - // // + " gl_FragColor = u_color;" - // // + " float fuzz;" - // // + " float len;" - // + " if (v_st.t == zero){ " - // // + " fuzz = - sqrt(dFdx(v_st.s) * dFdx(v_st.s) + dFdy(v_st.s) * dFdy(v_st.s));" - // + " float fuzz = -fwidth(v_st.s) * 1.5;" - // + " float len = abs(v_st.s) - width;" - // // + " if (len < fuzz)" - // + " gl_FragColor = u_color * smoothstep(zero, fuzz, len);" - // + " } else {" - // + " float fuzz = -max(fwidth(v_st.s), fwidth(v_st.t)) * 1.5;" - // + " float len = length(v_st) - width;" - // // + " if (len < fuzz)" - // + " gl_FragColor = u_color * smoothstep(zero, fuzz, len);" - // + " } " - // // + " if (len > zero)" - // // + " gl_FragColor = blank;" - // // + " discard;" - // // + " gl_FragColor = u_color;" - // // + " else if (len < fuzz)" - // // + " gl_FragColor = blank2;" - // // + " else " - // + " } else { " - // + " float fuzz = fwidth(v_st.s);" - // // + " gl_FragColor = u_color * smoothstep(fuzz, zero, abs(v_st.s) - u_width + fuzz);" - // // + " fuzz = - sqrt(dFdx(v_st.s) * dFdx(v_st.s) + dFdy(v_st.s) * dFdy(v_st.s)) * 1.5;" - // + " gl_FragColor = u_color * smoothstep(fuzz*0.5, -fuzz, abs(v_st.s) - width);" - // + " }" - // + "}"; - // - // final static String gLineFragmentShader = "" + - // "#extension GL_OES_standard_derivatives : enable\n" + - // "precision mediump float;" + - // "uniform int u_mode;" + - // "uniform vec4 u_color;" + - // "varying vec2 v_st;" + - // "void main() {" + - // " gl_FragColor = u_color;" + - // "}"; - - final static String gLineFragmentShaderSimple = "" + final static String polygonVertexShader = "" + "precision mediump float;" - + "uniform vec4 u_color;" - + "uniform vec2 u_mode;" - + "uniform float u_width;" - + "varying vec2 v_st;" - + "void main() {" - + " gl_FragColor = u_color;" - + " float width = u_width * u_mode[1];" - + " float len;" - + " if (v_st.t == 0.0) " - + " len = abs(v_st.s);" - + " else " - + " len = length(v_st);" - + " gl_FragColor.a = smoothstep(width, width - u_mode[1], width * len);" - - + "}"; - - final static String gPolygonVertexShader = "" - + "precision mediump float; \n" - + "uniform mat4 u_center;\n" + + "uniform mat4 mvp;" + "attribute vec4 a_position;" - + "const vec4 scale = vec4(1.0/16.0, 1.0/16.0, 0.0, 1.0);" + + "uniform float u_offset;" + "void main() {" - + " gl_Position = u_center * (scale * a_position);" + + " gl_Position = mvp * a_position;" + "}"; - final static String gPolygonFragmentShader = "" + final static String polygonFragmentShader = "" + "precision mediump float;" + "uniform vec4 u_color;" + "void main() {" + " gl_FragColor = u_color;" + "}"; + + final static String textVertexShader = "" + + "precision highp float; " + + "attribute vec4 vertex;" + + "attribute vec2 tex_coord;" + + "uniform mat4 mvp;" + + "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;" + + "}"; + + final static String textFragmentShader = "" + + "precision highp float;" + + "uniform sampler2D tex;" + + "uniform vec4 col;" + + "varying vec2 tex_c;" + + "void main() {" + + " gl_FragColor = texture2D(tex, tex_c.xy);" + + "}"; + + // final static String lineVertexZigZagShader = "" + // + "precision mediump float; \n" + // + "uniform mat4 mvp;" + // + "attribute vec4 a_pos1;" + // + "attribute vec2 a_st1;" + // + "attribute vec4 a_pos2;" + // + "attribute vec2 a_st2;" + // + "varying vec2 v_st;" + // + "uniform vec2 u_mode;" + // + "const float dscale = 1.0/1000.0;" + // + "void main() {" + // + "if (gl_VertexID & 1 == 0) {" + // + " vec2 dir = dscale * u_mode[1] * a_pos1.zw;" + // + " gl_Position = mvp * vec4(a_pos1.xy + dir, 0.0,1.0);" + // + " v_st = u_mode[1] * a_st1;" + // + "} else {" + // + " vec2 dir = dscale * u_mode[1] * a_pos2.zw;" + // + " gl_Position = mvp * vec4( a_pos1.xy, dir, 0.0,1.0);" + // + " v_st = u_mode[1] * vec2(-a_st2.s , a_st2.t);" + // + "}"; + + // final static String lineFragmentShader = "" + // + "#extension GL_OES_standard_derivatives : enable\n" + // + "precision mediump float;\n" + // + "uniform vec2 u_mode;" + // + "uniform vec4 u_color;" + // + "varying vec2 v_st;" + // + "const float zero = 0.0;" + // + "void main() {" + // + " vec4 color = u_color;" + // + " float width = u_mode[1];" + // + " float len;" + // + " if (v_st.t == zero)" + // + " len = abs(v_st.s);" + // + " else " + // + " len = length(v_st);" + // + " vec2 st_width = fwidth(v_st);" + // + " float fuzz = max(st_width.s, st_width.t);" + // + " color.a *= smoothstep(-pixel, fuzz*pixel, width - (len * width));" + // + " gl_FragColor = color;" + // + "}"; } diff --git a/src/org/mapsforge/android/glrenderer/ShortItem.java b/src/org/mapsforge/android/glrenderer/ShortItem.java index f880dba1..37a66083 100644 --- a/src/org/mapsforge/android/glrenderer/ShortItem.java +++ b/src/org/mapsforge/android/glrenderer/ShortItem.java @@ -24,5 +24,6 @@ public class ShortItem { used = 0; } - static int SIZE = 128; + // must be multiple of 6 and 4 (expected in LineLayer/PolygonLayer) + static final int SIZE = 240; } diff --git a/src/org/mapsforge/android/glrenderer/ShortPool.java b/src/org/mapsforge/android/glrenderer/ShortPool.java index 91dc670c..4b0e71c5 100644 --- a/src/org/mapsforge/android/glrenderer/ShortPool.java +++ b/src/org/mapsforge/android/glrenderer/ShortPool.java @@ -14,59 +14,80 @@ */ package org.mapsforge.android.glrenderer; -import android.annotation.SuppressLint; +import android.util.Log; public class ShortPool { - private static final int POOL_LIMIT = 8192; - - @SuppressLint("UseValueOf") - private static final Boolean lock = new Boolean(true); + private static final int POOL_LIMIT = 6000; static private ShortItem pool = null; static private int count = 0; + static private int countAll = 0; - static ShortItem get() { - synchronized (lock) { - - if (count == 0) - return new ShortItem(); - - count--; - - ShortItem it = pool; - pool = pool.next; - it.used = 0; - it.next = null; - return it; - } + static synchronized void finish() { + count = 0; + countAll = 0; + pool = null; } - static void add(ShortItem items) { + static synchronized ShortItem get() { + + if (pool == null) { + countAll++; + return new ShortItem(); + } + + count--; + + if (count < 0) { + int c = 0; + + for (ShortItem tmp = pool; tmp != null; tmp = tmp.next) + c++; + + Log.d("ShortPool", "eek wrong count: " + count + " left" + c); + return new ShortItem(); + } + + ShortItem it = pool; + pool = pool.next; + it.used = 0; + it.next = null; + return it; + } + + static synchronized void add(ShortItem items) { if (items == null) return; - synchronized (lock) { + // limit pool items + if (countAll < POOL_LIMIT) { + ShortItem last = items; - // limit pool items - while (count < POOL_LIMIT) { - if (last.next == null) { - break; - } - last = last.next; + while (true) { count++; - } - // clear references - ShortItem tmp2, tmp = last.next; - while (tmp != null) { - tmp2 = tmp; - tmp = tmp.next; - tmp2.next = null; + if (last.next == null) + break; + + last = last.next; } last.next = pool; pool = items; + + } else { + // int cleared = 0; + ShortItem prev, tmp = items; + while (tmp != null) { + prev = tmp; + tmp = tmp.next; + + countAll--; + + prev.next = null; + + } } } } diff --git a/src/org/mapsforge/android/glrenderer/TextItem.java b/src/org/mapsforge/android/glrenderer/TextItem.java index ee129cc9..5a8848e5 100644 --- a/src/org/mapsforge/android/glrenderer/TextItem.java +++ b/src/org/mapsforge/android/glrenderer/TextItem.java @@ -15,6 +15,7 @@ package org.mapsforge.android.glrenderer; import org.mapsforge.android.rendertheme.renderinstruction.Caption; +import org.mapsforge.android.rendertheme.renderinstruction.PathText; public class TextItem { TextItem next; @@ -22,14 +23,27 @@ public class TextItem { final float x, y; final String text; final Caption caption; + final PathText path; final float width; + short x1, y1, x2, y2; + public TextItem(float x, float y, String text, Caption caption) { this.x = x; this.y = y; this.text = text; this.caption = caption; this.width = caption.paint.measureText(text); + this.path = null; + } + + public TextItem(float x, float y, String text, PathText pathText, float width) { + this.x = x; + this.y = y; + this.text = text; + this.path = pathText; + this.caption = null; + this.width = width; } } diff --git a/src/org/mapsforge/android/glrenderer/TextRenderer.java b/src/org/mapsforge/android/glrenderer/TextRenderer.java index b4f2c9dd..b22100d7 100644 --- a/src/org/mapsforge/android/glrenderer/TextRenderer.java +++ b/src/org/mapsforge/android/glrenderer/TextRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright 2010, 2011, 2012 mapsforge.org + * 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 @@ -26,58 +26,59 @@ import android.graphics.Color; import android.graphics.Paint; import android.opengl.GLES20; import android.opengl.GLUtils; +import android.util.FloatMath; 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; - final static int MAX_LABELS = 30; - - private final Bitmap mBitmap; - private final Canvas mCanvas; - private int mFontPadX = 1; - private int mFontPadY = 1; - private int mBitmapFormat; - private int mBitmapType; - private ByteBuffer mByteBuffer; - private ShortBuffer mShortBuffer; - private TextTexture[] mTextures; - - private int mIndicesVBO; - private int mVerticesVBO; - - final static int INDICES_PER_SPRITE = 6; // Indices Per Sprite - final static int VERTICES_PER_SPRITE = 4; // Vertices Per Sprite + final static int INDICES_PER_SPRITE = 6; + final static int VERTICES_PER_SPRITE = 4; final static int SHORTS_PER_VERTICE = 6; + final static int MAX_LABELS = 35; + + private static Bitmap mBitmap; + private static Canvas mCanvas; + private static int mFontPadX = 1; + private static int mFontPadY = 1; + private static int mBitmapFormat; + private static int mBitmapType; + private static ByteBuffer mByteBuffer; + private static ShortBuffer mShortBuffer; + private static TextTexture[] mTextures; + + private static int mIndicesVBO; + private static int mVerticesVBO; private static int mTextProgram; - static int hTextUVPMatrix; - static int hTextVertex; - static int hTextScale; - static int hTextTextureCoord; - static int hTextUColor; + private static int hTextUVPMatrix; + private static int hTextVertex; + private static int hTextScale; + private static int hTextTextureCoord; + // private static int hTextUColor; static Paint mPaint = new Paint(Color.BLACK); - boolean debug = false; - short[] debugVertices = { + private static boolean debug = false; + private static short[] debugVertices = { 0, 0, - 0, TEXTURE_HEIGHT, + 0, TEXTURE_HEIGHT * 4, 0, TEXTURE_HEIGHT - 1, 0, 0, TEXTURE_WIDTH - 1, 0, - TEXTURE_WIDTH, TEXTURE_HEIGHT, + TEXTURE_WIDTH * 4, TEXTURE_HEIGHT * 4, TEXTURE_WIDTH - 1, TEXTURE_HEIGHT - 1, - TEXTURE_WIDTH, 0, + TEXTURE_WIDTH * 4, 0, }; - TextRenderer(int numTextures) { + static boolean init(int numTextures) { mBitmap = Bitmap .createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT, Bitmap.Config.ARGB_8888); mCanvas = new Canvas(mBitmap); @@ -85,15 +86,14 @@ public class TextRenderer { mBitmapFormat = GLUtils.getInternalFormat(mBitmap); mBitmapType = GLUtils.getType(mBitmap); - mTextProgram = GlUtils.createProgram(textVertexShader, textFragmentShader); + mTextProgram = GlUtils.createProgram(Shaders.textVertexShader, + Shaders.textFragmentShader); hTextUVPMatrix = GLES20.glGetUniformLocation(mTextProgram, "mvp"); - hTextUColor = GLES20.glGetUniformLocation(mTextProgram, "col"); hTextVertex = GLES20.glGetAttribLocation(mTextProgram, "vertex"); hTextScale = GLES20.glGetUniformLocation(mTextProgram, "scale"); hTextTextureCoord = GLES20.glGetAttribLocation(mTextProgram, "tex_coord"); - // mVertexBuffer = new float[]; int bufferSize = numTextures * MAX_LABELS * VERTICES_PER_SPRITE * SHORTS_PER_VERTICE * (Short.SIZE / 8); @@ -121,12 +121,12 @@ public class TextRenderer { GLES20.GL_CLAMP_TO_EDGE); // Set V Wrapping // load the generated bitmap onto the texture - // GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmapFormat, mBitmap, mBitmapType, 0); textures[i] = new TextTexture(textureIds[i]); } + GlUtils.checkGlError("init textures"); mTextures = textures; @@ -136,12 +136,18 @@ public class TextRenderer { int len = indices.length; short j = 0; for (int i = 0; i < len; i += INDICES_PER_SPRITE, j += VERTICES_PER_SPRITE) { + // indices[i + 0] = (short) (j + 0); + // indices[i + 1] = (short) (j + 1); + // indices[i + 2] = (short) (j + 2); + // indices[i + 3] = (short) (j + 2); + // indices[i + 4] = (short) (j + 3); + // indices[i + 5] = (short) (j + 0); indices[i + 0] = (short) (j + 0); - indices[i + 1] = (short) (j + 1); - indices[i + 2] = (short) (j + 2); - indices[i + 3] = (short) (j + 2); - indices[i + 4] = (short) (j + 3); - indices[i + 5] = (short) (j + 0); + indices[i + 1] = (short) (j + 0); + indices[i + 2] = (short) (j + 1); + indices[i + 3] = (short) (j + 3); + indices[i + 4] = (short) (j + 2); + indices[i + 5] = (short) (j + 2); } ShortBuffer tmpIndices = mByteBuffer.asShortBuffer(); @@ -151,21 +157,25 @@ public class TextRenderer { int[] mVboIds = new int[2]; GLES20.glGenBuffers(2, mVboIds, 0); - - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mVboIds[0]); - GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, len * (Short.SIZE / 8), - tmpIndices, - GLES20.GL_STATIC_DRAW); - GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); - mIndicesVBO = mVboIds[0]; mVerticesVBO = mVboIds[1]; + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesVBO); + GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, len * (Short.SIZE / 8), + tmpIndices, GLES20.GL_STATIC_DRAW); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVerticesVBO); + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, bufferSize, + mShortBuffer, GLES20.GL_DYNAMIC_DRAW); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + return true; } - boolean drawToTexture(GLMapTile tile) { + static boolean drawToTexture(GLMapTile tile) { TextTexture tex = null; - if (tile.labels.size() == 0) + if (tile.labels == null) return false; for (int i = 0; i < mTextures.length; i++) { @@ -194,9 +204,7 @@ public class TextRenderer { float x = mFontPadX; float width, height; - int max = tile.labels.size(); - if (max > MAX_LABELS) - max = MAX_LABELS; + int max = MAX_LABELS; if (debug) { mCanvas.drawLine(debugVertices[0], debugVertices[1], debugVertices[4], @@ -212,10 +220,18 @@ public class TextRenderer { int advanceY = 0; - for (int i = 0; i < max; i++) { - TextItem t = tile.labels.get(i); + TextItem t = tile.labels; + float yy; + short x1, x2, x3, x4, y1, y2, y3, y4; + + for (int i = 0; t != null && i < max; t = t.next, i++) { + + if (t.caption != null) { + height = (int) (t.caption.fontHeight) + 2 * mFontPadY; + } else { + height = (int) (t.path.fontHeight) + 2 * mFontPadY; + } - height = (int) (t.caption.fontHeight) + 2 * mFontPadY; width = t.width + 2 * mFontPadX; if (height > advanceY) @@ -227,45 +243,71 @@ public class TextRenderer { advanceY = (int) height; } - float yy = y + (height - 1) - t.caption.fontDescent - mFontPadY; + if (t.caption != null) { + yy = y + (height - 1) - t.caption.fontDescent - mFontPadY; + } else { + yy = y + (height - 1) - t.path.fontDescent - mFontPadY; + } if (yy > TEXTURE_HEIGHT) { Log.d(TAG, "reached max labels"); continue; } - if (t.caption.stroke != null) - mCanvas.drawText(t.text, x + t.width / 2, yy, t.caption.stroke); + if (t.caption != null) { + if (t.caption.stroke != null) + mCanvas.drawText(t.text, x + t.width / 2, yy, t.caption.stroke); - mCanvas.drawText(t.text, x + t.width / 2, yy, t.caption.paint); - - // Log.d(TAG, "draw: " + t.text + " at:" + (xx + t.width / 2) + " " + yy + " w:" - // + t.width + " " + cellHeight); + mCanvas.drawText(t.text, x + t.width / 2, yy, t.caption.paint); + } else { + if (t.path.stroke != null) + mCanvas.drawText(t.text, x + t.width / 2, yy, t.path.stroke); + mCanvas.drawText(t.text, x + t.width / 2, yy, t.path.paint); + } if (width > TEXTURE_WIDTH) width = TEXTURE_WIDTH; - float halfWidth = width / 2.0f; - float halfHeight = height / 2.0f; + float hw = width / 2.0f; + float hh = height / 2.0f; - // short x1 = (short) (2.0f * (t.x - halfWidth)); - // short y1 = (short) (2.0f * (t.y - halfHeight)); - // short x2 = (short) (2.0f * (t.x + halfWidth)); - // short y2 = (short) (2.0f * (t.y + halfHeight)); + 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)); + } + else { + float vx = t.x1 - t.x2; + float vy = t.y1 - t.y2; + float a = FloatMath.sqrt(vx * vx + vy * vy); + vx = vx / a; + vy = vy / a; - short x1 = (short) (2.0f * (-halfWidth)); - short y1 = (short) (2.0f * (-halfHeight)); - short x2 = (short) (2.0f * (halfWidth)); - short y2 = (short) (2.0f * (halfHeight)); + float ux = -vy; + float uy = vx; - short u1 = (short) (2.0f * x); - short v1 = (short) (2.0f * y); - short u2 = (short) (2.0f * (x + width)); - short v2 = (short) (2.0f * (y + height)); + x1 = (short) (SCALE_FACTOR * (vx * hw + ux * hh)); + y1 = (short) (SCALE_FACTOR * (vy * hw + uy * hh)); - short tx = (short) (2.0f * t.x); - short ty = (short) (2.0f * t.y); + 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)); + + } + 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 tx = (short) (SCALE_FACTOR * t.x); + short ty = (short) (SCALE_FACTOR * t.y); + // top-left buf[pos++] = tx; buf[pos++] = ty; buf[pos++] = x1; @@ -273,23 +315,26 @@ public class TextRenderer { buf[pos++] = u1; buf[pos++] = v2; + // top-right buf[pos++] = tx; buf[pos++] = ty; buf[pos++] = x2; - buf[pos++] = y1; + buf[pos++] = y3; buf[pos++] = u2; buf[pos++] = v2; + // bot-right buf[pos++] = tx; buf[pos++] = ty; - buf[pos++] = x2; - buf[pos++] = y2; + buf[pos++] = x4; + buf[pos++] = y4; buf[pos++] = u2; buf[pos++] = v1; + // bot-left buf[pos++] = tx; buf[pos++] = ty; - buf[pos++] = x1; + buf[pos++] = x3; buf[pos++] = y2; buf[pos++] = u1; buf[pos++] = v1; @@ -305,19 +350,16 @@ public class TextRenderer { tex.length = pos; tile.texture = tex; tex.tile = tile; - // GlUtils.checkGlError("0"); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tex.id); - // GlUtils.checkGlError("1"); - GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, mBitmap, mBitmapFormat, - mBitmapType); - // GlUtils.checkGlError("2"); + GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, 0, mBitmap, + mBitmapFormat, mBitmapType); return true; } private static String TAG = "TextRenderer"; - void compileTextures() { + static void compileTextures() { int offset = 0; TextTexture tex; @@ -339,16 +381,18 @@ public class TextRenderer { // Log.d(TAG, "compileTextures" + mFloatBuffer.remaining() + " " + offset); // TODO use sub-bufferdata function - GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, offset * (Short.SIZE / 8), - mShortBuffer, GLES20.GL_DYNAMIC_DRAW); + GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, offset * (Short.SIZE / 8), + mShortBuffer); } - void beginDraw(float scale) { + static void beginDraw(float scale) { GLES20.glUseProgram(mTextProgram); GLES20.glEnableVertexAttribArray(hTextTextureCoord); GLES20.glEnableVertexAttribArray(hTextVertex); + GLES20.glUniform1f(hTextScale, scale); + if (debug) { GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); mShortBuffer.clear(); @@ -365,13 +409,13 @@ public class TextRenderer { } } - void endDraw() { + static void endDraw() { GLES20.glDisableVertexAttribArray(hTextTextureCoord); GLES20.glDisableVertexAttribArray(hTextVertex); } - void drawTile(GLMapTile tile, float[] matrix) { + static void drawTile(GLMapTile tile, float[] matrix) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, tile.texture.id); @@ -384,43 +428,12 @@ public class TextRenderer { GLES20.glVertexAttribPointer(hTextVertex, 4, GLES20.GL_SHORT, false, 12, tile.texture.offset * (Short.SIZE / 8)); - // GLES20.glVertexAttribPointer(hTextVertexOffset, 2, - // GLES20.GL_SHORT, false, 8, tile.texture.offset * (Short.SIZE / 8) + 4); - // GLES20.glVertexAttribPointer(hTextTextureCoord, 2, GLES20.GL_SHORT, false, 12, tile.texture.offset * (Short.SIZE / 8) + 8); - GLES20.glDrawElements(GLES20.GL_TRIANGLES, (tile.texture.length / 24) * + GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, (tile.texture.length / 24) * INDICES_PER_SPRITE, GLES20.GL_UNSIGNED_SHORT, 0); } - } - - private static String textVertexShader = "" - + "precision highp float; " - + "attribute vec4 vertex;" - // + "attribute vec4 offset;" - + "attribute vec2 tex_coord;" - + "uniform mat4 mvp;" - + "uniform float scale;" - + "varying vec2 tex_c;" - + "const vec2 div = vec2(1.0/1024.0,1.0/512.0);" - + "const vec4 dp = vec4(0.5,0.5,0.5,0.5);" - + "void main() {" - + " vec4 s = dp * vertex;" - + " gl_Position = mvp * vec4(s.x + (s.z / scale), s.y + (s.w / scale), 0.0, 1.0);" - // + " gl_Position = vec4(pos.x + s.z, pos.y + s.w, 0.0, 1.0);" - + " tex_c = tex_coord * div;" - + "}"; - - private static String textFragmentShader = "" - + "precision highp float;" - + "uniform sampler2D tex;" - + "uniform vec4 col;" - + "varying vec2 tex_c;" - + "void main() {" - + " gl_FragColor = texture2D(tex, tex_c.xy);" - + "}"; - } diff --git a/src/org/mapsforge/android/glrenderer/WayDecorator.java b/src/org/mapsforge/android/glrenderer/WayDecorator.java new file mode 100644 index 00000000..411bff25 --- /dev/null +++ b/src/org/mapsforge/android/glrenderer/WayDecorator.java @@ -0,0 +1,285 @@ +/* + * Copyright 2010, 2011, 2012 mapsforge.org + * + * 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 org.mapsforge.android.rendertheme.renderinstruction.PathText; +import org.mapsforge.android.utils.GeometryUtils; + +import android.util.FloatMath; + +final class WayDecorator { + // /** + // * Minimum distance in pixels before the symbol is repeated. + // */ + // private static final int DISTANCE_BETWEEN_SYMBOLS = 200; + + /** + * Minimum distance in pixels before the way name is repeated. + */ + private static final int DISTANCE_BETWEEN_WAY_NAMES = 500; + + // /** + // * Distance in pixels to skip from both ends of a segment. + // */ + // private static final int SEGMENT_SAFETY_DISTANCE = 30; + + // static void renderSymbol(Bitmap symbolBitmap, boolean alignCenter, + // boolean repeatSymbol, float[][] coordinates, + // List waySymbols) { + // int skipPixels = SEGMENT_SAFETY_DISTANCE; + // + // // get the first way point coordinates + // float previousX = coordinates[0][0]; + // float previousY = coordinates[0][1]; + // + // // draw the symbol on each way segment + // float segmentLengthRemaining; + // float segmentSkipPercentage; + // float symbolAngle; + // for (int i = 2; i < coordinates[0].length; i += 2) { + // // get the current way point coordinates + // float currentX = coordinates[0][i]; + // float currentY = coordinates[0][i + 1]; + // + // // calculate the length of the current segment (Euclidian distance) + // float diffX = currentX - previousX; + // float diffY = currentY - previousY; + // double segmentLengthInPixel = Math.sqrt(diffX * diffX + diffY * diffY); + // segmentLengthRemaining = (float) segmentLengthInPixel; + // + // while (segmentLengthRemaining - skipPixels > SEGMENT_SAFETY_DISTANCE) { + // // calculate the percentage of the current segment to skip + // segmentSkipPercentage = skipPixels / segmentLengthRemaining; + // + // // move the previous point forward towards the current point + // previousX += diffX * segmentSkipPercentage; + // previousY += diffY * segmentSkipPercentage; + // symbolAngle = (float) Math.toDegrees(Math.atan2(currentY - previousY, + // currentX - previousX)); + // + // waySymbols.add(new SymbolContainer(symbolBitmap, previousX, previousY, + // alignCenter, symbolAngle)); + // + // // check if the symbol should only be rendered once + // if (!repeatSymbol) { + // return; + // } + // + // // recalculate the distances + // diffX = currentX - previousX; + // diffY = currentY - previousY; + // + // // recalculate the remaining length of the current segment + // segmentLengthRemaining -= skipPixels; + // + // // set the amount of pixels to skip before repeating the symbol + // skipPixels = DISTANCE_BETWEEN_SYMBOLS; + // } + // + // skipPixels -= segmentLengthRemaining; + // if (skipPixels < SEGMENT_SAFETY_DISTANCE) { + // skipPixels = SEGMENT_SAFETY_DISTANCE; + // } + // + // // set the previous way point coordinates for the next loop + // previousX = currentX; + // previousY = currentY; + // } + // } + + static TextItem renderText(float[] coordinates, String text, PathText pathText, + int pos, int len, TextItem textItems) { + TextItem items = textItems; + TextItem t = null; + // calculate the way name length plus some margin of safety + float wayNameWidth = -1; + float minWidth = 100; + int skipPixels = 0; + + // get the first way point coordinates + int previousX = (int) coordinates[pos + 0]; + int previousY = (int) coordinates[pos + 1]; + + // find way segments long enough to draw the way name on them + for (int i = pos + 2; i < pos + len; i += 2) { + // get the current way point coordinates + int currentX = (int) coordinates[i]; + int currentY = (int) coordinates[i + 1]; + + // calculate the length of the current segment (Euclidian distance) + float diffX = currentX - previousX; + float diffY = currentY - previousY; + + for (int j = i + 2; j < pos + len; j += 2) { + int nextX = (int) coordinates[j]; + int nextY = (int) coordinates[j + 1]; + + if (diffY == 0) { + if ((currentY - nextY) != 0) + break; + + currentX = nextX; + currentY = nextY; + continue; + } else if ((currentY - nextY) == 0) + break; + + float diff = ((diffX) / (diffY) - (float) (currentX - nextX) + / (currentY - nextY)); + + // skip segments with corners + if (diff >= 0.1f || diff <= -0.1f) + break; + + currentX = nextX; + currentY = nextY; + } + + diffX = currentX - previousX; + diffY = currentY - previousY; + + if (diffX < 0) + diffX = -diffX; + if (diffY < 0) + diffY = -diffY; + + if (diffX + diffY < minWidth) { + previousX = currentX; + previousY = currentY; + continue; + } + + if (wayNameWidth > 0 && diffX + diffY < wayNameWidth) { + previousX = currentX; + previousY = currentY; + continue; + } + + float segmentLengthInPixel = FloatMath.sqrt(diffX * diffX + diffY * diffY); + + if (skipPixels > 0) { + skipPixels -= segmentLengthInPixel; + + } else if (segmentLengthInPixel > minWidth) { + + if (wayNameWidth < 0) { + wayNameWidth = pathText.paint.measureText(text); + } + + if (segmentLengthInPixel > wayNameWidth + 25) { + + float s = (wayNameWidth + 25) / segmentLengthInPixel; + int width, height; + int x1, y1, x2, y2; + + if (previousX < currentX) { + x1 = previousX; + y1 = previousY; + x2 = currentX; + y2 = currentY; + } else { + x1 = currentX; + y1 = currentY; + x2 = previousX; + y2 = previousY; + } + + // estimate position of text on path + width = (x2 - x1) / 2; + x2 = x2 - (int) (width - s * width); + x1 = x1 + (int) (width - s * width); + + height = (y2 - y1) / 2; + y2 = y2 - (int) (height - s * height); + y1 = y1 + (int) (height - s * height); + + short top = (short) (y1 < y2 ? y1 : y2); + short bot = (short) (y1 < y2 ? y2 : y1); + + boolean intersects = false; + + for (TextItem t2 = items; t2 != null; t2 = t2.next) { + + // check crossings + if (GeometryUtils.lineIntersect(x1, y1, x2, y2, t2.x1, t2.y1, + t2.x2, t2.y2)) { + intersects = true; + break; + } + + // check overlapping labels of road with more than one + // way + short top2 = (t2.y1 < t2.y2 ? t2.y1 : t2.y2); + short bot2 = (t2.y1 < t2.y2 ? t2.y2 : t2.y1); + + if (x1 - 10 < t2.x2 && t2.x1 - 10 < x2 && top - 10 < bot2 + && top2 - 10 < bot) { + + if (t2.text.equals(text)) { + intersects = true; + break; + } + } + } + + if (intersects) { + previousX = (int) coordinates[pos + i]; + previousY = (int) coordinates[pos + i + 1]; + continue; + } + + // Log.d("mapsforge", "add " + text + " " + first + " " + last); + + if (previousX < currentX) { + x1 = previousX; + y1 = previousY; + x2 = currentX; + y2 = currentY; + } else { + x1 = currentX; + y1 = currentY; + x2 = previousX; + y2 = previousY; + } + + // if (t == null) + t = new TextItem(x1 + (x2 - x1) / 2, y1 + (y2 - y1) / 2, text, + pathText, wayNameWidth); + + t.x1 = (short) x1; + t.y1 = (short) y1; + t.x2 = (short) x2; + t.y2 = (short) y2; + + t.next = items; + items = t; + + // skipPixels = DISTANCE_BETWEEN_WAY_NAMES; + + return items; + } + } + + // store the previous way point coordinates + previousX = currentX; + previousY = currentY; + } + return items; + } + + private WayDecorator() { + throw new IllegalStateException(); + } +} diff --git a/src/org/mapsforge/android/input/TouchHandler.java b/src/org/mapsforge/android/input/TouchHandler.java index 75602a6a..b7bcb53d 100644 --- a/src/org/mapsforge/android/input/TouchHandler.java +++ b/src/org/mapsforge/android/input/TouchHandler.java @@ -19,14 +19,12 @@ import org.mapsforge.core.Tile; import android.content.Context; import android.os.CountDownTimer; -import android.os.SystemClock; import android.util.Log; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ViewConfiguration; -import android.view.animation.DecelerateInterpolator; import android.widget.Scroller; /** @@ -220,27 +218,15 @@ public class TouchHandler { } // if (ret) { - // Log.d("", "" + ); - // - // // try { - // // - // // Thread.sleep(10); - // // } catch (InterruptedException e) { - // // // TODO Auto-generated catch block - // // // e.printStackTrace(); - // // } - // + // // throttle input + // long diff = SystemClock.uptimeMillis() - lastRun; + // if (diff < 16 && diff > 5) { + // // Log.d("", "" + diff); + // SystemClock.sleep(16 - diff); // } - if (ret) { - // throttle input - long diff = SystemClock.uptimeMillis() - lastRun; - if (diff < 16) { - // Log.d("", "" + diff); - SystemClock.sleep(16 - diff); - } - lastRun = SystemClock.uptimeMillis(); - } - // the event was not handled + // lastRun = SystemClock.uptimeMillis(); + // } + return ret; } @@ -250,7 +236,8 @@ public class TouchHandler { private CountDownTimer mTimer = null; public MapGestureDetector(MapView mapView) { - mScroller = new Scroller(mapView.getContext(), new DecelerateInterpolator()); + mScroller = new Scroller(mapView.getContext(), + new android.view.animation.LinearInterpolator()); } @Override @@ -284,8 +271,8 @@ public class TouchHandler { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - int w = Tile.TILE_SIZE * 20; - int h = Tile.TILE_SIZE * 20; + int w = Tile.TILE_SIZE * 10; + int h = Tile.TILE_SIZE * 10; mPrevX = 0; mPrevY = 0; @@ -297,7 +284,7 @@ public class TouchHandler { mScroller.fling(0, 0, Math.round(velocityX) / 2, Math.round(velocityY) / 2, -w, w, -h, h); // animate for two seconds - mTimer = new CountDownTimer(2000, 20) { + mTimer = new CountDownTimer(2000, 40) { @Override public void onTick(long tick) { if (!scroll()) diff --git a/src/org/mapsforge/android/mapgenerator/JobQueue.java b/src/org/mapsforge/android/mapgenerator/JobQueue.java index a2445204..3afa9e0b 100644 --- a/src/org/mapsforge/android/mapgenerator/JobQueue.java +++ b/src/org/mapsforge/android/mapgenerator/JobQueue.java @@ -46,14 +46,14 @@ public class JobQueue { * @param mapGeneratorJob * the job to be added to this queue. */ - public synchronized void addJob(MapGeneratorJob mapGeneratorJob) { - if (!mPriorityQueue.contains(mapGeneratorJob)) - // priorityQueue.remove(mapGeneratorJob); - { - mapGeneratorJob.tile.isLoading = true; - mPriorityQueue.offer(mapGeneratorJob); - } - } + // public synchronized void addJob(MapGeneratorJob mapGeneratorJob) { + // if (!mPriorityQueue.contains(mapGeneratorJob)) + // // priorityQueue.remove(mapGeneratorJob); + // { + // //mapGeneratorJob.tile.isLoading = true; + // mPriorityQueue.offer(mapGeneratorJob); + // } + // } /** * @param jobs diff --git a/src/org/mapsforge/android/mapgenerator/MapTile.java b/src/org/mapsforge/android/mapgenerator/MapTile.java index dcae8202..00b5a925 100644 --- a/src/org/mapsforge/android/mapgenerator/MapTile.java +++ b/src/org/mapsforge/android/mapgenerator/MapTile.java @@ -43,7 +43,7 @@ public class MapTile extends Tile { /** * distance from center, used in TileScheduler set by updateVisibleList. */ - public long distance; + public float distance; /** * @param tileX diff --git a/src/org/mapsforge/android/mapgenerator/MapWorker.java b/src/org/mapsforge/android/mapgenerator/MapWorker.java index 18bd1213..9b115334 100644 --- a/src/org/mapsforge/android/mapgenerator/MapWorker.java +++ b/src/org/mapsforge/android/mapgenerator/MapWorker.java @@ -86,7 +86,7 @@ public class MapWorker extends PausableThread { @Override protected int getThreadPriority() { - return (Thread.NORM_PRIORITY + Thread.MIN_PRIORITY) / 2; + return (Thread.NORM_PRIORITY + Thread.MIN_PRIORITY) / 3; // return mPrio; } diff --git a/src/org/mapsforge/android/rendertheme/IRenderCallback.java b/src/org/mapsforge/android/rendertheme/IRenderCallback.java index bbaff643..e679e51f 100644 --- a/src/org/mapsforge/android/rendertheme/IRenderCallback.java +++ b/src/org/mapsforge/android/rendertheme/IRenderCallback.java @@ -17,6 +17,7 @@ package org.mapsforge.android.rendertheme; import org.mapsforge.android.rendertheme.renderinstruction.Area; import org.mapsforge.android.rendertheme.renderinstruction.Caption; import org.mapsforge.android.rendertheme.renderinstruction.Line; +import org.mapsforge.android.rendertheme.renderinstruction.PathText; import android.graphics.Bitmap; import android.graphics.Paint; @@ -31,7 +32,7 @@ public interface IRenderCallback { * @param area * ... */ - void renderArea(Area area); + void renderArea(Area area, int level); /** * Renders an area caption with the given text. @@ -83,7 +84,7 @@ public interface IRenderCallback { * @param line * ... */ - void renderWay(Line line); + void renderWay(Line line, int level); /** * Renders a way with the given symbol along the way path. @@ -100,12 +101,8 @@ public interface IRenderCallback { /** * Renders a way with the given text along the way path. * - * @param text - * the text to be rendered. - * @param paint - * the paint to be used for rendering the text. - * @param stroke - * an optional paint for the text casing (may be null). + * @param pathText + * ... */ - void renderWayText(String text, Paint paint, Paint stroke); + void renderWayText(PathText pathText); } diff --git a/src/org/mapsforge/android/rendertheme/NegativeMatcher.java b/src/org/mapsforge/android/rendertheme/NegativeMatcher.java index 1ef5fb3f..24cc18c6 100644 --- a/src/org/mapsforge/android/rendertheme/NegativeMatcher.java +++ b/src/org/mapsforge/android/rendertheme/NegativeMatcher.java @@ -21,8 +21,9 @@ import org.mapsforge.core.Tag; class NegativeMatcher implements AttributeMatcher { private final String[] mKeyList; private final String[] mValueList; + private final boolean mExclusive; - NegativeMatcher(List keyList, List valueList) { + NegativeMatcher(List keyList, List valueList, boolean exclusive) { mKeyList = new String[keyList.size()]; for (int i = 0; i < mKeyList.length; i++) mKeyList[i] = keyList.get(i).intern(); @@ -30,6 +31,8 @@ class NegativeMatcher implements AttributeMatcher { mValueList = new String[valueList.size()]; for (int i = 0; i < mValueList.length; i++) mValueList[i] = valueList.get(i).intern(); + + mExclusive = exclusive; } @Override @@ -46,9 +49,9 @@ class NegativeMatcher implements AttributeMatcher { for (Tag tag : tags) { for (String value : mValueList) if (value == tag.value) - return true; + return !mExclusive; } - return false; + return mExclusive; } private boolean keyListDoesNotContainKeys(Tag[] tags) { diff --git a/src/org/mapsforge/android/rendertheme/NegativeRule.java b/src/org/mapsforge/android/rendertheme/NegativeRule.java index b59c0c60..cc2b1994 100644 --- a/src/org/mapsforge/android/rendertheme/NegativeRule.java +++ b/src/org/mapsforge/android/rendertheme/NegativeRule.java @@ -29,14 +29,14 @@ class NegativeRule extends Rule { @Override boolean matchesNode(Tag[] tags, byte zoomLevel) { return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement == Element.NODE || mElement == Element.ANY) + && (mElement != Element.WAY) && mAttributeMatcher.matches(tags); } @Override boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement == Element.WAY || mElement == Element.ANY) + && (mElement != Element.NODE) && (mClosed == closed || mClosed == Closed.ANY) && mAttributeMatcher.matches(tags); } diff --git a/src/org/mapsforge/android/rendertheme/PositiveRule.java b/src/org/mapsforge/android/rendertheme/PositiveRule.java index 636ac718..5df1525d 100644 --- a/src/org/mapsforge/android/rendertheme/PositiveRule.java +++ b/src/org/mapsforge/android/rendertheme/PositiveRule.java @@ -37,16 +37,16 @@ class PositiveRule extends Rule { @Override boolean matchesNode(Tag[] tags, byte zoomLevel) { - return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement == Element.NODE || mElement == Element.ANY) + return (mElement != Element.WAY) + && mZoomMin <= zoomLevel && mZoomMax >= zoomLevel && (mKeyMatcher == null || mKeyMatcher.matches(tags)) && (mValueMatcher == null || mValueMatcher.matches(tags)); } @Override boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { - return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel - && (mElement == Element.WAY || mElement == Element.ANY) + return (mElement != Element.NODE) + && mZoomMin <= zoomLevel && mZoomMax >= zoomLevel && (mClosed == closed || mClosed == Closed.ANY) && (mKeyMatcher == null || mKeyMatcher.matches(tags)) && (mValueMatcher == null || mValueMatcher.matches(tags)); diff --git a/src/org/mapsforge/android/rendertheme/RenderTheme.java b/src/org/mapsforge/android/rendertheme/RenderTheme.java index 8324d31f..91d21153 100644 --- a/src/org/mapsforge/android/rendertheme/RenderTheme.java +++ b/src/org/mapsforge/android/rendertheme/RenderTheme.java @@ -17,7 +17,6 @@ package org.mapsforge.android.rendertheme; import java.util.ArrayList; import java.util.List; -import org.mapsforge.android.rendertheme.renderinstruction.Line; import org.mapsforge.android.rendertheme.renderinstruction.RenderInstruction; import org.mapsforge.core.LRUCache; import org.mapsforge.core.Tag; @@ -136,40 +135,112 @@ public class RenderTheme { * ... * @param zoomLevel * ... + * @return ... */ - public void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel) { - // List matchingList = matchingListNode; - // MatchingCacheKey matchingCacheKey = matchingCacheKeyNode; + public synchronized RenderInstruction[] matchNode(IRenderCallback renderCallback, + Tag[] tags, + byte zoomLevel) { - // if (!changed) { - // if (matchingList != null) { - // for (int i = 0, n = matchingList.size(); i < n; ++i) { - // matchingList.get(i).renderNode(renderCallback, tags); - // } - // } - // return; - // } - // matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); - // matchingList = matchingCacheNodes.get(matchingCacheKey); - // - // if (matchingList != null) { - // // cache hit - // for (int i = 0, n = matchingList.size(); i < n; ++i) { - // matchingList.get(i).renderNode(renderCallback, tags); - // } - // } else { + RenderInstruction[] renderInstructions = null; - // cache miss - // matchingList = new ArrayList(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).matchNode(renderCallback, tags, zoomLevel); - // , matchingList + MatchingCacheKey matchingCacheKey; + + matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); + boolean found = mMatchingCacheNodes.containsKey(matchingCacheKey); + if (found) { + renderInstructions = mMatchingCacheNodes.get(matchingCacheKey); + } else { + // cache miss + List matchingList = new ArrayList(4); + for (int i = 0, n = mRulesList.size(); i < n; ++i) + mRulesList.get(i) + .matchNode(renderCallback, tags, zoomLevel, matchingList); + + int size = matchingList.size(); + if (size > 0) { + renderInstructions = new RenderInstruction[size]; + matchingList.toArray(renderInstructions); + } + mMatchingCacheNodes.put(matchingCacheKey, renderInstructions); } - // matchingCacheNodes.put(matchingCacheKey, matchingList); - // } - // - // matchingListNode = matchingList; - // matchingCacheKeyNode = matchingCacheKey; + + if (renderInstructions != null) { + for (int i = 0, n = renderInstructions.length; i < n; i++) + renderInstructions[i].renderNode(renderCallback, tags); + } + + return renderInstructions; + + } + + /** + * Matches a way with the given parameters against this RenderTheme. + * + * @param renderCallback + * the callback implementation which will be executed on each match. + * @param tags + * the tags of the way. + * @param zoomLevel + * the zoom level at which the way should be matched. + * @param closed + * way is Closed + * @param render + * ... + * @return currently processed render instructions + */ + public synchronized RenderInstruction[] matchWay(IRenderCallback renderCallback, + Tag[] tags, + byte zoomLevel, + boolean closed, boolean render) { + RenderInstruction[] renderInstructions = null; + + LRUCache matchingCache; + MatchingCacheKey matchingCacheKey; + + if (closed) { + matchingCache = mMatchingCacheArea; + } else { + matchingCache = mMatchingCacheWay; + } + + matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); + boolean found = matchingCache.containsKey(matchingCacheKey); + if (found) { + renderInstructions = matchingCache.get(matchingCacheKey); + } else { + // cache miss + int c = (closed ? Closed.YES : Closed.NO); + List matchingList = new ArrayList(4); + for (int i = 0, n = mRulesList.size(); i < n; ++i) { + mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c, + matchingList); + } + int size = matchingList.size(); + if (size > 0) { + renderInstructions = new RenderInstruction[size]; + matchingList.toArray(renderInstructions); + } + matchingCache.put(matchingCacheKey, renderInstructions); + } + + if (render && renderInstructions != null) { + for (int i = 0, n = renderInstructions.length; i < n; i++) + renderInstructions[i].renderWay(renderCallback, tags); + } + + return renderInstructions; + } + + void addRule(Rule rule) { + mRulesList.add(rule); + } + + void complete() { + mRulesList.trimToSize(); + for (int i = 0, n = mRulesList.size(); i < n; ++i) { + mRulesList.get(i).onComplete(); + } + } /** @@ -196,107 +267,6 @@ public class RenderTheme { } } - // private RenderInstruction[] mRenderInstructions = null; - - /** - * Matches a way with the given parameters against this RenderTheme. - * - * @param renderCallback - * the callback implementation which will be executed on each match. - * @param tags - * the tags of the way. - * @param zoomLevel - * the zoom level at which the way should be matched. - * @param closed - * way is Closed - * @param changed - * ... - * @return currently processed render instructions - */ - public synchronized RenderInstruction[] matchWay(IRenderCallback renderCallback, - Tag[] tags, - byte zoomLevel, - boolean closed, boolean render) { - RenderInstruction[] renderInstructions = null; - - LRUCache matchingCache; - MatchingCacheKey matchingCacheKey; - - // if (!changed) { - // renderInstructions = mRenderInstructions; - // - // if (renderInstructions != null) { - // for (int i = 0, n = renderInstructions.length; i < n; i++) - // renderInstructions[i].renderWay(renderCallback, tags); - // } - // return; - // } - - if (closed) { - matchingCache = mMatchingCacheArea; - } else { - matchingCache = mMatchingCacheWay; - } - - matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); - boolean found = matchingCache.containsKey(matchingCacheKey); - if (found) { - renderInstructions = matchingCache.get(matchingCacheKey); - } else { - // cache miss - int c = (closed ? Closed.YES : Closed.NO); - List matchingList = new ArrayList(4); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c, - matchingList); - } - int size = matchingList.size(); - if (size > 0) { - renderInstructions = new RenderInstruction[matchingList.size()]; - for (int i = 0, n = matchingList.size(); i < n; ++i) { - RenderInstruction renderInstruction = matchingList.get(i); - - renderInstructions[i] = renderInstruction; - } - } - matchingCache.put(matchingCacheKey, renderInstructions); - } - - if (render && renderInstructions != null) { - for (int i = 0, n = renderInstructions.length; i < n; i++) - renderInstructions[i].renderWay(renderCallback, tags); - } - // mRenderInstructions = renderInstructions; - return renderInstructions; - } - - void addRule(Rule rule) { - mRulesList.add(rule); - } - - void complete() { - mRulesList.trimToSize(); - for (int i = 0, n = mRulesList.size(); i < n; ++i) { - mRulesList.get(i).onComplete(); - } - - } - - private final ArrayList outlineLayers = new ArrayList(); - - void addOutlineLayer(Line line) { - outlineLayers.add(line); - } - - /** - * @param layer - * ... - * @return Line (paint and level) used for outline - */ - public Line getOutline(int layer) { - return outlineLayers.get(layer); - } - void setLevels(int levels) { mLevels = levels; } diff --git a/src/org/mapsforge/android/rendertheme/RenderThemeHandler.java b/src/org/mapsforge/android/rendertheme/RenderThemeHandler.java index 4b615ca4..1d89d22b 100644 --- a/src/org/mapsforge/android/rendertheme/RenderThemeHandler.java +++ b/src/org/mapsforge/android/rendertheme/RenderThemeHandler.java @@ -16,6 +16,7 @@ package org.mapsforge.android.rendertheme; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; @@ -26,9 +27,11 @@ import javax.xml.parsers.SAXParserFactory; import org.mapsforge.android.rendertheme.renderinstruction.Area; import org.mapsforge.android.rendertheme.renderinstruction.Caption; import org.mapsforge.android.rendertheme.renderinstruction.Circle; +import org.mapsforge.android.rendertheme.renderinstruction.AreaLevel; import org.mapsforge.android.rendertheme.renderinstruction.Line; import org.mapsforge.android.rendertheme.renderinstruction.LineSymbol; import org.mapsforge.android.rendertheme.renderinstruction.PathText; +import org.mapsforge.android.rendertheme.renderinstruction.RenderInstruction; import org.mapsforge.android.rendertheme.renderinstruction.Symbol; import org.xml.sax.Attributes; import org.xml.sax.InputSource; @@ -41,14 +44,23 @@ import org.xml.sax.helpers.DefaultHandler; * SAX2 handler to parse XML render theme files. */ public class RenderThemeHandler extends DefaultHandler { - private static final Logger LOG = Logger.getLogger(RenderThemeHandler.class.getName()); + private static final Logger LOG = Logger + .getLogger(RenderThemeHandler.class.getName()); private static enum Element { - RENDER_THEME, RENDERING_INSTRUCTION, RULE; + RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE; } private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme"; private static final String ELEMENT_NAME_RULE = "rule"; + private static final String ELEMENT_NAME_STYPE_PATH_TEXT = "style-pathtext"; + private static final String ELEMENT_NAME_STYLE_AREA = "style-area"; + private static final String ELEMENT_NAME_STYLE_LINE = "style-line"; + private static final String ELEMENT_NAME_STYLE_OUTLINE = "style-outline"; + private static final String ELEMENT_NAME_USE_STYLE_PATH_TEXT = "use-text"; + private static final String ELEMENT_NAME_USE_STYLE_AREA = "use-area"; + private static final String ELEMENT_NAME_USE_STYLE_LINE = "use-line"; + private static final String ELEMENT_NAME_USE_STYLE_OUTLINE = "use-outline"; private static final String UNEXPECTED_ELEMENT = "unexpected element: "; /** @@ -62,10 +74,12 @@ public class RenderThemeHandler extends DefaultHandler { * @throws IOException * if an I/O error occurs while reading from the input stream. */ - public static RenderTheme getRenderTheme(InputStream inputStream) throws SAXException, + public static RenderTheme getRenderTheme(InputStream inputStream) + throws SAXException, ParserConfigurationException, IOException { RenderThemeHandler renderThemeHandler = new RenderThemeHandler(); - XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); + XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser() + .getXMLReader(); xmlReader.setContentHandler(renderThemeHandler); xmlReader.parse(new InputSource(inputStream)); return renderThemeHandler.mRenderTheme; @@ -83,7 +97,8 @@ public class RenderThemeHandler extends DefaultHandler { * @param attributeIndex * the XML attribute index position. */ - public static void logUnknownAttribute(String element, String name, String value, int attributeIndex) { + public static void logUnknownAttribute(String element, String name, String value, + int attributeIndex) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("unknown attribute in element "); stringBuilder.append(element); @@ -110,6 +125,7 @@ public class RenderThemeHandler extends DefaultHandler { mRenderTheme.setLevels(mLevel); mRenderTheme.complete(); + tmpStyleHash.clear(); } @Override @@ -131,8 +147,12 @@ public class RenderThemeHandler extends DefaultHandler { LOG.log(Level.SEVERE, null, exception); } + private static HashMap tmpStyleHash = new HashMap( + 10); + @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, String localName, String qName, + Attributes attributes) throws SAXException { try { if (ELEMENT_NAME_RENDER_THEME.equals(localName)) { checkState(localName, Element.RENDER_THEME); @@ -149,10 +169,53 @@ public class RenderThemeHandler extends DefaultHandler { mRuleStack.push(mCurrentRule); } + else if (ELEMENT_NAME_STYPE_PATH_TEXT.equals(localName)) { + checkState(localName, Element.STYLE); + PathText pathText = PathText.create(localName, attributes); + tmpStyleHash.put("t" + pathText.style, pathText); + // System.out.println("add style: " + pathText.style); + } + + else if (ELEMENT_NAME_STYLE_AREA.equals(localName)) { + checkState(localName, Element.STYLE); + Area area = Area.create(localName, attributes, 0); + tmpStyleHash.put("a" + area.style, area); + // System.out.println("add style: " + area.style); + } + + else if (ELEMENT_NAME_STYLE_LINE.equals(localName)) { + checkState(localName, Element.STYLE); + String style = null; + if ((style = attributes.getValue("from")) != null) { + RenderInstruction ri = tmpStyleHash.get("l" + style); + if (ri instanceof Line) { + Line line = Line.create((Line) ri, localName, attributes, 0, + false); + tmpStyleHash.put("l" + line.style, line); + // System.out.println("add style: " + line.style + " from " + style); + } + else { + // System.out.println("couldnt check the style yo! " + style); + } + } else { + Line line = Line.create(null, localName, attributes, 0, false); + tmpStyleHash.put("l" + line.style, line); + // System.out.println("add style: " + line.style); + } + } + + else if (ELEMENT_NAME_STYLE_OUTLINE.equals(localName)) { + checkState(localName, Element.RENDERING_INSTRUCTION); + Line line = Line.create(null, localName, attributes, mLevel++, true); + tmpStyleHash.put("o" + line.style, line); + // outlineLayers.add(line); + } + else if ("area".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); Area area = Area.create(localName, attributes, mLevel++); - mRuleStack.peek().addRenderingInstruction(area); + // mRuleStack.peek().addRenderingInstruction(area); + mCurrentRule.addRenderingInstruction(area); } else if ("caption".equals(localName)) { @@ -169,17 +232,10 @@ public class RenderThemeHandler extends DefaultHandler { else if ("line".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); - Line line = Line.create(localName, attributes, mLevel++); + Line line = Line.create(null, localName, attributes, mLevel++, false); mCurrentRule.addRenderingInstruction(line); } - else if ("outline".equals(localName)) { - checkState(localName, Element.RENDERING_INSTRUCTION); - Line line = Line.create(localName, attributes, mLevel++); - mRenderTheme.addOutlineLayer(line); - // mCurrentRule.addRenderingInstruction(line); - } - else if ("lineSymbol".equals(localName)) { checkState(localName, Element.RENDERING_INSTRUCTION); LineSymbol lineSymbol = LineSymbol.create(localName, attributes); @@ -198,7 +254,45 @@ public class RenderThemeHandler extends DefaultHandler { mCurrentRule.addRenderingInstruction(symbol); } - else { + else if (ELEMENT_NAME_USE_STYLE_LINE.equals(localName)) { + checkState(localName, Element.RENDERING_INSTRUCTION); + String style = attributes.getValue("name"); + if (style != null) { + Line line = (Line) tmpStyleHash.get("l" + style); + if (line != null) { + // System.out.println("found style line : " + line.style); + Line newLine = Line.create(line, localName, attributes, + mLevel++, false); + + mCurrentRule.addRenderingInstruction(newLine); + } + } + } else if (ELEMENT_NAME_USE_STYLE_OUTLINE.equals(localName)) { + checkState(localName, Element.RENDERING_INSTRUCTION); + String style = attributes.getValue("name"); + if (style != null) { + Line line = (Line) tmpStyleHash.get("o" + style); + if (line != null && line.outline) + mCurrentRule.addRenderingInstruction(line); + } + } else if (ELEMENT_NAME_USE_STYLE_AREA.equals(localName)) { + checkState(localName, Element.RENDERING_INSTRUCTION); + String style = attributes.getValue("name"); + if (style != null) { + Area area = (Area) tmpStyleHash.get("a" + style); + if (area != null) + mCurrentRule.addRenderingInstruction(new AreaLevel(area, + mLevel++)); + } + } else if (ELEMENT_NAME_USE_STYLE_PATH_TEXT.equals(localName)) { + checkState(localName, Element.RENDERING_INSTRUCTION); + String style = attributes.getValue("name"); + if (style != null) { + PathText pt = (PathText) tmpStyleHash.get("t" + style); + if (pt != null) + mCurrentRule.addRenderingInstruction(pt); + } + } else { throw new SAXException("unknown element: " + localName); } } catch (IllegalArgumentException e) { @@ -214,6 +308,7 @@ public class RenderThemeHandler extends DefaultHandler { } private void checkElement(String elementName, Element element) throws SAXException { + Element parentElement; switch (element) { case RENDER_THEME: if (!mElementStack.empty()) { @@ -222,8 +317,16 @@ public class RenderThemeHandler extends DefaultHandler { return; case RULE: - Element parentElement = mElementStack.peek(); - if (parentElement != Element.RENDER_THEME && parentElement != Element.RULE) { + parentElement = mElementStack.peek(); + if (parentElement != Element.RENDER_THEME + && parentElement != Element.RULE) { + throw new SAXException(UNEXPECTED_ELEMENT + elementName); + } + return; + + case STYLE: + parentElement = mElementStack.peek(); + if (parentElement != Element.RENDER_THEME) { throw new SAXException(UNEXPECTED_ELEMENT + elementName); } return; diff --git a/src/org/mapsforge/android/rendertheme/Rule.java b/src/org/mapsforge/android/rendertheme/Rule.java index f164eded..35032b31 100644 --- a/src/org/mapsforge/android/rendertheme/Rule.java +++ b/src/org/mapsforge/android/rendertheme/Rule.java @@ -32,6 +32,7 @@ abstract class Rule { private static final Map, AttributeMatcher> MATCHERS_CACHE_VALUE = new HashMap, AttributeMatcher>(); private static final Pattern SPLIT_PATTERN = Pattern.compile("\\|"); private static final String STRING_NEGATION = "~"; + private static final String STRING_EXCLUSIVE = "-"; private static final String STRING_WILDCARD = "*"; private static Rule createRule(Stack ruleStack, int element, String keys, @@ -44,11 +45,18 @@ abstract class Rule { .split(values))); if (valueList.remove(STRING_NEGATION)) { - AttributeMatcher attributeMatcher = new NegativeMatcher(keyList, valueList); + AttributeMatcher attributeMatcher = new NegativeMatcher(keyList, valueList, + false); return new NegativeRule(element, closed, zoomMin, zoomMax, attributeMatcher); } + if (valueList.remove(STRING_EXCLUSIVE)) { + AttributeMatcher attributeMatcher = new NegativeMatcher(keyList, valueList, + true); + return new NegativeRule(element, closed, zoomMin, zoomMax, + attributeMatcher); + } AttributeMatcher keyMatcher = getKeyMatcher(keyList); AttributeMatcher valueMatcher = getValueMatcher(valueList); @@ -189,13 +197,14 @@ abstract class Rule { abstract boolean matchesWay(Tag[] tags, byte zoomLevel, int closed); - void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel) { + void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, + List matchingList) { if (matchesNode(tags, zoomLevel)) { for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) - mRenderInstructionArray[i].renderNode(renderCallback, tags); + matchingList.add(mRenderInstructionArray[i]); for (int i = 0, n = mSubRuleArray.length; i < n; i++) - mSubRuleArray[i].matchNode(renderCallback, tags, zoomLevel); + mSubRuleArray[i].matchNode(renderCallback, tags, zoomLevel, matchingList); } } @@ -220,14 +229,15 @@ abstract class Rule { MATCHERS_CACHE_VALUE.clear(); mRenderInstructionArray = new RenderInstruction[mRenderInstructions.size()]; - - for (int i = 0, n = mRenderInstructions.size(); i < n; i++) - mRenderInstructionArray[i] = mRenderInstructions.get(i); + mRenderInstructions.toArray(mRenderInstructionArray); + // for (int i = 0, n = mRenderInstructions.size(); i < n; i++) + // mRenderInstructionArray[i] = mRenderInstructions.get(i); mSubRuleArray = new Rule[mSubRules.size()]; + mSubRules.toArray(mSubRuleArray); - for (int i = 0, n = mSubRules.size(); i < n; i++) - mSubRuleArray[i] = mSubRules.get(i); + // for (int i = 0, n = mSubRules.size(); i < n; i++) + // mSubRuleArray[i] = mSubRules.get(i); mRenderInstructions.clear(); mRenderInstructions = null; diff --git a/src/org/mapsforge/android/rendertheme/osmarender/osmarender.xml b/src/org/mapsforge/android/rendertheme/osmarender/osmarender.xml index 1d4cc5d1..e87a8cc7 100644 --- a/src/org/mapsforge/android/rendertheme/osmarender/osmarender.xml +++ b/src/org/mapsforge/android/rendertheme/osmarender/osmarender.xml @@ -2,322 +2,437 @@ + version="1" map-background="#fcfcfa"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - + + + - + - + - - + + - - + + + + + + + stroke="#e4e4e4" width="0.2" /> --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + + - + - - - - - + + + + + + + + + - - + + + - + + - + + - + - + + - + - - + + + + + + + + + + + + + + + + + + - - - - + + + + - - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + @@ -325,7 +440,7 @@ - + @@ -340,30 +455,28 @@ - + + + + - - + fill="#707070" /> + --> + - - - - + + + - + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + - - + - + - - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + stroke="#909090" width="0.1" cap="butt" /> --> - + - + - - + @@ -583,47 +805,42 @@ - - - - - + + - + - - + + - - - - + - + @@ -631,37 +848,33 @@ - + - + - + - + - + @@ -669,26 +882,26 @@ - - - - + - + - + - + + + + @@ -708,22 +921,22 @@ + width="2.0" /> --> + width="2.0" /> --> - - + @@ -734,7 +947,7 @@ - + @@ -744,12 +957,12 @@ - + - + diff --git a/src/org/mapsforge/android/rendertheme/renderTheme.xsd b/src/org/mapsforge/android/rendertheme/renderTheme.xsd index 2513efb0..3435f0cd 100644 --- a/src/org/mapsforge/android/rendertheme/renderTheme.xsd +++ b/src/org/mapsforge/android/rendertheme/renderTheme.xsd @@ -132,11 +132,11 @@ - - @@ -163,6 +163,7 @@ + @@ -190,7 +191,7 @@ - + @@ -198,6 +199,7 @@ + @@ -214,6 +216,12 @@ + + + + + + diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/Area.java b/src/org/mapsforge/android/rendertheme/renderinstruction/Area.java index 1f9a6125..442ddeb4 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/Area.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/Area.java @@ -14,23 +14,17 @@ */ package org.mapsforge.android.rendertheme.renderinstruction; -import java.io.IOException; - import org.mapsforge.android.rendertheme.IRenderCallback; import org.mapsforge.android.rendertheme.RenderThemeHandler; import org.mapsforge.core.Tag; import org.xml.sax.Attributes; import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Cap; -import android.graphics.Paint.Style; -import android.graphics.Shader; /** * Represents a closed polygon on the map. */ -public final class Area implements RenderInstruction { +public final class Area extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -39,11 +33,8 @@ public final class Area implements RenderInstruction { * @param level * the drawing level of this instruction. * @return a new Area with the given rendering attributes. - * @throws IOException - * if an I/O error occurs while reading a resource. */ - public static Area create(String elementName, Attributes attributes, int level) - throws IOException { + public static Area create(String elementName, Attributes attributes, int level) { String src = null; int fill = Color.BLACK; int stroke = Color.TRANSPARENT; @@ -51,12 +42,14 @@ public final class Area implements RenderInstruction { int fade = -1; int blend = -1; int blendFill = Color.BLACK; + String style = null; for (int i = 0; i < attributes.getLength(); ++i) { String name = attributes.getLocalName(i); String value = attributes.getValue(i); - - if ("src".equals(name)) { + if ("name".equals(name)) + style = value; + else if ("src".equals(name)) { src = value; } else if ("fill".equals(name)) { fill = Color.parseColor(value); @@ -76,7 +69,8 @@ public final class Area implements RenderInstruction { } validate(strokeWidth); - return new Area(src, fill, stroke, strokeWidth, fade, level, blend, blendFill); + return new Area(style, src, fill, stroke, strokeWidth, fade, level, blend, + blendFill); } private static void validate(float strokeWidth) { @@ -86,32 +80,38 @@ public final class Area implements RenderInstruction { } } - private Area(String src, int fill, int stroke, float strokeWidth, int fade, - int level, int blend, int blendFill) - throws IOException { + private Area(String style, String src, int fill, int stroke, float strokeWidth, + int fade, int level, int blend, int blendFill) { super(); + this.style = style; - if (fill == Color.TRANSPARENT) { - paintFill = null; - } else { - paintFill = new Paint(Paint.ANTI_ALIAS_FLAG); - if (src != null) { - Shader shader = BitmapUtils.createBitmapShader(src); - paintFill.setShader(shader); - } - paintFill.setStyle(Style.FILL); - paintFill.setColor(fill); - paintFill.setStrokeCap(Cap.ROUND); - } + // if (fill == Color.TRANSPARENT) { + // paintFill = null; + // } else { + // paintFill = new Paint(Paint.ANTI_ALIAS_FLAG); + // if (src != null) { + // Shader shader = BitmapUtils.createBitmapShader(src); + // paintFill.setShader(shader); + // } + // paintFill.setStyle(Style.FILL); + // paintFill.setColor(fill); + // paintFill.setStrokeCap(Cap.ROUND); + // } + // + // if (stroke == Color.TRANSPARENT) { + // paintOutline = null; + // } else { + // paintOutline = new Paint(Paint.ANTI_ALIAS_FLAG); + // paintOutline.setStyle(Style.STROKE); + // paintOutline.setColor(stroke); + // paintOutline.setStrokeCap(Cap.ROUND); + // } - if (stroke == Color.TRANSPARENT) { - paintOutline = null; - } else { - paintOutline = new Paint(Paint.ANTI_ALIAS_FLAG); - paintOutline.setStyle(Style.STROKE); - paintOutline.setColor(stroke); - paintOutline.setStrokeCap(Cap.ROUND); - } + // if (stroke == Color.TRANSPARENT) { + // stroke = null; + // } else{ + // stroke = new Line() + // } color = new float[4]; color[0] = (fill >> 16 & 0xff) / 255.0f; @@ -135,47 +135,31 @@ public final class Area implements RenderInstruction { this.level = level; } - @Override - public void destroy() { - // do nothing - } - - @Override - public void renderNode(IRenderCallback renderCallback, Tag[] tags) { - // do nothing - } - @Override public void renderWay(IRenderCallback renderCallback, Tag[] tags) { - if (paintFill != null) { - renderCallback.renderArea(this); - } + renderCallback.renderArea(this, this.level); } - @Override - public void scaleStrokeWidth(float scaleFactor) { - if (paintOutline != null) { - paintOutline.setStrokeWidth(strokeWidth * scaleFactor); - } - } - - @Override - public void scaleTextSize(float scaleFactor) { - // do nothing - } + // @Override + // public void scaleStrokeWidth(float scaleFactor) { + // // if (paintOutline != null) { + // // paintOutline.setStrokeWidth(strokeWidth * scaleFactor); + // // } + // } + public String style; /** * */ - public final int level; + private final int level; /** * */ - public final Paint paintFill; + // public final Paint paintFill; /** * */ - public final Paint paintOutline; + // public final Paint paintOutline; /** * */ diff --git a/src/org/mapsforge/android/glrenderer/TextLayer.java b/src/org/mapsforge/android/rendertheme/renderinstruction/AreaLevel.java similarity index 60% rename from src/org/mapsforge/android/glrenderer/TextLayer.java rename to src/org/mapsforge/android/rendertheme/renderinstruction/AreaLevel.java index a833b601..5beb5a2f 100644 --- a/src/org/mapsforge/android/glrenderer/TextLayer.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/AreaLevel.java @@ -12,17 +12,22 @@ * 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; +package org.mapsforge.android.rendertheme.renderinstruction; -import java.util.ArrayList; +import org.mapsforge.android.rendertheme.IRenderCallback; +import org.mapsforge.core.Tag; -import org.mapsforge.android.rendertheme.renderinstruction.Caption; - -public class TextLayer { - public ArrayList labels; - - void addLabel(float x, float y, String text, Caption caption) { +public class AreaLevel extends RenderInstruction { + private final Area area; + private final int level; + public AreaLevel(Area area, int level) { + this.area = area; + this.level = level; } + @Override + public void renderWay(IRenderCallback renderCallback, Tag[] tags) { + renderCallback.renderArea(this.area, level); + } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/Caption.java b/src/org/mapsforge/android/rendertheme/renderinstruction/Caption.java index 9c5da127..3c4ec79f 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/Caption.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/Caption.java @@ -32,7 +32,7 @@ import android.util.FloatMath; /** * Represents a text label on the map. */ -public final class Caption implements RenderInstruction { +public final class Caption extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -130,11 +130,6 @@ public final class Caption implements RenderInstruction { fontDescent = FloatMath.ceil(Math.abs(fm.descent)); } - @Override - public void destroy() { - // do nothing - } - @Override public void renderNode(IRenderCallback renderCallback, Tag[] tags) { renderCallback.renderPointOfInterestCaption(this); @@ -145,11 +140,6 @@ public final class Caption implements RenderInstruction { renderCallback.renderAreaCaption(this); } - @Override - public void scaleStrokeWidth(float scaleFactor) { - // do nothing - } - @Override public void scaleTextSize(float scaleFactor) { paint.setTextSize(fontSize * scaleFactor); diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/Circle.java b/src/org/mapsforge/android/rendertheme/renderinstruction/Circle.java index 354a78c2..025d0d1d 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/Circle.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/Circle.java @@ -26,7 +26,7 @@ import android.graphics.Paint.Style; /** * Represents a round area on the map. */ -public final class Circle implements RenderInstruction { +public final class Circle extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -68,11 +68,13 @@ public final class Circle implements RenderInstruction { private static void validate(String elementName, Float radius, float strokeWidth) { if (radius == null) { - throw new IllegalArgumentException("missing attribute r for element: " + elementName); + throw new IllegalArgumentException("missing attribute r for element: " + + elementName); } else if (radius.floatValue() < 0) { throw new IllegalArgumentException("radius must not be negative: " + radius); } else if (strokeWidth < 0) { - throw new IllegalArgumentException("stroke-width must not be negative: " + strokeWidth); + throw new IllegalArgumentException("stroke-width must not be negative: " + + strokeWidth); } } @@ -84,7 +86,8 @@ public final class Circle implements RenderInstruction { private final boolean mScaleRadius; private final float mStrokeWidth; - private Circle(Float radius, boolean scaleRadius, int fill, int stroke, float strokeWidth, int level) { + private Circle(Float radius, boolean scaleRadius, int fill, int stroke, + float strokeWidth, int level) { super(); mRadius = radius.floatValue(); @@ -117,11 +120,6 @@ public final class Circle implements RenderInstruction { } } - @Override - public void destroy() { - // do nothing - } - @Override public void renderNode(IRenderCallback renderCallback, Tag[] tags) { if (mOutline != null) { @@ -132,11 +130,6 @@ public final class Circle implements RenderInstruction { } } - @Override - public void renderWay(IRenderCallback renderCallback, Tag[] tags) { - // do nothing - } - @Override public void scaleStrokeWidth(float scaleFactor) { if (mScaleRadius) { @@ -146,9 +139,4 @@ public final class Circle implements RenderInstruction { } } } - - @Override - public void scaleTextSize(float scaleFactor) { - // do nothing - } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/Line.java b/src/org/mapsforge/android/rendertheme/renderinstruction/Line.java index ec16dd32..1df42522 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/Line.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/Line.java @@ -14,7 +14,6 @@ */ package org.mapsforge.android.rendertheme.renderinstruction; -import java.io.IOException; import java.util.Locale; import java.util.regex.Pattern; @@ -24,73 +23,94 @@ import org.mapsforge.core.Tag; import org.xml.sax.Attributes; import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; import android.graphics.Paint.Cap; -import android.graphics.Paint.Style; -import android.graphics.Shader; /** * Represents a polyline on the map. */ -public final class Line implements RenderInstruction { +public final class Line extends RenderInstruction { private static final Pattern SPLIT_PATTERN = Pattern.compile(","); /** + * @param line + * ... * @param elementName * the name of the XML element. * @param attributes * the attributes of the XML element. * @param level * the drawing level of this instruction. + * @param isOutline + * ... * @return a new Line with the given rendering attributes. - * @throws IOException - * if an I/O error occurs while reading a resource. */ - public static Line create(String elementName, Attributes attributes, int level) - throws IOException { + public static Line create(Line line, String elementName, Attributes attributes, + int level, boolean isOutline) { String src = null; int stroke = Color.BLACK; float strokeWidth = 0; float[] strokeDasharray = null; Cap strokeLinecap = Cap.ROUND; - int outline = -1; int fade = -1; boolean fixed = false; + String style = null; + float blur = 0; + + if (line != null) { + fixed = line.fixed; + fade = line.fade; + strokeLinecap = line.cap; + blur = line.blur; + } for (int i = 0; i < attributes.getLength(); ++i) { String name = attributes.getLocalName(i); String value = attributes.getValue(i); - if ("src".equals(name)) { + if ("name".equals(name)) + style = value; + else if ("src".equals(name)) { src = value; } else if ("stroke".equals(name)) { stroke = Color.parseColor(value); - } else if ("stroke-width".equals(name)) { + } else if ("width".equals(name)) { strokeWidth = Float.parseFloat(value); } else if ("stroke-dasharray".equals(name)) { strokeDasharray = parseFloatArray(value); - } else if ("stroke-linecap".equals(name)) { + } else if ("cap".equals(name)) { strokeLinecap = Cap.valueOf(value.toUpperCase(Locale.ENGLISH)); - } else if ("outline".equals(name)) { - outline = Integer.parseInt(value); } else if ("fade".equals(name)) { fade = Integer.parseInt(value); } else if ("fixed".equals(name)) { fixed = Boolean.parseBoolean(value); + } else if ("blur".equals(name)) { + blur = Float.parseFloat(value); + } else if ("from".equals(name)) { } else { RenderThemeHandler.logUnknownAttribute(elementName, name, value, i); } } - validate(strokeWidth); - return new Line(src, stroke, strokeWidth, strokeDasharray, strokeLinecap, level, - outline, fixed, fade); + if (line != null) { + + strokeWidth = line.width + strokeWidth; + if (strokeWidth <= 0) + strokeWidth = 1; + + return new Line(line, style, src, stroke, strokeWidth, strokeDasharray, + strokeLinecap, level, fixed, fade, blur, isOutline); + } + + if (!isOutline) + validate(strokeWidth); + + return new Line(style, src, stroke, strokeWidth, strokeDasharray, strokeLinecap, + level, fixed, fade, blur, isOutline); } private static void validate(float strokeWidth) { if (strokeWidth < 0) { - throw new IllegalArgumentException("stroke-width must not be negative: " + throw new IllegalArgumentException("width must not be negative: " + strokeWidth); } } @@ -107,15 +127,15 @@ public final class Line implements RenderInstruction { /** * */ - public final int level; + private final int level; /** * */ - public final Paint paint; + // public final Paint paint; /** * */ - public final float strokeWidth; + public final float width; /** * */ @@ -127,7 +147,7 @@ public final class Line implements RenderInstruction { /** * */ - public final int outline; + public final boolean outline; /** * @@ -136,60 +156,83 @@ public final class Line implements RenderInstruction { public final int fade; - private Line(String src, int stroke, float strokeWidth, float[] strokeDasharray, - Cap strokeLinecap, int level, - int outline, boolean fixed, int fade) - throws IOException { + public final String style; + + public final Cap cap; + + public final float blur; + + private Line(String style, String src, int stroke, float strokeWidth, + float[] strokeDasharray, Cap strokeLinecap, int level, boolean fixed, + int fade, float blur, boolean isOutline) { super(); - Shader shader = BitmapUtils.createBitmapShader(src); + this.style = style; + + // paint = new Paint(Paint.ANTI_ALIAS_FLAG); + // + // if (src != null) { + // Shader shader = BitmapUtils.createBitmapShader(src); + // paint.setShader(shader); + // } + // + // paint.setStyle(Style.STROKE); + // paint.setColor(stroke); + // if (strokeDasharray != null) { + // paint.setPathEffect(new DashPathEffect(strokeDasharray, 0)); + // } + // paint.setStrokeCap(strokeLinecap); - paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setShader(shader); - paint.setStyle(Style.STROKE); - paint.setColor(stroke); - if (strokeDasharray != null) { - paint.setPathEffect(new DashPathEffect(strokeDasharray, 0)); - } - paint.setStrokeCap(strokeLinecap); round = (strokeLinecap == Cap.ROUND); + this.cap = strokeLinecap; + color = new float[4]; color[0] = (stroke >> 16 & 0xff) / 255.0f; color[1] = (stroke >> 8 & 0xff) / 255.0f; color[2] = (stroke >> 0 & 0xff) / 255.0f; color[3] = (stroke >> 24 & 0xff) / 255.0f; - this.strokeWidth = strokeWidth; + this.width = strokeWidth; this.level = level; - this.outline = outline; + this.outline = isOutline; this.fixed = fixed; + this.blur = blur; this.fade = fade; } - @Override - public void destroy() { - // do nothing - } + private Line(Line line, String style, String src, int stroke, float strokeWidth, + float[] strokeDasharray, Cap strokeLinecap, int level, boolean fixed, + int fade, float blur, boolean isOutline) { + super(); - @Override - public void renderNode(IRenderCallback renderCallback, Tag[] tags) { - // do nothing + this.style = style; + + round = (strokeLinecap == Cap.ROUND); + + color = line.color; + + this.width = strokeWidth; + this.level = level; + this.outline = isOutline; + this.fixed = fixed; + this.fade = fade; + this.cap = strokeLinecap; + this.blur = blur; } @Override public void renderWay(IRenderCallback renderCallback, Tag[] tags) { // renderCallback.renderWay(mPaint, mLevel, mColor, mStrokeWidth, mRound, mOutline); - renderCallback.renderWay(this); + renderCallback.renderWay(this, level); } - @Override - public void scaleStrokeWidth(float scaleFactor) { - paint.setStrokeWidth(strokeWidth * scaleFactor); - } + // @Override + // public void scaleStrokeWidth(float scaleFactor) { + // paint.setStrokeWidth(strokeWidth * scaleFactor); + // } - @Override - public void scaleTextSize(float scaleFactor) { - // do nothing + public int getLevel() { + return this.level; } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/LineSymbol.java b/src/org/mapsforge/android/rendertheme/renderinstruction/LineSymbol.java index 4e38a903..cc7e5009 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/LineSymbol.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/LineSymbol.java @@ -26,7 +26,7 @@ import android.graphics.Bitmap; /** * Represents an icon along a polyline on the map. */ -public final class LineSymbol implements RenderInstruction { +public final class LineSymbol extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -36,7 +36,8 @@ public final class LineSymbol implements RenderInstruction { * @throws IOException * if an I/O error occurs while reading a resource. */ - public static LineSymbol create(String elementName, Attributes attributes) throws IOException { + public static LineSymbol create(String elementName, Attributes attributes) + throws IOException { String src = null; boolean alignCenter = false; boolean repeat = false; @@ -62,7 +63,8 @@ public final class LineSymbol implements RenderInstruction { private static void validate(String elementName, String src) { if (src == null) { - throw new IllegalArgumentException("missing attribute src for element: " + elementName); + throw new IllegalArgumentException("missing attribute src for element: " + + elementName); } } @@ -70,7 +72,8 @@ public final class LineSymbol implements RenderInstruction { private final Bitmap mBitmap; private final boolean mRepeat; - private LineSymbol(String src, boolean alignCenter, boolean repeat) throws IOException { + private LineSymbol(String src, boolean alignCenter, boolean repeat) + throws IOException { super(); mBitmap = BitmapUtils.createBitmap(src); @@ -83,23 +86,8 @@ public final class LineSymbol implements RenderInstruction { mBitmap.recycle(); } - @Override - public void renderNode(IRenderCallback renderCallback, Tag[] tags) { - // do nothing - } - @Override public void renderWay(IRenderCallback renderCallback, Tag[] tags) { renderCallback.renderWaySymbol(mBitmap, mAlignCenter, mRepeat); } - - @Override - public void scaleStrokeWidth(float scaleFactor) { - // do nothing - } - - @Override - public void scaleTextSize(float scaleFactor) { - // do nothing - } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/PathText.java b/src/org/mapsforge/android/rendertheme/renderinstruction/PathText.java index 1254d8c7..2d255157 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/PathText.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/PathText.java @@ -24,13 +24,15 @@ import org.xml.sax.Attributes; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; +import android.graphics.Paint.FontMetrics; import android.graphics.Paint.Style; import android.graphics.Typeface; +import android.util.FloatMath; /** * Represents a text along a polyline on the map. */ -public final class PathText implements RenderInstruction { +public final class PathText extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -46,12 +48,14 @@ public final class PathText implements RenderInstruction { int fill = Color.BLACK; int stroke = Color.BLACK; float strokeWidth = 0; + String style = null; for (int i = 0; i < attributes.getLength(); ++i) { String name = attributes.getLocalName(i); String value = attributes.getValue(i); - - if ("k".equals(name)) { + if ("name".equals(name)) + style = value; + else if ("k".equals(name)) { textKey = TextKey.getInstance(value); } else if ("font-family".equals(name)) { fontFamily = FontFamily.valueOf(value.toUpperCase(Locale.ENGLISH)); @@ -72,67 +76,69 @@ public final class PathText implements RenderInstruction { validate(elementName, textKey, fontSize, strokeWidth); Typeface typeface = Typeface.create(fontFamily.toTypeface(), fontStyle.toInt()); - return new PathText(textKey, typeface, fontSize, fill, stroke, strokeWidth); + return new PathText(style, textKey, typeface, fontSize, fill, stroke, strokeWidth); } - private static void validate(String elementName, String textKey, float fontSize, float strokeWidth) { + private static void validate(String elementName, String textKey, float fontSize, + float strokeWidth) { if (textKey == null) { - throw new IllegalArgumentException("missing attribute k for element: " + elementName); + throw new IllegalArgumentException("missing attribute k for element: " + + elementName); } else if (fontSize < 0) { - throw new IllegalArgumentException("font-size must not be negative: " + fontSize); + throw new IllegalArgumentException("font-size must not be negative: " + + fontSize); } else if (strokeWidth < 0) { - throw new IllegalArgumentException("stroke-width must not be negative: " + strokeWidth); + throw new IllegalArgumentException("stroke-width must not be negative: " + + strokeWidth); } } - private final float mFontSize; - private final Paint mPaint; - private final Paint mStroke; - private final String mTextKey; + public final float fontSize; + public final Paint paint; + public Paint stroke; + public String textKey; + public final float fontHeight; + public final float fontDescent; + public String style; - private PathText(String textKey, Typeface typeface, float fontSize, int fill, int stroke, float strokeWidth) { + private PathText(String style, String textKey, Typeface typeface, float fontSize, + int fill, int outline, float strokeWidth) { super(); - mTextKey = textKey; + this.style = style; - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setTextAlign(Align.CENTER); - mPaint.setTypeface(typeface); - mPaint.setColor(fill); + this.textKey = textKey; - mStroke = new Paint(Paint.ANTI_ALIAS_FLAG); - mStroke.setStyle(Style.STROKE); - mStroke.setTextAlign(Align.CENTER); - mStroke.setTypeface(typeface); - mStroke.setColor(stroke); - mStroke.setStrokeWidth(strokeWidth); + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setTextAlign(Align.CENTER); + paint.setTypeface(typeface); + paint.setColor(fill); - mFontSize = fontSize; - } + stroke = new Paint(Paint.ANTI_ALIAS_FLAG); + stroke.setStyle(Style.STROKE); + stroke.setTextAlign(Align.CENTER); + stroke.setTypeface(typeface); + stroke.setColor(outline); + stroke.setStrokeWidth(strokeWidth); - @Override - public void destroy() { - // do nothing - } + this.fontSize = fontSize; - @Override - public void renderNode(IRenderCallback renderCallback, Tag[] tags) { - // do nothing + paint.setTextSize(fontSize); + stroke.setTextSize(fontSize); + + FontMetrics fm = paint.getFontMetrics(); + fontHeight = FloatMath.ceil(Math.abs(fm.bottom) + Math.abs(fm.top)); + fontDescent = FloatMath.ceil(Math.abs(fm.descent)); } @Override public void renderWay(IRenderCallback renderCallback, Tag[] tags) { - renderCallback.renderWayText(mTextKey, mPaint, mStroke); - } - - @Override - public void scaleStrokeWidth(float scaleFactor) { - // do nothing + renderCallback.renderWayText(this); } @Override public void scaleTextSize(float scaleFactor) { - mPaint.setTextSize(mFontSize * scaleFactor); - mStroke.setTextSize(mFontSize * scaleFactor); + paint.setTextSize(fontSize * scaleFactor); + stroke.setTextSize(fontSize * scaleFactor); } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/RenderInstruction.java b/src/org/mapsforge/android/rendertheme/renderinstruction/RenderInstruction.java index 165c3d83..643c965f 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/RenderInstruction.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/RenderInstruction.java @@ -20,11 +20,12 @@ import org.mapsforge.core.Tag; /** * A RenderInstruction is a basic graphical primitive to draw a map. */ -public interface RenderInstruction { +public abstract class RenderInstruction { /** * Destroys this RenderInstruction and cleans up all its internal resources. */ - void destroy(); + public void destroy() { + } /** * @param renderCallback @@ -32,7 +33,8 @@ public interface RenderInstruction { * @param tags * the tags of the node. */ - void renderNode(IRenderCallback renderCallback, Tag[] tags); + public void renderNode(IRenderCallback renderCallback, Tag[] tags) { + } /** * @param renderCallback @@ -40,7 +42,8 @@ public interface RenderInstruction { * @param tags * the tags of the way. */ - void renderWay(IRenderCallback renderCallback, Tag[] tags); + public void renderWay(IRenderCallback renderCallback, Tag[] tags) { + } /** * Scales the stroke width of this RenderInstruction by the given factor. @@ -48,7 +51,8 @@ public interface RenderInstruction { * @param scaleFactor * the factor by which the stroke width should be scaled. */ - void scaleStrokeWidth(float scaleFactor); + public void scaleStrokeWidth(float scaleFactor) { + } /** * Scales the text size of this RenderInstruction by the given factor. @@ -56,5 +60,6 @@ public interface RenderInstruction { * @param scaleFactor * the factor by which the text size should be scaled. */ - void scaleTextSize(float scaleFactor); + public void scaleTextSize(float scaleFactor) { + } } diff --git a/src/org/mapsforge/android/rendertheme/renderinstruction/Symbol.java b/src/org/mapsforge/android/rendertheme/renderinstruction/Symbol.java index e93f711f..0e594cb9 100644 --- a/src/org/mapsforge/android/rendertheme/renderinstruction/Symbol.java +++ b/src/org/mapsforge/android/rendertheme/renderinstruction/Symbol.java @@ -26,7 +26,7 @@ import android.graphics.Bitmap; /** * Represents an icon on the map. */ -public final class Symbol implements RenderInstruction { +public final class Symbol extends RenderInstruction { /** * @param elementName * the name of the XML element. @@ -36,7 +36,8 @@ public final class Symbol implements RenderInstruction { * @throws IOException * if an I/O error occurs while reading a resource. */ - public static Symbol create(String elementName, Attributes attributes) throws IOException { + public static Symbol create(String elementName, Attributes attributes) + throws IOException { String src = null; for (int i = 0; i < attributes.getLength(); ++i) { @@ -56,7 +57,8 @@ public final class Symbol implements RenderInstruction { private static void validate(String elementName, String src) { if (src == null) { - throw new IllegalArgumentException("missing attribute src for element: " + elementName); + throw new IllegalArgumentException("missing attribute src for element: " + + elementName); } } @@ -82,14 +84,4 @@ public final class Symbol implements RenderInstruction { public void renderWay(IRenderCallback renderCallback, Tag[] tags) { renderCallback.renderAreaSymbol(mBitmap); } - - @Override - public void scaleStrokeWidth(float scaleFactor) { - // do nothing - } - - @Override - public void scaleTextSize(float scaleFactor) { - // do nothing - } } diff --git a/src/org/mapsforge/android/swrenderer/MapGenerator.java b/src/org/mapsforge/android/swrenderer/MapGenerator.java index 9d6e71d0..c757342a 100644 --- a/src/org/mapsforge/android/swrenderer/MapGenerator.java +++ b/src/org/mapsforge/android/swrenderer/MapGenerator.java @@ -24,6 +24,7 @@ import org.mapsforge.android.rendertheme.RenderTheme; import org.mapsforge.android.rendertheme.renderinstruction.Area; import org.mapsforge.android.rendertheme.renderinstruction.Caption; import org.mapsforge.android.rendertheme.renderinstruction.Line; +import org.mapsforge.android.rendertheme.renderinstruction.PathText; import org.mapsforge.core.Tag; import org.mapsforge.core.Tile; import org.mapsforge.database.IMapDatabase; @@ -414,24 +415,24 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, private List mCurLevelContainer2; @Override - public void renderWay(Line line) { - List c = mDrawingLayer.add(line.level, mWayDataContainer, - line.paint); - - if (mCurLevelContainer1 == null) - mCurLevelContainer1 = c; - else if (mCurLevelContainer2 == null) - mCurLevelContainer2 = c; + public void renderWay(Line line, int level) { + // List c = mDrawingLayer.add(level, mWayDataContainer, + // line.paint); + // + // if (mCurLevelContainer1 == null) + // mCurLevelContainer1 = c; + // else if (mCurLevelContainer2 == null) + // mCurLevelContainer2 = c; } @Override - public void renderArea(Area area) { - if (area.paintFill != null) - mCurLevelContainer1 = mDrawingLayer.add(area.level, mWayDataContainer, - area.paintFill); - if (area.paintOutline != null) - mCurLevelContainer1 = mDrawingLayer.add(area.level, mWayDataContainer, - area.paintOutline); + public void renderArea(Area area, int level) { + // if (area.paintFill != null) + // mCurLevelContainer1 = mDrawingLayer.add(level, mWayDataContainer, + // area.paintFill); + // if (area.paintOutline != null) + // mCurLevelContainer1 = mDrawingLayer.add(level, mWayDataContainer, + // area.paintOutline); } @Override @@ -443,7 +444,7 @@ public class MapGenerator implements IMapGenerator, IRenderCallback, } @Override - public void renderWayText(String textKey, Paint paint, Paint outline) { + public void renderWayText(PathText pathText) { // if (mWayDataContainer.textPos[0] >= 0) // WayDecorator.renderText(this, paint, outline, mCoords, mWayDataContainer, mWayNames); } diff --git a/src/org/mapsforge/android/swrenderer/MapRenderer.java b/src/org/mapsforge/android/swrenderer/MapRenderer.java index 8380d8a0..21c4bbf7 100644 --- a/src/org/mapsforge/android/swrenderer/MapRenderer.java +++ b/src/org/mapsforge/android/swrenderer/MapRenderer.java @@ -33,6 +33,7 @@ import org.mapsforge.android.mapgenerator.JobParameters; import org.mapsforge.android.mapgenerator.MapGeneratorJob; import org.mapsforge.android.mapgenerator.TileCacheKey; import org.mapsforge.android.mapgenerator.TileDistanceSort; +import org.mapsforge.android.rendertheme.RenderTheme; import org.mapsforge.android.utils.GlUtils; import org.mapsforge.core.MapPosition; import org.mapsforge.core.MercatorProjection; @@ -213,7 +214,7 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { newTiles[tiles++] = tile; if (!tile.isReady || (tile.getScale() != scale)) { - tile.isLoading = true; + // tile.isLoading = true; // approximation for TileScheduler if (tileY < tileTop || tileY > tileBottom || tileX < tileLeft || tileX > tileRight) @@ -494,12 +495,6 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { } } } - // FIXME - // if (loadedTexture) { - // synchronized (mMapWorker) { - // mMapWorker.notify(); - // } - // } } @Override @@ -588,4 +583,10 @@ public class MapRenderer implements org.mapsforge.android.IMapRenderer { public IMapGenerator createMapGenerator() { return new MapGenerator(); } + + @Override + public void setRenderTheme(RenderTheme t) { + // TODO Auto-generated method stub + + } } diff --git a/src/org/mapsforge/android/swrenderer/WayDecorator.java b/src/org/mapsforge/android/swrenderer/WayDecorator.java index 875c195e..e83481ac 100644 --- a/src/org/mapsforge/android/swrenderer/WayDecorator.java +++ b/src/org/mapsforge/android/swrenderer/WayDecorator.java @@ -38,7 +38,8 @@ final class WayDecorator { */ private static final int SEGMENT_SAFETY_DISTANCE = 30; - static void renderSymbol(Bitmap symbolBitmap, boolean alignCenter, boolean repeatSymbol, float[][] coordinates, + static void renderSymbol(Bitmap symbolBitmap, boolean alignCenter, + boolean repeatSymbol, float[][] coordinates, List waySymbols) { int skipPixels = SEGMENT_SAFETY_DISTANCE; @@ -68,9 +69,11 @@ final class WayDecorator { // move the previous point forward towards the current point previousX += diffX * segmentSkipPercentage; previousY += diffY * segmentSkipPercentage; - symbolAngle = (float) Math.toDegrees(Math.atan2(currentY - previousY, currentX - previousX)); + symbolAngle = (float) Math.toDegrees(Math.atan2(currentY - previousY, + currentX - previousX)); - waySymbols.add(new SymbolContainer(symbolBitmap, previousX, previousY, alignCenter, symbolAngle)); + waySymbols.add(new SymbolContainer(symbolBitmap, previousX, previousY, + alignCenter, symbolAngle)); // check if the symbol should only be rendered once if (!repeatSymbol) { @@ -145,7 +148,8 @@ final class WayDecorator { } else if ((currentY - nextY) == 0) break; - float diff = ((float) (diffX) / (diffY) - (float) (currentX - nextX) / (currentY - nextY)); + float diff = ((float) (diffX) / (diffY) - (float) (currentX - nextX) + / (currentY - nextY)); // skip segments with corners if (diff >= 0.2 || diff <= -0.2) @@ -210,7 +214,7 @@ final class WayDecorator { y2 = previousY; } - // estimate position of test on path + // estimate position of text on path width = (x2 - x1) / 2; x2 = x2 - (int) (width - s * width); x1 = x1 + (int) (width - s * width); @@ -234,7 +238,8 @@ final class WayDecorator { break; // check crossings - if (GeometryUtils.lineIntersect(x1, y1, x2, y2, wtc2.x1, wtc2.y1, wtc2.x2, wtc2.y2)) { + if (GeometryUtils.lineIntersect(x1, y1, x2, y2, wtc2.x1, wtc2.y1, + wtc2.x2, wtc2.y2)) { intersects = true; break; } @@ -244,7 +249,8 @@ final class WayDecorator { short top2 = (wtc2.y1 < wtc2.y2 ? wtc2.y1 : wtc2.y2); short bot2 = (wtc2.y1 < wtc2.y2 ? wtc2.y2 : wtc2.y1); - if (x1 - 10 < wtc2.x2 && wtc2.x1 - 10 < x2 && top - 10 < bot2 && top2 - 10 < bot) { + if (x1 - 10 < wtc2.x2 && wtc2.x1 - 10 < x2 && top - 10 < bot2 + && top2 - 10 < bot) { if (wtc2.text.equals(text)) { intersects = true; @@ -260,7 +266,8 @@ final class WayDecorator { } Log.d("mapsforge", "add " + text + " " + first + " " + last); - WayTextContainer wtc = new WayTextContainer(first, last, wayDataContainer, text, + WayTextContainer wtc = new WayTextContainer(first, last, + wayDataContainer, text, paint); wtc.x1 = (short) x1; wtc.y1 = (short) y1; @@ -272,7 +279,8 @@ final class WayDecorator { containerSize++; if (outline != null) { - wayNames.add(new WayTextContainer(first, last, wayDataContainer, text, outline)); + wayNames.add(new WayTextContainer(first, last, wayDataContainer, + text, outline)); containerSize++; } // 500 ??? how big is a tile?! diff --git a/src/org/mapsforge/android/utils/GlConfigChooser.java b/src/org/mapsforge/android/utils/GlConfigChooser.java index 278ff10b..2ed335f9 100644 --- a/src/org/mapsforge/android/utils/GlConfigChooser.java +++ b/src/org/mapsforge/android/utils/GlConfigChooser.java @@ -29,7 +29,7 @@ public class GlConfigChooser implements GLSurfaceView.EGLConfigChooser { EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5, EGL10.EGL_ALPHA_SIZE, 0, - EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_DEPTH_SIZE, 16, // Requires that setEGLContextClientVersion(2) is called on the view. EGL10.EGL_RENDERABLE_TYPE, 4 /* EGL_OPENGL_ES2_BIT */, // EGL10.EGL_SAMPLE_BUFFERS, 1 /* true */, @@ -50,7 +50,7 @@ public class GlConfigChooser implements GLSurfaceView.EGLConfigChooser { EGL10.EGL_GREEN_SIZE, 6, EGL10.EGL_BLUE_SIZE, 5, EGL10.EGL_ALPHA_SIZE, 0, - EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_DEPTH_SIZE, 16, EGL10.EGL_RENDERABLE_TYPE, 4 /* EGL_OPENGL_ES2_BIT */, EGL10.EGL_STENCIL_SIZE, 4, EGL10.EGL_NONE }; @@ -93,7 +93,10 @@ public class GlConfigChooser implements GLSurfaceView.EGLConfigChooser { // // else // if (findConfigAttrib(egl, display, configs[i], EGL10.EGL_RED_SIZE, 0) == 5 // && - // findConfigAttrib(egl, display, configs[i], EGL10.EGL_ALPHA_SIZE, 0) == 0) { + // findConfigAttrib(egl, display, configs[i], EGL10.EGL_ALPHA_SIZE, 0) == 0 + // // && + // // findConfigAttrib(egl, display, configs[i], EGL10.EGL_DEPTH_SIZE, 0) == 16 + // ) { // index = i; // break; // } diff --git a/src/org/mapsforge/core/Tag.java b/src/org/mapsforge/core/Tag.java index 76fdb66d..38e246f6 100644 --- a/src/org/mapsforge/core/Tag.java +++ b/src/org/mapsforge/core/Tag.java @@ -92,9 +92,23 @@ public class Tag { this.hashCodeValue = calculateHashCode(); } + /** + * @param key + * the key of the tag. + * @param value + * the value of the tag. + * @param intern + * true when string should be intern()alized. + */ public Tag(String key, String value, boolean intern) { - this.key = (key == null ? null : key); - this.value = (value == null ? null : value); + if (intern) { + this.key = (key == null ? null : key.intern()); + this.value = (value == null ? null : value.intern()); + } + else { + this.key = (key == null ? null : key); + this.value = (value == null ? null : value); + } this.hashCodeValue = calculateHashCode(); } diff --git a/src/org/mapsforge/core/Tile.java b/src/org/mapsforge/core/Tile.java index 7d6a3fcd..a663417f 100644 --- a/src/org/mapsforge/core/Tile.java +++ b/src/org/mapsforge/core/Tile.java @@ -60,6 +60,8 @@ 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/mapfile/MapDatabase.java b/src/org/mapsforge/database/mapfile/MapDatabase.java index f92c48c3..1dc11917 100644 --- a/src/org/mapsforge/database/mapfile/MapDatabase.java +++ b/src/org/mapsforge/database/mapfile/MapDatabase.java @@ -251,7 +251,7 @@ public class MapDatabase implements IMapDatabase { @Override public String getMapProjection() { - return getMapFileInfo().projectionName; + return "WSG84"; // getMapFileInfo().projectionName; } /* @@ -883,14 +883,25 @@ public class MapDatabase implements IMapDatabase { boolean featureWayDoubleDeltaEncoding = (featureByte & WAY_FEATURE_DOUBLE_DELTA_ENCODING) != 0; // check if the way has a name - if ((featureByte & WAY_FEATURE_NAME) != 0) + if ((featureByte & WAY_FEATURE_NAME) != 0) { textPos[0] = mReadBuffer.readUnsignedInt(); + String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + + textPos[0]); + if (changed) { + Tag[] tmp = tags; + tags = new Tag[tmp.length + 1]; + System.arraycopy(tmp, 0, tags, 0, tmp.length); + } + tags[tags.length - 1] = new Tag("name", str, false); + } else textPos[0] = -1; // check if the way has a house number - if ((featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0) + if ((featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0) { textPos[1] = mReadBuffer.readUnsignedInt(); + + } else textPos[1] = -1; diff --git a/src/org/mapsforge/database/pbmap/MapDatabase.java b/src/org/mapsforge/database/pbmap/MapDatabase.java index 2b3e79d8..7a773b0a 100644 --- a/src/org/mapsforge/database/pbmap/MapDatabase.java +++ b/src/org/mapsforge/database/pbmap/MapDatabase.java @@ -19,10 +19,10 @@ import java.io.IOException; import java.io.InputStream; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.util.HashMap; -import java.util.zip.GZIPInputStream; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; -import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; @@ -33,7 +33,6 @@ import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicHeader; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; @@ -41,7 +40,6 @@ 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; import org.mapsforge.database.IMapDatabaseCallback; @@ -60,45 +58,62 @@ public class MapDatabase implements IMapDatabase { private static final MapFileInfo mMapInfo = new MapFileInfo(new BoundingBox(-180, -90, 180, 90), new Byte((byte) 4), new GeoPoint(53.11, 8.85), - WebMercator.NAME, 0, 0, 0, "de", "comment", "author"); + null, 0, 0, 0, "de", "comment", "author"); private boolean mOpenFile = false; // private static final String URL = "http://city.informatik.uni-bremen.de:8020/test/%d/%d/%d.osmtile"; private static final String URL = "http://city.informatik.uni-bremen.de/osmstache/test/%d/%d/%d.osmtile"; // private static final String URL = "http://city.informatik.uni-bremen.de/osmstache/gis2/%d/%d/%d.osmtile"; - private static final Header encodingHeader = - new BasicHeader("Accept-Encoding", "gzip"); + // private static final Header encodingHeader = + // new BasicHeader("Accept-Encoding", "gzip"); - private static volatile HashMap tagHash = new HashMap(100); + private static final int MAX_TAGS_CACHE = 100; + private static Map tagHash = Collections + .synchronizedMap(new LinkedHashMap( + MAX_TAGS_CACHE, 0.75f, true) { - private Tag[] curTags = new Tag[250]; + private static final long serialVersionUID = 1L; + + @Override + protected boolean removeEldestEntry(Entry e) { + if (size() < MAX_TAGS_CACHE) + return false; + + // Log.d(TAG, "cache: drop " + e.getValue()); + return true; + } + }); + + private final static float REF_TILE_SIZE = 4096.0f; + + private int MAX_TILE_TAGS = 100; + private Tag[] curTags = new Tag[MAX_TILE_TAGS]; private int mCurTagCnt; private HttpClient mClient; private IMapDatabaseCallback mMapGenerator; private float mScaleFactor; private HttpGet mRequest = null; + private Tile mTile; @Override public QueryResult executeQuery(Tile tile, IMapDatabaseCallback mapDatabaseCallback) { mCanceled = false; + // just used for debugging .... + mTile = tile; // Log.d(TAG, "get tile >> : " + tile); String url = String.format(URL, Integer.valueOf(tile.zoomLevel), Long.valueOf(tile.tileX), Long.valueOf(tile.tileY)); HttpGet getRequest = new HttpGet(url); - getRequest.addHeader(encodingHeader); mRequest = getRequest; mMapGenerator = mapDatabaseCallback; mCurTagCnt = 0; - // using variable coordinate scalefactor to take advantage of varint - mScaleFactor = 1 / 100f; - if (tile.zoomLevel < 12) - mScaleFactor = (float) Math.pow(2, (12 - tile.zoomLevel)) / 100f; + mScaleFactor = REF_TILE_SIZE / Tile.TILE_SIZE; try { HttpResponse response = mClient.execute(getRequest); @@ -110,17 +125,24 @@ public class MapDatabase implements IMapDatabase { entity.consumeContent(); return QueryResult.FAILED; } + + if (mTile.isCanceled) { + Log.d(TAG, "1 loading canceled " + mTile); + entity.consumeContent(); + return QueryResult.FAILED; + } + InputStream is = null; - GZIPInputStream zis = null; + // GZIPInputStream zis = null; try { is = entity.getContent(); - zis = new GZIPInputStream(is); + // zis = new GZIPInputStream(is); - decode(zis); + decode(is); } finally { - if (zis != null) - zis.close(); + // if (zis != null) + // zis.close(); if (is != null) is.close(); entity.consumeContent(); @@ -137,6 +159,12 @@ public class MapDatabase implements IMapDatabase { return QueryResult.FAILED; } mRequest = null; + + if (mTile.isCanceled) { + Log.d(TAG, "2 loading canceled " + mTile); + return QueryResult.FAILED; + } + // Log.d(TAG, "get tile << : " + tile); return QueryResult.SUCCESS; @@ -144,7 +172,7 @@ public class MapDatabase implements IMapDatabase { @Override public String getMapProjection() { - return WebMercator.NAME; + return null; } @Override @@ -158,8 +186,6 @@ public class MapDatabase implements IMapDatabase { } private void createClient() { - // mClient = AndroidHttpClient.newInstance("Android"); - mOpenFile = true; HttpParams params = new BasicHttpParams(); HttpConnectionParams.setStaleCheckingEnabled(params, false); @@ -193,7 +219,8 @@ public class MapDatabase implements IMapDatabase { return null; } - private static final int BUFFER_SIZE = 65536 * 2; + // // // hand sewed tile protocol buffers decoder // // // + private static final int BUFFER_SIZE = 65536; private final byte[] buffer = new byte[BUFFER_SIZE]; private int bufferPos; @@ -261,20 +288,25 @@ public class MapDatabase implements IMapDatabase { String tagString = decodeString(); Tag tag = tagHash.get(tagString); + if (tag == null) { - // Log.d(TAG, "tag:" + tagString); - if (tagString.equals("name=")) - tag = new Tag(tagString, false); + if (tagString.startsWith(Tag.TAG_KEY_NAME)) + tag = new Tag(Tag.TAG_KEY_NAME, tagString.substring(5), false); else tag = new Tag(tagString); tagHash.put(tagString, tag); } - // FIXME ... - if (mCurTagCnt < 250) - curTags[mCurTagCnt++] = tag; - // + // FIXME ... + if (mCurTagCnt >= MAX_TILE_TAGS) { + MAX_TILE_TAGS = mCurTagCnt + 10; + Tag[] tmp = new Tag[MAX_TILE_TAGS]; + System.arraycopy(curTags, 0, tmp, 0, mCurTagCnt); + curTags = tmp; + } + curTags[mCurTagCnt++] = tag; + return true; } @@ -285,7 +317,7 @@ public class MapDatabase implements IMapDatabase { int indexCnt = 0; int tagCnt = 0; int coordCnt = 0; - int layer = 0; + int layer = 5; Tag[] tags = null; short[] index = null; @@ -294,7 +326,6 @@ public class MapDatabase implements IMapDatabase { while (bytesRead < end) { // read tag and wire type - int val = decodeVarint32(); if (val == 0) break; @@ -350,18 +381,10 @@ public class MapDatabase implements IMapDatabase { } float[] coords = tmpCoords; - int pos = 0; - float z = mScaleFactor; - for (int j = 0, m = indexCnt; j < m; j++) { - float lastX = 0; - float lastY = 0; - - for (int n = index[j] + pos; pos < n; pos += 2) { - lastX = coords[pos] = (coords[pos] * z) + lastX; - lastY = coords[pos + 1] = (coords[pos + 1] * z) + lastY; - } - } + // FIXME !!!!! + if (layer == 0) + layer = 5; mMapGenerator.renderWay((byte) layer, tags, coords, index, polygon); return true; @@ -429,12 +452,20 @@ public class MapDatabase implements IMapDatabase { readBuffer(bytes); int cnt = 0; int end = bufferPos + bytes; - + float scale = mScaleFactor; // read repeated sint32 + int lastX = 0; + int lastY = 0; while (bufferPos < end && cnt < numNodes) { - float lon = decodeZigZag32(decodeVarint32()) * mScaleFactor; - float lat = decodeZigZag32(decodeVarint32()) * mScaleFactor; - mMapGenerator.renderPointOfInterest(layer, lat, lon, tags); + int lon = decodeZigZag32(decodeVarint32()); // * mScaleFactor; + int lat = decodeZigZag32(decodeVarint32()); // * mScaleFactor; + lastX = lon + lastX; + lastY = lat + lastY; + + mMapGenerator.renderPointOfInterest(layer, + lastY / scale, + lastX / scale, + tags); cnt += 2; } return cnt; @@ -443,13 +474,6 @@ public class MapDatabase implements IMapDatabase { private int MAX_WAY_COORDS = 32768; private float[] tmpCoords = new float[MAX_WAY_COORDS]; - // private boolean ensureBufferSize(int size) throws IOException { - // if (size > (bufferSize - bufferPos)) - // readBuffer(size - (bufferSize - bufferPos)); - // - // return true; - // } - private Tag[] decodeWayTags(int tagCnt) throws IOException { int bytes = decodeVarint32(); @@ -457,33 +481,45 @@ public class MapDatabase implements IMapDatabase { int cnt = 0; int end = bytesRead + bytes; - int max = curTags.length; + int max = mCurTagCnt; while (bytesRead < end) { int tagNum = decodeVarint32(); - if (tagNum < 0 || cnt == tagCnt) - continue; - + if (tagNum < 0 || cnt == tagCnt) { + Log.d(TAG, "NULL TAG: " + mTile + " invalid tag:" + tagNum + " " + + tagCnt + "/" + cnt); + break; + } if (tagNum < Tags.MAX) tags[cnt++] = Tags.tags[tagNum]; else { tagNum -= Tags.LIMIT; + if (tagNum >= 0 && tagNum < max) { // Log.d(TAG, "variable tag: " + curTags[tagNum]); tags[cnt++] = curTags[tagNum]; + } else { + Log.d(TAG, "NULL TAG: " + mTile + " could find tag:" + tagNum + " " + + tagCnt + "/" + cnt); } } - // else DEBUG... } + if (tagCnt != cnt) + Log.d(TAG, "NULL TAG: " + mTile + " ..."); return tags; } + private short[] mIndices = new short[10]; + private short[] decodeWayIndices(int indexCnt) throws IOException { int bytes = decodeVarint32(); - short[] index = new short[indexCnt]; + short[] index = mIndices; + if (index.length < indexCnt + 1) { + index = mIndices = new short[indexCnt + 1]; + } int cnt = 0; int end = bytesRead + bytes; @@ -495,6 +531,9 @@ public class MapDatabase implements IMapDatabase { // else DEBUG... } + + index[indexCnt] = -1; + return index; } @@ -514,67 +553,78 @@ public class MapDatabase implements IMapDatabase { int cnt = 0; int result; + int x, lastX = 0; + int y, lastY = 0; + boolean even = true; + + float scale = mScaleFactor; + // read repeated sint32 while (pos < end) { if (cnt >= MAX_WAY_COORDS) { + Log.d(TAG, "increase way coord buffer " + mTile); + MAX_WAY_COORDS += 128; float[] tmp = new float[MAX_WAY_COORDS]; System.arraycopy(coords, 0, tmp, 0, cnt); tmpCoords = coords = tmp; } - byte tmp = buf[pos++]; - if (tmp >= 0) { - result = tmp; + if (buf[pos] >= 0) { + result = buf[pos++]; + } else if (buf[pos + 1] >= 0) { + result = buf[pos] & 0x7f + | buf[pos + 1] << 7; + pos += 2; + } else if (buf[pos + 2] >= 0) { + result = buf[pos] & 0x7f + | buf[pos + 1] << 7 + | buf[pos + 2] << 14; + pos += 3; + } else if (buf[pos + 3] >= 0) { + result = buf[pos] & 0x7f + | buf[pos + 1] << 7 + | buf[pos + 2] << 14 + | buf[pos + 3] << 21; + pos += 4; + Log.d(TAG, "4 Stuffs too large " + mTile); } else { - result = tmp & 0x7f; - if ((tmp = buf[pos++]) >= 0) { - result |= tmp << 7; - } else { - result |= (tmp & 0x7f) << 7; - if ((tmp = buf[pos++]) >= 0) { - result |= tmp << 14; - } else { - result |= (tmp & 0x7f) << 14; - if ((tmp = buf[pos++]) >= 0) { - result |= tmp << 21; - } else { - result |= (tmp & 0x7f) << 21; - result |= (tmp = buf[pos++]) << 28; + result = buf[pos] & 0x7f + | buf[pos + 1] << 7 + | buf[pos + 2] << 14 + | buf[pos + 3] << 21 + | buf[pos + 4] << 28; + pos += 5; - if (tmp < 0) { - int i = 0; - // Discard upper 32 bits. - while (i++ < 5) { - if (buf[pos++] >= 0) - break; - } + Log.d(TAG, "5 Stuffs too large " + mTile); - if (i == 5) - // FIXME throw some poo - Log.d(TAG, "EEK malformedVarint"); - } - } + if (buf[pos + 4] < 0) { + Log.d(TAG, "Stuffs too large ..."); + int i = 0; + while (i++ < 5) { + if (buf[pos++] >= 0) + break; } + if (i == 5) + throw new IOException("EEEK malformed varInt"); } } - coords[cnt++] = (result >>> 1) ^ -(result & 1); + if (even) { + x = ((result >>> 1) ^ -(result & 1)); + lastX = lastX + x; + coords[cnt++] = lastX / scale; + even = false; + } else { + y = ((result >>> 1) ^ -(result & 1)); + lastY = lastY + y; + coords[cnt++] = lastY / scale; + even = true; + } } bufferPos = pos; bytesRead += bytes; - // while (bytesRead < end) { - // int val = decodeZigZag32(decodeVarint32()); - // if (cnt >= MAX_WAY_COORDS) { - // MAX_WAY_COORDS += 128; - // Log.d(TAG, "increase coords array " + MAX_WAY_COORDS); - // float[] tmp = new float[MAX_WAY_COORDS]; - // System.arraycopy(tmpCoords, 0, tmp, 0, cnt); - // tmpCoords = tmp; - // } - // tmpCoords[cnt++] = val; - // } return cnt; } @@ -589,9 +639,6 @@ public class MapDatabase implements IMapDatabase { } bufferSize = len; bufferPos = 0; - - // Log.d(TAG, "pos " + bufferPos + ", size " + bufferSize + ", read " - // + bytesRead); } private void readBuffer(int size) throws IOException { @@ -612,6 +659,10 @@ public class MapDatabase implements IMapDatabase { } while ((bufferSize - bufferPos) < size) { + if (mTile.isCanceled) { + throw new IOException("canceled " + mTile); + } + // read until requested size is available in buffer int len = inputStream.read(buffer, bufferSize, BUFFER_SIZE - bufferSize); if (len < 0) { @@ -621,13 +672,6 @@ public class MapDatabase implements IMapDatabase { bufferSize += len; - // if (len == 0) - // try { - // wait(50); - // } catch (InterruptedException e) { - // // just waiting for data - // } - if (mCanceled) throw new IOException("... canceld?"); } @@ -637,6 +681,17 @@ public class MapDatabase implements IMapDatabase { // + ", read " + bytesRead); } + private boolean mCanceled; + + @Override + public void cancel() { + mCanceled = true; + if (mRequest != null) { + mRequest.abort(); + mRequest = null; + } + } + /* All code below is taken from or based on Google's Protocol Buffers implementation: */ // Protocol Buffers - Google's data interchange format @@ -727,17 +782,4 @@ public class MapDatabase implements IMapDatabase { return (n >>> 1) ^ -(n & 1); } - private boolean mCanceled; - - @Override - public void cancel() { - mCanceled = true; - if (mRequest != null) { - mRequest.abort(); - mRequest = null; - } - // mClient.getConnectionManager().shutdown(); - // mClient = null; - } - } diff --git a/src/org/mapsforge/database/postgis/MapDatabase.java b/src/org/mapsforge/database/postgis/MapDatabase.java index c36a7ac4..25469bc2 100644 --- a/src/org/mapsforge/database/postgis/MapDatabase.java +++ b/src/org/mapsforge/database/postgis/MapDatabase.java @@ -45,14 +45,14 @@ 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(?,?,?)"; + private static final String QUERY = "SELECT tags, geom FROM __get_tile(?,?,?,false)"; - private final float mScale = 1; // 1000000.0f; + private final float mScale = 1; private int mCoordPos = 0; private int mIndexPos = 0; - private float[] mCoords = new float[100000]; - private short[] mIndex = new short[10000]; + private float[] mCoords; + private short[] mIndex; private Tag[] mTags; @@ -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-2.0"; + String dburl = "jdbc:postgresql://city.informatik.uni-bremen.de:5432/gis"; Properties dbOpts = new Properties(); dbOpts.setProperty("user", "osm"); @@ -212,6 +212,10 @@ public class MapDatabase implements IMapDatabase { @Override public FileOpenResult openFile(File mapFile) { mOpenFile = true; + if (mCoords == null) { + mCoords = new float[100000]; + mIndex = new short[100000]; + } return new FileOpenResult(); } @@ -226,6 +230,8 @@ public class MapDatabase implements IMapDatabase { connection = null; } } + mCoords = null; + mIndex = null; mOpenFile = false; }