From 6a34e478f5642203366c8efed58dced2eca7ca11 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek <hannes.janetzek@gmail.com> Date: Mon, 21 Jan 2013 03:11:53 +0100 Subject: [PATCH] improve text placement a little --- src/org/oscim/renderer/WayDecorator.java | 39 ++- src/org/oscim/renderer/layer/TextItem.java | 43 ++- .../oscim/renderer/overlays/TextOverlay.java | 275 ++++++++++++------ src/org/oscim/utils/GeometryUtils.java | 47 +-- 4 files changed, 273 insertions(+), 131 deletions(-) diff --git a/src/org/oscim/renderer/WayDecorator.java b/src/org/oscim/renderer/WayDecorator.java index 97dacfd3..60e495e3 100644 --- a/src/org/oscim/renderer/WayDecorator.java +++ b/src/org/oscim/renderer/WayDecorator.java @@ -179,7 +179,7 @@ public final class WayDecorator { continue; } - double segmentLength = Math.sqrt(vx * vx + vy * vy); + float segmentLength = (float) Math.sqrt(vx * vx + vy * vy); if (skipPixels > 0) { skipPixels -= segmentLength; @@ -202,9 +202,9 @@ public final class WayDecorator { continue; } - float s = wayNameWidth / (float) segmentLength; - int width, height; - int x1, y1, x2, y2; + float s = (wayNameWidth + 20) / segmentLength; + float width, height; + float x1, y1, x2, y2; if (prevX < curX) { x1 = prevX; @@ -219,13 +219,15 @@ public final class WayDecorator { } // estimate position of text on path - width = (x2 - x1) / 2; - x2 = x2 - (int) (width - s * width); - x1 = x1 + (int) (width - s * width); + width = (x2 - x1) / 2f; + //width += 4 * (width / wayNameWidth); + x2 = x2 - (width - s * width); + x1 = x1 + (width - s * width); - height = (y2 - y1) / 2; - y2 = y2 - (int) (height - s * height); - y1 = y1 + (int) (height - s * height); + height = (y2 - y1) / 2f; + //height += 4 * (height / wayNameWidth); + y2 = y2 - (height - s * height); + y1 = y1 + (height - s * height); // short top = (short) (y1 < y2 ? y1 : y2); // short bot = (short) (y1 < y2 ? y2 : y1); @@ -260,17 +262,24 @@ public final class WayDecorator { // previousY = (int) coordinates[pos + i + 1]; // continue; // } + TextItem n = TextItem.get(); - t = TextItem.get(); + // link items together + if (t != null) { + t.n1 = n; + n.n2 = t; + } + + t = n; t.x = x1 + (x2 - x1) / 2f; t.y = y1 + (y2 - y1) / 2f; t.string = string; t.text = text; t.width = wayNameWidth; - t.x1 = (short) x1; - t.y1 = (short) y1; - t.x2 = (short) x2; - t.y2 = (short) y2; + t.x1 = x1; + t.y1 = y1; + t.x2 = x2; + t.y2 = y2; t.length = (short) segmentLength; t.next = items; diff --git a/src/org/oscim/renderer/layer/TextItem.java b/src/org/oscim/renderer/layer/TextItem.java index 90f35d3e..dd349af8 100644 --- a/src/org/oscim/renderer/layer/TextItem.java +++ b/src/org/oscim/renderer/layer/TextItem.java @@ -58,6 +58,13 @@ public class TextItem { TextItem next = ti.next; ti.next = pool; + + // drop references + ti.string = null; + ti.text = null; + ti.n1 = null; + ti.n2 = null; + pool = ti; ti = next; @@ -92,13 +99,47 @@ public class TextItem { return this; } + public static boolean bboxOverlaps(TextItem it1, TextItem it2, float add) { + if (it1.y1 < it1.y2) { + if (it2.y1 < it2.y2) + return (it1.x1 - add < it2.x2) + && (it2.x1 < it1.x2 + add) + && (it1.y1 - add < it2.y2) + && (it2.y1 < it1.y2 + add); + + // flip it2 + return (it1.x1 - add < it2.x2) + && (it2.x1 < it1.x2 + add) + && (it1.y1 - add < it2.y1) + && (it2.y2 < it1.y2 + add); + } + + // flip it1 + if (it2.y1 < it2.y2) + return (it1.x1 - add < it2.x2) + && (it2.x1 < it1.x2 + add) + && (it1.y2 - add < it2.y2) + && (it2.y1 < it1.y1 + add); + + // flip both + return (it1.x1 - add < it2.x2) + && (it2.x1 < it1.x2 + add) + && (it1.y2 - add < it2.y1) + && (it2.y2 < it1.y1 + add); + } + public TextItem next; public float x, y; public String string; public Text text; public float width; - public short x1, y1, x2, y2; + public float x1, y1, x2, y2; public short length; + + // link to next/prev label of the way + public TextItem n1; + public TextItem n2; + public boolean active; // public byte placement } diff --git a/src/org/oscim/renderer/overlays/TextOverlay.java b/src/org/oscim/renderer/overlays/TextOverlay.java index 2da2c15b..04b32d3d 100644 --- a/src/org/oscim/renderer/overlays/TextOverlay.java +++ b/src/org/oscim/renderer/overlays/TextOverlay.java @@ -21,18 +21,26 @@ import org.oscim.renderer.GLRenderer; import org.oscim.renderer.MapTile; import org.oscim.renderer.TileManager; import org.oscim.renderer.TileSet; +import org.oscim.renderer.layer.Layer; +import org.oscim.renderer.layer.Layers; +import org.oscim.renderer.layer.LineLayer; import org.oscim.renderer.layer.TextItem; import org.oscim.renderer.layer.TextLayer; +import org.oscim.theme.renderinstruction.Line; import org.oscim.utils.FastMath; import org.oscim.utils.GeometryUtils; import org.oscim.utils.GlUtils; import org.oscim.utils.PausableThread; import org.oscim.view.MapView; +import android.graphics.Color; +import android.graphics.Paint.Cap; import android.opengl.Matrix; import android.os.SystemClock; +import android.util.Log; public class TextOverlay extends RenderOverlay { + private final static String TAG = TextOverlay.class.getName(); private TileSet mTiles; private LabelThread mThread; @@ -51,7 +59,7 @@ public class TextOverlay extends RenderOverlay { @Override protected void doWork() { - SystemClock.sleep(250); + SystemClock.sleep(400); if (!mRun) return; @@ -79,6 +87,80 @@ public class TextOverlay extends RenderOverlay { mThread.start(); } + private TextItem mPool; + + private byte checkOverlap(TextLayer tl, TextItem ti) { + + for (TextItem lp = tl.labels; lp != null;) { + if (lp.text.caption) { + lp = lp.next; + continue; + } + + // check bounding box + if (!TextItem.bboxOverlaps(ti, lp, 80)) { + lp = lp.next; + continue; + } + + if (lp.text == ti.text && (lp.string == ti.string || lp.string.equals(ti.string))) { + // make strings unique + ti.string = lp.string; + + Log.d(TAG, "overlap, same label in bbox " + lp.string + + " at " + ti.x + ":" + ti.y + ", " + lp.x + ":" + lp.y); + return 3; + } + + if (!TextItem.bboxOverlaps(ti, lp, 10)) { + lp = lp.next; + continue; + } + + byte intersect = GeometryUtils.linesIntersect( + ti.x1, ti.y1, ti.x2, ti.y2, + lp.x1, lp.y1, lp.x2, lp.y2); + + if (intersect != 0) { + //Log.d(TAG, "overlap " + lp.string + " <> " + ti.string + //+ " at " + ti.x + ":" + ti.y); + + if ((lp.n1 != null && lp.n1 == ti.n2) || + (lp.n2 != null && lp.n2 == ti.n1)) { + //Log.d(TAG, "overlap with adjacent label " + lp.string + // + " at " + ti.x + ":" + ti.y + ", " + lp.x + ":" + lp.y); + + return intersect; + } + + if ((ti.n1 != null || ti.n2 != null) && (lp.n1 == null && lp.n2 == null)) { + Log.d(TAG, "overlap, other is unique " + lp.string + " " + ti.string + + " at " + ti.x + ":" + ti.y + ", " + lp.x + ":" + lp.y); + return intersect; + } + // just to make it more deterministic + if (lp.x > ti.x) { + //Log.d(TAG, "drop " + lp.string); + + TextItem tmp = lp; + lp = lp.next; + + tl.removeText(tmp); + + tmp.next = mPool; + mPool = tmp; + continue; + } + return intersect; + } + + lp = lp.next; + } + return 0; + } + + private final Layers mDebugLayer = null; //new Layers(); + void updateLabels() { mTiles = TileManager.getActiveTiles(mTiles); @@ -102,6 +184,7 @@ public class TextOverlay extends RenderOverlay { if (diff > 1 || diff < -2) { // pass back the current layer synchronized (this) { + Log.d(TAG, "drop labels: diff " + diff); mCurLayer = tl; } return; @@ -112,22 +195,34 @@ public class TextOverlay extends RenderOverlay { float cos = (float) Math.cos(angle); float sin = (float) Math.sin(angle); - TextItem ti2 = null; - int maxx = Tile.TILE_SIZE << (mWorkPos.zoomLevel - 1); MapTile[] tiles = mTiles.tiles; + TextItem ti2 = null; - // order tiles by x/y coordinate to make placement more consistent - // while map position changes - //Arrays.sort(tiles, 0, mTiles.cnt, TileSet.coordComparator); - + if (mDebugLayer != null) { + mDebugLayer.clear(); + LineLayer ll = (LineLayer) mDebugLayer.getLayer(0, Layer.LINE); + ll.line = new Line(Color.BLUE, 1, Cap.BUTT); + ll.width = 2; + ll = (LineLayer) mDebugLayer.getLayer(3, Layer.LINE); + ll.line = new Line(Color.YELLOW, 1, Cap.BUTT); + ll.width = 2; + ll = (LineLayer) mDebugLayer.getLayer(1, Layer.LINE); + ll.line = new Line(Color.RED, 1, Cap.BUTT); + ll.width = 2; + ll = (LineLayer) mDebugLayer.getLayer(2, Layer.LINE); + ll.line = new Line(Color.GREEN, 1, Cap.BUTT); + ll.width = 2; + } // TODO more sophisticated placement :) for (int i = 0, n = mTiles.cnt; i < n; i++) { MapTile t = tiles[i]; if (!t.isVisible) continue; + // Log.d(TAG, "add: " + t); + float dx = (float) (t.pixelX - mWorkPos.x); float dy = (float) (t.pixelY - mWorkPos.y); @@ -142,18 +237,25 @@ public class TextOverlay extends RenderOverlay { for (TextItem ti = t.labels; ti != null; ti = ti.next) { - boolean overlaps = false; + // acquire a TextItem to add to TextLayer + if (ti2 == null) { + if (mPool == null) + ti2 = TextItem.get(); + else { + ti2 = mPool; + mPool = mPool.next; + ti2.next = null; + } + } if (ti.text.caption) { - if (ti2 == null) - ti2 = TextItem.get(); ti2.move(ti, dx, dy, scale); - int tx = (int) (ti2.x); int ty = (int) (ti2.y); int tw = (int) (ti2.width / 2); int th = (int) (ti2.text.fontHeight / 2); + boolean overlaps = false; for (TextItem lp = tl.labels; lp != null;) { int px = (int) (lp.x); int py = (int) (lp.y); @@ -170,89 +272,91 @@ public class TextOverlay extends RenderOverlay { } lp = lp.next; } - } else { - - if (ti.width > ti.length * scale) { - continue; - } - if (ti2 == null) - ti2 = TextItem.get(); - ti2.move(ti, dx, dy, scale); - - if (cos * (ti.x2 - ti.x1) - sin * (ti.y2 - ti.y1) < 0) { - // flip label upside-down - ti2.x1 = (short) ((ti.x2 * scale + dx)); - ti2.y1 = (short) ((ti.y2 * scale + dy)); - ti2.x2 = (short) ((ti.x1 * scale + dx)); - ti2.y2 = (short) ((ti.y1 * scale + dy)); - } else { - ti2.x1 = (short) ((ti.x1 * scale + dx)); - ti2.y1 = (short) ((ti.y1 * scale + dy)); - ti2.x2 = (short) ((ti.x2 * scale + dx)); - ti2.y2 = (short) ((ti.y2 * scale + dy)); + if (!overlaps) { + tl.addText(ti2); + ti2 = null; } - //float normalLength = (float) Math.hypot(ti2.x2 - ti2.x1, ti2.y2 - ti2.y1); - - for (TextItem lp = tl.labels; lp != null;) { - if (lp.text.caption) { - lp = lp.next; - continue; - } - - if (GeometryUtils.lineIntersect(ti2.x1, ti2.y1, ti2.x2, ti2.y2, - lp.x1, lp.y1, lp.x2, lp.y2)) { - // just to make it more deterministic - if (lp.width > ti2.width) { - TextItem tmp = lp; - lp = lp.next; - - tl.removeText(tmp); - tmp.next = null; - TextItem.release(tmp); - continue; - } - overlaps = true; - break; - } - - if ((ti2.x1) < (lp.x2) - && (lp.x1) < (ti2.x2) - && (ti2.y1) < (lp.y2) - && (lp.y1) < (ti2.y2)) { - - // just to make it more deterministic - if (lp.width > ti2.width) { - TextItem tmp = lp; - lp = lp.next; - - tl.removeText(tmp); - tmp.next = null; - TextItem.release(tmp); - continue; - } - overlaps = true; - break; - } - - lp = lp.next; - } + continue; } - if (!overlaps) { + /* text is way label */ + + // check if path at current scale is long enough for text + if (mDebugLayer == null && ti.width > ti.length * scale) + continue; + + // set line endpoints relative to view to be able to + // check intersections with label from other tiles + float width = (ti.x2 - ti.x1) / 2f; + float height = (ti.y2 - ti.y1) / 2f; + + ti2.move(ti, dx, dy, scale); + ti2.x2 = (ti2.x + width); + ti2.x1 = (ti2.x - width); + ti2.y2 = (ti2.y + height); + ti2.y1 = (ti2.y - height); + + byte overlaps = checkOverlap(tl, ti2); + + if (mDebugLayer != null) { + + LineLayer ll; + if (ti.width > ti.length * scale) { + ll = (LineLayer) mDebugLayer.getLayer(1, Layer.LINE); + overlaps = 3; + } + else if (overlaps == 1) + ll = (LineLayer) mDebugLayer.getLayer(0, Layer.LINE); + else if (overlaps == 2) + ll = (LineLayer) mDebugLayer.getLayer(3, Layer.LINE); + else + ll = (LineLayer) mDebugLayer.getLayer(2, Layer.LINE); + + float[] points = new float[4]; + short[] indices = { 4 }; + points[0] = ti2.x1 / scale; + points[1] = ti2.y1 / scale; + points[2] = ti2.x2 / scale; + points[3] = ti2.y2 / scale; + ll.addLine(points, indices, false); + } + + if (overlaps == 0) { tl.addText(ti2); ti2 = null; } } } - if (ti2 != null) - TextItem.release(ti2); + for (TextItem ti = tl.labels; ti != null; ti = ti.next) { + // scale back to fixed zoom-level. could be done in setMatrix + ti.x /= scale; + ti.y /= scale; - // scale back to fixed zoom-level. could be done in setMatrix.. - for (TextItem lp = tl.labels; lp != null; lp = lp.next) { - lp.x /= scale; - lp.y /= scale; + if (ti.text.caption) + continue; + + // flip label upside-down + if (cos * (ti.x2 - ti.x1) - sin * (ti.y2 - ti.y1) < 0) { + float tmp = ti.x1; + ti.x1 = ti.x2; + ti.x2 = tmp; + + tmp = ti.y1; + ti.y1 = ti.y2; + ti.y2 = tmp; + } + } + + // release temporarily used TextItems + if (ti2 != null) { + ti2.next = mPool; + mPool = ti2; + } + if (mPool != null) { + TextItem.release(mPool); + mPool = null; } // draw text to bitmaps and create vertices @@ -279,6 +383,11 @@ public class TextOverlay extends RenderOverlay { // clear textures and text items from previous layer layers.clear(); + if (mDebugLayer != null) { + layers.layers = mDebugLayer.layers; + mDebugLayer.layers = null; + } + // set new TextLayer to be uploaded and used layers.textureLayers = mCurLayer; diff --git a/src/org/oscim/utils/GeometryUtils.java b/src/org/oscim/utils/GeometryUtils.java index 9812f607..ddb6a3cb 100644 --- a/src/org/oscim/utils/GeometryUtils.java +++ b/src/org/oscim/utils/GeometryUtils.java @@ -64,11 +64,12 @@ public final class GeometryUtils { throw new IllegalStateException(); } - static boolean linesIntersect(double x1, double y1, double x2, double y2, double x3, double y3, - double x4, double y4) { + public static byte linesIntersect( + double x1, double y1, double x2, double y2, + double x3, double y3, double x4, double y4) { // Return false if either of the lines have zero length if (x1 == x2 && y1 == y2 || x3 == x4 && y3 == y4) { - return false; + return 0; } // Fastest method, based on Franklin Antonio's // "Faster Line Segment Intersection" topic "in Graphics Gems III" book @@ -85,21 +86,21 @@ public final class GeometryUtils { if (commonDenominator > 0) { if (alphaNumerator < 0 || alphaNumerator > commonDenominator) { - return false; + return 0; } } else if (commonDenominator < 0) { if (alphaNumerator > 0 || alphaNumerator < commonDenominator) { - return false; + return 0; } } double betaNumerator = ax * cy - ay * cx; if (commonDenominator > 0) { if (betaNumerator < 0 || betaNumerator > commonDenominator) { - return false; + return 0; } } else if (commonDenominator < 0) { if (betaNumerator > 0 || betaNumerator < commonDenominator) { - return false; + return 0; } } if (commonDenominator == 0) { @@ -120,13 +121,13 @@ public final class GeometryUtils { if (y1 >= y3 && y1 <= y4 || y1 <= y3 && y1 >= y4 || y2 >= y3 && y2 <= y4 || y2 <= y3 && y2 >= y4 || y3 >= y1 && y3 <= y2 || y3 <= y1 && y3 >= y2) { - return true; + return 2; } } } - return false; + return 0; } - return true; + return 1; } static boolean doesIntersect(double l1x1, double l1y1, double l1x2, double l1y2, double l2x1, @@ -144,27 +145,10 @@ public final class GeometryUtils { return ((ua >= 0.0d) && (ua <= 1.0d) && (ub >= 0.0d) && (ub <= 1.0d)); } - /** - * @param x1 - * ... - * @param y1 - * ... - * @param x2 - * ... - * @param y2 - * ... - * @param x3 - * ... - * @param y3 - * ... - * @param x4 - * ... - * @param y4 - * ... - * @return ... - */ - public static boolean lineIntersect(int x1, int y1, int x2, int y2, int x3, int y3, int x4, - int y4) { + public static boolean lineIntersect( + int x1, int y1, int x2, int y2, + int x3, int y3, int x4, int y4) { + float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (denom == 0.0) { // Lines are parallel. return false; @@ -172,7 +156,6 @@ public final class GeometryUtils { float ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom; float ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom; if (ua >= 0.0f && ua <= 1.0f && ub >= 0.0f && ub <= 1.0f) { - // Get the intersection point. return true; }