splitup labeling
This commit is contained in:
parent
e620d15964
commit
b6d9ed254e
@ -17,6 +17,8 @@
|
||||
package org.oscim.layers.tile.vector.labeling;
|
||||
|
||||
import org.oscim.backend.canvas.Color;
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.renderer.MapRenderer.Matrices;
|
||||
import org.oscim.renderer.elements.ElementLayers;
|
||||
import org.oscim.renderer.elements.LineLayer;
|
||||
import org.oscim.renderer.elements.TextItem;
|
||||
@ -25,8 +27,10 @@ import org.oscim.theme.styles.Line;
|
||||
class Debug {
|
||||
|
||||
private final static float[] mDebugPoints = new float[8];
|
||||
// TODO Auto-generated method stub
|
||||
static ElementLayers dbg;
|
||||
|
||||
static void addDebugBox(ElementLayers dbg, Label l, TextItem ti, int overlaps, boolean prev,
|
||||
static void addDebugBox(Label l, TextItem ti, int overlaps, boolean prev,
|
||||
float scale) {
|
||||
|
||||
LineLayer ll;
|
||||
@ -75,4 +79,19 @@ class Debug {
|
||||
dbg.addLineLayer(5, new Line((Color.MAGENTA & alpha), 2));
|
||||
}
|
||||
|
||||
public static void draw(MapPosition pos, Matrices m, ElementLayers layers) {
|
||||
// if (layers.baseLayers != null) {
|
||||
// //setMatrix(pos, m, true);
|
||||
//
|
||||
// for (RenderElement l = layers.baseLayers; l != null;) {
|
||||
// if (l.type == RenderElement.POLYGON) {
|
||||
// l = PolygonLayer.Renderer.draw(pos, l, m, true, 1, false);
|
||||
// } else {
|
||||
// //float div = (float) (mMapPosition.scale / (1 << pos.zoomLevel));
|
||||
// l = LineLayer.Renderer.draw(layers, l, pos, m, div);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,10 +17,9 @@
|
||||
package org.oscim.layers.tile.vector.labeling;
|
||||
|
||||
import org.oscim.renderer.elements.TextItem;
|
||||
import org.oscim.tiling.MapTile;
|
||||
import org.oscim.utils.OBB2D;
|
||||
|
||||
class Label extends TextItem {
|
||||
final class Label extends TextItem {
|
||||
TextItem item;
|
||||
|
||||
//Link blocking;
|
||||
@ -28,34 +27,75 @@ class Label extends TextItem {
|
||||
// shared list of all label for a tile
|
||||
//Link siblings;
|
||||
|
||||
MapTile tile;
|
||||
int tileX;
|
||||
int tileY;
|
||||
int tileZ;
|
||||
|
||||
//public byte origin;
|
||||
public int active;
|
||||
public OBB2D bbox;
|
||||
|
||||
public TextItem move(TextItem ti, float dx, float dy, float scale) {
|
||||
this.x = (dx + ti.x) * scale;
|
||||
this.y = (dy + ti.y) * scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clone(TextItem ti) {
|
||||
public Label clone(TextItem ti) {
|
||||
this.string = ti.string;
|
||||
this.text = ti.text;
|
||||
this.width = ti.width;
|
||||
this.length = ti.length;
|
||||
}
|
||||
|
||||
public void setAxisAlignedBBox() {
|
||||
this.x1 = x - width / 2;
|
||||
this.y1 = y - text.fontHeight / 2;
|
||||
this.x2 = x + width / 2;
|
||||
this.y2 = y + text.fontHeight / 2;
|
||||
return this;
|
||||
}
|
||||
|
||||
static int comparePriority(Label l1, Label l2) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static boolean shareText(Label l, Label ll) {
|
||||
if (l.text != ll.text)
|
||||
return false;
|
||||
|
||||
if (l.string == ll.string)
|
||||
return true;
|
||||
|
||||
if (l.string.equals(ll.string)) {
|
||||
// make strings unique, should be done only once..
|
||||
l.string = ll.string;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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 void setAxisAlignedBBox() {
|
||||
this.x1 = (int) (x - width / 2);
|
||||
this.y1 = (int) (y - text.fontHeight / 2);
|
||||
this.x2 = (int) (x + width / 2);
|
||||
this.y2 = (int) (y + text.fontHeight / 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,32 +21,83 @@ import org.oscim.event.MotionEvent;
|
||||
import org.oscim.layers.Layer;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.tiling.TileRenderer;
|
||||
import org.oscim.utils.async.SimpleWorker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateListener {
|
||||
static final Logger log = LoggerFactory.getLogger(LabelLayer.class);
|
||||
private final TextRenderer mTextRenderer;
|
||||
|
||||
//private int multi;
|
||||
private final static long MAX_RELABEL_DELAY = 100;
|
||||
|
||||
public LabelLayer(Map map, TileRenderer tileRenderLayer) {
|
||||
private final LabelPlacement mLabelPlacer;
|
||||
private final Worker mWorker;
|
||||
|
||||
public LabelLayer(Map map, TileRenderer tileRenderer) {
|
||||
super(map);
|
||||
mLabelPlacer = new LabelPlacement(map, tileRenderer);
|
||||
mWorker = new Worker(map);
|
||||
mRenderer = new TextRenderer(mWorker);
|
||||
}
|
||||
|
||||
//mTextLayer = new org.oscim.renderer.layers.TextRenderLayer(map, tileRenderLayer);
|
||||
mTextRenderer = new TextRenderer(map, tileRenderLayer);
|
||||
mRenderer = mTextRenderer;
|
||||
class Worker extends SimpleWorker<LabelTask> {
|
||||
|
||||
public Worker(Map map) {
|
||||
super(map, 50, new LabelTask(), new LabelTask());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doWork(LabelTask t) {
|
||||
|
||||
if (mLabelPlacer.updateLabels(t)) {
|
||||
mMap.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(LabelTask t) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
mLabelPlacer.cleanup();
|
||||
}
|
||||
|
||||
public synchronized boolean isRunning() {
|
||||
return mRunning;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLabels() {
|
||||
mWorker.cancel(true);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
mWorker.submit(MAX_RELABEL_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
// TODO stop and clear labeling thread
|
||||
log.debug("DETACH");
|
||||
mTextRenderer.clearLabels();
|
||||
|
||||
// clear labels
|
||||
mWorker.cancel(true);
|
||||
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) {
|
||||
if (clear)
|
||||
mWorker.cancel(true);
|
||||
|
||||
mWorker.submit(MAX_RELABEL_DELAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMotionEvent(MotionEvent e) {
|
||||
// int action = e.getAction() & MotionEvent.ACTION_MASK;
|
||||
@ -64,31 +115,4 @@ public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateLi
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) {
|
||||
if (clear)
|
||||
mTextRenderer.clearLabels();
|
||||
|
||||
mTextRenderer.update();
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean onTouchEvent(MotionEvent e) {
|
||||
// int action = e.getAction() & MotionEvent.ACTION_MASK;
|
||||
// if (action == MotionEvent.ACTION_POINTER_DOWN) {
|
||||
// multi++;
|
||||
// mTextRenderer.hold(true);
|
||||
// } else if (action == MotionEvent.ACTION_POINTER_UP) {
|
||||
// multi--;
|
||||
// if (multi == 0)
|
||||
// mTextRenderer.hold(false);
|
||||
// } else if (action == MotionEvent.ACTION_CANCEL) {
|
||||
// multi = 0;
|
||||
// log.debug("cancel " + multi);
|
||||
// mTextRenderer.hold(false);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,483 @@
|
||||
package org.oscim.layers.tile.vector.labeling;
|
||||
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.renderer.elements.SymbolItem;
|
||||
import org.oscim.renderer.elements.SymbolLayer;
|
||||
import org.oscim.renderer.elements.TextItem;
|
||||
import org.oscim.tiling.MapTile;
|
||||
import org.oscim.tiling.TileRenderer;
|
||||
import org.oscim.tiling.TileSet;
|
||||
import org.oscim.utils.FastMath;
|
||||
import org.oscim.utils.OBB2D;
|
||||
|
||||
public class LabelPlacement {
|
||||
static final boolean dbg = false;
|
||||
|
||||
private final static float MIN_CAPTION_DIST = 5;
|
||||
private final static float MIN_WAY_DIST = 3;
|
||||
|
||||
/** thread local pool of for unused label items */
|
||||
private final LabelPool mPool = new LabelPool();
|
||||
|
||||
private final TileSet mTileSet = new TileSet();
|
||||
private final TileRenderer mTileRenderer;
|
||||
private final Map mMap;
|
||||
|
||||
/** list of current labels */
|
||||
private Label mLabels;
|
||||
|
||||
private float mSquareRadius;
|
||||
|
||||
/**
|
||||
* incremented each update, to prioritize labels
|
||||
* that became visible ealier.
|
||||
*/
|
||||
private int mRelabelCnt;
|
||||
|
||||
public LabelPlacement(Map map, TileRenderer tileRenderer) {
|
||||
mMap = map;
|
||||
mTileRenderer = tileRenderer;
|
||||
}
|
||||
|
||||
/** remove Label l from mLabels and return l.next */
|
||||
private Label removeLabel(Label l) {
|
||||
Label ret = (Label) l.next;
|
||||
mLabels = (Label) mPool.release(mLabels, l);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addLabel(Label l) {
|
||||
for (Label o = mLabels; o != null; o = (Label) o.next) {
|
||||
/* find other label with same text style */
|
||||
if (l.text == o.text) {
|
||||
while (o.next != null
|
||||
/* break if next item uses different text style */
|
||||
&& l.text == o.next.text
|
||||
/* check same string instance */
|
||||
&& l.string != o.string
|
||||
/* check same string */
|
||||
&& !l.string.equals(o.string))
|
||||
o = (Label) o.next;
|
||||
|
||||
/* Note: required for 'packing test' in prepare to work */
|
||||
Label.shareText(l, o);
|
||||
/* insert after text of same type or before same string */
|
||||
l.next = o.next;
|
||||
o.next = l;
|
||||
return;
|
||||
}
|
||||
}
|
||||
l.next = mLabels;
|
||||
mLabels = l;
|
||||
}
|
||||
|
||||
private byte checkOverlap(Label l) {
|
||||
|
||||
for (Label o = mLabels; o != null;) {
|
||||
//check bounding box
|
||||
if (!Label.bboxOverlaps(l, o, 150)) {
|
||||
o = (Label) o.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Label.shareText(l, o)) {
|
||||
// keep the label that was active earlier
|
||||
if (o.active <= l.active)
|
||||
return 1;
|
||||
|
||||
// keep the label with longer segment
|
||||
if (o.length < l.length) {
|
||||
o = removeLabel(o);
|
||||
continue;
|
||||
}
|
||||
// keep other
|
||||
return 2;
|
||||
}
|
||||
if (l.bbox.overlaps(o.bbox)) {
|
||||
if (o.active <= l.active)
|
||||
return 1;
|
||||
|
||||
if (!o.text.caption
|
||||
&& (o.text.priority > l.text.priority
|
||||
|| o.length < l.length)) {
|
||||
|
||||
o = removeLabel(o);
|
||||
continue;
|
||||
}
|
||||
// keep other
|
||||
return 1;
|
||||
}
|
||||
o = (Label) o.next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean isVisible(float x, float y) {
|
||||
// rough filter
|
||||
float dist = x * x + y * y;
|
||||
if (dist > mSquareRadius)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean wayIsVisible(Label ti) {
|
||||
// rough filter
|
||||
float dist = ti.x * ti.x + ti.y * ti.y;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
dist = ti.x1 * ti.x1 + ti.y1 * ti.y1;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
dist = ti.x2 * ti.x2 + ti.y2 * ti.y2;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Label getLabel() {
|
||||
Label l = (Label) mPool.get();
|
||||
l.active = Integer.MAX_VALUE;
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
private static float flipLongitude(float dx, int max) {
|
||||
// flip around date-line
|
||||
if (dx > max)
|
||||
dx = dx - max * 2;
|
||||
else if (dx < -max)
|
||||
dx = dx + max * 2;
|
||||
|
||||
return dx;
|
||||
}
|
||||
|
||||
private void placeLabelFrom(Label l, TextItem ti) {
|
||||
// set line endpoints relative to view to be able to
|
||||
// check intersections with label from other tiles
|
||||
float w = (ti.x2 - ti.x1) / 2f;
|
||||
float h = (ti.y2 - ti.y1) / 2f;
|
||||
|
||||
l.x1 = (int) (l.x - w);
|
||||
l.y1 = (int) (l.y - h);
|
||||
l.x2 = (int) (l.x + w);
|
||||
l.y2 = (int) (l.y + h);
|
||||
}
|
||||
|
||||
private Label addWayLabels(MapTile t, Label l, float dx, float dy,
|
||||
double scale) {
|
||||
|
||||
for (TextItem ti = t.labels; ti != null; ti = ti.next) {
|
||||
if (ti.text.caption)
|
||||
continue;
|
||||
|
||||
/* acquire a TextItem to add to TextLayer */
|
||||
if (l == null)
|
||||
l = getLabel();
|
||||
|
||||
/* check if path at current scale is long enough */
|
||||
if (!dbg && ti.width > ti.length * scale)
|
||||
continue;
|
||||
|
||||
l.clone(ti);
|
||||
l.x = (float) ((dx + ti.x) * scale);
|
||||
l.y = (float) ((dy + ti.y) * scale);
|
||||
placeLabelFrom(l, ti);
|
||||
|
||||
if (!wayIsVisible(l))
|
||||
continue;
|
||||
|
||||
byte overlaps = -1;
|
||||
|
||||
if (l.bbox == null)
|
||||
l.bbox = new OBB2D(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
else
|
||||
l.bbox.set(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
|
||||
if (dbg || ti.width < ti.length * scale)
|
||||
overlaps = checkOverlap(l);
|
||||
|
||||
if (dbg)
|
||||
Debug.addDebugBox(l, ti, overlaps, false, (float) scale);
|
||||
|
||||
if (overlaps == 0) {
|
||||
addLabel(l);
|
||||
l.item = TextItem.copy(ti);
|
||||
l.tileX = t.tileX;
|
||||
l.tileY = t.tileY;
|
||||
l.tileZ = t.zoomLevel;
|
||||
l.active = mRelabelCnt;
|
||||
l = null;
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
private Label addNodeLabels(MapTile t, Label l, float dx, float dy,
|
||||
double scale, float cos, float sin) {
|
||||
O: for (TextItem ti = t.labels; ti != null; ti = ti.next) {
|
||||
if (!ti.text.caption)
|
||||
continue;
|
||||
|
||||
// acquire a TextItem to add to TextLayer
|
||||
if (l == null)
|
||||
l = getLabel();
|
||||
|
||||
l.clone(ti);
|
||||
l.x = (float) ((dx + ti.x) * scale);
|
||||
l.y = (float) ((dy + ti.y) * scale);
|
||||
if (!isVisible(l.x, l.y))
|
||||
continue;
|
||||
|
||||
if (l.bbox == null)
|
||||
l.bbox = new OBB2D();
|
||||
|
||||
l.bbox.setNormalized(l.x, l.y, cos, -sin,
|
||||
l.width + MIN_CAPTION_DIST,
|
||||
l.text.fontHeight + MIN_CAPTION_DIST,
|
||||
l.text.dy);
|
||||
|
||||
for (Label o = mLabels; o != null;) {
|
||||
if (l.bbox.overlaps(o.bbox)) {
|
||||
if (l.text.priority < o.text.priority) {
|
||||
o = removeLabel(o);
|
||||
continue;
|
||||
}
|
||||
continue O;
|
||||
}
|
||||
o = (Label) o.next;
|
||||
}
|
||||
|
||||
addLabel(l);
|
||||
l.item = TextItem.copy(ti);
|
||||
l.tileX = t.tileX;
|
||||
l.tileY = t.tileY;
|
||||
l.tileZ = t.zoomLevel;
|
||||
l.active = mRelabelCnt;
|
||||
l = null;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
boolean updateLabels(LabelTask work) {
|
||||
|
||||
/* get current tiles */
|
||||
boolean changedTiles = mTileRenderer.getVisibleTiles(mTileSet);
|
||||
|
||||
if (mTileSet.cnt == 0) {
|
||||
//log.debug("no tiles "+ mTileSet.getSerial());
|
||||
return false;
|
||||
}
|
||||
|
||||
MapPosition pos = work.pos;
|
||||
boolean changedPos = mMap.getViewport().getMapPosition(pos);
|
||||
|
||||
/* do not loop! */
|
||||
if (!changedTiles && !changedPos)
|
||||
return false;
|
||||
|
||||
mRelabelCnt++;
|
||||
|
||||
MapTile[] tiles = mTileSet.tiles;
|
||||
int zoom = tiles[0].zoomLevel;
|
||||
|
||||
/* estimation for visible area to be labeled */
|
||||
int mw = (mMap.getWidth() + Tile.SIZE) / 2;
|
||||
int mh = (mMap.getHeight() + Tile.SIZE) / 2;
|
||||
mSquareRadius = mw * mw + mh * mh;
|
||||
|
||||
/* scale of tiles zoom-level relative to current position */
|
||||
double scale = pos.scale / (1 << zoom);
|
||||
|
||||
double angle = Math.toRadians(pos.angle);
|
||||
float cos = (float) Math.cos(angle);
|
||||
float sin = (float) Math.sin(angle);
|
||||
|
||||
int maxx = Tile.SIZE << (zoom - 1);
|
||||
|
||||
// FIXME ???
|
||||
SymbolLayer sl = work.symbolLayer;
|
||||
sl.clearItems();
|
||||
|
||||
double tileX = (pos.x * (Tile.SIZE << zoom));
|
||||
double tileY = (pos.y * (Tile.SIZE << zoom));
|
||||
|
||||
/* put current label to previous label */
|
||||
Label prevLabels = mLabels;
|
||||
|
||||
/* new labels */
|
||||
mLabels = null;
|
||||
Label l = null;
|
||||
|
||||
/* add currently active labels first */
|
||||
for (l = prevLabels; l != null;) {
|
||||
|
||||
if (l.text.caption) {
|
||||
// TODO!!!
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
int diff = l.tileZ - zoom;
|
||||
if (diff > 1 || diff < -1) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
float div = FastMath.pow(diff);
|
||||
float sscale = (float) (pos.scale / (1 << l.tileZ));
|
||||
|
||||
if (l.width > l.length * sscale) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
float dx = (float) (l.tileX * Tile.SIZE - tileX * div);
|
||||
float dy = (float) (l.tileY * Tile.SIZE - tileY * div);
|
||||
|
||||
dx = flipLongitude(dx, maxx);
|
||||
l.x = (float) ((dx + l.item.x) * sscale);
|
||||
l.y = (float) ((dy + l.item.y) * sscale);
|
||||
placeLabelFrom(l, l.item);
|
||||
|
||||
if (!wayIsVisible(l)) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
l.bbox.set(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
|
||||
byte overlaps = checkOverlap(l);
|
||||
|
||||
if (dbg)
|
||||
Debug.addDebugBox(l, l.item, overlaps, true, sscale);
|
||||
|
||||
if (overlaps == 0) {
|
||||
Label ll = l;
|
||||
l = (Label) l.next;
|
||||
|
||||
ll.next = null;
|
||||
addLabel(ll);
|
||||
continue;
|
||||
}
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
}
|
||||
|
||||
/* add way labels */
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
l = addWayLabels(t, l, dx, dy, scale);
|
||||
}
|
||||
}
|
||||
|
||||
/* add caption */
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
l = addNodeLabels(t, l, dx, dy, scale, cos, sin);
|
||||
}
|
||||
}
|
||||
|
||||
for (Label ti = mLabels; ti != null; ti = (Label) ti.next) {
|
||||
/* add caption symbols */
|
||||
if (ti.text.caption) {
|
||||
if (ti.text.texture != null) {
|
||||
SymbolItem s = SymbolItem.pool.get();
|
||||
s.texRegion = ti.text.texture;
|
||||
s.x = ti.x;
|
||||
s.y = ti.y;
|
||||
s.billboard = true;
|
||||
sl.addSymbol(s);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* flip way label orientation */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* add symbol items */
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
for (SymbolItem ti = t.symbols; ti != null; ti = ti.next) {
|
||||
if (ti.texRegion == null)
|
||||
continue;
|
||||
|
||||
int x = (int) ((dx + ti.x) * scale);
|
||||
int y = (int) ((dy + ti.y) * scale);
|
||||
|
||||
if (!isVisible(x, y))
|
||||
continue;
|
||||
|
||||
SymbolItem s = SymbolItem.pool.get();
|
||||
s.texRegion = ti.texRegion;
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
s.billboard = true;
|
||||
sl.addSymbol(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* temporary used Label */
|
||||
l = (Label) mPool.release(l);
|
||||
|
||||
/* draw text to bitmaps and create vertices */
|
||||
work.textLayer.labels = mLabels;
|
||||
work.textLayer.prepare();
|
||||
work.textLayer.labels = null;
|
||||
|
||||
/* remove tile locks */
|
||||
mTileRenderer.releaseTiles(mTileSet);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
mLabels = (Label) mPool.releaseAll(mLabels);
|
||||
mTileSet.releaseTiles();
|
||||
}
|
||||
}
|
||||
25
vtm/src/org/oscim/layers/tile/vector/labeling/LabelPool.java
Normal file
25
vtm/src/org/oscim/layers/tile/vector/labeling/LabelPool.java
Normal file
@ -0,0 +1,25 @@
|
||||
package org.oscim.layers.tile.vector.labeling;
|
||||
|
||||
import org.oscim.renderer.elements.TextItem;
|
||||
import org.oscim.utils.pool.Pool;
|
||||
|
||||
final class LabelPool extends Pool<TextItem> {
|
||||
Label releaseAndGetNext(Label l) {
|
||||
if (l.item != null)
|
||||
l.item = TextItem.pool.release(l.item);
|
||||
|
||||
// drop references
|
||||
l.item = null;
|
||||
l.string = null;
|
||||
Label ret = (Label) l.next;
|
||||
|
||||
// ignore warning
|
||||
super.release(l);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Label createItem() {
|
||||
return new Label();
|
||||
}
|
||||
}
|
||||
26
vtm/src/org/oscim/layers/tile/vector/labeling/LabelTask.java
Normal file
26
vtm/src/org/oscim/layers/tile/vector/labeling/LabelTask.java
Normal file
@ -0,0 +1,26 @@
|
||||
package org.oscim.layers.tile.vector.labeling;
|
||||
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.renderer.elements.SymbolLayer;
|
||||
import org.oscim.renderer.elements.TextLayer;
|
||||
import org.oscim.renderer.elements.TextureLayer;
|
||||
|
||||
final class LabelTask {
|
||||
|
||||
final TextureLayer layers;
|
||||
final TextLayer textLayer;
|
||||
final SymbolLayer symbolLayer;
|
||||
|
||||
final MapPosition pos;
|
||||
|
||||
LabelTask() {
|
||||
pos = new MapPosition();
|
||||
|
||||
symbolLayer = new SymbolLayer();
|
||||
textLayer = new TextLayer();
|
||||
|
||||
layers = symbolLayer;
|
||||
symbolLayer.next = textLayer;
|
||||
}
|
||||
|
||||
}
|
||||
@ -27,582 +27,27 @@ package org.oscim.layers.tile.vector.labeling;
|
||||
// 2.4 use 4 point labeling
|
||||
// 3 join segments that belong to one feature
|
||||
// 4 handle zoom-level changes
|
||||
// 5 R-Tree might be handy
|
||||
// 5 QuadTree might be handy
|
||||
//
|
||||
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.Viewport;
|
||||
import org.oscim.layers.tile.vector.labeling.LabelLayer.Worker;
|
||||
import org.oscim.renderer.ElementRenderer;
|
||||
import org.oscim.renderer.GLState;
|
||||
import org.oscim.renderer.MapRenderer.Matrices;
|
||||
import org.oscim.renderer.elements.ElementLayers;
|
||||
import org.oscim.renderer.elements.LineLayer;
|
||||
import org.oscim.renderer.elements.PolygonLayer;
|
||||
import org.oscim.renderer.elements.RenderElement;
|
||||
import org.oscim.renderer.elements.SymbolItem;
|
||||
import org.oscim.renderer.elements.SymbolLayer;
|
||||
import org.oscim.renderer.elements.TextItem;
|
||||
import org.oscim.renderer.elements.TextLayer;
|
||||
import org.oscim.renderer.elements.TextureLayer;
|
||||
import org.oscim.tiling.MapTile;
|
||||
import org.oscim.tiling.TileRenderer;
|
||||
import org.oscim.tiling.TileSet;
|
||||
import org.oscim.utils.FastMath;
|
||||
import org.oscim.utils.OBB2D;
|
||||
import org.oscim.utils.async.SimpleWorker;
|
||||
import org.oscim.utils.pool.Pool;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class TextRenderer extends ElementRenderer {
|
||||
static final Logger log = LoggerFactory.getLogger(TextRenderer.class);
|
||||
static final boolean dbg = false;
|
||||
|
||||
private final static float MIN_CAPTION_DIST = 5;
|
||||
private final static float MIN_WAY_DIST = 3;
|
||||
|
||||
private final static long MAX_RELABEL_DELAY = 200;
|
||||
|
||||
private final Viewport mViewport;
|
||||
private final TileSet mTileSet;
|
||||
|
||||
//private ElementLayers mDebugLayer;
|
||||
|
||||
class LabelTask {
|
||||
final TextureLayer layers;
|
||||
final TextLayer textLayer;
|
||||
final SymbolLayer symbolLayer;
|
||||
|
||||
final MapPosition pos;
|
||||
|
||||
LabelTask() {
|
||||
pos = new MapPosition();
|
||||
|
||||
symbolLayer = new SymbolLayer();
|
||||
textLayer = new TextLayer();
|
||||
|
||||
layers = symbolLayer;
|
||||
symbolLayer.next = textLayer;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// thread local pool (labeling)
|
||||
class LabelPool extends Pool<TextItem> {
|
||||
Label releaseAndGetNext(Label l) {
|
||||
if (l.item != null)
|
||||
l.item = TextItem.pool.release(l.item);
|
||||
|
||||
// drop references
|
||||
l.item = null;
|
||||
l.tile = null;
|
||||
l.string = null;
|
||||
|
||||
Label ret = (Label) l.next;
|
||||
|
||||
// ignore warning
|
||||
super.release(l);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TextItem createItem() {
|
||||
return new Label();
|
||||
}
|
||||
}
|
||||
|
||||
private final LabelPool mPool = new LabelPool();
|
||||
|
||||
// list of current labels
|
||||
private Label mLabels;
|
||||
|
||||
//private final float[] mTmpCoords = new float[8];
|
||||
|
||||
//private final HashMap<MapTile, LabelTile> mActiveTiles;
|
||||
|
||||
private float mSquareRadius;
|
||||
private int mRelabelCnt;
|
||||
private final TileRenderer mTileLayer;
|
||||
private final Map mMap;
|
||||
private final Worker mWorker;
|
||||
|
||||
public TextRenderer(Map map, TileRenderer baseLayer) {
|
||||
mMap = map;
|
||||
mViewport = map.getViewport();
|
||||
mTileLayer = baseLayer;
|
||||
mTileSet = new TileSet();
|
||||
|
||||
layers.textureLayers = new TextLayer();
|
||||
layers.textureLayers.next = new SymbolLayer();
|
||||
|
||||
//mActiveTiles = new HashMap<MapTile, LabelTile>();
|
||||
mRelabelCnt = 0;
|
||||
|
||||
mWorker = new Worker(map);
|
||||
}
|
||||
|
||||
// remove Label l from mLabels and return l.next
|
||||
private Label removeLabel(Label l) {
|
||||
Label ret = (Label) l.next;
|
||||
mLabels = (Label) mPool.release(mLabels, l);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void addLabel(Label l) {
|
||||
TextItem ll = mLabels;
|
||||
|
||||
for (; ll != null; ll = ll.next) {
|
||||
// find other label with same text style
|
||||
if (l.text == ll.text) {
|
||||
while (ll.next != null
|
||||
// break if next item uses different text style
|
||||
&& l.text == ll.next.text
|
||||
// check same string instance
|
||||
&& l.string != ll.string
|
||||
// check same string
|
||||
&& !l.string.equals(ll.string))
|
||||
ll = ll.next;
|
||||
|
||||
// Note: this is required for 'packing test' in prepare to work!
|
||||
TextItem.shareText(l, ll);
|
||||
|
||||
// insert after text of same type and/or before same string
|
||||
l.next = ll.next;
|
||||
ll.next = l;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
l.next = mLabels;
|
||||
mLabels = l;
|
||||
}
|
||||
|
||||
private byte checkOverlap(Label l) {
|
||||
|
||||
for (Label ll = mLabels; ll != null;) {
|
||||
// check bounding box
|
||||
if (!TextItem.bboxOverlaps(l, ll, 150)) {
|
||||
ll = (Label) ll.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TextItem.shareText(l, ll)) {
|
||||
|
||||
// keep the label that was active earlier
|
||||
if (ll.active <= l.active)
|
||||
return 1;
|
||||
|
||||
// keep the label with longer segment
|
||||
if (ll.length < l.length) {
|
||||
ll = removeLabel(ll);
|
||||
continue;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
boolean intersect = l.bbox.overlaps(ll.bbox);
|
||||
|
||||
if (intersect) {
|
||||
if (ll.active <= l.active)
|
||||
return 1;
|
||||
|
||||
//log.debug("intersection " + lp.string + " <> " + ti.string
|
||||
// + " at " + ti.x + ":" + ti.y);
|
||||
|
||||
if (!ll.text.caption
|
||||
&& (ll.text.priority > l.text.priority || ll.length < l.length)) {
|
||||
|
||||
ll = removeLabel(ll);
|
||||
continue;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
ll = (Label) ll.next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean nodeIsVisible(TextItem ti) {
|
||||
// rough filter
|
||||
float dist = ti.x * ti.x + ti.y * ti.y;
|
||||
if (dist > mSquareRadius)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean iconIsVisible(int x, int y) {
|
||||
// rough filter
|
||||
float dist = x * x + y * y;
|
||||
if (dist > mSquareRadius)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean wayIsVisible(TextItem ti) {
|
||||
// rough filter
|
||||
float dist = ti.x * ti.x + ti.y * ti.y;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
dist = ti.x1 * ti.x1 + ti.y1 * ti.y1;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
dist = ti.x2 * ti.x2 + ti.y2 * ti.y2;
|
||||
if (dist < mSquareRadius)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Label getLabel() {
|
||||
Label l = (Label) mPool.get();
|
||||
l.active = Integer.MAX_VALUE;
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
private static float flipLongitude(float dx, int max) {
|
||||
// flip around date-line
|
||||
if (dx > max)
|
||||
dx = dx - max * 2;
|
||||
else if (dx < -max)
|
||||
dx = dx + max * 2;
|
||||
|
||||
return dx;
|
||||
}
|
||||
|
||||
private Label updateWayLabels(MapTile t, Label l, float dx, float dy, double scale,
|
||||
ElementLayers dbg) {
|
||||
|
||||
for (TextItem ti = t.labels; ti != null; ti = ti.next) {
|
||||
if (ti.text.caption)
|
||||
continue;
|
||||
|
||||
// acquire a TextItem to add to TextLayer
|
||||
if (l == null)
|
||||
l = getLabel();
|
||||
|
||||
// check if path at current scale is long enough for text
|
||||
if (dbg == null && ti.width > ti.length * scale)
|
||||
continue;
|
||||
|
||||
l.clone(ti);
|
||||
l.move(ti, dx, dy, (float) scale);
|
||||
|
||||
// set line endpoints relative to view to be able to
|
||||
// check intersections with label from other tiles
|
||||
float w = (ti.x2 - ti.x1) / 2f;
|
||||
float h = (ti.y2 - ti.y1) / 2f;
|
||||
l.bbox = null;
|
||||
l.x1 = l.x - w;
|
||||
l.y1 = l.y - h;
|
||||
l.x2 = l.x + w;
|
||||
l.y2 = l.y + h;
|
||||
|
||||
if (!wayIsVisible(l))
|
||||
continue;
|
||||
|
||||
byte overlaps = -1;
|
||||
|
||||
if (l.bbox == null)
|
||||
l.bbox = new OBB2D(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
else
|
||||
l.bbox.set(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
|
||||
if (dbg == null || ti.width < ti.length * scale)
|
||||
overlaps = checkOverlap(l);
|
||||
|
||||
if (dbg != null)
|
||||
Debug.addDebugBox(dbg, l, ti, overlaps, false, (float) scale);
|
||||
|
||||
if (overlaps == 0) {
|
||||
addLabel(l);
|
||||
l.item = TextItem.copy(ti);
|
||||
l.tile = t;
|
||||
l.active = mRelabelCnt;
|
||||
l = null;
|
||||
}
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
private Label updateNodeLabels(MapTile t, Label l, float dx, float dy, double scale, float cos,
|
||||
float sin) {
|
||||
O: for (TextItem ti = t.labels; ti != null; ti = ti.next) {
|
||||
if (!ti.text.caption)
|
||||
continue;
|
||||
|
||||
// acquire a TextItem to add to TextLayer
|
||||
if (l == null)
|
||||
l = getLabel();
|
||||
|
||||
l.clone(ti);
|
||||
l.move(ti, dx, dy, (float) scale);
|
||||
if (!nodeIsVisible(l))
|
||||
continue;
|
||||
|
||||
if (l.bbox == null)
|
||||
l.bbox = new OBB2D();
|
||||
|
||||
l.bbox.setNormalized(l.x, l.y, cos, -sin,
|
||||
l.width + MIN_CAPTION_DIST,
|
||||
l.text.fontHeight + MIN_CAPTION_DIST,
|
||||
l.text.dy);
|
||||
|
||||
for (Label lp = mLabels; lp != null;) {
|
||||
if (l.bbox.overlaps(lp.bbox)) {
|
||||
if (l.text.priority < lp.text.priority) {
|
||||
lp = removeLabel(lp);
|
||||
continue;
|
||||
}
|
||||
continue O;
|
||||
}
|
||||
lp = (Label) lp.next;
|
||||
}
|
||||
|
||||
addLabel(l);
|
||||
l.item = TextItem.copy(ti);
|
||||
l.tile = t;
|
||||
l.active = mRelabelCnt;
|
||||
l = null;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
boolean updateLabels(LabelTask work) {
|
||||
// nextLayer is not loaded yet
|
||||
//if (mNextLayer.ready)
|
||||
// return false;
|
||||
|
||||
// get current tiles
|
||||
boolean changedTiles = mTileLayer.getVisibleTiles(mTileSet);
|
||||
boolean changedPos;
|
||||
|
||||
if (mTileSet.cnt == 0) {
|
||||
//log.debug("no tiles "+ mTileSet.getSerial());
|
||||
return false;
|
||||
}
|
||||
|
||||
MapPosition pos = work.pos;
|
||||
|
||||
synchronized (mViewport) {
|
||||
changedPos = mViewport.getMapPosition(pos);
|
||||
//mViewport.getMapViewProjection(coords);
|
||||
}
|
||||
|
||||
if (!changedTiles && !changedPos) {
|
||||
//log.debug("not changed " + changedTiles + " " + changedPos);
|
||||
return false;
|
||||
}
|
||||
|
||||
ElementLayers dbg = null;
|
||||
//if (mMap.getDebugSettings().debugLabels)
|
||||
// dbg = new ElementLayers();
|
||||
|
||||
int mw = (mMap.getWidth() + Tile.SIZE) / 2;
|
||||
int mh = (mMap.getHeight() + Tile.SIZE) / 2;
|
||||
mSquareRadius = mw * mw + mh * mh;
|
||||
|
||||
MapTile[] tiles = mTileSet.tiles;
|
||||
int zoom = tiles[0].zoomLevel;
|
||||
|
||||
// scale of tiles zoom-level relative to current position
|
||||
double scale = pos.scale / (1 << zoom);
|
||||
|
||||
double angle = Math.toRadians(pos.angle);
|
||||
float cos = (float) Math.cos(angle);
|
||||
float sin = (float) Math.sin(angle);
|
||||
|
||||
int maxx = Tile.SIZE << (zoom - 1);
|
||||
|
||||
//if (dbg != null)
|
||||
// Debug.addDebugLayers(dbg);
|
||||
|
||||
SymbolLayer sl = work.symbolLayer;
|
||||
sl.clearItems();
|
||||
|
||||
mRelabelCnt++;
|
||||
|
||||
double tileX = (pos.x * (Tile.SIZE << zoom));
|
||||
double tileY = (pos.y * (Tile.SIZE << zoom));
|
||||
|
||||
Label prevLabels = mLabels;
|
||||
mLabels = null;
|
||||
|
||||
for (Label l = prevLabels; l != null;) {
|
||||
if (l.text.caption) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
int diff = l.tile.zoomLevel - zoom;
|
||||
if (diff > 1 || diff < -1) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
float div = FastMath.pow(diff);
|
||||
float sscale = (float) (pos.scale / (1 << l.tile.zoomLevel));
|
||||
|
||||
if (l.width > l.length * sscale) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
float dx = (float) (l.tile.tileX * Tile.SIZE - tileX * div);
|
||||
float dy = (float) (l.tile.tileY * Tile.SIZE - tileY * div);
|
||||
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
l.move(l.item, dx, dy, sscale);
|
||||
|
||||
// set line endpoints relative to view to be able to
|
||||
// check intersections with label from other tiles
|
||||
float w = (l.item.x2 - l.item.x1) / 2f;
|
||||
float h = (l.item.y2 - l.item.y1) / 2f;
|
||||
l.x1 = l.x - w;
|
||||
l.y1 = l.y - h;
|
||||
l.x2 = l.x + w;
|
||||
l.y2 = l.y + h;
|
||||
|
||||
if (!wayIsVisible(l)) {
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
continue;
|
||||
}
|
||||
|
||||
l.bbox.set(l.x, l.y, l.x1, l.y1,
|
||||
l.width + MIN_WAY_DIST,
|
||||
l.text.fontHeight + MIN_WAY_DIST);
|
||||
|
||||
byte overlaps = checkOverlap(l);
|
||||
|
||||
if (dbg != null)
|
||||
Debug.addDebugBox(dbg, l, l.item, overlaps, true, sscale);
|
||||
|
||||
if (overlaps == 0) {
|
||||
Label ll = l;
|
||||
l = (Label) l.next;
|
||||
|
||||
ll.next = null;
|
||||
addLabel(ll);
|
||||
continue;
|
||||
}
|
||||
l = mPool.releaseAndGetNext(l);
|
||||
}
|
||||
|
||||
Label l = null;
|
||||
|
||||
/* add way labels */
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
l = updateWayLabels(t, l, dx, dy, scale, dbg);
|
||||
}
|
||||
}
|
||||
|
||||
/* add caption */
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
l = updateNodeLabels(t, l, dx, dy, scale, cos, sin);
|
||||
}
|
||||
}
|
||||
|
||||
for (Label ti = mLabels; ti != null; ti = (Label) ti.next) {
|
||||
if (ti.text.caption) {
|
||||
if (ti.text.texture != null) {
|
||||
SymbolItem s = SymbolItem.pool.get();
|
||||
s.texRegion = ti.text.texture;
|
||||
s.x = ti.x;
|
||||
s.y = ti.y;
|
||||
s.billboard = true;
|
||||
sl.addSymbol(s);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, n = mTileSet.cnt; i < n; i++) {
|
||||
MapTile t = tiles[i];
|
||||
synchronized (t) {
|
||||
if (!t.state(MapTile.STATE_READY))
|
||||
continue;
|
||||
|
||||
float dx = (float) (t.tileX * Tile.SIZE - tileX);
|
||||
float dy = (float) (t.tileY * Tile.SIZE - tileY);
|
||||
dx = flipLongitude(dx, maxx);
|
||||
|
||||
for (SymbolItem ti = t.symbols; ti != null; ti = ti.next) {
|
||||
if (ti.texRegion == null)
|
||||
continue;
|
||||
|
||||
int x = (int) ((dx + ti.x) * scale);
|
||||
int y = (int) ((dy + ti.y) * scale);
|
||||
|
||||
if (!iconIsVisible(x, y))
|
||||
continue;
|
||||
|
||||
SymbolItem s = SymbolItem.pool.get();
|
||||
s.texRegion = ti.texRegion;
|
||||
s.x = x;
|
||||
s.y = y;
|
||||
s.billboard = true;
|
||||
sl.addSymbol(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// temporary used Label
|
||||
l = (Label) mPool.release(l);
|
||||
|
||||
// draw text to bitmaps and create vertices
|
||||
work.textLayer.labels = mLabels;
|
||||
work.textLayer.prepare();
|
||||
work.textLayer.labels = null;
|
||||
|
||||
// remove tile locks
|
||||
mTileLayer.releaseTiles(mTileSet);
|
||||
|
||||
//mDebugLayer = dbg;
|
||||
//mNextLayer.ready = true;
|
||||
|
||||
return true;
|
||||
public TextRenderer(Worker worker) {
|
||||
mWorker = worker;
|
||||
}
|
||||
|
||||
long lastDraw = 0;
|
||||
@ -613,12 +58,13 @@ class TextRenderer extends ElementRenderer {
|
||||
|
||||
LabelTask t;
|
||||
synchronized (mWorker) {
|
||||
|
||||
t = mWorker.poll();
|
||||
|
||||
if (t == null)
|
||||
if (t == null) {
|
||||
if (!mWorker.isRunning()) {
|
||||
mWorker.submit(50);
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
layers.clear();
|
||||
}
|
||||
|
||||
@ -626,70 +72,21 @@ class TextRenderer extends ElementRenderer {
|
||||
layers.textureLayers = t.layers;
|
||||
mMapPosition = t.pos;
|
||||
compile();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void render(MapPosition pos, Matrices m) {
|
||||
GLState.test(false, false);
|
||||
//Debug.draw(pos, layers);
|
||||
|
||||
layers.vbo.bind();
|
||||
|
||||
float scale = (float) (mMapPosition.scale / pos.scale);
|
||||
|
||||
if (layers.baseLayers != null) {
|
||||
setMatrix(pos, m, true);
|
||||
|
||||
for (RenderElement l = layers.baseLayers; l != null;) {
|
||||
if (l.type == RenderElement.POLYGON) {
|
||||
l = PolygonLayer.Renderer.draw(pos, l, m, true, 1, false);
|
||||
} else {
|
||||
float div = (float) (mMapPosition.scale / (1 << pos.zoomLevel));
|
||||
l = LineLayer.Renderer.draw(layers, l, pos, m, div);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMatrix(pos, m, false);
|
||||
|
||||
for (RenderElement l = layers.textureLayers; l != null;)
|
||||
l = TextureLayer.Renderer.draw(l, scale, m);
|
||||
}
|
||||
|
||||
final class Worker extends SimpleWorker<LabelTask> {
|
||||
|
||||
public Worker(Map map) {
|
||||
super(map, 10, new LabelTask(), new LabelTask());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doWork(LabelTask t) {
|
||||
|
||||
if (updateLabels(t)) {
|
||||
mMap.render();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(LabelTask t) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
mLabels = (Label) mPool.releaseAll(mLabels);
|
||||
mTileSet.releaseTiles();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearLabels() {
|
||||
mWorker.cancel(true);
|
||||
}
|
||||
|
||||
public void update() {
|
||||
mWorker.submit(MAX_RELABEL_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,22 +57,6 @@ public class TextItem extends Inlist<TextItem> {
|
||||
return ti;
|
||||
}
|
||||
|
||||
public static boolean shareText(TextItem ti1, TextItem ti2) {
|
||||
if (ti1.text != ti2.text)
|
||||
return false;
|
||||
|
||||
if (ti1.string == ti2.string)
|
||||
return true;
|
||||
|
||||
if (ti1.string.equals(ti2.string)) {
|
||||
// make strings unique, should be done only once..
|
||||
ti1.string = ti2.string;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public TextItem set(float x, float y, String string, Text text) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@ -86,35 +70,6 @@ public class TextItem extends Inlist<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);
|
||||
}
|
||||
|
||||
// link to next node
|
||||
//public TextItem next;
|
||||
|
||||
|
||||
@ -38,7 +38,6 @@ public final class TextLayer extends TextureLayer {
|
||||
|
||||
public TextLayer() {
|
||||
super(RenderElement.SYMBOL);
|
||||
//mCanvas = Graphics.res.getCanvas();
|
||||
mCanvas = CanvasAdapter.g.getCanvas();
|
||||
fixed = true;
|
||||
}
|
||||
@ -58,7 +57,7 @@ public final class TextLayer extends TextureLayer {
|
||||
&& !item.string.equals(it.string))
|
||||
it = it.next;
|
||||
|
||||
// unify duplicate string :)
|
||||
// unify duplicate string
|
||||
// Note: this is required for 'packing test' in prepare to work!
|
||||
if (item.string != it.string && item.string.equals(it.string))
|
||||
item.string = it.string;
|
||||
@ -139,117 +138,22 @@ public final class TextLayer extends TextureLayer {
|
||||
if (width > TEXTURE_WIDTH)
|
||||
width = TEXTURE_WIDTH;
|
||||
|
||||
float hw = width / 2.0f;
|
||||
float hh = height / 2.0f;
|
||||
|
||||
float hh2 = hh;
|
||||
//if (!it.text.caption) {
|
||||
// // displace by baseline
|
||||
// float desc = 0; //(hh - (height - it.text.fontDescent) / 2);
|
||||
//
|
||||
// //float desc = it.text.fontDescent / 2;
|
||||
// hh2 = hh + desc;
|
||||
// hh = hh - desc;
|
||||
//}
|
||||
|
||||
// texture coordinates
|
||||
short u1 = (short) (COORD_SCALE * x);
|
||||
short v1 = (short) (COORD_SCALE * y);
|
||||
short u2 = (short) (COORD_SCALE * (x + width));
|
||||
short v2 = (short) (COORD_SCALE * (y + height));
|
||||
|
||||
while (it != null) {
|
||||
|
||||
short x1, x2, x3, x4, y1, y3, y2, y4;
|
||||
|
||||
if (it.text.caption) {
|
||||
//if (it.origin == 0) {
|
||||
x1 = x3 = (short) (COORD_SCALE * -hw);
|
||||
x2 = x4 = (short) (COORD_SCALE * hw);
|
||||
y1 = y2 = (short) (COORD_SCALE * (it.text.dy + hh));
|
||||
y3 = y4 = (short) (COORD_SCALE * (it.text.dy - hh));
|
||||
//} else {
|
||||
// x1 = x3 = (short) (SCALE * 0);
|
||||
// x2 = x4 = (short) (SCALE * width);
|
||||
// y1 = y2 = (short) (SCALE * 0);
|
||||
// y3 = y4 = (short) (SCALE * -height);
|
||||
//}
|
||||
} else {
|
||||
float vx = it.x1 - it.x2;
|
||||
float vy = it.y1 - it.y2;
|
||||
float a = (float) Math.sqrt(vx * vx + vy * vy);
|
||||
vx = vx / a;
|
||||
vy = vy / a;
|
||||
|
||||
float ux = -vy * hh;
|
||||
float uy = vx * hh;
|
||||
|
||||
float ux2 = -vy * hh2;
|
||||
float uy2 = vx * hh2;
|
||||
|
||||
vx *= hw;
|
||||
vy *= hw;
|
||||
|
||||
// top-left
|
||||
x1 = (short) (COORD_SCALE * (vx - ux));
|
||||
y1 = (short) (COORD_SCALE * (vy - uy));
|
||||
// top-right
|
||||
x2 = (short) (COORD_SCALE * (-vx - ux));
|
||||
y2 = (short) (COORD_SCALE * (-vy - uy));
|
||||
// bot-right
|
||||
x4 = (short) (COORD_SCALE * (-vx + ux2));
|
||||
y4 = (short) (COORD_SCALE * (-vy + uy2));
|
||||
// bot-left
|
||||
x3 = (short) (COORD_SCALE * (vx + ux2));
|
||||
y3 = (short) (COORD_SCALE * (vy + uy2));
|
||||
}
|
||||
|
||||
// add vertices
|
||||
int tmp = (int) (COORD_SCALE * it.x) & LBIT_MASK;
|
||||
short tx = (short) (tmp | (it.text.caption ? 1 : 0));
|
||||
short ty = (short) (COORD_SCALE * it.y);
|
||||
|
||||
if (pos == VertexItem.SIZE) {
|
||||
vi.used = VertexItem.SIZE;
|
||||
vi = VertexItem.pool.getNext(vi);
|
||||
buf = vi.vertices;
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
// top-left
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x1;
|
||||
buf[pos++] = y1;
|
||||
buf[pos++] = u1;
|
||||
buf[pos++] = v2;
|
||||
// bot-left
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x3;
|
||||
buf[pos++] = y3;
|
||||
buf[pos++] = u1;
|
||||
buf[pos++] = v1;
|
||||
// top-right
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x2;
|
||||
buf[pos++] = y2;
|
||||
buf[pos++] = u2;
|
||||
buf[pos++] = v2;
|
||||
// bot-right
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x4;
|
||||
buf[pos++] = y4;
|
||||
buf[pos++] = u2;
|
||||
buf[pos++] = v1;
|
||||
addItem(buf, pos, it, width, height, x, y);
|
||||
pos += 24;
|
||||
|
||||
// six indices to draw the four vertices
|
||||
numIndices += TextureLayer.INDICES_PER_SPRITE;
|
||||
numVertices += 4;
|
||||
|
||||
if (it.next == null || (it.next.text != it.text)
|
||||
if (it.next == null
|
||||
|| (it.next.text != it.text)
|
||||
|| (it.next.string != it.string)) {
|
||||
it = it.next;
|
||||
break;
|
||||
@ -268,6 +172,86 @@ public final class TextLayer extends TextureLayer {
|
||||
return true;
|
||||
}
|
||||
|
||||
void addItem(short[] buf, int pos, TextItem it, float width, float height, float x, float y) {
|
||||
// texture coordinates
|
||||
short u1 = (short) (COORD_SCALE * x);
|
||||
short v1 = (short) (COORD_SCALE * y);
|
||||
short u2 = (short) (COORD_SCALE * (x + width));
|
||||
short v2 = (short) (COORD_SCALE * (y + height));
|
||||
|
||||
short x1, x2, x3, x4, y1, y3, y2, y4;
|
||||
float hw = width / 2.0f;
|
||||
float hh = height / 2.0f;
|
||||
if (it.text.caption) {
|
||||
x1 = x3 = (short) (COORD_SCALE * -hw);
|
||||
x2 = x4 = (short) (COORD_SCALE * hw);
|
||||
y1 = y2 = (short) (COORD_SCALE * (it.text.dy + hh));
|
||||
y3 = y4 = (short) (COORD_SCALE * (it.text.dy - hh));
|
||||
} else {
|
||||
float vx = it.x1 - it.x2;
|
||||
float vy = it.y1 - it.y2;
|
||||
float a = (float) Math.sqrt(vx * vx + vy * vy);
|
||||
vx = vx / a;
|
||||
vy = vy / a;
|
||||
|
||||
float ux = -vy * hh;
|
||||
float uy = vx * hh;
|
||||
|
||||
float ux2 = -vy * hh;
|
||||
float uy2 = vx * hh;
|
||||
|
||||
vx *= hw;
|
||||
vy *= hw;
|
||||
|
||||
// top-left
|
||||
x1 = (short) (COORD_SCALE * (vx - ux));
|
||||
y1 = (short) (COORD_SCALE * (vy - uy));
|
||||
// top-right
|
||||
x2 = (short) (COORD_SCALE * (-vx - ux));
|
||||
y2 = (short) (COORD_SCALE * (-vy - uy));
|
||||
// bot-right
|
||||
x4 = (short) (COORD_SCALE * (-vx + ux2));
|
||||
y4 = (short) (COORD_SCALE * (-vy + uy2));
|
||||
// bot-left
|
||||
x3 = (short) (COORD_SCALE * (vx + ux2));
|
||||
y3 = (short) (COORD_SCALE * (vy + uy2));
|
||||
}
|
||||
|
||||
// add vertices
|
||||
int tmp = (int) (COORD_SCALE * it.x) & LBIT_MASK;
|
||||
short tx = (short) (tmp | (it.text.caption ? 1 : 0));
|
||||
short ty = (short) (COORD_SCALE * it.y);
|
||||
|
||||
// top-left
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x1;
|
||||
buf[pos++] = y1;
|
||||
buf[pos++] = u1;
|
||||
buf[pos++] = v2;
|
||||
// bot-left
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x3;
|
||||
buf[pos++] = y3;
|
||||
buf[pos++] = u1;
|
||||
buf[pos++] = v1;
|
||||
// top-right
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x2;
|
||||
buf[pos++] = y2;
|
||||
buf[pos++] = u2;
|
||||
buf[pos++] = v2;
|
||||
// bot-right
|
||||
buf[pos++] = tx;
|
||||
buf[pos++] = ty;
|
||||
buf[pos++] = x4;
|
||||
buf[pos++] = y4;
|
||||
buf[pos++] = u2;
|
||||
buf[pos++] = v1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
// release textures
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user