Create vtm-jts module, closes #53

This commit is contained in:
Emux
2016-07-07 20:16:33 +03:00
parent e74052c164
commit 37ace257cf
21 changed files with 519 additions and 89 deletions

18
vtm-jts/build.gradle Normal file
View File

@@ -0,0 +1,18 @@
apply plugin: 'java'
apply plugin: 'maven'
dependencies {
compile project(':vtm')
compile 'com.vividsolutions:jts:1.13'
}
sourceSets {
main.java.srcDirs = ['src']
}
// Automated Gradle project deployment to Sonatype OSSRH
if (isReleaseVersion && project.hasProperty("SONATYPE_USERNAME")) {
afterEvaluate {
project.apply from: "${rootProject.projectDir}/deploy.gradle"
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright 2012 osmdroid authors: Viesturs Zarins, Martin Pearman
* Copyright 2012 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.layers;
import java.util.ArrayList;
import java.util.List;
import org.oscim.core.GeoPoint;
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.utils.geom.GeomBuilder;
import com.vividsolutions.jts.geom.LineString;
/** This class draws a path line in given color. */
public class JtsPathLayer extends VectorLayer {
protected final ArrayList<GeoPoint> mPoints;
protected Style mStyle;
protected LineDrawable mDrawable;
public JtsPathLayer(Map map, int lineColor, float lineWidth) {
super(map);
mStyle = Style.builder()
.strokeColor(lineColor)
.strokeWidth(lineWidth)
.build();
mPoints = new ArrayList<GeoPoint>();
}
public JtsPathLayer(Map map, int lineColor) {
this(map, lineColor, 2);
}
public void setStyle(int lineColor, float lineWidth) {
mStyle = Style.builder()
.strokeColor(lineColor)
.strokeWidth(lineWidth)
.build();
}
public void clearPath() {
if (!mPoints.isEmpty())
mPoints.clear();
updatePoints();
}
public void setPoints(List<GeoPoint> pts) {
mPoints.clear();
mPoints.addAll(pts);
updatePoints();
}
public void addPoint(GeoPoint pt) {
mPoints.add(pt);
updatePoints();
}
public void addPoint(int latitudeE6, int longitudeE6) {
mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
updatePoints();
}
private void updatePoints() {
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<GeoPoint> getPoints() {
return mPoints;
}
/**
* Draw a great circle. Calculate a point for every 100km along the path.
*
* @param startPoint
* start point of the great circle
* @param endPoint
* end point of the great circle
*/
public void addGreatCircle(GeoPoint startPoint, GeoPoint endPoint) {
synchronized (mPoints) {
/* get the great circle path length in meters */
double length = startPoint.distanceTo(endPoint);
/* add one point for every 100kms of the great circle path */
int numberOfPoints = (int) (length / 100000);
addGreatCircle(startPoint, endPoint, numberOfPoints);
}
}
/**
* Draw a great circle.
*
* @param startPoint
* start point of the great circle
* @param endPoint
* end point of the great circle
* @param numberOfPoints
* number of points to calculate along the path
*/
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 */
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;
double lon2 = endPoint.getLongitude() * Math.PI / 180;
double d = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin((lat1 - lat2) / 2), 2)
+ Math.cos(lat1) * Math.cos(lat2)
* Math.pow(Math.sin((lon1 - lon2) / 2), 2)));
double bearing = Math.atan2(
Math.sin(lon1 - lon2) * Math.cos(lat2),
Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1)
* Math.cos(lat2)
* Math.cos(lon1 - lon2))
/ -(Math.PI / 180);
bearing = bearing < 0 ? 360 + bearing : bearing;
for (int i = 0, j = numberOfPoints + 1; i < j; i++) {
double f = 1.0 / numberOfPoints * i;
double A = Math.sin((1 - f) * d) / Math.sin(d);
double B = Math.sin(f * d) / Math.sin(d);
double x = A * Math.cos(lat1) * Math.cos(lon1) + B * Math.cos(lat2)
* Math.cos(lon2);
double y = A * Math.cos(lat1) * Math.sin(lon1) + B * Math.cos(lat2)
* Math.sin(lon2);
double z = A * Math.sin(lat1) + B * Math.sin(lat2);
double latN = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
double lonN = Math.atan2(y, x);
gb.point(latN / (Math.PI / 180), lonN / (Math.PI / 180));
}
setLineString(gb.toLineString());
}
public void setLineString(LineString path) {
synchronized (this) {
if (mDrawable != null)
remove(mDrawable);
mDrawable = new LineDrawable(path, mStyle);
add(mDrawable);
}
mWorker.submit(0);
}
public void setLineString(double[] lonLat) {
synchronized (this) {
if (mDrawable != null)
remove(mDrawable);
mDrawable = new LineDrawable(lonLat, mStyle);
add(mDrawable);
}
mWorker.submit(0);
}
}

