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;
 		}