package com.bbn.openmap.event; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.text.Format; import java.util.Properties; import com.bbn.openmap.LatLonPoint; import com.bbn.openmap.MapBean; import com.bbn.openmap.omGraphics.OMCircle; import com.bbn.openmap.omGraphics.OMPoint; import com.bbn.openmap.omGraphics.OMText; import com.bbn.openmap.proj.GreatCircle; import com.bbn.openmap.proj.ProjMath; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.PropUtils; /** * Mouse mode for drawing temporary range rings on a map bean.
* 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.
* * @author Stéphane Wasserhardt * */ public class RangeRingsMouseMode extends CoordMouseMode { private static final long serialVersionUID = 6208201699394207932L; public final static transient String modeID = "RangeRings".intern(); /** * The property string used to set the numRings member variable. */ public static final String NUM_RINGS_PROPERTY = "numRings"; /** * Format used to draw distances. */ protected Format distanceFormat; /** * Number of rings to draw. Must be a positive integer, or else the value 1 * will be used. Default value is 3.
*/ protected int numRings = 3; /** * Origin point of the range rings to be drawn. */ protected LatLonPoint origin = null; /** * Temporary destination point of the range rings to be drawn. */ protected LatLonPoint intermediateDest = null; /** * Destination point of the range rings to be drawn. */ protected LatLonPoint destination = null; /** * Active MapBean. */ protected MapBean mapBean; 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)); } /** * Return the map bean. * * @return The map bean. */ public MapBean getMapBean() { return mapBean; } /** * Set the map bean. * * @param aMap a map bean */ public void setMapBean(MapBean aMap) { mapBean = aMap; } /** * 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; redraw(); } /** * 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; redraw(); } public void mouseClicked(MouseEvent e) { if (e.getSource() instanceof MapBean) { setMapBean((MapBean) e.getSource()); // if double (or more) mouse clicked if (e.getClickCount() >= 2) { // Clean the range rings cleanUp(); redraw(); } } } public void mousePressed(MouseEvent e) { e.getComponent().requestFocus(); } public void mouseReleased(MouseEvent e) { if ((e.getComponent().hasFocus()) && (e.getSource() instanceof MapBean)) { setMapBean((MapBean) e.getSource()); LatLonPoint pt = getMapBean().getProjection().inverse(e.getPoint()); // If this is the first click (real first click or click after // "finished" rangeRings) if ((origin == null) || ((origin != null) && (destination != null))) { // First, we clear up the map (erase any previous rangeRings) cleanUp(); redraw(); origin = pt; // Just to be sure destination = null; startUp(); } // This case is when only the origin is known else { // The click then corresponds to the selection of a destination destination = pt; finished(); } redraw(); } } public void mouseMoved(MouseEvent e) { if (e.getSource() instanceof MapBean) { setMapBean((MapBean) (e.getSource())); if (origin != null) { intermediateDest = getMapBean().getProjection().inverse(e.getPoint()); fireMouseLocation(e); update(); redraw(); } else { fireMouseLocation(e); } } } public void mouseEntered(MouseEvent e) { if (e.getSource() instanceof MapBean) { setMapBean((MapBean) e.getSource()); } } /** * Repaints the map bean. When this mouse mode is active, it is registered * as a paintListener for the mapBean by the mouseDelegator, * so the listenerPaint method can draw the range rings on * the map bean. */ public void redraw() { MapBean mb = getMapBean(); if (mb != null) { mb.repaint(); } } public void listenerPaint(Graphics g) { // We paint only if we know the origin ... if (origin != null) { paintOrigin(g); // ... and we paint the rings if we know either of destination or // intermediateDest if (destination != null) { paintRangeRings(destination, g); } else if (intermediateDest != null) { paintRangeRings(intermediateDest, g); } } } /** * Paints the origin point of the range rings and its label on the map bean. */ protected void paintOrigin() { MapBean mb = getMapBean(); if (mb != null) { Graphics g = mb.getGraphics(); paintOrigin(g); } } /** * Paints the origin point of the range rings and its label on the given * Graphics. * * @param graphics The Graphics to paint on. */ protected void paintOrigin(Graphics graphics) { MapBean mb = getMapBean(); if (mb == null) { return; } paintOriginPoint(graphics); paintOriginLabel(graphics); } /** * Paints the origin point of the range rings on the given Graphics. * * @param graphics The Graphics to paint on. */ protected void paintOriginPoint(Graphics graphics) { MapBean mb = getMapBean(); Projection proj = mb.getProjection(); OMPoint pt = new OMPoint(origin.getLatitude(), origin.getLongitude()); Graphics2D g = (Graphics2D) graphics; g.setPaintMode(); g.setColor(Color.BLACK); preparePoint(pt); pt.generate(proj); pt.render(g); } /** * Paints the origin label of the range rings on the given Graphics. * * @param graphics The Graphics to paint on. */ protected void paintOriginLabel(Graphics graphics) { MapBean mb = getMapBean(); Projection proj = mb.getProjection(); Point pt = proj.forward(origin); String infoText = getOriginLabel(); Graphics2D g = (Graphics2D) graphics; Rectangle2D r = g.getFontMetrics().getStringBounds(infoText, graphics); pt.translate((int) -(r.getWidth() / 2d), (int) (-r.getHeight() / 2d)); OMText text = new OMText(pt.x, pt.y, infoText, OMText.JUSTIFY_LEFT); g.setPaintMode(); g.setColor(Color.BLACK); prepareLabel(text); text.generate(proj); text.render(g); } /** * Paints the circles and their labels on the map bean. * * @param dest The destination point, used with the origin * member variable to compute the rings. */ protected void paintRangeRings(LatLonPoint dest) { MapBean mb = getMapBean(); if (mb != null) { Graphics g = mb.getGraphics(); paintRangeRings(dest, g); } } /** * Paints the circles and their labels on the given Graphics. * * @param dest The destination point, used with the origin * member variable to compute the rings. * @param graphics The Graphics to paint on. */ protected void paintRangeRings(LatLonPoint dest, Graphics graphics) { MapBean mb = getMapBean(); if (mb == null) { return; } Projection proj = mb.getProjection(); AffineTransform xyTranslation = getTranslation(origin, dest, proj); LatLonPoint p = null; for (int i = 0; i < Math.max(1, numRings); i++) { if (p == null) { p = dest; } else { p = translate(p, xyTranslation, proj); } paintCircle(p, graphics); paintLabel(p, graphics); } } /** * Paints a unique circle centered on origin and which * crosses dest on the map bean. * * @param dest A point on the circle. */ protected void paintCircle(LatLonPoint dest) { MapBean mb = getMapBean(); if (mb != null) { Graphics g = mb.getGraphics(); paintCircle(dest, g); } } /** * Paints a unique circle centered on origin and which * crosses dest on the given Graphics. * * @param dest A point on the circle. * @param graphics The Graphics to paint on. */ protected void paintCircle(LatLonPoint dest, Graphics graphics) { float oLat = origin.getLatitude(); float oLon = origin.getLongitude(); float radphi1 = ProjMath.degToRad(oLat); float radlambda0 = ProjMath.degToRad(oLon); float radphi = ProjMath.degToRad(dest.getLatitude()); float radlambda = ProjMath.degToRad(dest.getLongitude()); // calculate the circle radius double dRad = GreatCircle.spherical_distance(radphi1, radlambda0, radphi, radlambda); // convert into decimal degrees float rad = (float) ProjMath.radToDeg(dRad); // make the circle OMCircle circle = new OMCircle(oLat, oLon, rad); prepareCircle(circle); // get the map projection Projection proj = getMapBean().getProjection(); // prepare the circle for rendering circle.generate(proj); // render the circle graphic circle.render(graphics); } /** * Paints a label for the circle drawn using dest on the map * bean. * * @param dest A point on the circle. */ protected void paintLabel(LatLonPoint dest) { MapBean mb = getMapBean(); if (mb != null) { Graphics g = mb.getGraphics(); paintLabel(dest, g); } } /** * Paints a label for the circle drawn using dest on the * given Graphics. * * @param dest A point on the circle. * @param graphics The Graphics to paint on. */ protected void paintLabel(LatLonPoint dest, Graphics graphics) { String infoText = getLabelFor(dest); Graphics2D g = (Graphics2D) graphics; Rectangle2D r = g.getFontMetrics().getStringBounds(infoText, graphics); double th = r.getHeight(); LatLonPoint llp = new LatLonPoint(origin); float distance = llp.distance(dest); if (llp.getLatitude() > 0) { llp.setLatLon((float) (Math.toRadians(llp.getLatitude()) - distance), (float) Math.toRadians(llp .getLongitude()), true); } else { llp.setLatLon((float) (Math.toRadians(llp.getLatitude()) + distance), (float) Math.toRadians(llp .getLongitude()), true); th = -th / 2d; } Projection proj = getMapBean().getProjection(); Point pt = proj.forward(llp); pt.translate((int) -(r.getWidth() / 2d), (int) th); OMText text = new OMText(pt.x, pt.y, infoText, OMText.JUSTIFY_LEFT); g.setPaintMode(); g.setColor(Color.BLACK); prepareLabel(text); text.generate(proj); text.render(g); } /** * Customizes the given OMPoint before it is rendered. * * @param point OMPoint. */ protected void preparePoint(OMPoint point) { } /** * Customizes the given OMCicle before it is rendered. * * @param circle OMCircle. */ protected void prepareCircle(OMCircle circle) { } /** * Customizes the given OMText before it is rendered. * * @param text OMText. */ protected void prepareLabel(OMText text) { text.setLinePaint(Color.BLACK); text.setTextMatteColor(getMapBean().getBackground()); text.setTextMatteStroke(new BasicStroke(4)); } /** * Returns the String to be used as a labeller for the origin point of the * range rings. * * @return label String. */ protected String getOriginLabel() { return "(" + df.format(origin.getLatitude()) + " ; " + df.format(origin.getLongitude()) + ")"; } /** * Returns the String to be used as a labeller for the circle drawn using * dest. * * @param dest A point on a circle. * @return label String. */ protected String getLabelFor(LatLonPoint dest) { Format distFormat = getDistanceFormat(); float distance = origin.distance(dest); if (distFormat == null) { return Float.toString(distance); } return distFormat.format(new Float(distance)); } /** * Called when the origin point of the range rings has been selected, before * painting on the map. */ protected void startUp() { } /** * Called when the origin point of the range is is known, and the mouse is * moving on the map, but before painting on the map. */ protected void update() { } /** * Called when the end point of the range rings has been selected, before * painting on the map. */ protected void finished() { } /** * 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(LatLonPoint pt1, LatLonPoint pt2, Projection proj) { Point p1 = proj.forward(pt1); Point p2 = proj.forward(pt2); return AffineTransform.getTranslateInstance(p2.x - p1.x, p2.y - p1.y); } private LatLonPoint translate(LatLonPoint pt, AffineTransform xyTranslation, Projection proj) { Point p = proj.forward(pt); xyTranslation.transform(p, p); return proj.inverse(p); } public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); prefix = PropUtils.getScopedPropertyPrefix(prefix); numRings = PropUtils.intFromProperties(props, prefix + NUM_RINGS_PROPERTY, numRings); } public Properties getProperties(Properties props) { props = super.getProperties(props); String prefix = PropUtils.getScopedPropertyPrefix(getPropertyPrefix()); props.setProperty(prefix + NUM_RINGS_PROPERTY, Integer.toString(numRings)); return props; } public Properties getPropertyInfo(Properties list) { list = super.getPropertyInfo(list); list.setProperty(NUM_RINGS_PROPERTY, "Number of range rings to be drawn (minimum=1; default=3)."); return list; } }