package com.bbn.openmap.event;
import static com.bbn.openmap.event.DistanceMouseMode.ShowAngleProperty;
import static com.bbn.openmap.event.DistanceMouseMode.ShowCircleProperty;
import static com.bbn.openmap.event.DistanceMouseMode.UnitProperty;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.text.DecimalFormat;
import java.text.Format;
import java.util.Properties;
import com.bbn.openmap.MapBean;
import com.bbn.openmap.geo.Geo;
import com.bbn.openmap.omGraphics.DrawingAttributes;
import com.bbn.openmap.omGraphics.OMCircle;
import com.bbn.openmap.omGraphics.OMGraphicConstants;
import com.bbn.openmap.omGraphics.OMPoint;
import com.bbn.openmap.omGraphics.OMText;
import com.bbn.openmap.proj.Length;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.PropUtils;
/**
* Mouse mode for drawing temporary range rings on a map bean.<br>
* The whole map bean is repainted each time the range rings needs to be
* repainted. The map bean needs to use a mouseDelegator to repaint properly.
* <br>
*
* @author Stephane Wasserhardt
*
*/
public class RangeRingsMouseMode extends CoordMouseMode {
private static final long serialVersionUID = 6208201699394207932L;
public final static transient String modeID = "RangeRings";
/**
* The property string used to set the numRings member variable.
*/
public static final String NUM_RINGS_PROPERTY = "numRings";
public static final String UNITS_PROPERTY = "units";
public transient DecimalFormat df = new DecimalFormat("0.###");
/**
* Format used to draw distances.
*/
protected Format distanceFormat = new DecimalFormat("0.###");
/**
* Number of rings to draw. Must be a positive integer, or else the value 1
* will be used. Default value is 3.<br>
*/
protected int numRings = 3;
/**
* Origin point of the range rings to be drawn.
*/
protected Point2D origin = null;
/**
* Temporary destination point of the range rings to be drawn.
*/
protected Point2D intermediateDest = null;
/**
* Destination point of the range rings to be drawn.
*/
protected Point2D destination = null;
protected DrawingAttributes rrAttributes = DrawingAttributes.getDefaultClone();
/**
* Distance units for label.
*/
protected Length units = Length.MILE;
public RangeRingsMouseMode() {
this(true);
}
public RangeRingsMouseMode(boolean shouldConsumeEvents) {
super(modeID, shouldConsumeEvents);
init();
}
public RangeRingsMouseMode(String name, boolean shouldConsumeEvents) {
super(name, shouldConsumeEvents);
init();
}
protected void init() {
setModeCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
rrAttributes.setLinePaint(Color.GRAY);
rrAttributes.setMattingPaint(Color.LIGHT_GRAY);
rrAttributes.setMatted(true);
}
/**
* Give the Format object used to display distances.
*
* @return Format.
*/
public Format getDistanceFormat() {
return distanceFormat;
}
/**
* Sets the Format object used to display distances.
*
* @param distanceFormat Format.
*/
public void setDistanceFormat(Format distanceFormat) {
this.distanceFormat = distanceFormat;
}
/**
* Returns the number of rings to display.
*
* @return the number of rings to display.
*/
public int getNumRings() {
return numRings;
}
/**
* Sets the number of rings to display.
*
* @param numRings the number of rings to display.
*/
public void setNumRings(int numRings) {
this.numRings = numRings;
}
public void setActive(boolean active) {
if (!active) {
cleanUp();
}
}
public void mouseClicked(MouseEvent e) {
MapBean theMap = e.getSource() instanceof MapBean ? (MapBean) e.getSource() : null;
if (theMap != null) {
// if double (or more) mouse clicked
if (e.getClickCount() >= 2) {
// Clean the range rings
cleanUp();
theMap.repaint();
}
}
}
public void mousePressed(MouseEvent e) {
Object obj = e.getSource();
if (obj instanceof MapBean) {
MapBean theMap = (MapBean) obj;
if (origin == null) {
Point pnt = e.getPoint();
origin = theMap.inverse(pnt.getX(), pnt.getY(), new LatLonPoint.Double());
theMap.addPaintListener(this);
}
}
}
public void mouseReleased(MouseEvent e) {
Object obj = e.getSource();
if (obj instanceof MapBean) {
MapBean theMap = (MapBean) obj;
if (origin != null && destination == null) {
Point pnt = e.getPoint();
Point2D originPnt = theMap.getProjection().forward(origin);
if (Math.abs(originPnt.getX() - pnt.getX()) > 5
&& Math.abs(originPnt.getY() - pnt.getY()) > 5) {
destination = theMap.inverse(pnt.getX(), pnt.getY(), new LatLonPoint.Double());
intermediateDest = null;
}
}
theMap.repaint();
}
}
public void mouseDragged(MouseEvent e) {
mouseMoved(e);
}
public void mouseMoved(MouseEvent e) {
Object obj = e.getSource();
if (obj instanceof MapBean) {
MapBean theMap = (MapBean) obj;
if (origin != null && destination == null) {
Point pnt = e.getPoint();
intermediateDest = theMap.inverse(pnt.getX(), pnt.getY(), new LatLonPoint.Double());
fireMouseLocation(e);
theMap.repaint();
} else {
fireMouseLocation(e);
}
}
}
/**
* PaintListener method.
*
* @param source the source object, most likely the MapBean
* @param g java.awt.Graphics
*/
public void listenerPaint(Object source, Graphics g) {
MapBean theMap = source instanceof MapBean ? (MapBean) source : null;
if (theMap != null) {
if (origin != null) {
paintOrigin(origin, g, theMap);
// ... and we paint the rings if we know either destination or
// intermediateDest
if (destination != null) {
paintRangeRings(origin, destination, g, theMap);
} else if (intermediateDest != null) {
paintRangeRings(origin, intermediateDest, g, theMap);
}
} else {
theMap.removePaintListener(this);
}
}
}
/**
* Paints the origin point of the range rings and its label on the given
* Graphics.
*
* @param llp the location of the origin.
* @param graphics The Graphics to paint on.
*/
protected void paintOrigin(Point2D llp, Graphics graphics, MapBean theMap) {
paintOriginPoint(llp, graphics, theMap);
paintOriginLabel(llp, graphics, theMap);
}
/**
* Paints the origin point of the range rings on the given Graphics.
*
* @param originPnt the origin point
* @param graphics The Graphics to paint on.
*/
protected void paintOriginPoint(Point2D originPnt, Graphics graphics, MapBean theMap) {
if (theMap != null && originPnt != null) {
OMPoint pt = new OMPoint(originPnt.getY(), originPnt.getX());
preparePoint(pt);
pt.generate(theMap.getRotatedProjection());
pt.render(graphics);
}
}
/**
* Paints the origin label of the range rings on the given Graphics.
*
* @param originPnt the origin point
* @param graphics The Graphics to paint on.
*/
protected void paintOriginLabel(Point2D originPnt, Graphics graphics, MapBean theMap) {
if (theMap != null && originPnt != null) {
OMText text = new OMText(originPnt.getY(), originPnt.getX(), getOriginLabel(), OMText.JUSTIFY_CENTER);
text.setBaseline(OMText.BASELINE_BOTTOM);
text.putAttribute(OMGraphicConstants.NO_ROTATE, Boolean.TRUE);
prepareLabel(text);
text.generate(theMap.getRotatedProjection());
text.render(graphics);
}
}
/**
* Paints the circles and their labels on the given Graphics.
*
* @param originPnt the origin location
* @param dest the location of the inner ring.
* @param graphics The Graphics to paint on.
*/
protected void paintRangeRings(Point2D originPnt, Point2D dest, Graphics graphics,
MapBean theMap) {
Geo originGeo = new Geo(originPnt.getY(), originPnt.getX(), true);
Geo destGeo = new Geo(dest.getY(), dest.getX(), true);
double distance = originGeo.distance(destGeo); // radians
for (int i = 1; i <= Math.max(1, numRings); i++) {
double ringDist = distance * (double) i;
paintCircle(originGeo, ringDist, graphics, theMap);
paintLabel(originGeo, ringDist, graphics, theMap);
}
}
/**
* Paints a unique circle centered on <code>origin</code> and which crosses
* <code>dest</code> on the given Graphics.
*
* @param originGeo the origin location
* @param distance the distance of the circle from the origin, in radians
* @param graphics The Graphics to paint on.
*/
protected void paintCircle(Geo originGeo, double distance, Graphics graphics, MapBean theMap) {
OMCircle circle = new OMCircle(originGeo.getLatitude(), originGeo.getLongitude(), Length.DECIMAL_DEGREE.fromRadians(distance));
prepareCircle(circle);
circle.generate(theMap.getRotatedProjection());
circle.render(graphics);
}
/**
* Paints a label for the circle drawn using <code>dest</code> on the given
* Graphics.
*
* @param originGeo the Geo for the origin location
* @param distance the distance of circle in radians.
* @param graphics The Graphics to paint in.
*/
protected void paintLabel(Geo originGeo, double distance, Graphics graphics, MapBean theMap) {
Geo ringGeo = originGeo.offset(distance, Math.PI);
OMText text = new OMText(ringGeo.getLatitude(), ringGeo.getLongitude(), getLabelFor(distance), OMText.JUSTIFY_CENTER);
text.putAttribute(OMGraphicConstants.NO_ROTATE, Boolean.TRUE);
text.setBaseline(OMText.BASELINE_BOTTOM);
prepareLabel(text);
text.generate(theMap.getRotatedProjection());
text.render(graphics);
}
/**
* Customizes the given OMPoint before it is rendered.
*
* @param point OMPoint.
*/
protected void preparePoint(OMPoint point) {
rrAttributes.setTo(point);
}
/**
* Customizes the given OMCicle before it is rendered.
*
* @param circle OMCircle.
*/
protected void prepareCircle(OMCircle circle) {
rrAttributes.setTo(circle);
}
/**
* Customizes the given OMText before it is rendered.
*
* @param text OMText.
*/
protected void prepareLabel(OMText text) {
rrAttributes.setTo(text);
text.setLinePaint(rrAttributes.getLinePaint());
text.setTextMatteColor((Color) rrAttributes.getMattingPaint());
text.setTextMatteStroke(new BasicStroke(4));
}
/**
* Returns the String to be used as a labeler for the origin point of the
* range rings.
*
* @return label String.
*/
protected String getOriginLabel() {
return "(" + df.format(origin.getY()) + ", " + df.format(origin.getX()) + ")";
}
/**
* Returns the String to be used as a labeler for the circle drawn using
* <code>dest</code>.
*
* @param distance The distance from the origin for the label, in radians.
* @return label String.
*/
protected String getLabelFor(double distance) {
Format distFormat = getDistanceFormat();
if (distFormat == null) {
return Double.toString(distance);
}
return distFormat.format(new Double(units.fromRadians(distance))) + " " + units.getAbbr();
}
/**
* Called when the range rings must be cleared, before repainting a clean
* map.
*/
protected void cleanUp() {
origin = null;
intermediateDest = null;
destination = null;
}
private AffineTransform getTranslation(Point2D pt1, Point2D pt2, Projection proj) {
Point2D p1 = proj.forward(pt1);
Point2D p2 = proj.forward(pt2);
return AffineTransform.getTranslateInstance(p2.getX() - p1.getX(), p2.getY() - p1.getY());
}
private LatLonPoint translate(Point2D pt, AffineTransform xyTranslation, Projection proj) {
Point2D p = proj.forward(pt);
xyTranslation.transform(p, p);
return (LatLonPoint) proj.inverse(p, new LatLonPoint.Double());
}
/**
* Set properties for this mouse mode
*
* @param prefix property prefix that should be prepended to property keys.
* @param props the properties containing key-values.
*/
public void setProperties(String prefix, Properties props) {
super.setProperties(prefix, props);
rrAttributes.setProperties(prefix, props);
prefix = PropUtils.getScopedPropertyPrefix(prefix);
units = Length.get(props.getProperty(prefix + UNITS_PROPERTY, units.getAbbr()));
numRings = PropUtils.intFromProperties(props, prefix + NUM_RINGS_PROPERTY, numRings);
}
/**
* Get the current Properties for this mouse mode.
*
* @param props The Properties object to add props to. A Properties object
* will be created if null.
* @return props
*/
public Properties getProperties(Properties props) {
props = super.getProperties(props);
rrAttributes.getProperties(props);
String prefix = PropUtils.getScopedPropertyPrefix(getPropertyPrefix());
props.setProperty(prefix + NUM_RINGS_PROPERTY, Integer.toString(numRings));
props.setProperty(prefix + UNITS_PROPERTY, units.getAbbr());
return props;
}
/**
* Return property info metadata for this PropertyConsumer.
*
* @param list Properties to add to, may be null.
* @return Properties for this object.
*/
public Properties getPropertyInfo(Properties list) {
list = super.getPropertyInfo(list);
list = rrAttributes.getPropertyInfo(list);
list.setProperty(NUM_RINGS_PROPERTY, "Number of range rings to be drawn (minimum=1; default=3).");
list.setProperty(UNITS_PROPERTY, "Units of ring distance");
list.setProperty(initPropertiesProperty, UNITS_PROPERTY + " " + NUM_RINGS_PROPERTY);
return list;
}
}