From b174f651227aa6a4d03a77eb79f67b328bb3844e Mon Sep 17 00:00:00 2001
From: Hannes Janetzek
Date: Wed, 17 Jun 2015 23:21:57 +0200
Subject: [PATCH] added drawing api
add GeomBuilder, needs Jts
drawing_api: squashed
drawing_api: use JtsDrawable baseclass
- add makeCircle for testing
drawing_api: cleanups
drawing_api: refactor
drawing_api: use SpatialIndex
drawing_api: optimization + cleanup
drawing_api: VectorLayer
- use VectorLayer for PathLayer
drawing_api: make style builder more consistent with theme api
drawing_api: wip
---
.../android/test/PathOverlayActivity.java | 89 ++--
.../src/org/oscim/layers/OsmVectorLayer.java | 4 +-
.../src/org/oscim/layers/JeoVectorLayer.java | 2 +
vtm-jeo/src/org/oscim/layers/JtsLayer.java | 10 +-
.../src/org/oscim/test/PathLayerTest.java | 83 ++++
.../src/org/oscim/test/VectorLayerTest.java | 81 ++++
.../src/org/oscim/web/client/SearchBox.java | 2 +-
vtm/build.gradle | 6 +-
vtm/src/org/oscim/core/MapElement.java | 3 +-
vtm/src/org/oscim/layers/PathLayer.java | 344 +++----------
.../layers/vector/AbstractVectorLayer.java | 68 ++-
.../org/oscim/layers/vector/JtsConverter.java | 81 ++++
.../org/oscim/layers/vector/VectorLayer.java | 321 +++++++++++++
.../vector/geometries/CircleDrawable.java | 149 ++++++
.../layers/vector/geometries/Drawable.java | 16 +
.../vector/geometries/HexagonDrawable.java | 88 ++++
.../layers/vector/geometries/JtsDrawable.java | 64 +++
.../vector/geometries/LineDrawable.java | 44 ++
.../vector/geometries/PointDrawable.java | 36 ++
.../vector/geometries/PolygonDrawable.java | 116 +++++
.../vector/geometries/RectangleDrawable.java | 55 +++
.../oscim/layers/vector/geometries/Style.java | 198 ++++++++
.../org/oscim/utils/async/SimpleWorker.java | 3 +-
vtm/src/org/oscim/utils/geom/GeomBuilder.java | 453 ++++++++++++++++++
24 files changed, 1967 insertions(+), 349 deletions(-)
create mode 100644 vtm-playground/src/org/oscim/test/PathLayerTest.java
create mode 100644 vtm-playground/src/org/oscim/test/VectorLayerTest.java
create mode 100644 vtm/src/org/oscim/layers/vector/JtsConverter.java
create mode 100644 vtm/src/org/oscim/layers/vector/VectorLayer.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/Drawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
create mode 100644 vtm/src/org/oscim/layers/vector/geometries/Style.java
create mode 100644 vtm/src/org/oscim/utils/geom/GeomBuilder.java
diff --git a/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java b/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
index ae42aaa6..985664ae 100644
--- a/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/PathOverlayActivity.java
@@ -16,32 +16,52 @@
*/
package org.oscim.android.test;
-import static org.oscim.tiling.source.bitmap.DefaultSources.STAMEN_TONER;
-
import java.util.ArrayList;
-import java.util.List;
import org.oscim.backend.canvas.Color;
-import org.oscim.core.GeoPoint;
+import org.oscim.core.MapPosition;
+import org.oscim.event.Event;
import org.oscim.layers.PathLayer;
+import org.oscim.map.Map.UpdateListener;
import android.os.Bundle;
-import android.os.SystemClock;
+/**
+ * This is a very INEFFICIENT and somewhat less usefull example for how to use
+ * PathLayers!
+ */
public class PathOverlayActivity extends BitmapTileMapActivity {
public PathOverlayActivity() {
- super(STAMEN_TONER.build());
+ //super(STAMEN_TONER.build());
+ super(null);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mBitmapLayer.tileRenderer().setBitmapAlpha(0.5f);
+ //mBitmapLayer.tileRenderer().setBitmapAlpha(0.5f);
- createLayers(1, true);
+ mMap.setMapPosition(0, 0, 1 << 2);
+ for (double lat = -90; lat <= 90; lat += 5) {
+ int c = Color.fade(Color.rainbow((float) (lat + 90) / 180), 0.5f);
+ PathLayer pathLayer = new PathLayer(mMap, c, 6);
+ mMap.layers().add(pathLayer);
+ mPathLayers.add(pathLayer);
+ }
- looooop();
+ mMap.events.bind(new UpdateListener() {
+ @Override
+ public void onMapEvent(Event e, MapPosition mapPosition) {
+ //if (e == Map.UPDATE_EVENT) {
+ long t = System.currentTimeMillis();
+ float pos = t % 20000 / 10000f - 1f;
+ createLayers(pos);
+
+ mMap.updateMap(true);
+ //}
+ }
+ });
}
@Override
@@ -52,40 +72,18 @@ public class PathOverlayActivity extends BitmapTileMapActivity {
mMapView.onResume();
}
- void looooop() {
- mMap.postDelayed(new Runnable() {
- @Override
- public void run() {
- long t = SystemClock.uptimeMillis();
- float pos = t % 20000 / 10000f - 1f;
- createLayers(pos, false);
- //Samples.log.debug("update took" + (SystemClock.uptimeMillis() - t) + " " + pos);
- looooop();
- redraw();
- }
- }, 50);
- }
-
- void redraw() {
- mMap.render();
- }
-
ArrayList mPathLayers = new ArrayList();
- void createLayers(float pos, boolean init) {
+ void createLayers(float pos) {
int i = 0;
-
for (double lat = -90; lat <= 90; lat += 5) {
- List pts = new ArrayList();
-
+ double[] packedCoordinates = new double[360 + 2];
+ //List pts = new ArrayList();
+ int c = 0;
for (double lon = -180; lon <= 180; lon += 2) {
//pts.add(new GeoPoint(lat, lon));
- double longitude = lon + (pos * 180);
- if (longitude < -180)
- longitude += 360;
- if (longitude > 180)
- longitude -= 360;
+ double longitude = lon;
double latitude = lat + (pos * 90);
if (latitude < -90)
@@ -95,20 +93,15 @@ public class PathOverlayActivity extends BitmapTileMapActivity {
latitude += Math.sin((Math.abs(pos) * (lon / Math.PI)));
- pts.add(new GeoPoint(latitude, longitude));
- }
- PathLayer pathLayer;
- if (init) {
- int c = Color.fade(Color.rainbow((float) (lat + 90) / 180), 0.5f);
- pathLayer = new PathLayer(mMap, c, 6);
- mMap.layers().add(pathLayer);
- mPathLayers.add(pathLayer);
- } else {
- pathLayer = mPathLayers.get(i++);
+ packedCoordinates[c++] = longitude;
+ packedCoordinates[c++] = latitude;
}
- pathLayer.setPoints(pts);
+ //LineString line = new LineString(factory.create(packedCoordinates, 2), geomFactory);
+ //mPathLayers.get(i++).setLineString(line);
+
+ mPathLayers.get(i++).setLineString(packedCoordinates);
+
}
-
}
}
diff --git a/vtm-extras/src/org/oscim/layers/OsmVectorLayer.java b/vtm-extras/src/org/oscim/layers/OsmVectorLayer.java
index 91d7a8d1..63835ed2 100644
--- a/vtm-extras/src/org/oscim/layers/OsmVectorLayer.java
+++ b/vtm-extras/src/org/oscim/layers/OsmVectorLayer.java
@@ -1,6 +1,6 @@
package org.oscim.layers;
-import org.oscim.core.BoundingBox;
+import org.oscim.core.Box;
import org.oscim.core.osm.OsmElement;
import org.oscim.layers.vector.AbstractVectorLayer;
import org.oscim.map.Map;
@@ -12,7 +12,7 @@ public class OsmVectorLayer extends AbstractVectorLayer {
}
@Override
- protected void processFeatures(Task t, BoundingBox b) {
+ protected void processFeatures(Task t, Box b) {
}
diff --git a/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java b/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java
index d78447df..e3581935 100644
--- a/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java
+++ b/vtm-jeo/src/org/oscim/layers/JeoVectorLayer.java
@@ -33,6 +33,8 @@ public class JeoVectorLayer extends JtsLayer {
private final RuleList mRules;
protected double mDropPointDistance = 0.01;
+ private double mMinX;
+ private double mMinY;
public JeoVectorLayer(Map map, VectorDataset data, Style style) {
super(map);
diff --git a/vtm-jeo/src/org/oscim/layers/JtsLayer.java b/vtm-jeo/src/org/oscim/layers/JtsLayer.java
index 6430e6e1..08e5d8b6 100644
--- a/vtm-jeo/src/org/oscim/layers/JtsLayer.java
+++ b/vtm-jeo/src/org/oscim/layers/JtsLayer.java
@@ -1,7 +1,7 @@
package org.oscim.layers;
import org.jeo.geom.CoordinatePath;
-import org.oscim.core.BoundingBox;
+import org.oscim.core.Box;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
@@ -19,14 +19,16 @@ import com.vividsolutions.jts.geom.Geometry;
public abstract class JtsLayer extends AbstractVectorLayer {
+ private double mMinX;
+ private double mMinY;
+
public JtsLayer(Map map) {
super(map);
}
@Override
- protected void processFeatures(Task t, BoundingBox bbox) {
- processFeatures(t, new Envelope(bbox.getMinLongitude(), bbox.getMaxLongitude(),
- bbox.getMinLatitude(), bbox.getMaxLatitude()));
+ protected void processFeatures(Task t, Box bbox) {
+ processFeatures(t, new Envelope(bbox.xmin, bbox.ymin, bbox.xmax, bbox.ymax));
}
diff --git a/vtm-playground/src/org/oscim/test/PathLayerTest.java b/vtm-playground/src/org/oscim/test/PathLayerTest.java
new file mode 100644
index 00000000..b6f3073a
--- /dev/null
+++ b/vtm-playground/src/org/oscim/test/PathLayerTest.java
@@ -0,0 +1,83 @@
+package org.oscim.test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.oscim.backend.canvas.Color;
+import org.oscim.core.GeoPoint;
+import org.oscim.core.MapPosition;
+import org.oscim.event.Event;
+import org.oscim.gdx.GdxMapApp;
+import org.oscim.layers.PathLayer;
+import org.oscim.map.Map;
+import org.oscim.map.Map.UpdateListener;
+
+public class PathLayerTest extends GdxMapApp {
+
+ @Override
+ public void createLayers() {
+ createLayers(1, true);
+
+ mMap.setMapPosition(0, 0, 1 << 2);
+
+ mMap.events.bind(new UpdateListener() {
+ @Override
+ public void onMapEvent(Event e, MapPosition mapPosition) {
+ if (e == Map.UPDATE_EVENT) {
+ long t = System.currentTimeMillis();
+ float pos = t % 20000 / 10000f - 1f;
+ createLayers(pos, false);
+ mMap.updateMap(true);
+ }
+ }
+ });
+ }
+
+ ArrayList mPathLayers = new ArrayList();
+
+ void createLayers(float pos, boolean init) {
+
+ int i = 0;
+
+ for (double lat = -90; lat <= 90; lat += 5) {
+ List pts = new ArrayList();
+
+ for (double lon = -180; lon <= 180; lon += 2) {
+ //pts.add(new GeoPoint(lat, lon));
+ // double longitude = lon + (pos * 180);
+ // if (longitude < -180)
+ // longitude += 360;
+ // if (longitude > 180)
+ // longitude -= 360;
+ double longitude = lon;
+
+ double latitude = lat + (pos * 90);
+ if (latitude < -90)
+ latitude += 180;
+ if (latitude > 90)
+ latitude -= 180;
+
+ latitude += Math.sin((Math.abs(pos) * (lon / Math.PI)));
+
+ pts.add(new GeoPoint(latitude, longitude));
+ }
+ PathLayer pathLayer;
+ if (init) {
+ int c = Color.fade(Color.rainbow((float) (lat + 90) / 180), 0.5f);
+ pathLayer = new PathLayer(mMap, c, 6);
+ mMap.layers().add(pathLayer);
+ mPathLayers.add(pathLayer);
+ } else {
+ pathLayer = mPathLayers.get(i++);
+ }
+
+ pathLayer.setPoints(pts);
+ }
+
+ }
+
+ public static void main(String[] args) {
+ GdxMapApp.init();
+ GdxMapApp.run(new PathLayerTest(), null, 400);
+ }
+}
diff --git a/vtm-playground/src/org/oscim/test/VectorLayerTest.java b/vtm-playground/src/org/oscim/test/VectorLayerTest.java
new file mode 100644
index 00000000..dea93575
--- /dev/null
+++ b/vtm-playground/src/org/oscim/test/VectorLayerTest.java
@@ -0,0 +1,81 @@
+package org.oscim.test;
+
+import org.oscim.backend.canvas.Color;
+import org.oscim.gdx.GdxMapApp;
+import org.oscim.layers.vector.VectorLayer;
+import org.oscim.layers.vector.geometries.PointDrawable;
+import org.oscim.layers.vector.geometries.Style;
+import org.oscim.map.Map;
+import org.oscim.utils.ColorUtil;
+
+public class VectorLayerTest extends GdxMapApp {
+
+ @Override
+ public void createLayers() {
+ Map map = getMap();
+
+ //VectorTileLayer tileLayer = map.setBaseMap(new OSciMap4TileSource());
+
+ VectorLayer vectorLayer = new VectorLayer(map);
+
+ // vectorLayer.add(new PointDrawable(0, 180, Style.builder()
+ // .setBuffer(10)
+ // .setFillColor(Color.RED)
+ // .setFillAlpha(0.5)
+ // .build()));
+ //
+ // Geometry g = new GeomBuilder()
+ // .point(180, 0)
+ // .point()
+ // .buffer(6)
+ // .get();
+ //
+ // vectorLayer.add(new PolygonDrawable(g, defaultStyle()));
+ //
+
+ Style.Builder sb = Style.builder()
+ .buffer(0.4)
+ .fillColor(Color.RED)
+ .fillAlpha(0.2);
+
+ Style style = sb.fillAlpha(0.2).build();
+
+ // int tileSize = 5;
+ // for (int x = -180; x < 200; x += tileSize) {
+ // for (int y = -90; y < 90; y += tileSize) {
+ // // Style style = sb.setFillAlpha(FastMath.clamp(FastMath.length(x, y) / 180, 0.2, 1))
+ // // .build();
+ //
+ // vectorLayer.add(new RectangleDrawable(FastMath.clamp(y, -85, 85), x,
+ // FastMath.clamp(y + tileSize - 0.1, -85, 85),
+ // x + tileSize - 0.1, style));
+ //
+ // }
+ // }
+
+ for (int i = 0; i < 1000; i++) {
+ style = sb.buffer(Math.random() * 1)
+ .fillColor(ColorUtil.setHue(Color.RED,
+ Math.random()))
+ .fillAlpha(0.5)
+ .build();
+
+ vectorLayer.add(new PointDrawable(Math.random() * 180 - 90,
+ Math.random() * 360 - 180,
+ style));
+
+ }
+
+ map.layers().add(vectorLayer);
+
+ //map.layers().add(new LabelLayer(map, tileLayer));
+ //map.setTheme(VtmThemes.DEFAULT);
+
+ map.setMapPosition(0, 0, 1 << 2);
+ }
+
+ public static void main(String[] args) {
+ GdxMapApp.init();
+ GdxMapApp.run(new VectorLayerTest(), null, 400);
+ }
+}
diff --git a/vtm-web-app/src/org/oscim/web/client/SearchBox.java b/vtm-web-app/src/org/oscim/web/client/SearchBox.java
index 9acd9c5d..40044c54 100644
--- a/vtm-web-app/src/org/oscim/web/client/SearchBox.java
+++ b/vtm-web-app/src/org/oscim/web/client/SearchBox.java
@@ -268,7 +268,7 @@ public class SearchBox {
} catch (Exception e) {
log.debug(wkt);
}
- mOverlay.setGeom(g);
+ //FIXME mOverlay.setGeom(g);
//log.debug("add polygon " + p.length());
} else {
diff --git a/vtm/build.gradle b/vtm/build.gradle
index 19ad73c7..01959851 100644
--- a/vtm/build.gradle
+++ b/vtm/build.gradle
@@ -5,6 +5,7 @@ configurations { providedCompile }
dependencies {
compile 'org.slf4j:slf4j-api:1.7.6'
+ compile 'com.vividsolutions:jts:1.13'
providedCompile 'com.squareup.okhttp:okhttp:1.5.2'
providedCompile 'com.google.code.findbugs:annotations:2.0.1'
}
@@ -29,7 +30,10 @@ eclipse.classpath {
file.whenMerged { classpath ->
classpath.entries.findAll { entry ->
- entry.path.contains('annotations') }*.exported = false
+ entry.path.contains('annotations') ||
+ entry.path.contains('okhttp') ||
+ entry.path.contains('okio')
+ }*.exported = false
}
//if you don't want some classpath entries 'exported' in Eclipse
diff --git a/vtm/src/org/oscim/core/MapElement.java b/vtm/src/org/oscim/core/MapElement.java
index f98b3d02..81331e3c 100644
--- a/vtm/src/org/oscim/core/MapElement.java
+++ b/vtm/src/org/oscim/core/MapElement.java
@@ -44,9 +44,10 @@ public class MapElement extends GeometryBuffer {
}
@Override
- public void clear() {
+ public MapElement clear() {
layer = 5;
super.clear();
+ return this;
}
@Override
diff --git a/vtm/src/org/oscim/layers/PathLayer.java b/vtm/src/org/oscim/layers/PathLayer.java
index b16eb069..b1a97d1b 100644
--- a/vtm/src/org/oscim/layers/PathLayer.java
+++ b/vtm/src/org/oscim/layers/PathLayer.java
@@ -21,39 +21,30 @@ package org.oscim.layers;
import java.util.ArrayList;
import java.util.List;
-import org.oscim.backend.canvas.Paint.Cap;
import org.oscim.core.GeoPoint;
-import org.oscim.core.GeometryBuffer;
-import org.oscim.core.MapPosition;
-import org.oscim.core.MercatorProjection;
-import org.oscim.core.Tile;
+import org.oscim.layers.vector.VectorLayer;
+import org.oscim.layers.vector.geometries.LineDrawable;
+import org.oscim.layers.vector.geometries.Style;
import org.oscim.map.Map;
-import org.oscim.renderer.BucketRenderer;
-import org.oscim.renderer.GLViewport;
-import org.oscim.renderer.bucket.LineBucket;
-import org.oscim.renderer.bucket.RenderBuckets;
-import org.oscim.theme.styles.LineStyle;
-import org.oscim.utils.FastMath;
-import org.oscim.utils.async.SimpleWorker;
-import org.oscim.utils.geom.LineClipper;
+import org.oscim.utils.geom.GeomBuilder;
+
+import com.vividsolutions.jts.geom.LineString;
/** This class draws a path line in given color. */
-public class PathLayer extends Layer {
+public class PathLayer extends VectorLayer {
- /** Stores points, converted to the map projection. */
protected final ArrayList mPoints;
- protected boolean mUpdatePoints;
- /** Line style */
- LineStyle mLineStyle;
-
- final Worker mWorker;
+ protected Style mStyle;
+ protected LineDrawable mDrawable;
public PathLayer(Map map, int lineColor, float lineWidth) {
super(map);
- mWorker = new Worker(map);
- mLineStyle = new LineStyle(lineColor, lineWidth, Cap.BUTT);
- mRenderer = new RenderPath();
+ mStyle = Style.builder()
+ .strokeColor(lineColor)
+ .strokeWidth(lineWidth)
+ .build();
+
mPoints = new ArrayList();
}
@@ -61,60 +52,59 @@ public class PathLayer extends Layer {
this(map, lineColor, 2);
}
- public void clearPath() {
- if (mPoints.isEmpty())
- return;
+ public void setStyle(int lineColor, float lineWidth) {
+ mStyle = Style.builder()
+ .strokeColor(lineColor)
+ .strokeWidth(lineWidth)
+ .build();
+ }
- synchronized (mPoints) {
+ public void clearPath() {
+ if (!mPoints.isEmpty())
mPoints.clear();
- }
+
updatePoints();
}
public void setPoints(List pts) {
- synchronized (mPoints) {
- mPoints.clear();
- mPoints.addAll(pts);
- }
+ mPoints.clear();
+ mPoints.addAll(pts);
updatePoints();
}
public void addPoint(GeoPoint pt) {
- synchronized (mPoints) {
- mPoints.add(pt);
- }
+ mPoints.add(pt);
updatePoints();
}
public void addPoint(int latitudeE6, int longitudeE6) {
- synchronized (mPoints) {
- mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
- }
+ mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
updatePoints();
}
private void updatePoints() {
- mWorker.submit(10);
- mUpdatePoints = true;
+ synchronized (this) {
+
+ if (mDrawable != null) {
+ remove(mDrawable);
+ mDrawable = null;
+ }
+
+ if (!mPoints.isEmpty()) {
+ mDrawable = new LineDrawable(mPoints, mStyle);
+ if (mDrawable.getGeometry() == null)
+ mDrawable = null;
+ else
+ add(mDrawable);
+ }
+ }
+ mWorker.submit(0);
}
public List getPoints() {
return mPoints;
}
- /**
- * FIXME To be removed
- *
- * @deprecated
- *
- */
- public void setGeom(GeometryBuffer geom) {
- mGeom = geom;
- mWorker.submit(10);
- }
-
- GeometryBuffer mGeom;
-
/**
* Draw a great circle. Calculate a point for every 100km along the path.
*
@@ -148,11 +138,14 @@ public class PathLayer extends Layer {
*/
public void addGreatCircle(GeoPoint startPoint, GeoPoint endPoint,
final int numberOfPoints) {
- // adapted from page
- // http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map-in.html
- // which was adapted from page http://maps.forum.nu/gm_flight_path.html
+ /* adapted from page
+ * http://compastic.blogspot.co.uk/2011/07/how-to-draw-great-circle-on-map
+ * -in.html
+ * which was adapted from page http://maps.forum.nu/gm_flight_path.html */
- // convert to radians
+ GeomBuilder gb = new GeomBuilder();
+
+ /* convert to radians */
double lat1 = startPoint.getLatitude() * Math.PI / 180;
double lon1 = startPoint.getLongitude() * Math.PI / 180;
double lat2 = endPoint.getLatitude() * Math.PI / 180;
@@ -181,230 +174,31 @@ public class PathLayer extends Layer {
double latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
double lonN = Math.atan2(y, x);
- addPoint((int) (latN / (Math.PI / 180) * 1E6), (int) (lonN / (Math.PI / 180) * 1E6));
+
+ gb.point(latN / (Math.PI / 180), lonN / (Math.PI / 180));
}
+
+ setLineString(gb.toLineString());
}
- /***
- * everything below runs on GL- and Worker-Thread
- ***/
- final class RenderPath extends BucketRenderer {
-
- public RenderPath() {
-
- buckets.addLineBucket(0, mLineStyle);
- }
-
- private int mCurX = -1;
- private int mCurY = -1;
- private int mCurZ = -1;
-
- @Override
- public synchronized void update(GLViewport v) {
- int tz = 1 << v.pos.zoomLevel;
- int tx = (int) (v.pos.x * tz);
- int ty = (int) (v.pos.y * tz);
-
- // update layers when map moved by at least one tile
- if ((tx != mCurX || ty != mCurY || tz != mCurZ)) {
- mWorker.submit(100);
- mCurX = tx;
- mCurY = ty;
- mCurZ = tz;
- }
-
- Task t = mWorker.poll();
- if (t == null)
- return;
-
- // keep position to render relative to current state
- mMapPosition.copy(t.pos);
-
- // compile new layers
- buckets.set(t.bucket.get());
- compile();
+ public void setLineString(LineString path) {
+ synchronized (this) {
+ if (mDrawable != null)
+ remove(mDrawable);
+ mDrawable = new LineDrawable(path, mStyle);
+ add(mDrawable);
}
+ mWorker.submit(0);
}
- final static class Task {
- RenderBuckets bucket = new RenderBuckets();
- MapPosition pos = new MapPosition();
+ public void setLineString(double[] lonLat) {
+ synchronized (this) {
+ if (mDrawable != null)
+ remove(mDrawable);
+ mDrawable = new LineDrawable(lonLat, mStyle);
+ add(mDrawable);
+ }
+ mWorker.submit(0);
}
- final class Worker extends SimpleWorker {
-
- // limit coords
- private final int max = 2048;
-
- public Worker(Map map) {
- super(map, 0, new Task(), new Task());
- mClipper = new LineClipper(-max, -max, max, max);
- mPPoints = new float[0];
- }
-
- private static final int MIN_DIST = 3;
-
- // pre-projected points
- private double[] mPreprojected = new double[2];
-
- // projected points
- private float[] mPPoints;
- private final LineClipper mClipper;
- private int mNumPoints;
-
- @Override
- public boolean doWork(Task task) {
-
- int size = mNumPoints;
-
- if (mUpdatePoints) {
- synchronized (mPoints) {
- mUpdatePoints = false;
- mNumPoints = size = mPoints.size();
-
- ArrayList geopoints = mPoints;
- double[] points = mPreprojected;
-
- if (size * 2 >= points.length) {
- points = mPreprojected = new double[size * 2];
- mPPoints = new float[size * 2];
- }
-
- for (int i = 0; i < size; i++)
- MercatorProjection.project(geopoints.get(i), points, i);
- }
-
- } else if (mGeom != null) {
- GeometryBuffer geom = mGeom;
- mGeom = null;
- size = geom.index[0];
-
- double[] points = mPreprojected;
-
- if (size > points.length) {
- points = mPreprojected = new double[size * 2];
- mPPoints = new float[size * 2];
- }
-
- for (int i = 0; i < size; i += 2)
- MercatorProjection.project(geom.points[i + 1],
- geom.points[i], points,
- i >> 1);
- mNumPoints = size = size >> 1;
-
- }
- if (size == 0) {
- if (task.bucket.get() != null) {
- task.bucket.clear();
- mMap.render();
- }
- return true;
- }
-
- RenderBuckets layers = task.bucket;
-
- LineBucket ll = layers.getLineBucket(0);
- ll.line = mLineStyle;
- ll.scale = ll.line.width;
-
- mMap.getMapPosition(task.pos);
-
- int zoomlevel = task.pos.zoomLevel;
- task.pos.scale = 1 << zoomlevel;
-
- double mx = task.pos.x;
- double my = task.pos.y;
- double scale = Tile.SIZE * task.pos.scale;
-
- // flip around dateline
- int flip = 0;
- int maxx = Tile.SIZE << (zoomlevel - 1);
-
- int x = (int) ((mPreprojected[0] - mx) * scale);
- int y = (int) ((mPreprojected[1] - my) * scale);
-
- if (x > maxx) {
- x -= (maxx * 2);
- flip = -1;
- } else if (x < -maxx) {
- x += (maxx * 2);
- flip = 1;
- }
-
- mClipper.clipStart(x, y);
-
- float[] projected = mPPoints;
- int i = addPoint(projected, 0, x, y);
-
- float prevX = x;
- float prevY = y;
-
- float[] segment = null;
-
- for (int j = 2; j < size * 2; j += 2) {
- x = (int) ((mPreprojected[j + 0] - mx) * scale);
- y = (int) ((mPreprojected[j + 1] - my) * scale);
-
- int flipDirection = 0;
- if (x > maxx) {
- x -= maxx * 2;
- flipDirection = -1;
- } else if (x < -maxx) {
- x += maxx * 2;
- flipDirection = 1;
- }
-
- if (flip != flipDirection) {
- flip = flipDirection;
- if (i > 2)
- ll.addLine(projected, i, false);
-
- mClipper.clipStart(x, y);
- i = addPoint(projected, 0, x, y);
- continue;
- }
-
- int clip = mClipper.clipNext(x, y);
- if (clip < 1) {
- if (i > 2)
- ll.addLine(projected, i, false);
-
- if (clip < 0) {
- /* add line segment */
- segment = mClipper.getLine(segment, 0);
- ll.addLine(segment, 4, false);
- prevX = mClipper.outX2;
- prevY = mClipper.outY2;
- }
- i = 0;
- continue;
- }
-
- float dx = x - prevX;
- float dy = y - prevY;
- if ((i == 0) || FastMath.absMaxCmp(dx, dy, MIN_DIST)) {
- projected[i++] = prevX = x;
- projected[i++] = prevY = y;
- }
- }
- if (i > 2)
- ll.addLine(projected, i, false);
-
- // trigger redraw to let renderer fetch the result.
- mMap.render();
-
- return true;
- }
-
- @Override
- public void cleanup(Task task) {
- task.bucket.clear();
- }
-
- private int addPoint(float[] points, int i, int x, int y) {
- points[i++] = x;
- points[i++] = y;
- return i;
- }
- }
}
diff --git a/vtm/src/org/oscim/layers/vector/AbstractVectorLayer.java b/vtm/src/org/oscim/layers/vector/AbstractVectorLayer.java
index d99c4bbd..a3c90a20 100644
--- a/vtm/src/org/oscim/layers/vector/AbstractVectorLayer.java
+++ b/vtm/src/org/oscim/layers/vector/AbstractVectorLayer.java
@@ -1,9 +1,8 @@
package org.oscim.layers.vector;
-import org.oscim.core.BoundingBox;
+import org.oscim.core.Box;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapPosition;
-import org.oscim.core.Tile;
import org.oscim.event.Event;
import org.oscim.layers.Layer;
import org.oscim.map.Map;
@@ -26,13 +25,10 @@ public abstract class AbstractVectorLayer extends Layer implements UpdateList
protected final TileClipper mClipper = new TileClipper(-1024, -1024, 1024, 1024);
protected final Worker mWorker;
- protected long mUpdateDelay = 100;
+ protected long mUpdateDelay = 50;
protected boolean mUpdate = true;
- protected double mMinX;
- protected double mMinY;
-
public AbstractVectorLayer(Map map) {
super(map);
mWorker = new Worker(mMap);
@@ -52,7 +48,7 @@ public abstract class AbstractVectorLayer extends Layer implements UpdateList
mUpdate = false;
mWorker.submit(0);
} else if (e == Map.POSITION_EVENT || e == Map.CLEAR_EVENT) {
- // throttle worker
+ /* throttle worker */
mWorker.submit(mUpdateDelay);
}
}
@@ -61,7 +57,7 @@ public abstract class AbstractVectorLayer extends Layer implements UpdateList
mWorker.submit(0);
}
- abstract protected void processFeatures(Task t, BoundingBox b);
+ abstract protected void processFeatures(Task t, Box b);
protected static class Task {
public final RenderBuckets buckets = new RenderBuckets();
@@ -84,28 +80,69 @@ public abstract class AbstractVectorLayer extends Layer implements UpdateList
/** running on worker thread */
@Override
public boolean doWork(Task t) {
- Viewport v = mMap.viewport();
- BoundingBox bbox;
+
+ Box bbox;
+ float[] box = new float[8];
+
+ Viewport v = mMap.viewport().getSyncViewport();
synchronized (v) {
bbox = v.getBBox(null, 0);
- mMap.getMapPosition(t.position);
+ v.getMapExtents(box, 0);
+ v.getMapPosition(t.position);
}
- double scale = t.position.scale * Tile.SIZE;
+ /* Hmm what is this for? */
+ // double scale = t.position.scale * Tile.SIZE;
+ // t.position.x = (long) (t.position.x * scale) / scale;
+ // t.position.y = (long) (t.position.y * scale) / scale;
+
+ bbox.map2mercator();
+
+ // double xmin = bbox.xmin;
+ // double xmax = bbox.xmax;
+ // Box lbox = null;
+ // Box rbox = null;
+ // if (bbox.xmin < -180) {
+ // bbox.xmin = -180;
+ // lbox = new Box(bbox);
+ // }
+ // if (bbox.xmax > 180) {
+ // bbox.xmax = 180;
+ // rbox = new Box(bbox);
+ // }
- t.position.x = (long) (t.position.x * scale) / scale;
- t.position.y = (long) (t.position.y * scale) / scale;
processFeatures(t, bbox);
+ //if (lbox != null) {
+ // t.position.x += 1;
+ // lbox.xmax = 180;
+ // lbox.xmin = xmin + 180;
+ // processFeatures(t, lbox);
+ // t.position.x -= 1;
+ //}
+ //
+ //if (rbox != null) {
+ // t.position.x -= 1;
+ // rbox.xmin = -180;
+ // rbox.xmax = xmax - 180;
+ // processFeatures(t, rbox);
+ // t.position.x += 1;
+ //}
+
+ t.buckets.prepare();
+
mMap.render();
return true;
}
-
}
public class Renderer extends BucketRenderer {
MapPosition mTmpPos = new MapPosition();
+ public Renderer() {
+ mFlipOnDateLine = true;
+ }
+
@Override
public void update(GLViewport v) {
@@ -120,7 +157,6 @@ public abstract class AbstractVectorLayer extends Layer implements UpdateList
buckets.setFrom(t.buckets);
compile();
- //log.debug("is ready " + isReady() + " " + layers.getSize());
}
}
}
diff --git a/vtm/src/org/oscim/layers/vector/JtsConverter.java b/vtm/src/org/oscim/layers/vector/JtsConverter.java
new file mode 100644
index 00000000..ec62c535
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/JtsConverter.java
@@ -0,0 +1,81 @@
+package org.oscim.layers.vector;
+
+import static org.oscim.core.MercatorProjection.latitudeToY;
+import static org.oscim.core.MercatorProjection.longitudeToX;
+
+import org.oscim.core.GeometryBuffer;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.CoordinateSequence;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+
+public class JtsConverter {
+ double x, y, scale;
+ final double outScale;
+
+ public void setPosition(double x, double y, double scale) {
+ this.x = x;
+ this.y = y;
+ this.scale = scale * outScale;
+ }
+
+ public JtsConverter(double outScale) {
+ this.outScale = outScale;
+ }
+
+ private final Coordinate mTmpCoord = new Coordinate();
+
+ public void transformPolygon(GeometryBuffer g, Polygon polygon) {
+ Coordinate coord = mTmpCoord;
+
+ CoordinateSequence ring = polygon.getExteriorRing().getCoordinateSequence();
+
+ g.startPolygon();
+ for (int j = 0; j < ring.size() - 1; j++) {
+ ring.getCoordinate(j, coord);
+ addPoint(g, coord);
+ }
+ for (int j = 0, n = polygon.getNumInteriorRing(); j < n; j++) {
+ g.startHole();
+ ring = polygon.getInteriorRingN(j).getCoordinateSequence();
+ for (int k = 0; k < ring.size() - 1; k++) {
+ ring.getCoordinate(k, coord);
+ addPoint(g, coord);
+ }
+ }
+ }
+
+ public void transformLineString(GeometryBuffer g, LineString linestring) {
+ Coordinate coord = mTmpCoord;
+
+ CoordinateSequence line = linestring.getCoordinateSequence();
+
+ g.startLine();
+ for (int j = 0, n = line.size(); j < n; j++) {
+ line.getCoordinate(j, coord);
+ addPoint(g, coord);
+ }
+ }
+
+ public void transformPoint(GeometryBuffer g, Point point) {
+ Coordinate coord = mTmpCoord;
+
+ g.startPoints();
+ coord.x = point.getX();
+ coord.y = point.getY();
+ addPoint(g, coord);
+ }
+
+ public void addPoint(GeometryBuffer g, Coordinate coord) {
+ g.addPoint((float) ((longitudeToX(coord.x) - x) * scale),
+ (float) ((latitudeToY(coord.y) - y) * scale));
+ }
+
+ public void addPoint(GeometryBuffer g, double lon, double lat) {
+ g.addPoint((float) ((longitudeToX(lon) - x) * scale),
+ (float) ((latitudeToY(lat) - y) * scale));
+ }
+
+}
diff --git a/vtm/src/org/oscim/layers/vector/VectorLayer.java b/vtm/src/org/oscim/layers/vector/VectorLayer.java
new file mode 100644
index 00000000..a4067c22
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/VectorLayer.java
@@ -0,0 +1,321 @@
+package org.oscim.layers.vector;
+
+import static org.oscim.core.MercatorProjection.latitudeToY;
+import static org.oscim.core.MercatorProjection.longitudeToX;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.oscim.backend.canvas.Color;
+import org.oscim.core.Box;
+import org.oscim.core.GeometryBuffer;
+import org.oscim.core.MapPosition;
+import org.oscim.core.Tile;
+import org.oscim.layers.vector.geometries.Drawable;
+import org.oscim.layers.vector.geometries.LineDrawable;
+import org.oscim.layers.vector.geometries.PointDrawable;
+import org.oscim.layers.vector.geometries.Style;
+import org.oscim.map.Map;
+import org.oscim.renderer.bucket.LineBucket;
+import org.oscim.renderer.bucket.MeshBucket;
+import org.oscim.theme.styles.AreaStyle;
+import org.oscim.theme.styles.LineStyle;
+import org.oscim.utils.FastMath;
+import org.oscim.utils.QuadTree;
+import org.oscim.utils.SpatialIndex;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.vividsolutions.jts.geom.Envelope;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+import com.vividsolutions.jts.simplify.DouglasPeuckerSimplifier;
+
+/* TODO keep bounding box of geometries - only try to render when bbox intersects viewport */
+
+/**
+ * Use this layer to draw predefined geometries from layers.vector.geometries
+ * package and
+ * JTS geometries together with a GeometryStyle
+ *
+ */
+public class VectorLayer extends AbstractVectorLayer {
+
+ public static final Logger log = LoggerFactory.getLogger(VectorLayer.class);
+
+ //private final SpatialIndex mDrawables = new RTree();
+ protected final SpatialIndex mDrawables = new QuadTree(1 << 30, 18);
+
+ protected final List tmpDrawables = new ArrayList(128);
+
+ protected final JtsConverter mConverter;
+ protected double mMinX;
+ protected double mMinY;
+
+ private static class GeometryWithStyle implements Drawable {
+ final Geometry geometry;
+ final Style style;
+
+ GeometryWithStyle(Geometry g, Style s) {
+ geometry = g;
+ style = s;
+ }
+
+ @Override
+ public Style getStyle() {
+ return style;
+ }
+
+ @Override
+ public Geometry getGeometry() {
+ return geometry;
+ }
+ }
+
+ protected Polygon mEnvelope;
+
+ public VectorLayer(Map map, SpatialIndex index) {
+ this(map);
+ }
+
+ public VectorLayer(Map map) {
+ super(map);
+ mConverter = new JtsConverter(Tile.SIZE / UNSCALE_COORD);
+ }
+
+ private static Box bbox(Geometry geometry, Style style) {
+ Envelope e = geometry.getEnvelopeInternal();
+ Box bbox = new Box(e.getMinX(), e.getMinY(), e.getMaxX(), e.getMaxY());
+ //if ("Point".equals(geometry.getGeometryType())){
+ // bbox.
+ //}
+
+ bbox.scale(1E6);
+ return bbox;
+ }
+
+ /**
+ * Adds a drawable to a list of geometries that have to be drawn in the next
+ * map update.
+ *
+ * @param drawable
+ */
+ public void add(Drawable drawable) {
+ mDrawables.insert(bbox(drawable.getGeometry(), drawable.getStyle()), drawable);
+ }
+
+ /**
+ * Adds a JTS geometry and a style to a list of geometries that have to be
+ * drawn in the next map update.
+ *
+ * @param geometry
+ * @param style
+ */
+ public synchronized void add(Geometry geometry, Style style) {
+ mDrawables.insert(bbox(geometry, style), new GeometryWithStyle(geometry, style));
+ }
+
+ /**
+ * Removes the drawable from the list of drawn geometries.
+ *
+ * @param drawable
+ */
+ public synchronized void remove(Drawable drawable) {
+ mDrawables.remove(bbox(drawable.getGeometry(), drawable.getStyle()), drawable);
+ }
+
+ /**
+ * removes the JTS geometry and its style from the list of drawn geometries.
+ *
+ * @param geometry
+ */
+ public synchronized void remove(Geometry geometry) {
+ Drawable toRemove = null;
+ Box bbox = bbox(geometry, null);
+
+ synchronized (this) {
+ tmpDrawables.clear();
+ mDrawables.search(bbox, tmpDrawables);
+ for (Drawable d : tmpDrawables) {
+ if (d.getGeometry() == geometry)
+ toRemove = d;
+ }
+ }
+
+ if (toRemove == null) {
+ log.error("Can't find geometry to remove.");
+ return;
+ }
+
+ mDrawables.remove(bbox, toRemove);
+ //mMap.render();
+ }
+
+ @Override
+ protected void processFeatures(Task t, Box bbox) {
+ //log.debug("bbox {}", bbox);
+ if (Double.isNaN(bbox.xmin))
+ return;
+
+ // mEnvelope = new GeomBuilder()
+ // .point(bbox.xmin, bbox.ymin)
+ // .point(bbox.xmin, bbox.ymax)
+ // .point(bbox.xmax, bbox.ymax)
+ // .point(bbox.xmax, bbox.ymin)
+ // .point(bbox.xmin, bbox.ymin)
+ // .toPolygon();
+
+ /* reduce lines points min distance */
+ mMinX = ((bbox.xmax - bbox.xmin) / mMap.getWidth());
+ mMinY = ((bbox.ymax - bbox.ymin) / mMap.getHeight());
+
+ mConverter.setPosition(t.position.x, t.position.y, t.position.scale);
+
+ bbox.scale(1E6);
+
+ int level = 0;
+ Style lastStyle = null;
+
+ /* go through features, find the matching style and draw */
+ synchronized (this) {
+ tmpDrawables.clear();
+ mDrawables.search(bbox, tmpDrawables);
+ // TODO sort by some order...
+
+ for (Drawable d : tmpDrawables) {
+ Style style = d.getStyle();
+ draw(t, level, d, style);
+
+ if (style != lastStyle)
+ level += 2;
+
+ lastStyle = style;
+ }
+ }
+ }
+
+ protected void draw(Task task, int level, Drawable d, Style style) {
+ Geometry geom = d.getGeometry();
+
+ if (d instanceof LineDrawable) {
+ drawLine(task, level, geom, style);
+ } else if (d instanceof PointDrawable) {
+ drawPoint(task, level, geom, style);
+ } else {
+ drawPolygon(task, level, geom, style);
+ }
+ }
+
+ protected void drawPoint(Task t, int level, Geometry points, Style style) {
+
+ MeshBucket mesh = t.buckets.getMeshBucket(level);
+ if (mesh.area == null) {
+ mesh.area = new AreaStyle(Color.fade(style.fillColor,
+ style.fillAlpha));
+ }
+
+ LineBucket ll = t.buckets.getLineBucket(level + 1);
+ if (ll.line == null) {
+ ll.line = new LineStyle(2, style.strokeColor, style.strokeWidth);
+ }
+
+ for (int i = 0; i < points.getNumGeometries(); i++) {
+ Point p = (Point) points.getGeometryN(i);
+ addCircle(mGeom.clear(), t.position, p.getX(), p.getY(), style);
+
+ if (!mClipper.clip(mGeom))
+ continue;
+
+ mesh.addConvexMesh(mGeom);
+ ll.addLine(mGeom);
+ }
+ }
+
+ protected void drawLine(Task t, int level, Geometry line, Style style) {
+
+ LineBucket ll = t.buckets.getLineBucket(level);
+ if (ll.line == null) {
+ ll.line = new LineStyle(0, style.strokeColor, style.strokeWidth);
+ }
+
+ if (style.generalization != Style.GENERALIZATION_NONE) {
+ line = DouglasPeuckerSimplifier.simplify(line, mMinX * style.generalization);
+ }
+
+ //line = line.intersection(mEnvelope);
+
+ for (int i = 0; i < line.getNumGeometries(); i++) {
+ mConverter.transformLineString(mGeom.clear(), (LineString) line.getGeometryN(i));
+ if (!mClipper.clip(mGeom))
+ continue;
+
+ ll.addLine(mGeom);
+ }
+ }
+
+ protected void drawPolygon(Task t, int level, Geometry polygon, Style style) {
+
+ MeshBucket mesh = t.buckets.getMeshBucket(level);
+ if (mesh.area == null) {
+ mesh.area = new AreaStyle(Color.fade(style.fillColor,
+ style.fillAlpha));
+ }
+
+ LineBucket ll = t.buckets.getLineBucket(level + 1);
+ if (ll.line == null) {
+ ll.line = new LineStyle(2, style.strokeColor, style.strokeWidth);
+ }
+
+ if (style.generalization != Style.GENERALIZATION_NONE) {
+ polygon = DouglasPeuckerSimplifier.simplify(polygon, mMinX * style.generalization);
+ }
+
+ // if (polygon.isRectangle())
+
+ for (int i = 0; i < polygon.getNumGeometries(); i++) {
+ mConverter.transformPolygon(mGeom.clear(), (Polygon) polygon.getGeometryN(i));
+
+ if (mGeom.getNumPoints() < 3)
+ continue;
+
+ if (!mClipper.clip(mGeom))
+ continue;
+
+ mesh.addMesh(mGeom);
+ ll.addLine(mGeom);
+ }
+ }
+
+ protected void addCircle(GeometryBuffer g, MapPosition pos,
+ double px, double py, Style style) {
+
+ double scale = pos.scale * Tile.SIZE / UNSCALE_COORD;
+ double x = (longitudeToX(px) - pos.x) * scale;
+ double y = (latitudeToY(py) - pos.y) * scale;
+
+ /* TODO in the next line I was only able to interpolate a function
+ * that makes up for the zoom level. The circle should not grow, it
+ * should stickto the map. 0.01 / (1 << startLvl) makes it retain
+ * its size. Correction? */
+ int zoomScale = (1 << style.scalingZoomLevel);
+
+ /* Keep the circle's size constant in relation to the underlying map */
+ double radius = style.buffer;
+
+ if (pos.scale > zoomScale)
+ radius = (radius * 0.01) / zoomScale * (scale - zoomScale);
+
+ int quality = (int) (Math.sqrt(radius) * 8);
+ quality = FastMath.clamp(quality, 4, 32);
+
+ double step = 2.0 * Math.PI / quality;
+
+ g.startPolygon();
+ for (int i = 0; i < quality; i++) {
+ g.addPoint((float) (x + radius * Math.cos(i * step)),
+ (float) (y + radius * Math.sin(i * step)));
+ }
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java
new file mode 100644
index 00000000..916b15d6
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/CircleDrawable.java
@@ -0,0 +1,149 @@
+package org.oscim.layers.vector.geometries;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.utils.geom.GeomBuilder;
+
+/**
+ * Predefined class for drawing circles on the map. Circles are by default
+ * made of 32 segments.
+ */
+public class CircleDrawable extends JtsDrawable {
+
+ public static int MEDIUM_QUALITY = 32;
+ public static int HIGH_QUALITY = 64;
+
+ /**
+ * Constructs a circle given the real-world radius in km. Keep in mind that
+ * this technique is computationally costly for circles with huge number or
+ * segments.
+ *
+ * @param center GeoPoint - center of the circle
+ * @param radiusKm Radius of the circle in kilometers.
+ */
+ public CircleDrawable(GeoPoint center, double radiusKm) {
+ super(Style.DEFAULT_STYLE);
+ GeomBuilder gb = new GeomBuilder();
+ for (int i = 0; i < MEDIUM_QUALITY; i++) {
+ GeoPoint point = findGeoPointWithGivenDistance(center,
+ i * Math.PI / MEDIUM_QUALITY * 2,
+ radiusKm);
+ gb.points(point.getLongitude(), point.getLatitude());
+ }
+ geometry = gb.toPolygon();
+ }
+
+ /**
+ * Constructs a circle given the real-world radius in km. Keep in mind that
+ * this technique is computationally costly for circles with huge number or
+ * segments.
+ *
+ * @param center GeoPoint - center of the circle
+ * @param radiusKm Radius of the circle in kilometers. The size of the
+ * circle may be distorted due to the Mercator projections
+ * properties.
+ * @param style FillableGeometryStyle with color and transparency
+ * information for the circle
+ */
+ public CircleDrawable(GeoPoint center, double radiusKm, Style style) {
+ super(style);
+ GeomBuilder gb = new GeomBuilder();
+ for (int i = 0; i < MEDIUM_QUALITY; i++) {
+ GeoPoint point = findGeoPointWithGivenDistance(center,
+ i * Math.PI / MEDIUM_QUALITY * 2,
+ radiusKm);
+ gb.points(point.getLongitude(), point.getLatitude());
+ }
+ geometry = gb.toPolygon();
+ }
+
+ /**
+ * Constructs a circle given the real-world radius in km. Keep in mind that
+ * this technique is computationally costly for circles with huge number or
+ * segments.
+ *
+ * @param center GeoPoint - center of the circle
+ * @param radiusKm Radius of the circle in kilometers. The size of the
+ * circle may be distorted due to the Mercator projections
+ * properties.
+ * @param quadrantSegments the number of segments a quarter of circle will
+ * have. Use Circle.LOW_PRECISION for quick rendering,
+ * Circle.MEDIUM_PRECISION for good rendering and quality or
+ * Circle.HIGH_PRECISION for high quality.
+ * @param style FillableGeometryStyle with color and transparency
+ * information for the circle
+ */
+ public CircleDrawable(GeoPoint center, double radiusKm, int quadrantSegments,
+ Style style) {
+ super(style);
+ GeomBuilder gb = new GeomBuilder();
+ for (int i = 0; i < quadrantSegments; i++) {
+ GeoPoint point = findGeoPointWithGivenDistance(center,
+ i * Math.PI / quadrantSegments * 2,
+ radiusKm);
+ gb.points(point.getLongitude(), point.getLatitude());
+ }
+ geometry = gb.toPolygon();
+ }
+
+ /**
+ * This function finds a GeoPoint offset by a distance in the direction
+ * given in the bearing parameter. It is an approximation due to the
+ * Mercator projections properties
+ *
+ * @param startPoint
+ * @param initialBearingRadians
+ * @param distanceKilometres
+ * @return a new GeoPoint located distanceKilometers away from the
+ * startPoint in the direction of the initialBearing
+ */
+ private static GeoPoint findGeoPointWithGivenDistance(GeoPoint startPoint,
+ double initialBearingRadians, double distanceKilometres)
+ {
+ double radiusEarthKilometres = 6371.01;
+ double distRatio = distanceKilometres / radiusEarthKilometres;
+ double distRatioSine = Math.sin(distRatio);
+ double distRatioCosine = Math.cos(distRatio);
+
+ double startLatRad = degreesToRadians(startPoint.getLatitude());
+ double startLonRad = degreesToRadians(startPoint.getLongitude());
+
+ double startLatCos = Math.cos(startLatRad);
+ double startLatSin = Math.sin(startLatRad);
+
+ double endLatRads = Math.asin((startLatSin * distRatioCosine)
+ + (startLatCos * distRatioSine * Math.cos(initialBearingRadians)));
+
+ double endLonRads = startLonRad
+ + Math.atan2(
+ Math.sin(initialBearingRadians) * distRatioSine * startLatCos,
+ distRatioCosine - startLatSin * Math.sin(endLatRads));
+
+ return new GeoPoint(radiansToDegrees(endLatRads), radiansToDegrees(endLonRads));
+
+ }
+
+ /**
+ * translates an angle from degrees to radians
+ *
+ * @param degrees
+ * @return the angle in radians
+ */
+ private static double degreesToRadians(double degrees)
+ {
+ double degToRadFactor = Math.PI / 180;
+ return degrees * degToRadFactor;
+ }
+
+ /**
+ * translates an angle from radians to degrees
+ *
+ * @param radians
+ * @return the angle in degrees
+ */
+ private static double radiansToDegrees(double radians)
+ {
+ double radToDegFactor = 180 / Math.PI;
+ return radians * radToDegFactor;
+ }
+
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/Drawable.java b/vtm/src/org/oscim/layers/vector/geometries/Drawable.java
new file mode 100644
index 00000000..9214e1e4
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/Drawable.java
@@ -0,0 +1,16 @@
+package org.oscim.layers.vector.geometries;
+
+import com.vividsolutions.jts.geom.Geometry;
+
+public interface Drawable {
+
+ /**
+ * @return
+ */
+ public Style getStyle();
+
+ /**
+ * @return
+ */
+ public Geometry getGeometry();
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
new file mode 100644
index 00000000..74d919e0
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/HexagonDrawable.java
@@ -0,0 +1,88 @@
+package org.oscim.layers.vector.geometries;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.core.MercatorProjection;
+import org.oscim.core.Point;
+import org.oscim.utils.geom.GeomBuilder;
+
+/**
+ * Predefined class for drawing hexagons on the map.
+ */
+public class HexagonDrawable extends JtsDrawable {
+
+ /**
+ * @param center GeoPoint - center of the hexagon
+ * @param radiusKm Radius of the hexagon in kilometers. The size of the
+ * hexagon may be distorted due to the Mercator projections
+ * properties.
+ */
+ public HexagonDrawable(GeoPoint center, double radiusKm) {
+ super(Style.DEFAULT_STYLE);
+ GeomBuilder gb = new GeomBuilder();
+
+ for (int i = 0; i < 6; i++) {
+ GeoPoint point = findGeoPointWithGivenDistance(center, i * Math.PI / 3, radiusKm);
+ gb.points(point.getLongitude(), point.getLatitude());
+ }
+ geometry = gb.toPolygon();
+ }
+
+ /**
+ * @param center GeoPoint - center of the hexagon
+ * @param radiusKm Radius of the hexagon in kilometers. The size of the
+ * hexagon may be distorted due to the Mercator projections
+ * properties.
+ * @param rotationRad rotation of the hexagon in radians
+ * @param style
+ */
+ public HexagonDrawable(GeoPoint center, double radiusKm, double rotationRad, Style style) {
+ super(style);
+ GeomBuilder gb = new GeomBuilder();
+ Point tmp = new Point();
+
+ for (int i = 0; i < 6; i++) {
+ GeoPoint point = findGeoPointWithGivenDistance(center,
+ rotationRad + i * Math.PI / 3,
+ radiusKm);
+ MercatorProjection.project(point, tmp);
+ gb.points(tmp.x, tmp.y);
+ }
+ geometry = gb.toPolygon();
+ }
+
+ /**
+ * This function finds a GeoPoint offset by a distance in the direction
+ * given in the bearing parameter. It is an approximation due to the
+ * Mercator projections properties
+ *
+ * @param startPoint
+ * @param initialBearingRadians
+ * @param distanceKilometres
+ * @return a new GeoPoint located distanceKilometers away from the
+ * startPoint in the direction of the initialBearing
+ */
+ private static GeoPoint findGeoPointWithGivenDistance(GeoPoint startPoint,
+ double initialBearingRadians, double distanceKilometres)
+ {
+ double radiusEarthKilometres = 6371.01;
+ double distRatio = distanceKilometres / radiusEarthKilometres;
+ double distRatioSine = Math.sin(distRatio);
+ double distRatioCosine = Math.cos(distRatio);
+
+ double startLatRad = Math.toRadians(startPoint.getLatitude());
+ double startLonRad = Math.toRadians(startPoint.getLongitude());
+
+ double startLatCos = Math.cos(startLatRad);
+ double startLatSin = Math.sin(startLatRad);
+
+ double endLatRads = Math.asin((startLatSin * distRatioCosine)
+ + (startLatCos * distRatioSine * Math.cos(initialBearingRadians)));
+
+ double endLonRads = startLonRad
+ + Math.atan2(
+ Math.sin(initialBearingRadians) * distRatioSine * startLatCos,
+ distRatioCosine - startLatSin * Math.sin(endLatRads));
+
+ return new GeoPoint(Math.toDegrees(endLatRads), Math.toDegrees(endLonRads));
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java
new file mode 100644
index 00000000..beea3093
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/JtsDrawable.java
@@ -0,0 +1,64 @@
+package org.oscim.layers.vector.geometries;
+
+import java.util.List;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.utils.geom.GeomBuilder;
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory;
+
+public class JtsDrawable implements Drawable {
+
+ public static final PackedCoordinateSequenceFactory coordFactory;
+ public static final GeometryFactory geomFactory;
+ static {
+ coordFactory = new PackedCoordinateSequenceFactory();
+ geomFactory = new GeometryFactory(coordFactory);
+ }
+
+ protected Style style;
+ protected Geometry geometry;
+
+ public JtsDrawable(Style style) {
+ this.style = style;
+ }
+
+ public JtsDrawable(Geometry geometry, Style style) {
+ this.geometry = geometry;
+ this.style = style;
+ }
+
+ /**
+ * @param style
+ */
+ public void setStyle(Style style) {
+ this.style = style;
+ }
+
+ /* (non-Javadoc)
+ *
+ * @see org.oscim.core.geometries.Drawable#getStyle() */
+ @Override
+ public Style getStyle() {
+ return style;
+ }
+
+ /* (non-Javadoc)
+ *
+ * @see org.oscim.core.geometries.Drawable#getGeometry() */
+ @Override
+ public Geometry getGeometry() {
+ return geometry;
+ }
+
+ protected static GeomBuilder loadPoints(GeomBuilder gb, List points) {
+ for (GeoPoint point : points) {
+ gb.point(point.getLongitude(),
+ point.getLatitude());
+ }
+ return gb;
+ }
+
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java
new file mode 100644
index 00000000..ed608ef3
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/LineDrawable.java
@@ -0,0 +1,44 @@
+package org.oscim.layers.vector.geometries;
+
+import java.util.List;
+
+import org.oscim.core.GeoPoint;
+
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.LineString;
+
+/**
+ * Predefined class for drawing lines and line strings on the map.
+ */
+public class LineDrawable extends JtsDrawable {
+
+ public LineDrawable(Geometry line, Style style) {
+ super(style);
+ if (line.getDimension() != 1)
+ throw new IllegalArgumentException("Geometry not a Line");
+
+ this.geometry = line;
+ }
+
+ public LineDrawable(List points) {
+ this(points, Style.defaultStyle());
+ }
+
+ public LineDrawable(List points, Style style) {
+ super(style);
+ if (points.size() < 2)
+ return;
+
+ double[] coords = new double[points.size() * 2];
+ int c = 0;
+ for (GeoPoint p : points) {
+ coords[c++] = p.getLongitude();
+ coords[c++] = p.getLatitude();
+ }
+ this.geometry = new LineString(coordFactory.create(coords, 2), geomFactory);
+ }
+
+ public LineDrawable(double[] lonLat, Style style) {
+ this(new LineString(coordFactory.create(lonLat, 2), geomFactory), style);
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java
new file mode 100644
index 00000000..b410f57c
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/PointDrawable.java
@@ -0,0 +1,36 @@
+package org.oscim.layers.vector.geometries;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.utils.geom.GeomBuilder;
+
+/**
+ * Use this class to draw points and circles which size has to be specified in
+ * screen units. Points (and circles resulting from this class) do not have
+ * area,
+ * however during rendering to make the point visible at varying zoom levels
+ * a buffer area is built around it.
+ * To give the point custom size, create a GeometryBuffer with, set buffer to
+ * your value and assign the point the final style.
+ *
+ * Note that since points do not have any area, they are not generalized.
+ *
+ * Normally points retain their size in the screen units across all zoom levels
+ * but this can be customized. Use setStartLevel on the point's style to specify
+ * from which zoom level the point should "stick to the map" and not decrease in
+ * size.
+ */
+public class PointDrawable extends JtsDrawable {
+
+ public PointDrawable(GeoPoint point) {
+ this(point, Style.defaultStyle());
+ }
+
+ public PointDrawable(GeoPoint point, Style style) {
+ this(point.getLongitude(), point.getLatitude(), style);
+ }
+
+ public PointDrawable(double lat, double lon, Style style) {
+ super(style);
+ this.geometry = new GeomBuilder().points(lon, lat).toPoint();
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
new file mode 100644
index 00000000..febe2d46
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/PolygonDrawable.java
@@ -0,0 +1,116 @@
+package org.oscim.layers.vector.geometries;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.utils.geom.GeomBuilder;
+
+import com.vividsolutions.jts.geom.Geometry;
+
+/**
+ * Predefined class to draw polygons on the map.
+ */
+public class PolygonDrawable extends JtsDrawable {
+
+ /**
+ * Creates a polygon using a JTS geometry and a FillableGeometryStyle
+ *
+ * @param polygon
+ * @param style
+ */
+ public PolygonDrawable(Geometry polygon, Style style) {
+ super(style);
+
+ if (polygon.getDimension() != 2)
+ throw new IllegalArgumentException("Geometry not a Polygon");
+
+ this.geometry = polygon;
+ }
+
+ /**
+ * Creates a polygon using the coordinates provided in the List
+ *
+ * @param points
+ */
+ public PolygonDrawable(List points) {
+ this(points, Style.defaultStyle());
+ }
+
+ /**
+ * Create a polygon given the array of GeoPoints and a FillableGeometryStyle
+ *
+ * @param points
+ * @param style
+ */
+ public PolygonDrawable(Style style, GeoPoint... points) {
+ this(Arrays.asList(points), style);
+ }
+
+ /**
+ * @param points
+ * @param style
+ */
+ public PolygonDrawable(List points, Style style) {
+ this(loadPoints(new GeomBuilder(), points).toPolygon(), style);
+ }
+
+ /**
+ * Create a polygon given the array of GeoPoints for the boundary, the array
+ * of GeoPoints for the hole and outline and fill color and alpha
+ *
+ * @param points
+ * @param holePoints
+ * @param outlineColor
+ * @param outlineAlpha
+ * @param fillColor
+ * @param fillAlpha
+ */
+ public PolygonDrawable(GeoPoint[] points, GeoPoint[] holePoints, float lineWidth,
+ int lineColor,
+ int fillColor, float fillAlpha) {
+ this(Arrays.asList(points),
+ Arrays.asList(holePoints),
+ lineWidth, lineColor, fillColor, fillAlpha);
+ }
+
+ /**
+ * Create a polygon using the Coordinates provided in the first List, with a
+ * hole build from the Coordinates in the second List and outline and fill -
+ * color and alpha
+ *
+ * @param points
+ * @param holePoints
+ * @param outlineColor
+ * @param outlineAlpha
+ * @param fillColor
+ * @param fillAlpha
+ */
+ public PolygonDrawable(List points, List holePoints,
+ float lineWidth, int lineColor, int fillColor, float fillAlpha) {
+ this(points, holePoints, new Style.Builder()
+ .strokeWidth(lineWidth)
+ .strokeColor(lineColor)
+ .fillColor(fillColor)
+ .fillAlpha(fillAlpha)
+ .build());
+ }
+
+ /**
+ * Creates a polygon from a List of coordinates in the first List, with a
+ * hole from coordinates in the second List and requires a
+ * FillableGeometryStyle for the color information
+ *
+ * @param points
+ * @param holePoints
+ * @param style
+ */
+ public PolygonDrawable(List points, List holePoints, Style style) {
+ super(style);
+ GeomBuilder gb = new GeomBuilder();
+ loadPoints(gb, points).ring();
+ loadPoints(gb, holePoints).ring();
+ this.geometry = gb.toPolygon();
+ this.style = style;
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java b/vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
new file mode 100644
index 00000000..7f8472b0
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/RectangleDrawable.java
@@ -0,0 +1,55 @@
+package org.oscim.layers.vector.geometries;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.utils.geom.GeomBuilder;
+
+/**
+ * Predefined class to draw rectangles on the map
+ */
+public class RectangleDrawable extends JtsDrawable {
+
+ /**
+ * Creates a Rectangle given the top-left and the bottom-right coordinate of
+ * it
+ *
+ * @param topLeft
+ * @param bottomRight
+ */
+ public RectangleDrawable(GeoPoint topLeft, GeoPoint bottomRight) {
+ this(topLeft, bottomRight, Style.defaultStyle());
+ }
+
+ /**
+ * Creates a Rectangle given the top-left and the bottom-right coordinate of
+ * it
+ *
+ * @param topLeft
+ * @param bottomRight
+ */
+ public RectangleDrawable(GeoPoint topLeft, GeoPoint bottomRight, Style style) {
+ super(style);
+ geometry = new GeomBuilder()
+ .point(topLeft.getLongitude(), topLeft.getLatitude())
+ .point(bottomRight.getLongitude(), topLeft.getLatitude())
+ .point(bottomRight.getLongitude(), bottomRight.getLatitude())
+ .point(topLeft.getLongitude(), bottomRight.getLatitude())
+ .toPolygon();
+ }
+
+ /**
+ * Creates a Rectangle given the top-left and the bottom-right coordinate of
+ * it
+ *
+ * @param topLeft
+ * @param bottomRight
+ */
+ public RectangleDrawable(double minLat, double minLon, double maxLat, double maxLon, Style style) {
+ super(style);
+ geometry = new GeomBuilder()
+ .point(minLon, minLat)
+ .point(minLon, maxLat)
+ .point(maxLon, maxLat)
+ .point(maxLon, minLat)
+ .toPolygon();
+ }
+}
diff --git a/vtm/src/org/oscim/layers/vector/geometries/Style.java b/vtm/src/org/oscim/layers/vector/geometries/Style.java
new file mode 100644
index 00000000..7298dc23
--- /dev/null
+++ b/vtm/src/org/oscim/layers/vector/geometries/Style.java
@@ -0,0 +1,198 @@
+package org.oscim.layers.vector.geometries;
+
+import org.oscim.backend.canvas.Color;
+
+/**
+ * Class encapsulating style information for drawing geometries on the map.
+ */
+public class Style {
+
+ public static final int GENERALIZATION_HIGH = 1 << 3;
+ public static final int GENERALIZATION_MEDIUM = 1 << 2;
+ public static final int GENERALIZATION_SMALL = 1 << 0;
+ public static final int GENERALIZATION_NONE = 0;
+
+ public final float strokeWidth;
+ public final int strokeColor;
+
+ public final int fillColor;
+ public final float fillAlpha;
+
+ public final double buffer;
+ public final int scalingZoomLevel;
+
+ public final int generalization;
+
+ private Style(Builder builder) {
+ strokeWidth = builder.strokeWidth;
+ strokeColor = builder.strokeColor;
+
+ fillColor = builder.fillColor;
+ fillAlpha = builder.fillAlpha;
+
+ buffer = builder.buffer;
+ scalingZoomLevel = builder.scalingZoomLevel;
+
+ generalization = builder.generalization;
+ }
+
+ /**
+ * Geometry style builder. Usage example:
+ *
+ *
+ * {
+ * Style style = Style.builder()
+ * .strokeWidth(1f).strokeColor(Color.BLACK).build();
+ * }
+ *
+ */
+ public static class Builder {
+
+ private float strokeWidth = 1f;
+ private int strokeColor = Color.GRAY;
+ private int fillColor = Color.GRAY;
+ private float fillAlpha = 0.25f;
+
+ private double buffer = 1;
+ private int scalingZoomLevel = 1;
+
+ private int generalization = GENERALIZATION_NONE;
+
+ protected Builder() {
+
+ }
+
+ /**
+ * Builds the GeometryStyle from the specified parameters.
+ *
+ * @return
+ */
+ public Style build() {
+ return new Style(this);
+ }
+
+ /**
+ * Sets the line width for the geometry's line or outline.
+ *
+ * @param lineWidth
+ * @return
+ */
+ public Builder strokeWidth(float lineWidth) {
+ this.strokeWidth = lineWidth;
+ return this;
+ }
+
+ /**
+ * Sets the color for the geometry's line or outline.
+ *
+ * @param stokeColor
+ * @return
+ */
+ public Builder strokeColor(int stokeColor) {
+ this.strokeColor = stokeColor;
+ return this;
+ }
+
+ /**
+ * Sets the color for the geometry's area.
+ *
+ * @param fillColor
+ * @return
+ */
+ public Builder fillColor(int fillColor) {
+ this.fillColor = fillColor;
+ return this;
+ }
+
+ /**
+ * Sets alpha channel value for the geometry's area.
+ *
+ * @param fillAlpha
+ * @return
+ */
+ public Builder fillAlpha(double fillAlpha) {
+ this.fillAlpha = (float) fillAlpha;
+ return this;
+ }
+
+ /**
+ * This function has effect only on Points:
+ * use it to control the size on the circle that
+ * will be built from a buffer around the point.
+ *
+ * @param buffer
+ * @return itself
+ */
+ public Builder buffer(double buffer) {
+ this.buffer = buffer;
+ return this;
+ }
+
+ /**
+ * This function has effect only on Points:
+ * use it to specify from which zoom level the point
+ * should stop decreasing in size and "stick to the map".
+ *
+ * @param zoomlvl
+ */
+ public Builder scaleZoomLevel(int zoomlvl) {
+ this.scalingZoomLevel = zoomlvl;
+ return this;
+ }
+
+ /**
+ * Sets generalization factor for the geometry.
+ * Use predefined GeometryStyle.GENERALIZATION_HIGH,
+ * GENERALIZATION_MEDIUM or GENERALIZATION_SMALL
+ *
+ * @param generalization
+ * @return
+ */
+ public Builder generalization(int generalization) {
+ this.generalization = generalization;
+ return this;
+ }
+ }
+
+ public float getStrokeWidth() {
+ return strokeWidth;
+ }
+
+ public int getStrokeColor() {
+ return strokeColor;
+ }
+
+ public int getFillColor() {
+ return fillColor;
+ }
+
+ public float getFillAlpha() {
+ return fillAlpha;
+ }
+
+ public int getGeneralization() {
+ return generalization;
+ }
+
+ public double getBuffer() {
+ return buffer;
+ }
+
+ public int getScalingZoomLevel() {
+ return scalingZoomLevel;
+ }
+
+ static final Style DEFAULT_STYLE = new Builder()
+ .fillColor(0xcccccccc)
+ .fillAlpha(1)
+ .build();
+
+ public static Style defaultStyle() {
+ return DEFAULT_STYLE;
+ }
+
+ public static Style.Builder builder() {
+ return new Style.Builder();
+ }
+
+}
diff --git a/vtm/src/org/oscim/utils/async/SimpleWorker.java b/vtm/src/org/oscim/utils/async/SimpleWorker.java
index c2de6a3a..8ae5af08 100644
--- a/vtm/src/org/oscim/utils/async/SimpleWorker.java
+++ b/vtm/src/org/oscim/utils/async/SimpleWorker.java
@@ -146,7 +146,8 @@ public abstract class SimpleWorker implements Runnable {
return;
}
- cleanup(mTaskTodo);
+ if (mTaskTodo != null)
+ cleanup(mTaskTodo);
finish();
}
diff --git a/vtm/src/org/oscim/utils/geom/GeomBuilder.java b/vtm/src/org/oscim/utils/geom/GeomBuilder.java
new file mode 100644
index 00000000..b79cffc3
--- /dev/null
+++ b/vtm/src/org/oscim/utils/geom/GeomBuilder.java
@@ -0,0 +1,453 @@
+/* Copyright 2013 The jeo project. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.oscim.utils.geom;
+
+import java.lang.reflect.Array;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Deque;
+import java.util.Iterator;
+
+import com.vividsolutions.jts.geom.Coordinate;
+import com.vividsolutions.jts.geom.Geometry;
+import com.vividsolutions.jts.geom.GeometryCollection;
+import com.vividsolutions.jts.geom.GeometryFactory;
+import com.vividsolutions.jts.geom.LineString;
+import com.vividsolutions.jts.geom.LinearRing;
+import com.vividsolutions.jts.geom.MultiLineString;
+import com.vividsolutions.jts.geom.MultiPoint;
+import com.vividsolutions.jts.geom.MultiPolygon;
+import com.vividsolutions.jts.geom.Point;
+import com.vividsolutions.jts.geom.Polygon;
+import com.vividsolutions.jts.geom.PrecisionModel;
+
+/**
+ * Builder for geometry objects.
+ *
+ * Example usage:
+ *
+ *
+ *
+ * GeometryBuilder gb = new GeometryBuilder();
+ *
+ * // create array two 2d points and turn into a line string
+ * gb.points(1,2,3,4,5,6).toLineString();
+ *
+ * // build a polygon with holes
+ * gb.points(0,0,10,0,10,10,0,10,0,0).ring()
+ * .points(4,4,6,4,6,6,4,6,4,4).ring()
+ * .toPolygon();
+ *
+ *
+ *
+ *
+ *
+ *
+ * @author Justin Deoliveira, OpenGeo
+ *
+ */
+public class GeomBuilder {
+
+ GeometryFactory factory;
+ Deque cstack = new ArrayDeque();
+ Deque gstack = new ArrayDeque();
+
+ /**
+ * Constructs a builder with the default geometry factory.
+ */
+ public GeomBuilder() {
+ this(new GeometryFactory());
+ }
+
+ /**
+ * Constructs a builder with an explicit geometry factory.
+ */
+ public GeomBuilder(GeometryFactory factory) {
+ this.factory = factory;
+ }
+
+ /**
+ * Constructs a builder with a specific srid for geometry objects.
+ */
+ public GeomBuilder(int srid) {
+ this.factory = new GeometryFactory(new PrecisionModel(), srid);
+ }
+
+ /**
+ * Adds a 2d point to the coordinate stack.
+ */
+ public GeomBuilder point(double x, double y) {
+ cstack.push(new Coordinate(x, y));
+ return this;
+ }
+
+ /**
+ * Adds a 3d point to the coordinate stack.
+ */
+ public GeomBuilder pointz(double x, double y, double z) {
+ cstack.push(new Coordinate(x, y, z));
+ return this;
+ }
+
+ /**
+ * Adds an array of 2d points to the coordinate stack.
+ */
+ public GeomBuilder points(double... ord) {
+ if (ord.length % 2 != 0) {
+ throw new IllegalArgumentException("Must specify even number of ordinates");
+ }
+
+ for (int i = 0; i < ord.length; i += 2) {
+ point(ord[i], ord[i + 1]);
+ }
+
+ return this;
+ }
+
+ /**
+ * Adds an array of 3d points to the coordinate stack.
+ */
+ public GeomBuilder pointsz(double... ord) {
+ if (ord.length % 3 != 0) {
+ throw new IllegalArgumentException("Must specify ordinates as triples");
+ }
+
+ for (int i = 0; i < ord.length; i += 3) {
+ pointz(ord[i], ord[i + 1], ord[i + 2]);
+ }
+
+ return this;
+ }
+
+ /**
+ * Creates a Point from the last point on the coordinate stack, and places
+ * the result
+ * on the geometry stack.
+ */
+ public GeomBuilder point() {
+ gstack.push(factory.createPoint(cpop()));
+ return this;
+ }
+
+ /**
+ * Creates a LineString from all points on the coordinate stack, and places
+ * the result
+ * on the geometry stack.
+ */
+ public GeomBuilder lineString() {
+ gstack.push(factory.createLineString(cpopAll()));
+ return this;
+ }
+
+ /**
+ * Creates a LinearRing from all points on the coordinate stack, and places
+ * the result
+ * on the geometry stack.
+ *
+ * If the first and last coordinate on the point stack are not equal an
+ * additional point will be added.
+ *
+ */
+ public GeomBuilder ring() {
+ Coordinate[] coords = cpopAll();
+ if (coords.length > 1 && !coords[0].equals(coords[coords.length - 1])) {
+ Coordinate[] tmp = new Coordinate[coords.length + 1];
+ System.arraycopy(coords, 0, tmp, 0, coords.length);
+ tmp[tmp.length - 1] = new Coordinate(tmp[0]);
+ coords = tmp;
+ }
+
+ gstack.push(factory.createLinearRing(coords));
+ return this;
+ }
+
+ /**
+ * Creates a Polygon from all LinearRings on the geometry stack and places
+ * the result back
+ * on the geometry stack.
+ */
+ public GeomBuilder polygon() {
+ if (gstack.isEmpty() || !(gstack.peek() instanceof LinearRing)) {
+ ring();
+ }
+
+ LinearRing[] rings = gpopAll(LinearRing.class);
+ LinearRing outer = rings[0];
+ LinearRing[] inner = null;
+ if (rings.length > 1) {
+ inner = Arrays.copyOfRange(rings, 1, rings.length);
+ }
+
+ gstack.push(factory.createPolygon(outer, inner));
+ return this;
+ }
+
+ /**
+ * Creates a MultiPoint from all coordinates on the coordinate stack,
+ * plaching the result
+ * back on the geometry stack.
+ *
+ * If the coordinate stack is empty this method will consume all Point
+ * geometries on the geometry stack.
+ *
+ */
+ public GeomBuilder multiPoint() {
+ if (!cstack.isEmpty()) {
+ gstack.push(factory.createMultiPoint(cpopAll()));
+ }
+ else {
+ gstack.push(factory.createMultiPoint(gpopAll(Point.class)));
+ }
+ return this;
+ }
+
+ /**
+ * Creates a MultiLineString from all LineStrings on the geometry stack and
+ * places the result
+ * back on the geometry stack.
+ */
+ public GeomBuilder multiLineString() {
+ gstack.push(factory.createMultiLineString(gpopAll(LineString.class)));
+ return this;
+ }
+
+ /**
+ * Creates a MultiPolygon from all Polygons on the geometry stack and places
+ * the result
+ * back on the geometry stack.
+ */
+ public GeomBuilder multiPolygon() {
+ gstack.push(factory.createMultiPolygon(gpopAll(Polygon.class)));
+ return this;
+ }
+
+ /**
+ * Creates a GeometryCollection from all Geometries on the geometry stack
+ * and places the result
+ * back on the geometry stack.
+ */
+ public GeomBuilder collection() {
+ gstack.push(factory.createGeometryCollection(gpopAll(Geometry.class)));
+ return this;
+ }
+
+ /**
+ * Buffers the geometry at the top of the geometry stack, and places the
+ * result back on the
+ * geometry stack.
+ */
+ public GeomBuilder buffer(double amt) {
+ gstack.push(gpop(Geometry.class).buffer(amt));
+ return this;
+ }
+
+ /**
+ * Consumes the top of the geometry stack.
+ */
+ public Geometry get() {
+ return gpop(Geometry.class);
+ }
+
+ /**
+ * Builds and returns a Point.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (Point) point().get();
+ *
+ *
+ *
+ */
+ public Point toPoint() {
+ return point().gpop(Point.class);
+ }
+
+ /**
+ * Builds and returns a LineString.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (LineString) lineString().get();
+ *
+ *
+ *
+ */
+ public LineString toLineString() {
+ return lineString().gpop(LineString.class);
+ }
+
+ /**
+ * Builds and returns a LineString.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (LinearRing) ring().get();
+ *
+ *
+ *
+ */
+ public LinearRing toLinearRing() {
+ return ring().gpop(LinearRing.class);
+ }
+
+ /**
+ * Builds and returns a Polygon.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (Polygon) polygon().get();
+ *
+ *
+ *
+ */
+ public Polygon toPolygon() {
+ return polygon().gpop(Polygon.class);
+ }
+
+ /**
+ * Builds and returns a MultiPoint.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (MultiPoint) multiPoint().get();
+ *
+ *
+ *
+ */
+ public MultiPoint toMultiPoint() {
+ return multiPoint().gpop(MultiPoint.class);
+ }
+
+ /**
+ * Builds and returns a MultiLineString.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (MultiLineString) multiLineString().get();
+ *
+ *
+ *
+ */
+ public MultiLineString toMultiLineString() {
+ return multiLineString().gpop(MultiLineString.class);
+ }
+
+ /**
+ * Builds and returns a MultiPolygon.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (MultiPolygon) multiPolygon().get();
+ *
+ *
+ *
+ */
+ public MultiPolygon toMultiPolygon() {
+ return multiPolygon().gpop(MultiPolygon.class);
+ }
+
+ /**
+ * Builds and returns a GEometryCollection.
+ *
+ * This method is equivalent to:
+ *
+ *
+ * (GeometryCollection) collection().get();
+ *
+ *
+ *
+ */
+ public GeometryCollection toCollection() {
+ return collection().gpop(GeometryCollection.class);
+ }
+
+ Coordinate cpop() {
+ return cpop(1)[0];
+ }
+
+ Coordinate[] cpop(int n) {
+ if (cstack.size() < n) {
+ throw new IllegalStateException(String.format("Expected %d values on coordinate stack, "
+ + "but found %d",
+ n,
+ cstack.size()));
+ }
+
+ Coordinate[] c = new Coordinate[n];
+ for (int i = 0; i < n; i++) {
+ c[n - i - 1] = cstack.pop();
+ }
+ return c;
+ }
+
+ Coordinate[] cpopAll() {
+ if (cstack.isEmpty()) {
+ throw new IllegalStateException("Coordinate stack is empty");
+ }
+
+ return cpop(cstack.size());
+ }
+
+ T gpop(Class clazz) {
+ return gpop(1, clazz)[0];
+ }
+
+ T[] gpop(int n, Class clazz) {
+ if (gstack.size() < n) {
+ throw new IllegalStateException(String.format("Expected %d values on geometry stack, "
+ + "but found %d", n, gstack.size()));
+ }
+
+ @SuppressWarnings("unchecked")
+ T[] l = (T[]) Array.newInstance(clazz, n);
+ for (int i = 0; i < n; i++) {
+ Object g = gstack.pop();
+ if (!clazz.isInstance(g)) {
+ throw new IllegalStateException(String.format("Expected %s on geometry stack, but "
+ + "found %s", clazz.getSimpleName(), g.getClass().getSimpleName()));
+ }
+
+ l[n - i - 1] = clazz.cast(g);
+ }
+ return l;
+ }
+
+ T[] gpopAll(Class clazz) {
+ if (gstack.isEmpty()) {
+ throw new IllegalArgumentException("Geometry stack is empty");
+ }
+
+ int n = 0;
+ Iterator it = gstack.iterator();
+ while (it.hasNext() && clazz.isInstance(it.next())) {
+ n++;
+ }
+
+ if (n == 0) {
+ throw new IllegalArgumentException(
+ String.format("Expected %s on geometry stack",
+ clazz.getSimpleName()));
+ }
+
+ return gpop(n, clazz);
+ }
+}