/* * * 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.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.util.HashMap; import sim.field.continuous.Continuous2D; import sim.field.geo.GeomVectorField; import sim.portrayal.*; import sim.util.Bag; import sim.util.Double2D; import sim.util.geo.MasonGeometry; /** * Portrayal for MasonGeometry objects. The portrayal handles drawing and hit-testing (for inspectors). * * * <p> * GeomVectorFieldPortrayal overrides getPortrayalForObject to do a different thing than normal FieldPortrayals. * Specifically: * * <p> * <ol> * <li>The object passed in is expected to be a MasonGeometry. From this we extract USER, the MASON user data of the * geometry, and GEOMETRY, the JTS Geometry object. * <li>If there is a portrayalForAll, return it. * <li>If user exists and is a Portrayal, return user as the Portrayal. * <li>If a portrayal is registered for user, return it. * <li>If a portrayal is registered for geometry, return it. * <li>If a portrayal is registered for user's class, return it. * <li>If a portrayal is registered for the geometry's class, return it. * <li>If there is a portrayalForRemainder, return it. * <li>Else return the getDefaultPortrayal * </ol> * * <p> * Note that nowhere do we return portrayals for null objects: there is no PortrayalForNull and no DefaultNullPortrayal. * Indeed, the method setPortrayalForNull will throw an error -- you are not permitted to call it. */ public class GeomVectorFieldPortrayal extends FieldPortrayal2D { /** Throws an exception. Do not call this method. */ @Override public void setPortrayalForNull(Portrayal portrayal) { // this bad boy throws an exception throw new RuntimeException("setPortrayalForNull(Portrayal) may NOT be called on a GeomVectorFieldPortrayal"); } /** * Returns the appropriate Portrayal. See the class header for more information on the implementation of this * method. */ @Override public Portrayal getPortrayalForObject(Object obj) { // return the portrayal-for-all if any if (portrayalForAll != null) { return portrayalForAll; } MasonGeometry mg = (MasonGeometry) obj; Geometry geometry = mg.getGeometry(); Object user = mg.getUserData(); Portrayal tmp; // we don't check for null values of obj, so this is simpler than the // one in FieldPortrayal if (user != null && user instanceof Portrayal) { return (Portrayal) user; } if (portrayalForNonNull != null) { return portrayalForNonNull; } if ((portrayals != null /* && !portrayals.isEmpty() */) && // a little // efficiency // -- avoid // making // weak keys // etc. ((tmp = ((Portrayal) (portrayals.get(user)))) != null)) { return tmp; } if ((portrayals != null /* && !portrayals.isEmpty() */) && // a little // efficiency // -- avoid // making // weak keys // etc. ((tmp = ((Portrayal) (portrayals.get(geometry)))) != null)) { return tmp; } if (user != null && (classPortrayals != null /* * && * !classPortrayals.isEmpty * () */) && // a little // efficiency -- // avoid making weak // keys etc. ((tmp = ((Portrayal) (classPortrayals.get(user.getClass())))) != null)) { return tmp; } if (geometry != null && (classPortrayals != null /* * && * !classPortrayals.isEmpty * () */) && // a little // efficiency -- // avoid making // weak keys // etc. ((tmp = ((Portrayal) (classPortrayals.get(geometry.getClass())))) != null)) { return tmp; } if (portrayalForRemainder != null) { return portrayalForRemainder; } return getDefaultPortrayal(); } private static final long serialVersionUID = 8409421628913847667L; /** The underlying portrayal */ GeomPortrayal defaultPortrayal = new GeomPortrayal(); /** Default constructor */ public GeomVectorFieldPortrayal() { super(); setImmutableField(false); } /** Constructor which sets the field's immutable flag */ public GeomVectorFieldPortrayal(boolean immutableField) { super(); setImmutableField(immutableField); } /** Return the underlying portrayal */ @Override public Portrayal getDefaultPortrayal() { return defaultPortrayal; } /** Caches immutable fields. */ BufferedImage buffer = null; RenderingHints hints = null; /** Handles hit-testing and drawing of the underlying geometry objects. */ @Override protected void hitOrDraw(Graphics2D graphics, DrawInfo2D info, Bag putInHere) { if (field == null) { return; } // If we're drawing (and not inspecting), re-fresh the buffer if the // associated field is immutable. if (graphics != null && immutableField && !info.precise) { GeomVectorField geomField = (GeomVectorField) field; double x = info.clip.x; double y = info.clip.y; boolean dirty = false; // make a new buffer? or did the user change the zoom? Or change the // rendering hints? if (buffer == null || buffer.getWidth() != info.clip.width || buffer.getHeight() != info.clip.height || hints == null || !hints.equals(graphics.getRenderingHints())) { hints = graphics.getRenderingHints(); buffer = new BufferedImage((int) info.clip.width, (int) info.clip.height, BufferedImage.TYPE_INT_ARGB); dirty = true; } // handles the case for scrolling if (geomField.drawX != x || geomField.drawY != y) { dirty = true; } // save the origin of the drawn region for later geomField.drawX = x; geomField.drawY = y; // re-draw into the buffer if (dirty) { clearBufferedImage(buffer); Graphics2D newGraphics = (Graphics2D) buffer.getGraphics(); newGraphics.setRenderingHints(hints); hitOrDraw2(newGraphics, new DrawInfo2D(info, -x, -y), putInHere); newGraphics.dispose(); } // draw buffer on screen graphics.drawImage(buffer, (int) x, (int) y, null); } else if (graphics == null) // we're just hitting { hitOrDraw2(graphics, info, putInHere); } else // might as well clear the buffer -- likely we're doing precise drawing { // do regular MASON-style drawing buffer = null; hitOrDraw2(graphics, info, putInHere); } } /** Clears the BufferedImage by setting all the pixels to RGB(0,0,0,0) */ void clearBufferedImage(BufferedImage image) { int len = image.getHeight() * image.getWidth(); WritableRaster raster = image.getRaster(); int[] data = new int[len]; for (int i = 0; i < len; i++) { data[i] = 0; } raster.setDataElements(0, 0, image.getWidth(), image.getHeight(), data); } /** * Helper function which performs the actual hit-testing and drawing for both immutable fields and non-immutable * fields. * * <p> * The objects in the field can either use GeomPortrayal or any SimplePortrayal2D for drawing. * */ void hitOrDraw2(Graphics2D graphics, DrawInfo2D info, Bag putInHere) { GeomVectorField geomField = (GeomVectorField) field; if (geomField == null) { return; } boolean objectSelected = !selectedWrappers.isEmpty(); geomField.updateTransform(info); Bag geometries; geometries = geomField.queryField(geomField.clipEnvelope); if (geometries == null || geometries.isEmpty()) { // FIXME This is a hack to correct for situation where when // doing hit, not drawing, the incorrect geometries are returned. geometries = geomField.getGeometries(); // Sometimes there really *isn't* anything to render. if (geometries.isEmpty()) { return; } } // else // { // System.out.println("clipped: " + geometries.size()); // } GeomInfo2D gInfo = new GeomInfo2D(info, geomField.worldToScreen); final double xScale = info.draw.width / geomField.getFieldWidth(); final double yScale = info.draw.height / geomField.getFieldHeight(); GeomInfo2D newinfo = new GeomInfo2D(new DrawInfo2D(info.gui, info.fieldPortrayal, new Rectangle2D.Double(0, 0, xScale, yScale), info.clip), geomField.worldToScreen); newinfo.fieldPortrayal = this; // use this for determining which objects we should be concerned with GeometryFactory geomFactory = ((MasonGeometry)geometries.objs[0]).getGeometry().getFactory(); Geometry clipGeometry = geomFactory.toGeometry(geomField.clipEnvelope); for (int i = 0; i < geometries.size(); i++) { MasonGeometry gm = (MasonGeometry) geometries.objs[i]; // FIXME: *Why* the hell would this happen? if (gm == null) { continue; } Geometry geom = gm.getGeometry(); if (clipGeometry.intersects(geom.getEnvelope())) { Portrayal p = getPortrayalForObject(gm); if (!(p instanceof SimplePortrayal2D)) { throw new RuntimeException("Unexpected Portrayal " + p + " for object " + gm + " -- expected a SimplePortrayal2D or a GeomPortrayal"); } SimplePortrayal2D portrayal = (SimplePortrayal2D) p; if (graphics == null) { if (portrayal.hitObject(gm, info)) { // XXX getGeometryLocation merely returns the centroid of // the MasonGeometry object once it finds it in the GeomVectorField; // however, we *just got it* from that same field, so why // do we need to find it again? Just directly get the // centroid of the object and be done with it. // putInHere.add(new LocationWrapper(gm, geomField.getGeometryLocation(gm), this)); putInHere.add(new LocationWrapper(gm, gm.getGeometry().getCentroid(), this)); } } else { if (portrayal instanceof GeomPortrayal) { portrayal.draw(gm, graphics, gInfo); } else { // have a SimplePortrayal2D, Point pt = gm.geometry.getCentroid(); pt.apply(geomField.jtsTransform); pt.geometryChanged(); newinfo.selected = (objectSelected && selectedWrappers.get(gm) != null); newinfo.draw.x = pt.getX(); newinfo.draw.y = pt.getY(); portrayal.draw(gm, graphics, newinfo); } } } } } /** Sets the underlying field, after ensuring its a GeomVectorField. */ @Override public void setField(Object field) { if (field instanceof GeomVectorField) { super.setField(field); } // sets dirty field already else { throw new RuntimeException("Invalid field for GeomFieldPortrayal: " + field); } } HashMap<Object, LocationWrapper> selectedWrappers = new HashMap<Object, LocationWrapper>(); @Override public boolean setSelected(LocationWrapper wrapper, boolean selected) { if (wrapper == null) { return true; } if (wrapper.getFieldPortrayal() != this) { return true; } Object obj = wrapper.getObject(); boolean b = getPortrayalForObject(obj).setSelected(wrapper, selected); if (selected) { if (b == false) { return false; } selectedWrappers.put(obj, wrapper); } else { selectedWrappers.remove(obj); } return true; } }