/*
* Copyright 2011 by Mark Coletti, Keith Sullivan, Sean Luke, and
* George Mason University Mason University Licensed under the Academic
* Free License version 3.0
*
* See the file "LICENSE" for more information
*
* $Id$
*
*/
package sim.portrayal.geo;
import com.vividsolutions.jts.geom.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import sim.display.GUIState;
import sim.portrayal.*;
import sim.portrayal.inspector.TabbedInspector;
import sim.util.Properties;
import sim.util.geo.MasonGeometry;
/**
* The portrayal for MasonGeometry objects. The class draws the JTS geometry
* object (currently, we can draw Point, LineString, Polygon, MultiLineString
* and MultiPolygon objects), and sets up the inspectors for the MasonGeometry
* object. The inspector is TabbedInspector with at most three tabs: the first
* tab shows various information about the JTS geometry, the second tab shows
* the associated attribute information, and the third tab shows information
* about the MasonGeometry userData field, which can be any Java object.
*/
public class GeomPortrayal extends SimplePortrayal2D
{
private static final long serialVersionUID = 472960663330467429L;
/** How to paint each object */
public Paint paint;
/** Scale for each object */
public double scale;
/** Should objects be filled when painting? */
public boolean filled;
/** Default constructor creates filled, gray objects with a scale of 1.0 */
public GeomPortrayal() {
this(Color.GRAY, 1.0, true);
}
public GeomPortrayal(Paint paint) {
this(paint, 1.0, true);
}
public GeomPortrayal(double scale) {
this(Color.GRAY, scale, true);
}
public GeomPortrayal(Paint paint, double scale) {
this(paint, scale, true);
}
public GeomPortrayal(Paint paint, boolean filled) {
this(paint, 1.0, filled);
}
public GeomPortrayal(double scale, boolean filled) {
this(Color.GRAY, scale, filled);
}
public GeomPortrayal(boolean filled) {
this(Color.GRAY, 1.0, filled);
}
public GeomPortrayal(Paint paint, double scale, boolean filled) {
this.paint = paint;
this.scale = scale;
this.filled = filled;
}
/**
* Use our custom Inspector. We create a TabbedInspector for each object
* that allows inspection of the JTS geometry, attribute information, and
* the MasonGeometry userData field.
*/
@Override
public Inspector getInspector(LocationWrapper wrapper, GUIState state)
{
if (wrapper == null)
{
return null;
}
TabbedInspector inspector = new TabbedInspector();
// for basic geometry information such as area, perimeter, etc.
// FIXME Need to fix this so that JTS detailed info is NOT propagated
inspector.addInspector(new SimpleInspector(wrapper.getObject(), state, null), "Geometry");
Object o = wrapper.getObject();
if (o instanceof MasonGeometry)
{
MasonGeometry gw = (MasonGeometry) o;
if (gw.hasAttributes())
{
if (! gw.hasHiddenAttributes())
{ // only add attributes tag if JTS geometry has attributes
// I.e., MASON already has a mechanism for dealing with
// collections, so why not use it?
Properties properties = Properties.getProperties(gw.getAttributes());
inspector.addInspector(new SimpleInspector(properties, state, null), "Attributes");
}
}
if (gw.getUserData() != null)
{
inspector.addInspector(new SimpleInspector(gw.getUserData(), state, null), "User Data");
}
}
return inspector;
}
/**
* Draw a JTS geometry object. The JTS geometries are converted to Java
* general path objects, which are then drawn using the native Graphics2D
* methods.
*/
@Override
public void draw(Object object, Graphics2D graphics, DrawInfo2D info)
{
GeomInfo2D gInfo;
if (info instanceof GeomInfo2D)
{
gInfo = (GeomInfo2D) info;
}
else
{
gInfo = new GeomInfo2D(info, new AffineTransform());
}
MasonGeometry gm = (MasonGeometry) object;
Geometry geometry = gm.getGeometry();
if (geometry.isEmpty())
{
return;
}
if (paint != null)
{
graphics.setPaint(paint);
}
// don't have cached shape or the transform changed, so need to build
// the shape
if ((gm.isMovable) || (gm.shape == null) || (!gm.transform.equals(gInfo.transform)))
{
gm.transform.setTransform(gInfo.transform);
if (geometry instanceof Point)
{
Point point = (Point) geometry;
double offset = 3 * scale / 2.0; // used to center point
Ellipse2D.Double ellipse = new Ellipse2D.Double(point.getX() - offset, point.getY() - offset,
3 * scale, 3 * scale);
GeneralPath path = (GeneralPath) (new GeneralPath(ellipse).createTransformedShape(gInfo.transform));
gm.shape = path;
}
else if (geometry instanceof LineString)
{
gm.shape = drawGeometry(geometry, gInfo, false);
filled = false;
}
else if (geometry instanceof Polygon)
{
gm.shape = drawPolygon((Polygon) geometry, gInfo, filled);
}
else if (geometry instanceof MultiLineString)
{
// draw each LineString individually
MultiLineString multiLine = (MultiLineString) geometry;
for (int i = 0; i < multiLine.getNumGeometries(); i++)
{
GeneralPath p = drawGeometry(multiLine.getGeometryN(i), gInfo, false);
if (i == 0)
{
gm.shape = p;
}
else
{
gm.shape.append(p, false);
}
}
filled = false;
}
else if (geometry instanceof MultiPolygon)
{
// draw each Polygon individually
MultiPolygon multiPolygon = (MultiPolygon) geometry;
for (int i = 0; i < multiPolygon.getNumGeometries(); i++)
{
GeneralPath p = drawPolygon((Polygon) multiPolygon.getGeometryN(i), gInfo, filled);
if (i == 0)
{
gm.shape = p;
}
else
{
gm.shape.append(p, false);
}
}
}
else
{
throw new UnsupportedOperationException("Unsupported JTS type for draw()" + geometry);
}
}
// now draw it!
if (filled)
{
graphics.fill(gm.shape);
}
else
{
graphics.draw(gm.shape);
}
}
/**
* Helper function for drawing a JTS polygon.
*
* <p>
* Polygons have two sets of coordinates; one for the outer ring, and
* optionally another for internal ring coordinates. Draw the outer ring
* first, and then draw each internal ring, if they exist.
* */
GeneralPath drawPolygon(Polygon polygon, GeomInfo2D info, boolean fill)
{
GeneralPath p = drawGeometry(polygon.getExteriorRing(), info, fill);
for (int i = 0; i < polygon.getNumInteriorRing(); i++)
{ // fill for internal rings will always be false as they are literally
// "holes" in the polygon
p.append(drawGeometry(polygon.getInteriorRingN(i), info, false), false);
}
return p;
}
/**
* Helper function to draw a JTS geometry object. The coordinates of the JTS
* geometry are converted to a native Java GeneralPath which is used to draw
* the object.
*/
GeneralPath drawGeometry(Geometry geom, GeomInfo2D info, boolean fill)
{
Coordinate coords[] = geom.getCoordinates();
GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO, coords.length);
path.moveTo((float) coords[0].x, (float) coords[0].y);
for (int i = 1; i < coords.length; i++)
{
path.lineTo((float) coords[i].x, (float) coords[i].y);
}
path.transform(info.transform);
return path;
}
/** Determine if the object was hit or not. */
@Override
public boolean hitObject(Object object, DrawInfo2D range)
{
double SLOP = 2.0;
MasonGeometry geom = (MasonGeometry) object;
if (geom.shape == null)
{
return false;
}
return geom.shape.intersects(range.clip.x - SLOP / 2, range.clip.y - SLOP / 2, range.clip.width + SLOP / 2,
range.clip.height + SLOP / 2);
}
}