View File

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

View File

@@ -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<Drawable> {
public static final Logger log = LoggerFactory.getLogger(VectorLayer.class);
//private final SpatialIndex<Drawable> mDrawables = new RTree<Drawable>();
protected final SpatialIndex<Drawable> mDrawables = new QuadTree<Drawable>(1 << 30, 18);
protected final List<Drawable> tmpDrawables = new ArrayList<Drawable>(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<Drawable> 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)));
}
}
}

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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<GeoPoint> points) {
for (GeoPoint point : points) {
gb.point(point.getLongitude(),
point.getLatitude());
}
return gb;
}
}

View File

@@ -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<GeoPoint> points) {
this(points, Style.defaultStyle());
}
public LineDrawable(List<GeoPoint> 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);
}
}

View File

@@ -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();
}
}

View File

@@ -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<GeoPoint> 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<GeoPoint> 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<GeoPoint> points, List<GeoPoint> 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<GeoPoint> points, List<GeoPoint> holePoints, Style style) {
super(style);
GeomBuilder gb = new GeomBuilder();
loadPoints(gb, points).ring();
loadPoints(gb, holePoints).ring();
this.geometry = gb.toPolygon();
this.style = style;
}
}

View File

@@ -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();
}
}

View File

@@ -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:
*
* <pre>
* {
* Style style = Style.builder()
* .strokeWidth(1f).strokeColor(Color.BLACK).build();
* }
* </pre>
*/
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();
}
}

View File

@@ -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.
* <p>
* Example usage:
*
* <pre>
* <code>
* 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();
*
* </code>
* </pre>
*
* </p>
*
* @author Justin Deoliveira, OpenGeo
*
*/
public class GeomBuilder {
GeometryFactory factory;
Deque<Coordinate> cstack = new ArrayDeque<Coordinate>();
Deque<Geometry> gstack = new ArrayDeque<Geometry>();
/**
* 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.
* <p>
* If the first and last coordinate on the point stack are not equal an
* additional point will be added.
* </p>
*/
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.
* <p>
* If the coordinate stack is empty this method will consume all Point
* geometries on the geometry stack.
* </p>
*/
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.
* <p>
* This method is equivalent to:
*
* <pre>
* (Point) point().get();
* </pre>
*
* </p>
*/
public Point toPoint() {
return point().gpop(Point.class);
}
/**
* Builds and returns a LineString.
* <p>
* This method is equivalent to:
*
* <pre>
* (LineString) lineString().get();
* </pre>
*
* </p>
*/
public LineString toLineString() {
return lineString().gpop(LineString.class);
}
/**
* Builds and returns a LineString.
* <p>
* This method is equivalent to:
*
* <pre>
* (LinearRing) ring().get();
* </pre>
*
* </p>
*/
public LinearRing toLinearRing() {
return ring().gpop(LinearRing.class);
}
/**
* Builds and returns a Polygon.
* <p>
* This method is equivalent to:
*
* <pre>
* (Polygon) polygon().get();
* </pre>
*
* </p>
*/
public Polygon toPolygon() {
return polygon().gpop(Polygon.class);
}
/**
* Builds and returns a MultiPoint.
* <p>
* This method is equivalent to:
*
* <pre>
* (MultiPoint) multiPoint().get();
* </pre>
*
* </p>
*/
public MultiPoint toMultiPoint() {
return multiPoint().gpop(MultiPoint.class);
}
/**
* Builds and returns a MultiLineString.
* <p>
* This method is equivalent to:
*
* <pre>
* (MultiLineString) multiLineString().get();
* </pre>
*
* </p>
*/
public MultiLineString toMultiLineString() {
return multiLineString().gpop(MultiLineString.class);
}
/**
* Builds and returns a MultiPolygon.
* <p>
* This method is equivalent to:
*
* <pre>
* (MultiPolygon) multiPolygon().get();
* </pre>
*
* </p>
*/
public MultiPolygon toMultiPolygon() {
return multiPolygon().gpop(MultiPolygon.class);
}
/**
* Builds and returns a GEometryCollection.
* <p>
* This method is equivalent to:
*
* <pre>
* (GeometryCollection) collection().get();
* </pre>
*
* </p>
*/
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 extends Geometry> T gpop(Class<T> clazz) {
return gpop(1, clazz)[0];
}
<T extends Geometry> T[] gpop(int n, Class<T> 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 extends Geometry> T[] gpopAll(Class<T> clazz) {
if (gstack.isEmpty()) {
throw new IllegalArgumentException("Geometry stack is empty");
}
int n = 0;
Iterator<Geometry> 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);
}
}