/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* This library 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 2.1 of the License, or (at your option) any later version.
*
* This library 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 library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jts.android;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.drawable.shapes.Shape;
import com.vividsolutions.jts.android.geom.DrawableShape;
import com.vividsolutions.jts.android.geom.PathShape;
import com.vividsolutions.jts.android.geom.PolygonShape;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.LineString;
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;
/**
* Writes {@link Geometry}s into Java2D {@link Shape} objects
* of the appropriate type.
* This supports rendering geometries using Java2D.
* The ShapeWriter allows supplying a {@link PointTransformation}
* class, to transform coordinates from model space into view space.
* This is useful if a client is providing its own transformation
* logic, rather than relying on Java2D <tt>AffineTransform</tt>s.
* <p>
* The writer supports removing duplicate consecutive points
* (via the {@link #setRemoveDuplicatePoints(boolean)} method)
* as well as true <b>decimation</b>
* (via the {@link #setDecimation(double)} method.
* Enabling one of these strategies can substantially improve
* rendering speed for large geometries.
* It is only necessary to enable one strategy.
* Using decimation is preferred, but this requires
* determining a distance below which input geometry vertices
* can be considered unique (which may not always be feasible).
* If neither strategy is enabled, all vertices
* of the input <tt>Geometry</tt>
* will be represented in the output <tt>Shape</tt>.
* <p>
*
* <p>Modified for Android use.</p>
*
*/
public class ShapeWriter {
/**
* The point transformation used by default.
*/
public static final PointTransformation DEFAULT_POINT_TRANSFORMATION = new IdentityPointTransformation();
/**
* The point shape factory used by default.
*/
public static final PointShapeFactory DEFAULT_POINT_FACTORY = new PointShapeFactory.Circle(30.0);
private PointTransformation pointTransformer = DEFAULT_POINT_TRANSFORMATION;
private PointShapeFactory pointFactory = DEFAULT_POINT_FACTORY;
/**
* Cache a PointF object to use to transfer coordinates into shape
*/
private PointF transPoint = new PointF();
/**
* If true, decimation will be used to reduce the number of vertices
* by removing consecutive duplicates.
*
*/
private boolean doRemoveDuplicatePoints = false;
private double decimationDistance = 0;
/**
* Creates a new ShapeWriter with a specified point transformation
* and point shape factory.
*
* @param pointTransformer a transformation from model to view space to use
* @param pointFactory the PointShapeFactory to use
*/
public ShapeWriter( PointTransformation pointTransformer, PointShapeFactory pointFactory ) {
if (pointTransformer != null)
this.pointTransformer = pointTransformer;
if (pointFactory != null)
this.pointFactory = pointFactory;
}
/**
* Creates a new ShapeWriter with a specified point transformation
* and the default point shape factory.
*
* @param pointTransformer a transformation from model to view space to use
*/
public ShapeWriter( PointTransformation pointTransformer ) {
this(pointTransformer, null);
}
public ShapeWriter( PointTransformation pointTransformer, String shapeName, float size ) {
this(pointTransformer, getShape(shapeName, size));
}
private static PointShapeFactory getShape( String shapeName, float size ) {
if (shapeName.equals("circle")) {
return new PointShapeFactory.Circle(size);
} else if (shapeName.equals("cross")) {
return new PointShapeFactory.Cross(size);
} else if (shapeName.equals("square")) {
return new PointShapeFactory.Square(size);
} else if (shapeName.equals("star")) {
return new PointShapeFactory.Star(size);
} else if (shapeName.equals("triangle")) {
return new PointShapeFactory.Triangle(size);
} else if (shapeName.equals("X")) {
return new PointShapeFactory.X(size);
} else {
return DEFAULT_POINT_FACTORY;
}
}
/**
* Creates a new ShapeWriter with the default (identity) point transformation.
*
*/
public ShapeWriter() {
}
/**
* Sets whether duplicate consecutive points should be eliminated.
* This can reduce the size of the generated Shapes
* and improve rendering speed, especially in situations
* where a transform reduces the extent of the geometry.
* <p>
* The default is <tt>false</tt>.
*
* @param doDecimation whether decimation is to be used
*/
public void setRemoveDuplicatePoints( boolean doRemoveDuplicatePoints ) {
this.doRemoveDuplicatePoints = doRemoveDuplicatePoints;
}
/**
* Sets the decimation distance used to determine
* whether vertices of the input geometry are
* considered to be duplicate and thus removed.
* The distance is axis distance, not Euclidean distance.
* The distance is specified in the input geometry coordinate system
* (NOT the transformed output coordinate system).
* <p>
* When rendering to a screen image, a suitably small distance should be used
* to avoid obvious rendering defects.
* A distance equivalent to the equivalent of 1.5 pixels or less is recommended
* (and perhaps even smaller to avoid any chance of visible artifacts).
* <p>
* The default distance is 0.0, which disables decimation.
*
* @param decimationDistance the distance below which vertices are considered to be duplicates
*/
public void setDecimation( double decimationDistance ) {
this.decimationDistance = decimationDistance;
}
/**
* Creates a {@link Shape} representing a {@link Geometry},
* according to the specified PointTransformation
* and PointShapeFactory (if relevant).
* <p>
* Note that Shapes do not
* preserve information fragment_about which elements in heterogeneous collections
* are 1D and which are 2D.
* For example, a GeometryCollection containing a ring and a
* disk will render as two disks if Graphics.fill is used,
* or as two rings if Graphics.draw is used.
* To avoid this issue use separate shapes for the components.
*
* @param geometry the geometry to convert
* @return a Shape representing the geometry
*/
public DrawableShape toShape( Geometry geometry ) {
if (geometry.isEmpty())
return new PathShape(new Path());
else if (geometry instanceof Polygon)
return toShape((Polygon) geometry);
else if (geometry instanceof MultiPolygon)
return toShape((MultiPolygon) geometry);
else if (geometry instanceof LineString)
return toShape((LineString) geometry);
else if (geometry instanceof MultiLineString)
return toShape((MultiLineString) geometry);
else if (geometry instanceof Point)
return toShape((Point) geometry);
else if (geometry instanceof MultiPoint)
return toShape((MultiPoint) geometry);
else if (geometry instanceof GeometryCollection)
return toShape((GeometryCollection) geometry);
throw new IllegalArgumentException("Unrecognized Geometry class: " + geometry.getClass());
}
private DrawableShape toShape( Polygon p ) {
PolygonShape poly = new PolygonShape();
appendRing(poly, p.getExteriorRing().getCoordinates());
for( int j = 0; j < p.getNumInteriorRing(); j++ ) {
appendRing(poly, p.getInteriorRingN(j).getCoordinates());
}
return poly;
}
private void appendRing( PolygonShape poly, Coordinate[] coords ) {
double prevx = Double.NaN;
double prevy = Double.NaN;
Coordinate prev = null;
Path tmpPath = null;
int n = coords.length - 1;
/**
* Don't include closing point.
* Ring path will be closed explicitly, which provides a
* more accurate path representation.
*/
for( int i = 0; i < n; i++ ) {
if (decimationDistance > 0.0) {
boolean isDecimated = prev != null && Math.abs(coords[i].x - prev.x) < decimationDistance
&& Math.abs(coords[i].y - prev.y) < decimationDistance;
if (i < n && isDecimated)
continue;
prev = coords[i];
}
transformPoint(coords[i], transPoint);
if (doRemoveDuplicatePoints) {
// skip duplicate points (except the last point)
boolean isDup = transPoint.x == prevx && transPoint.y == prevy;
if (i < n && isDup)
continue;
prevx = transPoint.x;
prevy = transPoint.y;
}
if (tmpPath != null) {
tmpPath.lineTo(transPoint.x, transPoint.y);
} else {
tmpPath = new Path();
tmpPath.moveTo(transPoint.x, transPoint.y);
}
}
// handle closing point
tmpPath.close();
Path mainPath = poly.getPath();
if (mainPath == null) {
poly.initPath();
mainPath = poly.getPath();
}
mainPath.addPath(tmpPath);
}
private DrawableShape toShape( MultiPolygon mp ) {
GeometryCollectionShape shapes = new GeometryCollectionShape();
for( int i = 0; i < mp.getNumGeometries(); i++ ) {
Polygon polygon = (Polygon) mp.getGeometryN(i);
DrawableShape shape = toShape(polygon);
shapes.add(shape);
}
return shapes;
}
private DrawableShape toShape( GeometryCollection gc ) {
GeometryCollectionShape shape = new GeometryCollectionShape();
// add components to GC shape
for( int i = 0; i < gc.getNumGeometries(); i++ ) {
Geometry g = (Geometry) gc.getGeometryN(i);
shape.add(toShape(g));
}
return shape;
}
private PathShape toShape( MultiLineString mls ) {
Path path = new Path();
for( int i = 0; i < mls.getNumGeometries(); i++ ) {
LineString lineString = (LineString) mls.getGeometryN(i);
PathShape shape = toShape(lineString);
path.addPath(shape.getPath());
}
return new PathShape(path);
}
private PathShape toShape( LineString lineString ) {
Path shape = new Path();
Coordinate prev = lineString.getCoordinateN(0);
transformPoint(prev, transPoint);
shape.moveTo((float) transPoint.x, (float) transPoint.y);
double prevx = (double) transPoint.x;
double prevy = (double) transPoint.y;
int n = lineString.getNumPoints() - 1;
// int count = 0;
for( int i = 1; i <= n; i++ ) {
Coordinate currentCoord = lineString.getCoordinateN(i);
if (decimationDistance > 0.0) {
boolean isDecimated = prev != null && Math.abs(currentCoord.x - prev.x) < decimationDistance
&& Math.abs(currentCoord.y - prev.y) < decimationDistance;
if (i < n && isDecimated) {
continue;
}
prev = currentCoord;
}
transformPoint(currentCoord, transPoint);
if (doRemoveDuplicatePoints) {
// skip duplicate points (except the last point)
boolean isDup = transPoint.x == prevx && transPoint.y == prevy;
if (i < n && isDup)
continue;
prevx = transPoint.x;
prevy = transPoint.y;
// count++;
}
shape.lineTo((float) transPoint.x, (float) transPoint.y);
}
// System.out.println(count);
return new PathShape(shape);
}
private DrawableShape toShape( Point point ) {
PointF viewPoint = transformPoint(point.getCoordinate());
return pointFactory.createPoint(viewPoint);
}
private DrawableShape toShape( MultiPoint points ) {
GeometryCollectionShape shapes = new GeometryCollectionShape();
int numGeometries = points.getNumGeometries();
for( int i = 0; i < numGeometries; i++ ) {
Point point = (Point) points.getGeometryN(i);
PointF viewPoint = transformPoint(point.getCoordinate());
DrawableShape drawableShape = pointFactory.createPoint(viewPoint);
shapes.add(drawableShape);
}
return shapes;
}
private PointF transformPoint( Coordinate model ) {
return transformPoint(model, new PointF());
}
private PointF transformPoint( Coordinate model, PointF view ) {
pointTransformer.transform(model, view);
return view;
}
}