diff --git a/resources/rendertheme.xsd b/resources/rendertheme.xsd
index fe4af0f0..4d11511b 100644
--- a/resources/rendertheme.xsd
+++ b/resources/rendertheme.xsd
@@ -145,7 +145,8 @@
-
+
+
diff --git a/vtm-themes/resources/assets/styles/default.xml b/vtm-themes/resources/assets/styles/default.xml
index a18ff1d5..3c0c5246 100644
--- a/vtm-themes/resources/assets/styles/default.xml
+++ b/vtm-themes/resources/assets/styles/default.xml
@@ -260,6 +260,7 @@
-->
+
@@ -401,6 +402,7 @@
+
@@ -521,7 +523,18 @@
-
+
+
+
+
+
+
+
+
+
+
@@ -554,7 +567,7 @@
-
@@ -929,7 +942,7 @@
-
+
@@ -984,6 +997,7 @@
+
@@ -998,7 +1012,7 @@
+ stroke-width="2.0" />
@@ -1039,7 +1053,7 @@
-
+
diff --git a/vtm/src/org/oscim/core/GeometryBuffer.java b/vtm/src/org/oscim/core/GeometryBuffer.java
index d9dc57fc..9e2c1ec1 100644
--- a/vtm/src/org/oscim/core/GeometryBuffer.java
+++ b/vtm/src/org/oscim/core/GeometryBuffer.java
@@ -1,5 +1,6 @@
/*
* Copyright 2013 Hannes Janetzek
+ * Copyright 2016 Andrey Novikov
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -411,6 +412,27 @@ public class GeometryBuffer {
}
}
+ /**
+ * Calculates geometry area, only polygon outer ring is taken into account.
+ *
+ * @return polygon area, 0 for other geometries
+ */
+ public float area() {
+ if (isPoint() || isLine() || getNumPoints() < 3)
+ return 0f;
+
+ float area = 0f;
+ // use only outer ring
+ int n = index[0];
+
+ for (int i = 0; i < n - 2; i += 2) {
+ area = area + (points[i] * points[i+3]) - (points[i+1] * points[i+2]);
+ }
+ area = area + (points[n-2] * points[1]) - (points[n-1] * points[0]);
+
+ return 0.5f * area;
+ }
+
public String toString() {
StringBuffer sb = new StringBuffer();
int o = 0;
diff --git a/vtm/src/org/oscim/core/MapElement.java b/vtm/src/org/oscim/core/MapElement.java
index fd2187f6..a0258181 100644
--- a/vtm/src/org/oscim/core/MapElement.java
+++ b/vtm/src/org/oscim/core/MapElement.java
@@ -33,6 +33,8 @@ public class MapElement extends GeometryBuffer {
public final TagSet tags = new TagSet();
+ public PointF labelPosition;
+
public MapElement() {
super(1024, 16);
}
@@ -45,6 +47,10 @@ public class MapElement extends GeometryBuffer {
this.layer = layer;
}
+ public void setLabelPosition(float x, float y) {
+ labelPosition = new PointF(x, y);
+ }
+
@Override
public MapElement clear() {
layer = 5;
diff --git a/vtm/src/org/oscim/layers/tile/vector/labeling/LabelTileLoaderHook.java b/vtm/src/org/oscim/layers/tile/vector/labeling/LabelTileLoaderHook.java
index 88282388..d4bec050 100644
--- a/vtm/src/org/oscim/layers/tile/vector/labeling/LabelTileLoaderHook.java
+++ b/vtm/src/org/oscim/layers/tile/vector/labeling/LabelTileLoaderHook.java
@@ -1,5 +1,6 @@
/*
* Copyright 2016 devemux86
+ * Copyright 2016 Andrey Novikov
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -18,6 +19,7 @@ package org.oscim.layers.tile.vector.labeling;
import org.oscim.core.MapElement;
import org.oscim.core.PointF;
+import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile;
import org.oscim.layers.tile.vector.VectorTileLayer.TileLoaderThemeHook;
import org.oscim.renderer.bucket.RenderBuckets;
@@ -26,6 +28,7 @@ import org.oscim.renderer.bucket.TextItem;
import org.oscim.theme.styles.RenderStyle;
import org.oscim.theme.styles.SymbolStyle;
import org.oscim.theme.styles.TextStyle;
+import org.oscim.utils.geom.PolyLabel;
import static org.oscim.core.GeometryBuffer.GeometryType.LINE;
import static org.oscim.core.GeometryBuffer.GeometryType.POINT;
@@ -70,23 +73,25 @@ public class LabelTileLoaderHook implements TileLoaderThemeHook {
offset += length;
}
} else if (element.type == POLY) {
- // TODO place somewhere on polygon
String value = element.tags.getValue(text.textKey);
if (value == null || value.length() == 0)
return false;
- float x = 0;
- float y = 0;
- int n = element.index[0];
-
- for (int i = 0; i < n; ) {
- x += element.points[i++];
- y += element.points[i++];
+ if (text.areaSize > 0f) {
+ float area = element.area();
+ float ratio = area / (Tile.SIZE * Tile.SIZE); // we can't use static as it's recalculated based on dpi
+ if (ratio < text.areaSize)
+ return false;
}
- x /= (n / 2);
- y /= (n / 2);
- ld.labels.push(TextItem.pool.get().set(x, y, value, text));
+ PointF label = element.labelPosition;
+ if (label == null)
+ label = PolyLabel.get(element, 5f);
+
+ if (label.x < 0 || label.x > Tile.SIZE || label.y < 0 || label.y > Tile.SIZE)
+ return false;
+
+ ld.labels.push(TextItem.pool.get().set(label.x, label.y, value, text));
} else if (element.type == POINT) {
String value = element.tags.getValue(text.textKey);
if (value == null || value.length() == 0)
@@ -97,7 +102,7 @@ public class LabelTileLoaderHook implements TileLoaderThemeHook {
ld.labels.push(TextItem.pool.get().set(p.x, p.y, value, text));
}
}
- } else if ((element.type == POINT) && (style instanceof SymbolStyle)) {
+ } else if (style instanceof SymbolStyle) {
SymbolStyle symbol = (SymbolStyle) style;
if (symbol.bitmap == null && symbol.texture == null)
@@ -105,14 +110,32 @@ public class LabelTileLoaderHook implements TileLoaderThemeHook {
LabelTileData ld = get(tile);
- for (int i = 0, n = element.getNumPoints(); i < n; i++) {
- PointF p = element.getPoint(i);
+ if (element.type == POINT) {
+ for (int i = 0, n = element.getNumPoints(); i < n; i++) {
+ PointF p = element.getPoint(i);
+
+ SymbolItem it = SymbolItem.pool.get();
+ if (symbol.bitmap != null)
+ it.set(p.x, p.y, symbol.bitmap, true);
+ else
+ it.set(p.x, p.y, symbol.texture, true);
+ ld.symbols.push(it);
+ }
+ } else if (element.type == LINE) {
+ //TODO: implement
+ } else if (element.type == POLY) {
+ PointF centroid = element.labelPosition;
+ if (centroid == null)
+ return false;
+
+ if (centroid.x < 0 || centroid.x > Tile.SIZE || centroid.y < 0 || centroid.y > Tile.SIZE)
+ return false;
SymbolItem it = SymbolItem.pool.get();
if (symbol.bitmap != null)
- it.set(p.x, p.y, symbol.bitmap, true);
+ it.set(centroid.x, centroid.y, symbol.bitmap, true);
else
- it.set(p.x, p.y, symbol.texture, true);
+ it.set(centroid.x, centroid.y, symbol.texture, true);
ld.symbols.push(it);
}
}
diff --git a/vtm/src/org/oscim/theme/XmlThemeBuilder.java b/vtm/src/org/oscim/theme/XmlThemeBuilder.java
index 3e4d1f0b..4439cef0 100644
--- a/vtm/src/org/oscim/theme/XmlThemeBuilder.java
+++ b/vtm/src/org/oscim/theme/XmlThemeBuilder.java
@@ -3,6 +3,7 @@
* Copyright 2013 Hannes Janetzek
* Copyright 2016 devemux86
* Copyright 2016 Longri
+ * Copyright 2016 Andrey Novikov
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -863,6 +864,9 @@ public class XmlThemeBuilder extends DefaultHandler {
else if ("priority".equals(name))
b.priority = Integer.parseInt(value);
+ else if ("area-size".equals(name))
+ b.areaSize = Float.parseFloat(value);
+
else if ("dy".equals(name))
// NB: minus..
b.dy = -Float.parseFloat(value) * CanvasAdapter.dpi / 160;
diff --git a/vtm/src/org/oscim/theme/styles/TextStyle.java b/vtm/src/org/oscim/theme/styles/TextStyle.java
index e961c854..c2b79cdf 100644
--- a/vtm/src/org/oscim/theme/styles/TextStyle.java
+++ b/vtm/src/org/oscim/theme/styles/TextStyle.java
@@ -1,6 +1,7 @@
/*
* Copyright 2013 Hannes Janetzek
* Copyright 2016 devemux86
+ * Copyright 2016 Andrey Novikov
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -36,6 +37,7 @@ public final class TextStyle extends RenderStyle {
public boolean caption;
public float dy;
public int priority;
+ public float areaSize;
public Bitmap bitmap;
public TextureRegion texture;
public FontFamily fontFamily;
@@ -49,6 +51,7 @@ public final class TextStyle extends RenderStyle {
fontSize = 0;
caption = false;
priority = Integer.MAX_VALUE;
+ areaSize = 0f;
bitmap = null;
texture = null;
fillColor = Color.BLACK;
@@ -98,6 +101,11 @@ public final class TextStyle extends RenderStyle {
return self();
}
+ public T areaSize(float areaSize) {
+ this.areaSize = areaSize;
+ return self();
+ }
+
public T bitmap(Bitmap bitmap) {
this.bitmap = bitmap;
return self();
@@ -126,6 +134,7 @@ public final class TextStyle extends RenderStyle {
fontSize = other.fontSize;
caption = other.caption;
priority = other.priority;
+ areaSize = other.areaSize;
bitmap = other.bitmap;
texture = other.texture;
fillColor = other.fillColor;
@@ -141,6 +150,7 @@ public final class TextStyle extends RenderStyle {
this.caption = style.caption;
this.dy = style.dy;
this.priority = style.priority;
+ this.areaSize = style.areaSize;
this.bitmap = style.bitmap;
this.texture = style.texture;
this.fillColor = style.paint.getColor();
@@ -159,6 +169,7 @@ public final class TextStyle extends RenderStyle {
this.caption = tb.caption;
this.dy = tb.dy;
this.priority = tb.priority;
+ this.areaSize = tb.areaSize;
this.bitmap = tb.bitmap;
this.texture = tb.texture;
@@ -193,6 +204,7 @@ public final class TextStyle extends RenderStyle {
public final boolean caption;
public final float dy;
public final int priority;
+ public final float areaSize;
public float fontHeight;
public float fontDescent;
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
index 500f332f..b2529a12 100644
--- a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
+++ b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
@@ -2,6 +2,7 @@
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2013, 2014 Hannes Janetzek
* Copyright 2016 devemux86
+ * Copyright 2016 Andrey Novikov
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -840,9 +841,11 @@ public class MapDatabase implements ITileDataSource {
e.tags.add(new Tag(Tag.KEY_REF, str, false));
}
}
- if ((featureByte & WAY_FEATURE_LABEL_POSITION) != 0)
- // labelPosition =
- readOptionalLabelPosition();
+
+ int[] labelPosition = null;
+ if ((featureByte & WAY_FEATURE_LABEL_POSITION) != 0) {
+ labelPosition = readOptionalLabelPosition();
+ }
if ((featureByte & WAY_FEATURE_DATA_BLOCKS_BYTE) != 0) {
wayDataBlocks = mReadBuffer.readUnsignedInt();
@@ -870,6 +873,8 @@ public class MapDatabase implements ITileDataSource {
continue;
}
+ if (labelPosition != null && wayDataBlock == 0)
+ e.setLabelPosition(e.points[0] + labelPosition[0], e.points[1] + labelPosition[1]);
mTileProjection.project(e);
if (!e.tags.containsKey("building"))
@@ -879,6 +884,7 @@ public class MapDatabase implements ITileDataSource {
e.simplify(1, true);
e.setLayer(layer);
+
mapDataSink.process(e);
}
}
@@ -886,14 +892,14 @@ public class MapDatabase implements ITileDataSource {
return true;
}
- private float[] readOptionalLabelPosition() {
- float[] labelPosition = new float[2];
+ private int[] readOptionalLabelPosition() {
+ int[] labelPosition = new int[2];
/* get the label position latitude offset (VBE-S) */
- labelPosition[1] = mTileLatitude + mReadBuffer.readSignedInt();
+ labelPosition[1] = mReadBuffer.readSignedInt();
/* get the label position longitude offset (VBE-S) */
- labelPosition[0] = mTileLongitude + mReadBuffer.readSignedInt();
+ labelPosition[0] = mReadBuffer.readSignedInt();
return labelPosition;
}
@@ -1021,6 +1027,10 @@ public class MapDatabase implements ITileDataSource {
indices[idx] = (short) cnt;
}
}
+ if (e.labelPosition != null) {
+ e.labelPosition.x = projectLon(e.labelPosition.x);
+ e.labelPosition.y = projectLat(e.labelPosition.y);
+ }
}
}
}
diff --git a/vtm/src/org/oscim/utils/geom/PolyLabel.java b/vtm/src/org/oscim/utils/geom/PolyLabel.java
new file mode 100644
index 00000000..74dc0e89
--- /dev/null
+++ b/vtm/src/org/oscim/utils/geom/PolyLabel.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright 2016 Andrey Novikov
+ * Java implementation of
+ * https://github.com/mapbox/polylabel
+ *
+ * ISC License
+ * Copyright (c) 2016 Mapbox
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any purpose
+ * with or without fee is hereby granted, provided that the above copyright notice
+ * and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+ * IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+ * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+package org.oscim.utils.geom;
+
+import org.oscim.core.GeometryBuffer;
+import org.oscim.core.PointF;
+
+import java.util.Comparator;
+import java.util.PriorityQueue;
+
+public class PolyLabel {
+ private static float SQRT2 = (float) Math.sqrt(2);
+
+ /**
+ * Returns pole of inaccessibility, the most distant internal point from the polygon outline.
+ * @param polygon polygon geometry
+ * @param precision calculation precision
+ * @return optimal label placement point
+ */
+ public static PointF get(GeometryBuffer polygon, float precision) {
+ // find the bounding box of the outer ring
+ float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE, maxX = Float.MIN_VALUE, maxY = Float.MIN_VALUE;
+
+ int n = polygon.index[0];
+
+ for (int i = 0; i < n; ) {
+ float x = polygon.points[i++];
+ float y = polygon.points[i++];
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x > maxX) maxX = x;
+ if (y > maxY) maxY = y;
+ }
+
+ float width = maxX - minX;
+ float height = maxY - minY;
+ float cellSize = Math.min(width, height);
+ float h = cellSize / 2;
+
+ // a priority queue of cells in order of their "potential" (max distance to polygon)
+ PriorityQueue cellQueue = new PriorityQueue<>(1, new MaxComparator());
+
+ // cover polygon with initial cells
+ for (float x = minX; x < maxX; x += cellSize) {
+ for (float y = minY; y < maxY; y += cellSize) {
+ cellQueue.add(new Cell(x + h, y + h, h, polygon));
+ }
+ }
+
+ // take centroid as the first best guess
+ Cell bestCell = getCentroidCell(polygon);
+
+ // special case for rectangular polygons
+ Cell bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
+ if (bboxCell.d > bestCell.d) bestCell = bboxCell;
+
+ while (!cellQueue.isEmpty()) {
+ // pick the most promising cell from the queue
+ Cell cell = cellQueue.remove();
+
+ // update the best cell if we found a better one
+ if (cell.d > bestCell.d)
+ bestCell = cell;
+
+ // do not drill down further if there's no chance of a better solution
+ if (cell.max - bestCell.d <= precision) continue;
+
+ // split the cell into four cells
+ h = cell.h / 2;
+ cellQueue.add(new Cell(cell.x - h, cell.y - h, h, polygon));
+ cellQueue.add(new Cell(cell.x + h, cell.y - h, h, polygon));
+ cellQueue.add(new Cell(cell.x - h, cell.y + h, h, polygon));
+ cellQueue.add(new Cell(cell.x + h, cell.y + h, h, polygon));
+ }
+
+ return new PointF(bestCell.x, bestCell.y);
+ }
+
+ private static class MaxComparator implements Comparator
+ {
+ @Override
+ public int compare(Cell a, Cell b) {
+ return Float.compare(b.max, a.max);
+ }
+ }
+
+ private static class Cell {
+ final float x;
+ final float y;
+ final float h;
+ final float d;
+ final float max;
+
+ Cell(float x, float y, float h, GeometryBuffer polygon) {
+ this.x = x; // cell center x
+ this.y = y; // cell center y
+ this.h = h; // half the cell size
+ this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
+ this.max = this.d + this.h * SQRT2; // max distance to polygon within a cell
+ }
+ }
+
+ // signed distance from point to polygon outline (negative if point is outside)
+ private static float pointToPolygonDist(float x, float y, GeometryBuffer polygon) {
+ boolean inside = false;
+ float minDistSq = Float.POSITIVE_INFINITY;
+
+ int pos = 0;
+
+ for (int k = 0; k < polygon.index.length; k++) {
+ if (polygon.index[k] < 0)
+ break;
+ if (polygon.index[k] == 0)
+ continue;
+
+ for (int i = 0, n = polygon.index[k], j = n - 2; i < n; j = i, i += 2) {
+ float ax = polygon.points[pos+i];
+ float ay = polygon.points[pos+i+1];
+ float bx = polygon.points[pos+j];
+ float by = polygon.points[pos+j+1];
+
+ if (((ay > y) ^ (by > y)) &&
+ (x < (bx - ax) * (y - ay) / (by - ay) + ax)) inside = !inside;
+
+ minDistSq = Math.min(minDistSq, getSegDistSq(x, y, ax, ay, bx, by));
+ }
+
+ pos += polygon.index[k];
+ }
+
+ return (float) ((inside ? 1 : -1) * Math.sqrt(minDistSq));
+ }
+
+ // get polygon centroid
+ private static Cell getCentroidCell(GeometryBuffer polygon) {
+ float area = 0f;
+ float x = 0f;
+ float y = 0f;
+
+ for (int i = 0, n = polygon.index[0], j = n - 2; i < n; j = i, i += 2) {
+ float ax = polygon.points[i];
+ float ay = polygon.points[i+1];
+ float bx = polygon.points[j];
+ float by = polygon.points[j+1];
+ float f = ax * by - bx * ay;
+ x += (ax + bx) * f;
+ y += (ay + by) * f;
+ area += f * 3;
+ }
+ return new Cell(x / area, y / area, 0f, polygon);
+ }
+
+ // get squared distance from a point to a segment
+ private static float getSegDistSq(float px, float py, float ax, float ay, float bx, float by) {
+
+ float x = ax;
+ float y = ay;
+ float dx = bx - x;
+ float dy = by - y;
+
+ if (dx != 0f || dy != 0f) {
+
+ float t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
+
+ if (t > 1) {
+ x = bx;
+ y = by;
+
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = px - x;
+ dy = py - y;
+
+ return dx * dx + dy * dy;
+ }
+}
| |