// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** package com.bbn.openmap.gui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.MalformedURLException; import java.net.URL; import java.text.DecimalFormat; import java.util.Properties; import java.util.Vector; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.JButton; import com.bbn.openmap.BufferedMapBean; import com.bbn.openmap.MapBean; import com.bbn.openmap.MapHandler; import com.bbn.openmap.MouseDelegator; import com.bbn.openmap.event.CoordMouseMode; import com.bbn.openmap.event.MapMouseEvent; import com.bbn.openmap.event.MapMouseMode; import com.bbn.openmap.event.OMMouseMode; import com.bbn.openmap.event.ProjectionEvent; import com.bbn.openmap.event.ProjectionListener; import com.bbn.openmap.omGraphics.OMCircle; import com.bbn.openmap.omGraphics.OMColor; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMLine; import com.bbn.openmap.omGraphics.OMText; import com.bbn.openmap.proj.GreatCircle; import com.bbn.openmap.proj.Length; import com.bbn.openmap.proj.ProjMath; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.PropUtils; /** * The distance quicktool is a Tool object that uses an embedded mouse mode to * measure distance on the map. It's intended to be used with the OMMouseMode, * using it as a proxy, and listens to the MouseDelegator to make sure the * OMMouseMode is the active one while the dist quicktool button is enabled. */ public class DistQuickTool extends OMToolComponent implements Tool, PropertyChangeListener { private static final long serialVersionUID = 1L; protected Logger logger = Logger.getLogger("com.bbn.openmap.gui.DistQuickTool"); protected MouseMode mouseMode = null; protected JButton launchButton = null; protected boolean ommmActive = false; public DistQuickTool() { mouseMode = getMouseMode(); try { setOpaque(false); URL url = PropUtils.getResourceOrFileOrURL("com/bbn/openmap/tools/drawing/distance.png"); ImageIcon ii = new ImageIcon(url); launchButton = new JButton(ii); launchButton.setPreferredSize(new Dimension(25, 25)); launchButton.setToolTipText("Measure Distance"); launchButton.setFocusable(false); launchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { go(); } }); add(launchButton); } catch (MalformedURLException e) { e.printStackTrace(); } } protected void go() { MouseDelegator mouseDelegator = ((MapHandler) getBeanContext()).get(MouseDelegator.class); if (mouseDelegator != null) { MapMouseMode proxyParent = mouseDelegator.getActiveMouseMode(); if (proxyParent instanceof OMMouseMode) { ommmActive = true; mouseMode.go(proxyParent); launchButton.setEnabled(false); } } } public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); mouseMode.setProperties(prefix, props); } public Properties getProperties(Properties props) { props = super.getProperties(props); mouseMode.getProperties(props); return props; } public Properties getPropertyInfo(Properties props) { props = super.getPropertyInfo(props); mouseMode.getPropertyInfo(props); return props; } public void findAndInit(Object someObj) { super.findAndInit(someObj); if (someObj instanceof MouseDelegator) { ((MouseDelegator) someObj).addPropertyChangeListener(this); ommmActive = ((MouseDelegator)someObj).getActiveMouseMode() instanceof OMMouseMode; } } public void findAndUndo(Object someObj) { super.findAndUndo(someObj); if (someObj instanceof MouseDelegator) { ((MouseDelegator) someObj).removePropertyChangeListener(this); } } public MouseMode getMouseMode() { if (mouseMode == null) { mouseMode = new MouseMode(); } return mouseMode; } public class MouseMode extends CoordMouseMode implements ProjectionListener { private static final long serialVersionUID = 1L; public final static String UnitProperty = "units"; public final static String ShowCircleProperty = "showCircle"; public final static String ShowAngleProperty = "showAngle"; public final static transient String modeID = "Distance"; public transient DecimalFormat df = new DecimalFormat("0"); // The unit type, default mile private Length unit = Length.MILE; // Flag to display the azimuth angle. Default true boolean showAngle = true; /** * rPoint1 is the anchor point of a line segment */ public Point2D rPoint1; /** * rPoint2 is the new (current) point of a line segment */ public Point2D rPoint2; /** * Flag, true if the mouse has already been pressed */ public boolean mousePressed = false; /** * Vector to store all distance segments, first point and last point pairs */ public Vector<Point2D> segments = new Vector<Point2D>(); /** * Distance of the current segment */ public double distance = 0; /** * The cumulative distance from the first mouse click */ public double totalDistance = 0; /** * To display the rubberband circle, default true */ private boolean displayCircle = true; /** * Special units value for displaying all units ... use only in properties * file */ public final static String AllUnitsPropertyValue = "all"; protected BufferedMapBean theMap = null; protected String coordString = null; protected OMGraphicList distanceList; protected MapMouseMode proxyParent = null; public MouseMode() { super(modeID, true); } public void setProperties(String prefix, Properties props) { super.setProperties(prefix, props); prefix = PropUtils.getScopedPropertyPrefix(prefix); String name = props.getProperty(prefix + UnitProperty); if (name != null) { Length length = Length.get(name); if (length != null) { setUnit(length); } else if (name.equals(AllUnitsPropertyValue)) { setUnit(null); } } setDisplayCircle(PropUtils.booleanFromProperties(props, prefix + ShowCircleProperty, isDisplayCircle())); setShowAngle(PropUtils.booleanFromProperties(props, prefix + ShowAngleProperty, isShowAngle())); } public Properties getProperties(Properties props) { props = super.getProperties(props); String prefix = PropUtils.getScopedPropertyPrefix(this); String unitValue = (unit != null ? unit.toString() : AllUnitsPropertyValue); props.put(prefix + UnitProperty, unitValue); props.put(prefix + ShowCircleProperty, new Boolean(isDisplayCircle()).toString()); props.put(prefix + ShowAngleProperty, new Boolean(isShowAngle()).toString()); return props; } public Properties getPropertyInfo(Properties props) { props = super.getPropertyInfo(props); PropUtils.setI18NPropertyInfo(i18n, props, DistQuickTool.class, UnitProperty, "Units", "Units to use for measurements, from Length.name possibilities.", null); PropUtils.setI18NPropertyInfo(i18n, props, DistQuickTool.class, ShowCircleProperty, "Show Distance Circle", "Flag to set whether the range circle is drawn at the end of the line (true/false).", "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); PropUtils.setI18NPropertyInfo(i18n, props, DistQuickTool.class, ShowAngleProperty, "Show Angle", "Flag to note the azimuth angle of the line in the information line (true/false).", "com.bbn.openmap.util.propertyEditor.YesNoPropertyEditor"); return props; } /** * Checks the MouseEvent to see if a BufferedMapBean can be found. * * @param evt MouseEvent, or a MapMouseEvent * @return BufferedMapBean, or null if source is not a BufferedMapBean. */ protected BufferedMapBean getBufferedMapBean(MouseEvent evt) { if (evt instanceof MapMouseEvent) { MapBean mb = ((MapMouseEvent) evt).getMap(); if (mb instanceof BufferedMapBean) { return (BufferedMapBean) mb; } } else { Object src = evt.getSource(); if (src instanceof BufferedMapBean) { return (BufferedMapBean) src; } } return null; } /** * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent) * The first click for drag, the image is generated. This image is * redrawing when the mouse is move, but, I need to repain the * original image. */ public void mouseDragged(MouseEvent arg0) { if (theMap == null) { // OMMouseMode needs a BufferedMapBean return; } if (rPoint1 == null) { rPoint1 = theMap.getCoordinates(arg0); } else { // right mouse click, measure double lat1, lat2, long1, long2; // erase the old line and circle first // paintRubberband(rPoint1, rPoint2, coordString); // get the current mouse location in latlon rPoint2 = theMap.getCoordinates(arg0); lat1 = rPoint1.getY(); long1 = rPoint1.getX(); // lat, lon of current mouse position lat2 = rPoint2.getY(); long2 = rPoint2.getX(); // calculate great circle distance in nm // distance = getGreatCircleDist(lat1, long1, // lat2, long2, Length.NM); distance = GreatCircle.sphericalDistance(ProjMath.degToRad(lat1), ProjMath.degToRad(long1), ProjMath.degToRad(lat2), ProjMath.degToRad(long2)); // calculate azimuth angle dec deg double azimuth = getSphericalAzimuth(lat1, long1, lat2, long2); coordString = createDistanceInformationLine(rPoint2, distance, azimuth); // paint the new line and circle up to the current // mouse location paintRubberband(rPoint1, rPoint2, coordString); theMap.repaint(); } } /** * Process a mouse pressed event. Add the mouse location to the segment * vector. Calculate the cumulative total distance. * * @param e mouse event. */ public void mousePressed(MouseEvent e) { e.getComponent().requestFocus(); mousePressed = true; BufferedMapBean mb = getBufferedMapBean(e); if (mb == null) { // OMMouseMode needs a BufferedMapBean return; } theMap = mb; theMap.addPaintListener(this); if (mb != null) { // anchor the new first point of the line rPoint1 = theMap.getCoordinates(e); // ensure the second point is not yet set. rPoint2 = null; // add the distance to the total distance totalDistance = 0; } } public void mouseClicked(MouseEvent e) { } /** * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent) * Make Pan event for the map. */ public void mouseReleased(MouseEvent arg0) { reset(); } public void go(MapMouseMode proxyParent) { proxyParent.actAsProxyFor(this); this.proxyParent = proxyParent; } protected void reset() { if (theMap != null) { distanceList = null; // cleanup the drawing OMGraphics cleanUp(); theMap.removePaintListener(this); theMap = null; } if (proxyParent != null && proxyParent.isProxyFor(mouseMode)) { proxyParent.releaseProxy(); proxyParent = null; } launchButton.setEnabled(ommmActive); } /** * PaintListener interface, notifying the MouseMode that the MapBean has * repainted itself. Useful if the MouseMode is drawing stuff. */ public void listenerPaint(Object source, Graphics g) { if (distanceList != null) { distanceList.render(g); } } public void projectionChanged(ProjectionEvent e) { Projection p = e.getProjection(); if (p != null && distanceList != null) { distanceList.generate(p); } } /** * Draw a rubberband line and circle between two points * * @param pt1 the anchor point. * @param pt2 the current (mouse) position. */ @SuppressWarnings("serial") public void paintRubberband(Point2D pt1, Point2D pt2, String coordString) { if (distanceList == null) { distanceList = new OMGraphicList() { public void render(Graphics g) { Graphics g2 = g.create(); g2.setXORMode(java.awt.Color.lightGray); for (OMGraphic omg : this) { if (omg instanceof OMText) { omg.render(g); } else { omg.render(g2); } } g2.dispose(); } }; } distanceList.clear(); paintLine(pt1, pt2); paintCircle(pt1, pt2); paintText(pt1, pt2, coordString); } /** * Draw a rubberband line between two points * * @param pt1 the anchor point. * @param pt2 the current (mouse) position. */ public void paintLine(Point2D pt1, Point2D pt2) { if (pt1 != null && pt2 != null) { // the line connecting the segments OMLine cLine = new OMLine(pt1.getY(), pt1.getX(), pt2.getY(), pt2.getX(), OMGraphic.LINETYPE_GREATCIRCLE); // get the map projection Projection proj = theMap.getProjection(); // prepare the line for rendering cLine.generate(proj); distanceList.add(cLine); } } public void paintText(Point2D base, Point2D pt1, String coordString) { if (coordString != null) { base = theMap.getProjection().forward(base); pt1 = theMap.getProjection().forward(pt1); if (base.distance(pt1) > 3) { // g.drawString(coordString, (int) pt1.getX() + 5, (int) pt1 // .getY() - 5); OMText text = new OMText((int) pt1.getX() + 5, (int) pt1.getY() - 5, coordString, OMText.JUSTIFY_LEFT); Font font = text.getFont(); text.setFont(font.deriveFont(Font.BOLD, font.getSize() + 4)); text.setLinePaint(Color.BLACK); text.setTextMatteColor(Color.WHITE); text.setTextMatteStroke(new BasicStroke(5)); text.setMattingPaint(OMColor.clear); text.generate(theMap.getProjection()); distanceList.add(text); } } } /** * Draw a rubberband circle between two points * * @param pt1 the anchor point. * @param pt2 the current (mouse) position. */ public void paintCircle(Point2D pt1, Point2D pt2) { // do all this only if want to display the rubberband circle if (displayCircle) { if (pt1 != null && pt2 != null) { // first convert degrees to radians double radphi1 = ProjMath.degToRad(pt1.getY()); double radlambda0 = ProjMath.degToRad(pt1.getX()); double radphi = ProjMath.degToRad(pt2.getY()); double radlambda = ProjMath.degToRad(pt2.getX()); // calculate the circle radius double dRad = GreatCircle.sphericalDistance(radphi1, radlambda0, radphi, radlambda); // convert into decimal degrees double rad = ProjMath.radToDeg(dRad); // make the circle OMCircle circle = new OMCircle(pt1.getY(), pt1.getX(), rad); // get the map projection Projection proj = theMap.getProjection(); // prepare the circle for rendering circle.generate(proj); distanceList.add(circle); } } // end if(displayCircle) } /** * Reset the segments and distances */ public void cleanUp() { // a quick way to clean the vector segments = new Vector<Point2D>(); // reset the total distance totalDistance = 0.0; distance = 0.0; coordString = null; } /** * Return the azimuth angle in decimal degrees from north. Based on * spherical_azimuth. See class GreatCircle.java * * @param phi1 latitude in decimal degrees of start point * @param lambda0 longitude in decimal degrees of start point * @param phi latitude in decimal degrees of end point * @param lambda longitude in decimal degrees of end point * @return float azimuth angle in degrees */ public double getSphericalAzimuth(double phi1, double lambda0, double phi, double lambda) { // convert arguments to radians double radphi1 = ProjMath.degToRad(phi1); double radlambda0 = ProjMath.degToRad(lambda0); double radphi = ProjMath.degToRad(phi); double radlambda = ProjMath.degToRad(lambda); // get the spherical azimuth in radians between the two points double az = GreatCircle.sphericalAzimuth(radphi1, radlambda0, radphi, radlambda); return ProjMath.radToDeg(az); } protected String createDistanceInformationLine(Point2D llp, double distance, double azimuth) { // setup the distance info to be displayed String unitInfo = null; // what unit is asked for if (unit == null) { unitInfo = df.format(Length.NM.fromRadians((float) distance)) + Length.NM.getAbbr() + ", " + df.format(Length.KM.fromRadians((float) distance)) + Length.KM.getAbbr() + ", " + df.format(Length.MILE.fromRadians((float) distance)) + Length.MILE.getAbbr() + " "; } else { unitInfo = df.format(unit.fromRadians(distance)) + " " + unit.getAbbr(); } return unitInfo; } /** * Set the unit of distance to be displayed: Length.NM, Length.KM or * Length.MILE. If null, displays all of them. */ public void setUnit(Length units) { unit = units; } public boolean isShowAngle() { return showAngle; } public void setShowAngle(boolean showAngle) { this.showAngle = showAngle; } public boolean isDisplayCircle() { return displayCircle; } public void setDisplayCircle(boolean displayCircle) { this.displayCircle = displayCircle; } /** * Return the unit of distance being displayed: Length.NM, Length.KM or * Length.MILE. If null, displays all of them. */ public Length getUnit() { return unit; } } /* * (non-Javadoc) * * @seejava.beans.PropertyChangeListener#propertyChange(java.beans. * PropertyChangeEvent) */ public void propertyChange(PropertyChangeEvent evt) { Object obj = evt.getSource(); if (obj instanceof MouseDelegator) { String propName = evt.getPropertyName(); if (propName.equals(MouseDelegator.ActiveModeProperty)) { Object newVal = evt.getNewValue(); ommmActive = newVal instanceof OMMouseMode; launchButton.setEnabled(ommmActive); if (mouseMode != null) { mouseMode.reset(); } } else if (propName.equals(MouseDelegator.ProxyMouseModeProperty)) { if (!this.equals(evt.getNewValue())) { mouseMode.reset(); } ommmActive = ((MouseDelegator)obj).getActiveMouseMode() instanceof OMMouseMode; launchButton.setEnabled(evt.getNewValue() == null && ommmActive); } } } }