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