All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.util.measure.MeasureTool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.util.measure;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.util.List;

/**
 * A utility class to interactively draw shapes and measure distance and area across the terrain. When armed, the class
 * monitors mouse events to allow the definition of a measure shape that can be one of {@link #SHAPE_LINE}, {@link
 * #SHAPE_PATH}, {@link #SHAPE_POLYGON}, {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE} or {@link
 * #SHAPE_QUAD}. 

In order to allow user interaction with the measuring shape, a controller must be set by * calling {@link #setController(MeasureToolController)} with a new instance of a MeasureToolController.

*

The interaction sequence for drawing a shape and measuring is as follows:

  • Set the measure * shape.
  • Arm the MeasureTool object by calling its {@link #setArmed(boolean)} method with an * argument of true.
  • Click on the terrain to add points.
  • Disarm the MeasureTool object by * calling its {@link #setArmed(boolean)} method with an argument of false.
  • Read the measured length or area * by calling the MeasureTool {@link #getLength()} or {@link #getArea()} method. Note that the length and * area can be queried at any time during or after the process.

While entering points or after the * measure tool has been disarmed, dragging the control points allow to change the initial points positions and alter * the measure shape.

While the MeasureTool is armed, pressing and immediately releasing mouse * button one while also pressing the control key (Ctl) removes the last point entered. Once the * MeasureTool is disarmed, a measure shape of type SHAPE_POLYGON can be moved by dragging a control point * while pressing the alt/option key.

Arming and disarming the MeasureTool does not change the * contents or attributes of the measure tool's layer. Note that the measure tool will NOT disarm itself after the * second point of a line or a regular shape has been entered - the MeasureToolController has that responsibility.

*

Setting the measure shape from the application

The application can set the measure shape * to an arbitrary list of positions using {@link #setPositions(java.util.ArrayList)}. If the provided list contains two * positions, the measure shape will be set to {@link #SHAPE_LINE}. If more then two positions are provided, the measure * shape will be set to {@link #SHAPE_PATH} if the last position differs from the first (open path), or {@link * #SHAPE_POLYGON} if the path is closed.

The application can also set the measure shape to a predefined * regular shape by calling {@link #setMeasureShapeType(String, Position, double, double, Angle)}, providing a shape * type (one of {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}), a center * position, a width, a height (in meters) and a heading angle.

Finally, the application can use an existing * Polyline or SurfaceShape by using {@link #setMeasureShape(Polyline)} or {@link * #setMeasureShape(SurfaceShape)}. The surface shape can be one of SurfacePolyline, * SurfacePolygon, SurfaceQuad, SurfaceSquare, SurfaceEllipse or * SurfaceCircle.

Measuring

The application can read the measured length or area * by calling the MeasureTool {@link #getLength()} or {@link #getArea()} method. These methods will return * -1 when no value is available.

Regular shapes are defined by a center position, a width a height and a * heading angle. Those attributes can be accessed by calling the {@link #getCenterPosition()}, {@link #getWidth()}, * {@link #getHeight()} and {@link #getOrientation()} methods.

The measurements are displayed in units * specified in the measure tool's {@link UnitsFormat} object. Access to the units format is via the method {@link * #getUnitsFormat()}.

Events

The MeasureTool will send events on several * occasions: when the position list has changed - {@link #EVENT_POSITION_ADD}, {@link #EVENT_POSITION_REMOVE} or {@link * #EVENT_POSITION_REPLACE}, when metrics has changed {@link #EVENT_METRIC_CHANGED} or when the tool is armed or * disarmed {@link #EVENT_ARMED}.

Events will also be fired at the start and end of a rubber band operation * during shape creation: {@link #EVENT_RUBBERBAND_START} and {@link #EVENT_RUBBERBAND_STOP}.

See {@link * gov.nasa.worldwindx.examples.MeasureToolPanel} for some events usage.

Several instances of this class can * be used simultaneously. However, each instance should be disposed of after usage by calling the {@link #dispose()} * method.

* * @author Patrick Murris * @version $Id: MeasureTool.java 3297 2015-07-03 16:21:05Z dcollins $ * @see MeasureToolController */ public class MeasureTool extends AVListImpl implements Disposable { public static final String SHAPE_LINE = "MeasureTool.ShapeLine"; public static final String SHAPE_PATH = "MeasureTool.ShapePath"; public static final String SHAPE_POLYGON = "MeasureTool.ShapePolygon"; public static final String SHAPE_CIRCLE = "MeasureTool.ShapeCircle"; public static final String SHAPE_ELLIPSE = "MeasureTool.ShapeEllipse"; public static final String SHAPE_QUAD = "MeasureTool.ShapeQuad"; public static final String SHAPE_SQUARE = "MeasureTool.ShapeSquare"; public static final String EVENT_POSITION_ADD = "MeasureTool.AddPosition"; public static final String EVENT_POSITION_REMOVE = "MeasureTool.RemovePosition"; public static final String EVENT_POSITION_REPLACE = "MeasureTool.ReplacePosition"; public static final String EVENT_METRIC_CHANGED = "MeasureTool.MetricChanged"; public static final String EVENT_ARMED = "MeasureTool.Armed"; public static final String EVENT_RUBBERBAND_START = "MeasureTool.RubberBandStart"; public static final String EVENT_RUBBERBAND_STOP = "MeasureTool.RubberBandStop"; public static final String ANGLE_LABEL = "MeasureTool.AngleLabel"; public static final String AREA_LABEL = "MeasureTool.AreaLabel"; public static final String LENGTH_LABEL = "MeasureTool.LengthLabel"; public static final String PERIMETER_LABEL = "MeasureTool.PerimeterLabel"; public static final String RADIUS_LABEL = "MeasureTool.RadiusLabel"; public static final String HEIGHT_LABEL = "MeasureTool.HeightLabel"; public static final String WIDTH_LABEL = "MeasureTool.WidthLabel"; public static final String HEADING_LABEL = "MeasureTool.HeadingLabel"; public static final String CENTER_LATITUDE_LABEL = "MeasureTool.CenterLatitudeLabel"; public static final String CENTER_LONGITUDE_LABEL = "MeasureTool.CenterLongitudeLabel"; public static final String LATITUDE_LABEL = "MeasureTool.LatitudeLabel"; public static final String LONGITUDE_LABEL = "MeasureTool.LongitudeLabel"; public static final String ACCUMULATED_LABEL = "MeasureTool.AccumulatedLabel"; public static final String MAJOR_AXIS_LABEL = "MeasureTool.MajorAxisLabel"; public static final String MINOR_AXIS_LABEL = "MeasureTool.MinorAxisLabel"; public static final String CONTROL_TYPE_LOCATION_INDEX = "MeasureTool.ControlTypeLocationIndex"; public static final String CONTROL_TYPE_REGULAR_SHAPE = "MeasureTool.ControlTypeRegularShape"; public static final String CONTROL_TYPE_LEADER_ORIGIN = "MeasureTool.ControlTypeLeaderOrigin"; private static final String CENTER = "Center"; private static final String NORTH = "North"; private static final String EAST = "East"; private static final String SOUTH = "South"; private static final String WEST = "West"; private static final String NORTHEAST = "NE"; private static final String SOUTHEAST = "SE"; private static final String SOUTHWEST = "SW"; private static final String NORTHWEST = "NW"; private static final String NORTH_LEADER = "NorthLeader"; protected static final double SHAPE_MIN_WIDTH_METERS = 0.1; protected static final double SHAPE_MIN_HEIGHT_METERS = 0.1; protected static final int MAX_SHAPE_MOVE_ITERATIONS = 10; protected static final double SHAPE_CONTROL_EPSILON_METERS = 0.01; protected final WorldWindow wwd; protected MeasureToolController controller; protected ArrayList positions = new ArrayList(); protected ArrayList controlPoints = new ArrayList(); protected RenderableLayer applicationLayer; protected CustomRenderableLayer layer; protected CustomRenderableLayer controlPointsLayer; protected CustomRenderableLayer shapeLayer; protected Polyline line; protected SurfaceShape surfaceShape; protected ScreenAnnotation annotation; protected Color lineColor = Color.YELLOW; protected Color fillColor = new Color(.6f, .6f, .4f, .5f); protected double lineWidth = 2; protected String pathType = AVKey.GREAT_CIRCLE; protected AnnotationAttributes controlPointsAttributes; protected AnnotationAttributes controlPointWithLeaderAttributes; protected ShapeAttributes leaderAttributes; protected AnnotationAttributes annotationAttributes; protected String measureShapeType = SHAPE_LINE; protected boolean followTerrain = false; protected boolean showControlPoints = true; protected boolean showAnnotation = true; protected UnitsFormat unitsFormat = new UnitsFormat(); // Rectangle enclosed regular shapes attributes protected Rectangle2D.Double shapeRectangle = null; protected Position shapeCenterPosition = null; protected Angle shapeOrientation = null; protected int shapeIntervals = 64; protected static class CustomRenderableLayer extends RenderableLayer implements PreRenderable, Renderable { public void render(DrawContext dc) { if (dc.isPickingMode() && !this.isPickEnabled()) return; if (!this.isEnabled()) return; super.render(dc); } } /** * Construct a new measure tool drawing events from the specified WorldWindow. * * @param wwd the WorldWindow to draw events from. */ public MeasureTool(final WorldWindow wwd) { this(wwd, null); } /** * Construct a new measure tool drawing events from the specified WorldWindow and using the given * RenderableLayer. * * @param wwd the WorldWindow to draw events from. * @param applicationLayer the RenderableLayer to use. May be null. If specified, the caller is * responsible for adding the layer to the model and enabling it. */ public MeasureTool(final WorldWindow wwd, RenderableLayer applicationLayer) { if (wwd == null) { String msg = Logging.getMessage("nullValue.WorldWindow"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.wwd = wwd; this.applicationLayer = applicationLayer; // can be null // Set up layers this.layer = createCustomRenderableLayer(); this.shapeLayer = createCustomRenderableLayer(); this.controlPointsLayer = createCustomRenderableLayer(); this.shapeLayer.setPickEnabled(false); this.layer.setName("Measure Tool"); this.layer.addRenderable(this.shapeLayer); // add shape layer to render layer this.layer.addRenderable(this.controlPointsLayer); // add control points layer to render layer this.controlPointsLayer.setEnabled(this.showControlPoints); if (this.applicationLayer != null) this.applicationLayer.addRenderable(this.layer); // add render layer to the application provided layer else this.wwd.getModel().getLayers().add(this.layer); // add render layer to the globe model // Init control points rendering attributes this.controlPointsAttributes = new AnnotationAttributes(); // Define an 8x8 square centered on the screen point this.controlPointsAttributes.setFrameShape(AVKey.SHAPE_RECTANGLE); this.controlPointsAttributes.setLeader(AVKey.SHAPE_NONE); this.controlPointsAttributes.setAdjustWidthToText(AVKey.SIZE_FIXED); this.controlPointsAttributes.setSize(new Dimension(8, 8)); this.controlPointsAttributes.setDrawOffset(new Point(0, -4)); this.controlPointsAttributes.setInsets(new Insets(0, 0, 0, 0)); this.controlPointsAttributes.setBorderWidth(0); this.controlPointsAttributes.setCornerRadius(0); this.controlPointsAttributes.setBackgroundColor(Color.BLUE); // Normal color this.controlPointsAttributes.setTextColor(Color.GREEN); // Highlighted color this.controlPointsAttributes.setHighlightScale(1.2); this.controlPointsAttributes.setDistanceMaxScale(1); // No distance scaling this.controlPointsAttributes.setDistanceMinScale(1); this.controlPointsAttributes.setDistanceMinOpacity(1); // Init control point with leader rendering attributes. this.controlPointWithLeaderAttributes = new AnnotationAttributes(); this.controlPointWithLeaderAttributes.setDefaults(this.controlPointsAttributes); this.controlPointWithLeaderAttributes.setFrameShape(AVKey.SHAPE_ELLIPSE); this.controlPointWithLeaderAttributes.setSize(new Dimension(10, 10)); this.controlPointWithLeaderAttributes.setDrawOffset(new Point(0, -5)); this.controlPointWithLeaderAttributes.setBackgroundColor(Color.LIGHT_GRAY); this.leaderAttributes = new BasicShapeAttributes(); this.leaderAttributes.setOutlineMaterial(Material.WHITE); this.leaderAttributes.setOutlineOpacity(0.7); this.leaderAttributes.setOutlineWidth(3); // Annotation attributes this.setInitialLabels(); this.annotationAttributes = new AnnotationAttributes(); this.annotationAttributes.setFrameShape(AVKey.SHAPE_NONE); this.annotationAttributes.setInsets(new Insets(0, 0, 0, 0)); this.annotationAttributes.setDrawOffset(new Point(0, 10)); this.annotationAttributes.setTextAlign(AVKey.CENTER); this.annotationAttributes.setEffect(AVKey.TEXT_EFFECT_OUTLINE); this.annotationAttributes.setFont(Font.decode("Arial-Bold-14")); this.annotationAttributes.setTextColor(Color.WHITE); this.annotationAttributes.setBackgroundColor(Color.BLACK); this.annotationAttributes.setSize(new Dimension(220, 0)); this.annotation = new ScreenAnnotation("", new Point(0, 0), this.annotationAttributes); this.annotation.getAttributes().setVisible(false); this.annotation.getAttributes().setDrawOffset(null); // use defaults this.shapeLayer.addRenderable(this.annotation); } protected void setInitialLabels() { this.setLabel(ACCUMULATED_LABEL, Logging.getMessage(ACCUMULATED_LABEL)); this.setLabel(ANGLE_LABEL, Logging.getMessage(ANGLE_LABEL)); this.setLabel(AREA_LABEL, Logging.getMessage(AREA_LABEL)); this.setLabel(CENTER_LATITUDE_LABEL, Logging.getMessage(CENTER_LATITUDE_LABEL)); this.setLabel(CENTER_LONGITUDE_LABEL, Logging.getMessage(CENTER_LONGITUDE_LABEL)); this.setLabel(HEADING_LABEL, Logging.getMessage(HEADING_LABEL)); this.setLabel(HEIGHT_LABEL, Logging.getMessage(HEIGHT_LABEL)); this.setLabel(LATITUDE_LABEL, Logging.getMessage(LATITUDE_LABEL)); this.setLabel(LONGITUDE_LABEL, Logging.getMessage(LONGITUDE_LABEL)); this.setLabel(LENGTH_LABEL, Logging.getMessage(LENGTH_LABEL)); this.setLabel(MAJOR_AXIS_LABEL, Logging.getMessage(MAJOR_AXIS_LABEL)); this.setLabel(MINOR_AXIS_LABEL, Logging.getMessage(MINOR_AXIS_LABEL)); this.setLabel(PERIMETER_LABEL, Logging.getMessage(PERIMETER_LABEL)); this.setLabel(RADIUS_LABEL, Logging.getMessage(RADIUS_LABEL)); this.setLabel(WIDTH_LABEL, Logging.getMessage(WIDTH_LABEL)); } public WorldWindow getWwd() { return this.wwd; } /** * Return the {@link UnitsFormat} instance governing the measurement value display units and format. * * @return the tool's units format instance. */ public UnitsFormat getUnitsFormat() { return this.unitsFormat; } /** * Set the measure tool's @{link UnitsFormat} instance that governs measurement value display units and format. * * @param unitsFormat the units format instance. * * @throws IllegalArgumentException if the units format instance is null. */ public void setUnitsFormat(UnitsFormat unitsFormat) { if (unitsFormat == null) { String msg = Logging.getMessage("nullValue.Format"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.unitsFormat = unitsFormat; } /** @return Instance of the custom renderable layer to use of our internal layers */ protected CustomRenderableLayer createCustomRenderableLayer() { return new CustomRenderableLayer(); } /** * Set the controller object for this measure tool - can be null. * * @param controller the controller object for this measure tool. */ public void setController(MeasureToolController controller) { if (this.controller != null) { this.wwd.getInputHandler().removeMouseListener(this.controller); this.wwd.getInputHandler().removeMouseMotionListener(this.controller); this.wwd.removePositionListener(this.controller); this.wwd.removeSelectListener(this.controller); this.wwd.removeRenderingListener(this.controller); this.controller = null; } if (controller != null) { this.controller = controller; this.controller.setMeasureTool(this); this.wwd.getInputHandler().addMouseListener(this.controller); this.wwd.getInputHandler().addMouseMotionListener(this.controller); this.wwd.addPositionListener(this.controller); this.wwd.addSelectListener(this.controller); this.wwd.addRenderingListener(this.controller); } } public void setLabel(String labelName, String label) { if (labelName != null && labelName.length() > 0) this.setValue(labelName, label); } public String getLabel(String labelName) { if (labelName == null) { String msg = Logging.getMessage("nullValue.LabelName"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } String label = this.getStringValue(labelName); return label != null ? label : this.unitsFormat.getStringValue(labelName); } /** * Get the MeasureToolController for this measure tool. * * @return the MeasureToolController for this measure tool. */ public MeasureToolController getController() { return this.controller; } /** * Arms and disarms the measure tool controller. When armed, the controller monitors user input and builds the shape * in response to user actions. When disarmed, the controller ignores all user input. * * @param state true to arm the controller, false to disarm it. */ public void setArmed(boolean state) { if (this.controller != null) this.controller.setArmed(state); } /** * Identifies whether the measure tool controller is armed. * * @return true if armed, false if not armed. */ public boolean isArmed() { return this.controller != null && this.controller.isArmed(); } /** * Returns the measure tool layer. * * @return the layer containing the measure shape and control points. */ public RenderableLayer getLayer() { return this.layer; } /** * Returns the application layer passed to the constructor. * * @return the layer containing the measure shape and control points. */ public RenderableLayer getApplicationLayer() { return applicationLayer; } /** * Returns the polyline currently used to display lines and path. * * @return the polyline currently used to display lines and path. */ public Polyline getLine() { return this.line; } /** * Returns the surface shape currently used to display polygons. * * @return the surface shape currently used to display polygons. */ public SurfaceShape getSurfaceShape() { return this.surfaceShape; } /** * Get the list of positions that define the current measure shape. * * @return the list of positions that define the current measure shape. */ public ArrayList getPositions() { return this.positions; } /** * Set the measure shape to an arbitrary list of positions. If the provided list contains two positions, the measure * shape will be set to {@link #SHAPE_LINE}. If more then two positions are provided, the measure shape will be set * to {@link #SHAPE_PATH} if the last position differs from the first (open path), or {@link #SHAPE_POLYGON} * if the path is closed. * * @param newPositions the shape position list. */ public void setPositions(ArrayList newPositions) { if (newPositions == null) { String msg = Logging.getMessage("nullValue.PositionsListIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (newPositions.size() < 2) return; this.clear(); // Setup the proper measure shape boolean closedShape = newPositions.get(0).equals(newPositions.get(newPositions.size() - 1)); if (newPositions.size() > 2 && closedShape) setMeasureShapeType(SHAPE_POLYGON); else setMeasureShapeType(getPathType(newPositions)); // Import positions and create control points for (int i = 0; i < newPositions.size(); i++) { Position pos = newPositions.get(i); this.positions.add(pos); if (i < newPositions.size() - 1 || !closedShape) addControlPoint(pos, CONTROL_TYPE_LOCATION_INDEX, this.positions.size() - 1); } // Update line heading if needed if (this.measureShapeType.equals(SHAPE_LINE)) this.shapeOrientation = LatLon.greatCircleAzimuth(this.positions.get(0), this.positions.get(1)); // Update screen shapes updateMeasureShape(); this.firePropertyChange(EVENT_POSITION_REPLACE, null, null); this.wwd.redraw(); } /** * Get the list of control points associated with the current measure shape. * * @return the list of control points associated with the current measure shape. */ public ArrayList getControlPoints() { return this.controlPoints; } /** * Get the attributes associated with the control points. * * @return the attributes associated with the control points. */ public AnnotationAttributes getControlPointsAttributes() { return this.controlPointsAttributes; } /** * Get the attributes associated with the tool tip annotation. * * @return the attributes associated with the tool tip annotation. */ public AnnotationAttributes getAnnotationAttributes() { return this.annotationAttributes; } public void setLineColor(Color color) { if (color == null) { String msg = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.lineColor = color; if (this.line != null) { this.line.setColor(color); } if (this.surfaceShape != null) { ShapeAttributes attr = this.surfaceShape.getAttributes(); if (attr == null) attr = new BasicShapeAttributes(); attr.setOutlineMaterial(new Material(color)); attr.setOutlineOpacity(color.getAlpha() / 255d); this.surfaceShape.setAttributes(attr); } this.wwd.redraw(); } public Color getLineColor() { return this.lineColor; } public void setFillColor(Color color) { if (color == null) { String msg = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.fillColor = color; if (this.surfaceShape != null) { ShapeAttributes attr = this.surfaceShape.getAttributes(); if (attr == null) attr = new BasicShapeAttributes(); attr.setInteriorMaterial(new Material(color)); attr.setInteriorOpacity(color.getAlpha() / 255d); this.surfaceShape.setAttributes(attr); } this.wwd.redraw(); } public Color getFillColor() { return this.fillColor; } public void setLineWidth(double width) { this.lineWidth = width; if (this.line != null) this.line.setLineWidth(width); if (this.surfaceShape != null) { ShapeAttributes attr = this.surfaceShape.getAttributes(); if (attr == null) attr = new BasicShapeAttributes(); attr.setOutlineWidth(width); this.surfaceShape.setAttributes(attr); } this.wwd.redraw(); } public double getLineWidth() { return this.lineWidth; } public String getPathType() { return this.pathType; } public void setPathType(String type) { this.pathType = type; if (this.line != null) this.line.setPathType(polylinePathTypeFromKey(type)); if (this.surfaceShape != null) this.surfaceShape.setPathType(type); if (this.isRegularShape()) this.updateShapeControlPoints(); this.wwd.redraw(); } @SuppressWarnings({"StringEquality"}) protected static int polylinePathTypeFromKey(String type) { if (type != null && type == AVKey.GREAT_CIRCLE) { return Polyline.GREAT_CIRCLE; } else if (type != null && (type == AVKey.RHUMB_LINE || type == AVKey.LOXODROME)) { return Polyline.RHUMB_LINE; } else { return Polyline.LINEAR; } } protected static String keyFromPolylinePathType(int type) { if (type == Polyline.GREAT_CIRCLE) { return AVKey.GREAT_CIRCLE; } else if (type == Polyline.RHUMB_LINE) { return AVKey.RHUMB_LINE; } else { return AVKey.LINEAR; } } public boolean isShowControlPoints() { return this.showControlPoints; } public void setShowControlPoints(boolean state) { this.showControlPoints = state; this.controlPointsLayer.setEnabled(state); this.wwd.redraw(); } public boolean isShowAnnotation() { return this.showAnnotation; } public void setShowAnnotation(boolean state) { this.showAnnotation = state; } /** Removes all positions from the shape, clear attributes. */ public void clear() { while (this.positions.size() > 0 || this.controlPoints.size() > 0) { this.removeControlPoint(); } this.shapeCenterPosition = null; this.shapeOrientation = null; this.shapeRectangle = null; } public boolean isMeasureShape(Object o) { return o == this.shapeLayer; } /** * Get the measure shape type. can be one of {@link #SHAPE_LINE}, {@link #SHAPE_PATH}, {@link #SHAPE_POLYGON}, * {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}. * * @return the measure shape type. */ public String getMeasureShapeType() { return this.measureShapeType; } /** * Set the measure shape type. can be one of {@link #SHAPE_LINE}, {@link #SHAPE_PATH}, {@link #SHAPE_POLYGON}, * {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}. This will reset the * measure tool and clear the current measure shape. * * @param shape the measure shape type. */ public void setMeasureShapeType(String shape) { if (shape == null) { String msg = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (!this.measureShapeType.equals(shape)) { setArmed(false); clear(); this.measureShapeType = shape; } } /** * Set and initialize the measure shape to one of the regular shapes {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, * {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}. * * @param shapeType the shape type. * @param centerPosition the shape center position. * @param radius the shape radius of half width/height. */ public void setMeasureShapeType(String shapeType, Position centerPosition, double radius) { setMeasureShapeType(shapeType, centerPosition, radius * 2, radius * 2, Angle.ZERO); } /** * Set and initialize the measure shape to one of the regular shapes {@link #SHAPE_CIRCLE}, {@link #SHAPE_ELLIPSE}, * {@link #SHAPE_SQUARE} or {@link #SHAPE_QUAD}. * * @param shapeType the shape type. * @param centerPosition the shape center position. * @param width the shape width. * @param height the shape height. * @param orientation the shape orientation or azimuth angle - clockwise from north. */ public void setMeasureShapeType(String shapeType, Position centerPosition, double width, double height, Angle orientation) { if (shapeType == null) { String msg = Logging.getMessage("nullValue.ShapeType"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (centerPosition == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (orientation == null) { String msg = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (isRegularShape(shapeType)) { setArmed(false); clear(); if ((shapeType.equals(SHAPE_CIRCLE) || shapeType.equals(SHAPE_SQUARE)) && width != height) { width = Math.max(width, height); height = Math.max(width, height); } // Set regular shape properties this.measureShapeType = shapeType; this.shapeCenterPosition = centerPosition; this.shapeRectangle = new Rectangle2D.Double(0, 0, width, height); this.shapeOrientation = orientation; // Create control points for regular shapes updateShapeControlPoints(); // Update screen shapes updateMeasureShape(); this.firePropertyChange(EVENT_POSITION_REPLACE, null, null); this.wwd.redraw(); } } /** * Set the measure shape to an existing Polyline. * * @param line a Polyline instance. */ public void setMeasureShape(Polyline line) { if (line == null) { String msg = Logging.getMessage("nullValue.Shape"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } setArmed(false); this.clear(); // Clear and replace current shape if (this.surfaceShape != null) { this.shapeLayer.removeRenderable(this.surfaceShape); this.surfaceShape = null; } if (this.line != null) this.shapeLayer.removeRenderable(this.line); this.line = line; this.shapeLayer.addRenderable(line); // Grab some of the line attributes setFollowTerrain(line.isFollowTerrain()); setPathType(keyFromPolylinePathType(line.getPathType())); // Update position list and create control points int i = 0; for (Position pos : line.getPositions()) { this.positions.add(pos); addControlPoint(pos, CONTROL_TYPE_LOCATION_INDEX, i++); } // Set proper measure shape type this.measureShapeType = getPathType(this.positions); this.firePropertyChange(EVENT_POSITION_REPLACE, null, null); this.wwd.redraw(); } protected String getPathType(List positions) { return positions.size() > 2 ? SHAPE_PATH : SHAPE_LINE; } /** * Set the measure shape to an existing SurfaceShape. Can be one of SurfacePolygon, * SurfaceQuad, SurfaceSquare, SurfaceEllipse or SurfaceCircle. * * @param surfaceShape a SurfaceShape instance. */ public void setMeasureShape(SurfaceShape surfaceShape) { if (surfaceShape == null) { String msg = Logging.getMessage("nullValue.Shape"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } setArmed(false); this.clear(); // Clear and replace current surface shape if (this.surfaceShape != null) { this.shapeLayer.removeRenderable(this.surfaceShape); } if (this.line != null) { this.shapeLayer.removeRenderable(this.line); this.line = null; } this.surfaceShape = surfaceShape; this.shapeLayer.addRenderable(surfaceShape); this.setPathType(surfaceShape.getPathType()); if (surfaceShape instanceof SurfaceQuad) { // Set measure shape type this.measureShapeType = surfaceShape instanceof SurfaceSquare ? SHAPE_SQUARE : SHAPE_QUAD; // Set regular shape properties SurfaceQuad shape = ((SurfaceQuad) surfaceShape); this.shapeCenterPosition = new Position(shape.getCenter(), 0); this.shapeRectangle = new Rectangle2D.Double(0, 0, shape.getWidth(), shape.getHeight()); this.shapeOrientation = shape.getHeading(); // Create control points for regular shapes updateShapeControlPoints(); // Extract positions from shape updatePositionsFromShape(); } else if (surfaceShape instanceof SurfaceEllipse) { // Set measure shape type this.measureShapeType = surfaceShape instanceof SurfaceCircle ? SHAPE_CIRCLE : SHAPE_ELLIPSE; // Set regular shape properties SurfaceEllipse shape = ((SurfaceEllipse) surfaceShape); this.shapeCenterPosition = new Position(shape.getCenter(), 0); this.shapeRectangle = new Rectangle2D.Double(0, 0, shape.getMajorRadius() * 2, shape.getMinorRadius() * 2); this.shapeOrientation = shape.getHeading(); // Create control points for regular shapes updateShapeControlPoints(); // Extract positions from shape updatePositionsFromShape(); } else // SurfacePolygon, SurfacePolyline, SurfaceSector, or some custom shape { // Set measure shape type this.measureShapeType = SHAPE_POLYGON; // Extract positions from shape updatePositionsFromShape(); // Create control points for each position except the last that is the same as the first for (int i = 0; i < this.positions.size() - 1; i++) { addControlPoint(this.positions.get(i), CONTROL_TYPE_LOCATION_INDEX, i); } } this.firePropertyChange(EVENT_POSITION_REPLACE, null, null); this.wwd.redraw(); } public boolean isRegularShape() { return isRegularShape(this.measureShapeType); } protected boolean isRegularShape(String shape) { return (shape.equals(SHAPE_CIRCLE) || shape.equals(SHAPE_ELLIPSE) || shape.equals(SHAPE_QUAD) || shape.equals(SHAPE_SQUARE)); } public boolean isFollowTerrain() { return this.followTerrain; } public void setFollowTerrain(boolean followTerrain) { this.followTerrain = followTerrain; if (this.line != null) { this.line.setFollowTerrain(followTerrain); } } public boolean isCenterControl(ControlPoint controlPoint) { String control = controlPoint.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); return control != null && control.equals(CENTER); } public boolean isSideControl(ControlPoint controlPoint) { String control = controlPoint.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); return control != null && (control.equals(NORTH) || control.equals(EAST) || control.equals(SOUTH) || control.equals(WEST)); } public boolean isCornerControl(ControlPoint controlPoint) { String control = controlPoint.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); return control != null && (control.equals(NORTHEAST) || control.equals(SOUTHEAST) || control.equals(SOUTHWEST) || control.equals(NORTHWEST)); } // *** Metric accessors *** public double getLength() { Globe globe = this.wwd.getModel().getGlobe(); if (this.line != null) return this.line.getLength(globe); if (this.surfaceShape != null) return this.surfaceShape.getPerimeter(globe); return -1; } public double getArea() { Globe globe = this.wwd.getModel().getGlobe(); if (this.surfaceShape != null) return this.surfaceShape.getArea(globe, this.followTerrain); return -1; } public double getWidth() { if (this.shapeRectangle != null) return this.shapeRectangle.width; return -1; } public double getHeight() { if (this.shapeRectangle != null) return this.shapeRectangle.height; return -1; } public Angle getOrientation() { return this.shapeOrientation; } public Position getCenterPosition() { return this.shapeCenterPosition; } // *** Editing shapes *** /** * Add a control point to the current measure shape at the current WorldWindow position. * * @return The position of the new control point, or null if the control point could not be added. */ public Position addControlPoint() { Position curPos = this.wwd.getCurrentPosition(); if (curPos == null) return null; if (this.isRegularShape()) { // Regular shapes are defined in two steps: 1. center, 2. initial corner or edge. if (this.shapeCenterPosition == null) { this.shapeCenterPosition = curPos; this.shapeOrientation = this.getShapeInitialHeading(); updateShapeControlPoints(); } else if (this.shapeRectangle == null) { // Compute shape rectangle and heading, curPos being a corner String control = this.getShapeInitialControl(curPos); updateShapeProperties(control, curPos, null); // Update or create control points updateShapeControlPoints(); } } else { if (!this.measureShapeType.equals(SHAPE_POLYGON) || this.positions.size() <= 1) { // Line, path or polygons with less then two points this.positions.add(curPos); addControlPoint(this.positions.get(this.positions.size() - 1), CONTROL_TYPE_LOCATION_INDEX, this.positions.size() - 1); if (this.measureShapeType.equals(SHAPE_POLYGON) && this.positions.size() == 2) { // Once we have two points of a polygon, add an extra position // to loop back to the first position and have a closed shape this.positions.add(this.positions.get(0)); } if (this.measureShapeType.equals(SHAPE_LINE) && this.positions.size() > 1) { // Two points on a line, update line heading info this.shapeOrientation = LatLon.greatCircleAzimuth(this.positions.get(0), this.positions.get(1)); } } else { // For polygons with more then 2 points, the last position is the same as the first, so insert before it this.positions.add(positions.size() - 1, curPos); addControlPoint(this.positions.get(this.positions.size() - 2), CONTROL_TYPE_LOCATION_INDEX, this.positions.size() - 2); } } // Update screen shapes updateMeasureShape(); this.firePropertyChange(EVENT_POSITION_ADD, null, curPos); this.wwd.redraw(); return curPos; } /** Remove the last control point from the current measure shape. */ public void removeControlPoint() { Position currentLastPosition = null; if (this.isRegularShape()) { if (this.shapeRectangle != null) { this.shapeRectangle = null; this.shapeOrientation = null; this.positions.clear(); // remove all control points except center which is first while (this.controlPoints.size() > 1) { this.controlPoints.remove(1); } } else if (this.shapeCenterPosition != null) { this.shapeCenterPosition = null; this.controlPoints.clear(); } } else { if (this.positions.size() == 0) return; if (!this.measureShapeType.equals(SHAPE_POLYGON) || this.positions.size() == 1) { currentLastPosition = this.positions.get(this.positions.size() - 1); this.positions.remove(this.positions.size() - 1); } else { // For polygons with more then 2 points, the last position is the same as the first, so remove before it currentLastPosition = this.positions.get(this.positions.size() - 2); this.positions.remove(this.positions.size() - 2); if (positions.size() == 2) positions.remove(1); // remove last loop position when a polygon shrank to only two (same) positions } if (this.controlPoints.size() > 0) this.controlPoints.remove(this.controlPoints.size() - 1); } this.controlPointsLayer.setRenderables(this.controlPoints); // Update screen shapes updateMeasureShape(); this.firePropertyChange(EVENT_POSITION_REMOVE, currentLastPosition, null); this.wwd.redraw(); } /** * Update the current measure shape according to a given control point position. * * @param point one of the shape control points. */ public void moveControlPoint(ControlPoint point) { moveControlPoint(point, null); // use the default mode. } /** * Update the current measure shape according to a given control point position and shape edition mode. * * @param point one of the shape control points. * @param mode the shape edition mode. */ public void moveControlPoint(ControlPoint point, String mode) { if (point == null) { String msg = Logging.getMessage("nullValue.PointIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (point.getValue(CONTROL_TYPE_REGULAR_SHAPE) != null) { // Update shape properties updateShapeProperties((String) point.getValue(CONTROL_TYPE_REGULAR_SHAPE), point.getPosition(), mode); updateShapeControlPoints(); //positions = makeShapePositions(); } if (point.getValue(CONTROL_TYPE_LOCATION_INDEX) != null) { int positionIndex = (Integer) point.getValue(CONTROL_TYPE_LOCATION_INDEX); // Update positions Position surfacePosition = computeSurfacePosition(point.getPosition()); surfacePosition = new Position(point.getPosition(), surfacePosition.getAltitude()); positions.set(positionIndex, surfacePosition); // Update last pos too if polygon and first pos changed if (measureShapeType.equals(SHAPE_POLYGON) && positions.size() > 2 && positionIndex == 0) positions.set(positions.size() - 1, surfacePosition); // Update heading for simple line if (measureShapeType.equals(SHAPE_LINE) && positions.size() > 1) shapeOrientation = LatLon.greatCircleAzimuth(positions.get(0), positions.get(1)); } // Update rendered shapes updateMeasureShape(); } /** * Move the current measure shape along a great circle arc at a given azimuth Angle for a given * distance Angle. * * @param azimuth the azimuth Angle. * @param distance the distance Angle. */ public void moveMeasureShape(Angle azimuth, Angle distance) { if (distance == null) { String msg = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (azimuth == null) { String msg = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (this.isRegularShape()) { // Move regular shape center if (controlPoints.size() > 0) { ControlPoint point = this.getControlPoint(CENTER); point.setPosition( new Position(LatLon.greatCircleEndPosition(point.getPosition(), azimuth, distance), 0)); moveControlPoint(point); } } else { // Move all positions and control points for (int i = 0; i < positions.size(); i++) { Position newPos = computeSurfacePosition( LatLon.greatCircleEndPosition(positions.get(i), azimuth, distance)); positions.set(i, newPos); if (!this.measureShapeType.equals(SHAPE_POLYGON) || i < positions.size() - 1) ((ControlPoint) controlPoints.get(i)).setPosition(new Position(newPos, 0)); } // Update heading for simple line if (measureShapeType.equals(SHAPE_LINE) && positions.size() > 1) shapeOrientation = LatLon.greatCircleAzimuth(positions.get(0), positions.get(1)); // Update rendered shapes updateMeasureShape(); } } protected Position computeSurfacePosition(LatLon latLon) { Vec4 surfacePoint = wwd.getSceneController().getTerrain().getSurfacePoint(latLon.getLatitude(), latLon.getLongitude()); if (surfacePoint != null) return wwd.getModel().getGlobe().computePositionFromPoint(surfacePoint); else return new Position(latLon, wwd.getModel().getGlobe().getElevation(latLon.getLatitude(), latLon.getLongitude())); } public String getShapeInitialControl(Position position) { if (this.measureShapeType.equals(SHAPE_ELLIPSE) || this.measureShapeType.equals(SHAPE_CIRCLE)) { return EAST; } else if (this.measureShapeType.equals(SHAPE_QUAD) || this.measureShapeType.equals(SHAPE_SQUARE)) { return NORTHEAST; } return null; } protected Angle getShapeInitialHeading() { return this.wwd.getView().getHeading(); } protected void updateShapeProperties(String control, Position newPosition, String mode) { if (control.equals(CENTER)) { // Update the shape's center position this.updateShapeCenter(control, newPosition); } else if (control.equals(NORTH_LEADER)) { // Update the shape's orientation. this.updateShapeOrientation(control, newPosition); } else { // Update the shape's center position and dimensions. this.updateShapeSize(control, newPosition); } } protected void updateShapeCenter(String control, Position newPosition) { this.shapeCenterPosition = newPosition; } protected void updateShapeOrientation(String control, Position newPosition) { // Compute the control point's azimuth in shape local coordinates. Angle controlAzimuth = this.computeControlPointAzimuth(control, this.shapeRectangle.width, this.shapeRectangle.height); // Compute the shape's new azimuth as the difference between the great arc azimuth from the shape's // center position and the new corner position, and the corner's azimuth in shape local coordinates. Angle newShapeAzimuth = LatLon.greatCircleAzimuth(this.shapeCenterPosition, newPosition); newShapeAzimuth = newShapeAzimuth.subtract(controlAzimuth); // Set the shape's new orientation. this.shapeOrientation = computeNormalizedHeading(newShapeAzimuth); } protected void updateShapeSize(String control, Position newPosition) { if (this.measureShapeType.equals(SHAPE_ELLIPSE) || this.measureShapeType.equals(SHAPE_CIRCLE)) { // Compute azimuth and arc length which define the great arc spanning the shape's center position and the // control point's position. Angle refAzimiuth = this.computeControlPointAzimuth(control, 1d, 1d).add(this.shapeOrientation); Angle controlAzimuth = LatLon.greatCircleAzimuth(this.shapeCenterPosition, newPosition); Angle controlArcLength = LatLon.greatCircleDistance(this.shapeCenterPosition, newPosition); // Compute the arc length in meters of the great arc between the shape's center position and the control // point's position. Angle diffAngle = refAzimiuth.angularDistanceTo(controlAzimuth); double globeRadius = this.wwd.getModel().getGlobe().getRadiusAt(this.shapeCenterPosition); double arcLengthMeters = Math.abs(diffAngle.cos()) * Math.abs(controlArcLength.radians) * globeRadius; double widthMeters; double heightMeters; if (control.equals(EAST) || control.equals(WEST)) { widthMeters = 2d * arcLengthMeters; heightMeters = (this.shapeRectangle != null) ? this.shapeRectangle.getHeight() : widthMeters; if (this.measureShapeType.equals(SHAPE_CIRCLE)) { //noinspection SuspiciousNameCombination heightMeters = widthMeters; } // during shape creation else if (this.controller != null && this.controller.isActive()) { heightMeters = 0.6 * widthMeters; } } else // if (control.equals(NORTH) || control.equals(SOUTH)) { heightMeters = 2d * arcLengthMeters; widthMeters = (this.shapeRectangle != null) ? this.shapeRectangle.getWidth() : heightMeters; if (this.measureShapeType.equals(SHAPE_CIRCLE)) { //noinspection SuspiciousNameCombination widthMeters = heightMeters; } // during shape creation else if (this.controller != null && this.controller.isActive()) { widthMeters = 0.6 * heightMeters; } } if (widthMeters <= SHAPE_MIN_WIDTH_METERS) widthMeters = SHAPE_MIN_WIDTH_METERS; if (heightMeters <= SHAPE_MIN_HEIGHT_METERS) heightMeters = SHAPE_MIN_HEIGHT_METERS; this.shapeRectangle = new Rectangle2D.Double(0d, 0d, widthMeters, heightMeters); // Determine if the dragged control point crossed the shape's horizontal or vertical boundary, causing // the shape to "flip". If so, swap the control point with its horizontal or vertical opposite. This // ensures that control points have the correct orientation. this.swapEdgeControls(control, newPosition); } else if (this.measureShapeType.equals(SHAPE_QUAD) || this.measureShapeType.equals(SHAPE_SQUARE)) { ControlPoint oppositeControlPoint = this.getOppositeControl(control); // Compute the corner position diagonal from the current corner position, and compute the azimuth // and arc length which define a great arc between the two positions. Angle diagonalAzimuth = LatLon.greatCircleAzimuth(oppositeControlPoint.getPosition(), newPosition); Angle diagonalArcLength = LatLon.greatCircleDistance(oppositeControlPoint.getPosition(), newPosition); // Compute the shape's center location as the mid point on the great arc between the two diagonal // control points. LatLon newCenterLocation = LatLon.greatCircleEndPosition(oppositeControlPoint.getPosition(), diagonalAzimuth, diagonalArcLength.divide(2d)); // Compute the azimuth and arc length which define a great arc between the new center position and // the new corner position. Angle controlAzimuth = LatLon.greatCircleAzimuth(newCenterLocation, newPosition); controlAzimuth = this.computeControlPointAzimuthInShapeCoordinates(control, controlAzimuth); Angle controlArcLength = LatLon.greatCircleDistance(newCenterLocation, newPosition); // Compute the arc length in meters of the great arc between the new center position and the new // corner position. double globeRadius = this.wwd.getModel().getGlobe().getRadiusAt(newCenterLocation); double arcLengthMeters = controlArcLength.radians * globeRadius; // Compute shape's the width and height in meters from the diagonal between the shape's new center // position and its new corner position. double widthMeters = 2d * arcLengthMeters * Math.abs(controlAzimuth.sin()); double heightMeters = 2d * arcLengthMeters * Math.abs(controlAzimuth.cos()); if (widthMeters <= SHAPE_MIN_WIDTH_METERS) widthMeters = SHAPE_MIN_WIDTH_METERS; if (heightMeters <= SHAPE_MIN_HEIGHT_METERS) heightMeters = SHAPE_MIN_HEIGHT_METERS; if (this.measureShapeType.equals(SHAPE_SQUARE)) { // Force the square to have equivalent dimensions. double sizeMeters = Math.min(widthMeters, heightMeters); widthMeters = sizeMeters; heightMeters = sizeMeters; // Determine if the dragged control point crossed the shape's horizontal or vertical boundary, causing // the shape to "flip". If so, swap the control point with its horizontal or vertical opposite. This // ensures that control points have the correct orientation. We perform this operation prior to // adjusting the square's fixed corner location, because flipping the shape changes which corner we // adjust. this.swapCornerControls(control, newPosition); // Forcing the square to have equivalent width and height causes the opposite control point to move // from its current location. Move the square's opposite control point back to its original location // so that the square drags from a fixed corner out to the current control point. LatLon location = this.moveShapeByControlPoint(oppositeControlPoint, this.wwd.getModel().getGlobe(), this.shapeOrientation, newCenterLocation, widthMeters, heightMeters); if (location != null) newCenterLocation = location; } // Set the shape's new center position and new dimensions. this.shapeCenterPosition = new Position(newCenterLocation, 0d); this.shapeRectangle = new Rectangle2D.Double(0d, 0d, widthMeters, heightMeters); if (this.measureShapeType.equals(SHAPE_QUAD)) { // Determine if the dragged control point crossed the shape's horizontal or vertical boundary, causing // the shape to "flip". If so, swap the control point with its horizontal or vertical opposite. This // ensures that control points have the correct orientation. this.swapCornerControls(control, newPosition); } } } public ControlPoint getControlPoint(String control) { for (Renderable cp : this.controlPoints) { String value = ((ControlPoint) cp).getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (value != null && value.equals(control)) return (ControlPoint) cp; } return null; } protected String computeCornerControl(Position position) { if (this.shapeCenterPosition == null || this.shapeOrientation == null) return null; Angle azimuth = LatLon.greatCircleAzimuth(this.shapeCenterPosition, position).subtract(this.shapeOrientation); azimuth = computeNormalizedHeading(azimuth); if (azimuth.degrees < 90) return NORTHEAST; else if (azimuth.degrees < 180) return SOUTHEAST; else if (azimuth.degrees < 270) return SOUTHWEST; else return NORTHWEST; } protected ControlPoint getOppositeControl(String control) { if (this.controlPoints.size() == 0) return null; else if (this.controlPoints.size() == 1) return getControlPoint(CENTER); else if (control.equals(NORTH)) return getControlPoint(SOUTH); else if (control.equals(EAST)) return getControlPoint(WEST); else if (control.equals(SOUTH)) return getControlPoint(NORTH); else if (control.equals(WEST)) return getControlPoint(EAST); else if (control.equals(NORTHEAST)) return getControlPoint(SOUTHWEST); else if (control.equals(SOUTHEAST)) return getControlPoint(NORTHWEST); else if (control.equals(SOUTHWEST)) return getControlPoint(NORTHEAST); else if (control.equals(NORTHWEST)) return getControlPoint(SOUTHEAST); return null; } protected LatLon moveShapeByControlPoint(ControlPoint controlPoint, Globe globe, Angle heading, LatLon center, double width, double height) { double globeRadius = globe.getRadiusAt(center); String control = controlPoint.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (control == null) return center; LatLon newCenterLocation = center; // Iteratively move a location on the shape defined by heading, center, width, and height to the specified // control point's location. Because shapes are defined by an azimuth, center point, and real world dimensions, // we cannot assume that moving the shape's center point moves any of its corners or edges by exactly that // amount. However, the center and corners should move in a roughly similar manner, so we can iteratively move // a corner until it converges at the desired location. for (int i = 0; i < MAX_SHAPE_MOVE_ITERATIONS; i++) { // Compute the control point's corresponding location on the shape. LatLon shapeControlLocation = this.computeControlPointLocation(control, globe, heading, newCenterLocation, width, height); // Compute a great arc spanning the control point's location, and its corresponding location on the shape. Angle azimuth = LatLon.greatCircleAzimuth(shapeControlLocation, controlPoint.getPosition()); Angle pathLength = LatLon.greatCircleDistance(shapeControlLocation, controlPoint.getPosition()); // If the great circle distance between the control point's location and its corresponding location on the // shape is less than a predefined value, then we're done. double pathLengthMeters = pathLength.radians * globeRadius; if (pathLengthMeters < SHAPE_CONTROL_EPSILON_METERS) break; // Move the center to a new location on the great arc starting at the current center location, and with // azimuth and arc length equal to the arc spanning the corner location and the control point's location. newCenterLocation = LatLon.greatCircleEndPosition(newCenterLocation, azimuth, pathLength); } return newCenterLocation; } protected void swapEdgeControls(String control, Position position) { if (this.controlPoints.size() < 2) return; if (this.shapeCenterPosition == null || this.shapeOrientation == null) return; Angle azimuth = LatLon.greatCircleAzimuth(this.shapeCenterPosition, position).subtract(this.shapeOrientation); azimuth = computeNormalizedHeading(azimuth); if ((control.equals(NORTH) && azimuth.degrees < 270 && azimuth.degrees > 90) || (control.equals(SOUTH) && (azimuth.degrees > 270 || azimuth.degrees < 90))) { for (Renderable r : this.controlPoints) { ControlPoint cp = (ControlPoint) r; String c = cp.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (c == null) continue; if (c.equals(NORTH)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, SOUTH); else if (c.equals(SOUTH)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, NORTH); } } if ((control.equals(EAST) && (azimuth.degrees < 360 && azimuth.degrees > 180)) || (control.equals(WEST) && (azimuth.degrees < 180 && azimuth.degrees > 0))) { for (Renderable r : this.controlPoints) { ControlPoint cp = (ControlPoint) r; String c = cp.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (c == null) continue; if (c.equals(EAST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, WEST); else if (c.equals(WEST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, EAST); } } } protected void swapCornerControls(String control, Position position) { if (this.controlPoints.size() < 2) return; String newControl = this.computeCornerControl(position); if (control.equals(newControl)) return; // no need to swap // For corner controls NE, SE, SW, NW if (control.length() != 2 || newControl.length() != 2) return; if (control.charAt(0) != newControl.charAt(0)) { for (Renderable r : this.controlPoints) { ControlPoint cp = (ControlPoint) r; String c = cp.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (c == null) continue; if (c.equals(NORTHEAST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, SOUTHEAST); else if (c.equals(SOUTHEAST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, NORTHEAST); else if (c.equals(SOUTHWEST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, NORTHWEST); else if (c.equals(NORTHWEST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, SOUTHWEST); } } if (control.charAt(1) != newControl.charAt(1)) { for (Renderable r : this.controlPoints) { ControlPoint cp = (ControlPoint) r; String c = cp.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (c == null) continue; if (c.equals(NORTHEAST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, NORTHWEST); else if (c.equals(SOUTHEAST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, SOUTHWEST); else if (c.equals(SOUTHWEST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, SOUTHEAST); else if (c.equals(NORTHWEST)) cp.setValue(CONTROL_TYPE_REGULAR_SHAPE, NORTHEAST); } } } protected LatLon computeControlPointLocation(String control, Globe globe, Angle heading, LatLon center, double width, double height) { Angle azimuth = this.computeControlPointAzimuth(control, width, height); Angle pathLength = this.computeControlPointPathLength(control, width, height, globe.getRadiusAt(center)); if (control.equals(CENTER)) { return center; } else if (azimuth != null && pathLength != null) { azimuth = azimuth.add(heading); return LatLon.greatCircleEndPosition(center, azimuth, pathLength); } return null; } protected LatLon computeQuadEdgeMidpointLocation(String control, Globe globe, Angle heading, LatLon center, double width, double height) { LatLon ne = this.computeControlPointLocation(NORTHEAST, globe, heading, center, width, height); LatLon se = this.computeControlPointLocation(SOUTHEAST, globe, heading, center, width, height); LatLon sw = this.computeControlPointLocation(SOUTHWEST, globe, heading, center, width, height); LatLon nw = this.computeControlPointLocation(NORTHWEST, globe, heading, center, width, height); if (control.equals(NORTH)) return LatLon.interpolate(this.pathType, 0.5, nw, ne); else if (control.equals(EAST)) return LatLon.interpolate(this.pathType, 0.5, ne, se); else if (control.equals(SOUTH)) return LatLon.interpolate(this.pathType, 0.5, sw, se); else if (control.equals(WEST)) return LatLon.interpolate(this.pathType, 0.5, sw, nw); return null; } @SuppressWarnings({"SuspiciousNameCombination"}) protected Angle computeControlPointAzimuth(String control, double width, double height) { Angle azimuth = null; if (control.equals(NORTH)) azimuth = Angle.ZERO; else if (control.equals(EAST)) azimuth = Angle.POS90; else if (control.equals(SOUTH)) azimuth = Angle.POS180; else if (control.equals(WEST)) azimuth = Angle.fromDegrees(270); else if (control.equals(NORTHEAST)) azimuth = Angle.fromRadians(Math.atan2(width, height)); else if (control.equals(SOUTHEAST)) azimuth = Angle.fromRadians(Math.atan2(width, -height)); else if (control.equals(SOUTHWEST)) azimuth = Angle.fromRadians(Math.atan2(-width, -height)); else if (control.equals(NORTHWEST)) azimuth = Angle.fromRadians(Math.atan2(-width, height)); else if (control.equals(NORTH_LEADER)) azimuth = Angle.ZERO; return azimuth != null ? computeNormalizedHeading(azimuth) : null; } protected Angle computeControlPointAzimuthInShapeCoordinates(String control, Angle azimuth) { if (control.equals(NORTHEAST)) return azimuth.subtract(this.shapeOrientation); else if (control.equals(SOUTHEAST)) return this.shapeOrientation.addDegrees(180).subtract(azimuth); else if (control.equals(SOUTHWEST)) return azimuth.subtract(this.shapeOrientation.addDegrees(180)); else if (control.equals(NORTHWEST)) return this.shapeOrientation.subtract(azimuth); return null; } protected Angle computeControlPointPathLength(String control, double width, double height, double globeRadius) { Angle pathLength = null; if (control.equals(NORTH) || control.equals(SOUTH)) { pathLength = Angle.fromRadians((height / 2d) / globeRadius); } else if (control.equals(EAST) || control.equals(WEST)) { pathLength = Angle.fromRadians((width / 2d) / globeRadius); } else if (control.equals(NORTHEAST) || control.equals(SOUTHEAST) || control.equals(SOUTHWEST) || control.equals(NORTHWEST)) { double diag = Math.sqrt((width * width) / 4d + (height * height) / 4d); pathLength = Angle.fromRadians(diag / globeRadius); } else if (control.equals(NORTH_LEADER)) { pathLength = Angle.fromRadians(3d / 4d * height / globeRadius); } return pathLength; } protected static Angle computeNormalizedHeading(Angle heading) { double a = heading.degrees % 360; double degrees = a > 360 ? a - 360 : a < 0 ? 360 + a : a; return Angle.fromDegrees(degrees); } protected void updateShapeControlPoints() { if (this.shapeCenterPosition != null) { // Set center control point if (this.controlPoints.size() < 1) addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, CENTER); // Update center control point position ((ControlPoint) this.controlPoints.get(0)).setPosition(new Position(this.shapeCenterPosition, 0)); } if (this.shapeRectangle != null) { if (this.controlPoints.size() < 5) { // Add control points in four directions - CW from north if (this.measureShapeType.equals(SHAPE_ELLIPSE) || this.measureShapeType.equals(SHAPE_CIRCLE)) { this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, NORTH); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, EAST); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, SOUTH); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, WEST); } // Add control points at four corners - CW from north else if (this.measureShapeType.equals(SHAPE_QUAD) || this.measureShapeType.equals(SHAPE_SQUARE)) { this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, NORTHEAST); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, SOUTHEAST); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, SOUTHWEST); this.addControlPoint(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, NORTHWEST); } // Add a control point with a leader to the top of the shape. this.addControlPointWithLeader(Position.ZERO, CONTROL_TYPE_REGULAR_SHAPE, NORTH_LEADER, CONTROL_TYPE_LEADER_ORIGIN, NORTH); } Globe globe = this.getWwd().getModel().getGlobe(); // Update control points positions for (Renderable r : this.controlPoints) { ControlPoint cp = (ControlPoint) r; String control = cp.getStringValue(CONTROL_TYPE_REGULAR_SHAPE); if (control == null) continue; LatLon controlLocation = this.computeControlPointLocation(control, globe, this.shapeOrientation, this.shapeCenterPosition, this.shapeRectangle.getWidth(), this.shapeRectangle.getHeight()); if (controlLocation == null) continue; cp.setPosition(new Position(controlLocation, 0d)); if (cp instanceof ControlPointWithLeader) { this.updateControlPointWithLeader((ControlPointWithLeader) cp, controlLocation); } } } } protected void updateControlPointWithLeader(ControlPointWithLeader cp, LatLon controlLocation) { Globe globe = this.getWwd().getModel().getGlobe(); String leaderControl = cp.getStringValue(CONTROL_TYPE_LEADER_ORIGIN); if (leaderControl == null) return; LatLon leaderBegin; if (this.measureShapeType.equals(SHAPE_QUAD) || this.measureShapeType.equals(SHAPE_SQUARE)) { leaderBegin = this.computeQuadEdgeMidpointLocation(leaderControl, globe, this.shapeOrientation, this.shapeCenterPosition, this.shapeRectangle.getWidth(), this.shapeRectangle.getHeight()); } else { leaderBegin = this.computeControlPointLocation(leaderControl, globe, this.shapeOrientation, this.shapeCenterPosition, this.shapeRectangle.getWidth(), this.shapeRectangle.getHeight()); } if (leaderBegin == null) return; cp.setLeaderLocations(leaderBegin, controlLocation); } protected void updateMeasureShape() { // Update line if (this.measureShapeType.equals(SHAPE_LINE) || this.measureShapeType.equals(SHAPE_PATH)) { if (this.positions.size() > 1 && this.line == null) { // Init polyline this.line = new Polyline(); this.line.setFollowTerrain(this.isFollowTerrain()); this.line.setLineWidth(this.getLineWidth()); this.line.setColor(this.getLineColor()); this.line.setPathType(polylinePathTypeFromKey(this.getPathType())); //this.line.setNumSubsegments(this.followTerrain ? 10 : 1); this.shapeLayer.addRenderable(this.line); } if (this.positions.size() < 2 && this.line != null) { // Remove line if less then 2 positions this.shapeLayer.removeRenderable(this.line); this.line = null; } // Update current line if (this.positions.size() > 1 && this.line != null) this.line.setPositions(this.positions); if (this.surfaceShape != null) { // Remove surface shape if necessary this.shapeLayer.removeRenderable(this.surfaceShape); this.surfaceShape = null; } } // Update polygon else if (this.measureShapeType.equals(SHAPE_POLYGON)) { if (this.positions.size() >= 4 && this.surfaceShape == null) { // Init surface shape this.surfaceShape = new SurfacePolygon(this.positions); ShapeAttributes attr = new BasicShapeAttributes(); attr.setInteriorMaterial(new Material(this.getFillColor())); attr.setInteriorOpacity(this.getFillColor().getAlpha() / 255d); attr.setOutlineMaterial(new Material(this.getLineColor())); attr.setOutlineOpacity(this.getLineColor().getAlpha() / 255d); attr.setOutlineWidth(this.getLineWidth()); this.surfaceShape.setAttributes(attr); this.shapeLayer.addRenderable(this.surfaceShape); } if (this.positions.size() <= 3 && this.surfaceShape != null) { // Remove surface shape if only three positions or less - last is same as first this.shapeLayer.removeRenderable(this.surfaceShape); this.surfaceShape = null; } if (this.surfaceShape != null) { // Update current shape ((SurfacePolygon) this.surfaceShape).setLocations(this.positions); } // Remove line if necessary if (this.line != null) { this.shapeLayer.removeRenderable(this.line); this.line = null; } } // Update regular shape else if (this.isRegularShape()) { if (this.shapeCenterPosition != null && this.shapeRectangle != null && this.surfaceShape == null) { // Init surface shape if (this.measureShapeType.equals(SHAPE_QUAD)) this.surfaceShape = new SurfaceQuad(this.shapeCenterPosition, this.shapeRectangle.width, this.shapeRectangle.height, this.shapeOrientation); else if (this.measureShapeType.equals(SHAPE_SQUARE)) this.surfaceShape = new SurfaceSquare(this.shapeCenterPosition, this.shapeRectangle.width); else if (this.measureShapeType.equals(SHAPE_ELLIPSE)) this.surfaceShape = new SurfaceEllipse(this.shapeCenterPosition, this.shapeRectangle.width / 2, this.shapeRectangle.height / 2, this.shapeOrientation, this.shapeIntervals); else if (this.measureShapeType.equals(SHAPE_CIRCLE)) this.surfaceShape = new SurfaceCircle(this.shapeCenterPosition, this.shapeRectangle.width / 2, this.shapeIntervals); ShapeAttributes attr = new BasicShapeAttributes(); attr.setInteriorMaterial(new Material(this.getFillColor())); attr.setInteriorOpacity(this.getFillColor().getAlpha() / 255d); attr.setOutlineMaterial(new Material(this.getLineColor())); attr.setOutlineOpacity(this.getLineColor().getAlpha() / 255d); attr.setOutlineWidth(this.getLineWidth()); this.surfaceShape.setAttributes(attr); this.shapeLayer.addRenderable(this.surfaceShape); } if (this.shapeRectangle == null && this.surfaceShape != null) { // Remove surface shape if not defined this.shapeLayer.removeRenderable(this.surfaceShape); this.surfaceShape = null; this.positions.clear(); } if (this.surfaceShape != null) { // Update current shape if (this.measureShapeType.equals(SHAPE_QUAD) || this.measureShapeType.equals(SHAPE_SQUARE)) { ((SurfaceQuad) this.surfaceShape).setCenter(this.shapeCenterPosition); ((SurfaceQuad) this.surfaceShape).setSize(this.shapeRectangle.width, this.shapeRectangle.height); ((SurfaceQuad) this.surfaceShape).setHeading(this.shapeOrientation); } if (this.measureShapeType.equals(SHAPE_ELLIPSE) || this.measureShapeType.equals(SHAPE_CIRCLE)) { ((SurfaceEllipse) this.surfaceShape).setCenter(this.shapeCenterPosition); ((SurfaceEllipse) this.surfaceShape).setRadii(this.shapeRectangle.width / 2, this.shapeRectangle.height / 2); ((SurfaceEllipse) this.surfaceShape).setHeading(this.shapeOrientation); } // Update position from shape list with zero elevation updatePositionsFromShape(); } // Remove line if necessary if (this.line != null) { this.shapeLayer.removeRenderable(this.line); this.line = null; } } } protected void updatePositionsFromShape() { Globe globe = this.wwd.getModel().getGlobe(); this.positions.clear(); Iterable locations = this.surfaceShape.getLocations(globe); if (locations != null) { for (LatLon latLon : locations) { this.positions.add(new Position(latLon, 0)); } } } public void dispose() { this.setController(null); if (this.applicationLayer != null) this.applicationLayer.removeRenderable(this.layer); else this.wwd.getModel().getLayers().remove(this.layer); this.layer.removeAllRenderables(); this.shapeLayer.removeAllRenderables(); this.controlPoints.clear(); // this.controlPointsLayer.removeAllRenderables(); // TODO: why commented out? Are annotations being disposed? } // *** Control points *** public static class ControlPoint extends GlobeAnnotation { MeasureTool parent; public ControlPoint(Position position, AnnotationAttributes attributes, MeasureTool parent) { super("", position, attributes); this.parent = parent; } public MeasureTool getParent() { return this.parent; } } protected static class ControlPointWithLeader extends ControlPoint implements PreRenderable { protected SurfacePolyline leaderLine; public ControlPointWithLeader(Position position, AnnotationAttributes controlPointAttributes, ShapeAttributes leaderAttributes, MeasureTool parent) { super(position, controlPointAttributes, parent); this.leaderLine = new SurfacePolyline(leaderAttributes); } public void preRender(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.leaderLine != null) this.leaderLine.preRender(dc); } @Override public void render(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.leaderLine != null) this.leaderLine.render(dc); super.render(dc); } public void setLeaderLocations(LatLon begin, LatLon end) { if (begin == null) { String message = Logging.getMessage("nullValue.BeginIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (end == null) { String message = Logging.getMessage("nullValue.EndIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.leaderLine.setLocations(Arrays.asList(begin, end)); } } protected void addControlPoint(Position position, String key, Object value) { ControlPoint controlPoint = new ControlPoint(new Position(position, 0), this.controlPointsAttributes, this); controlPoint.setValue(key, value); this.doAddControlPoint(controlPoint); } protected void addControlPointWithLeader(Position position, String controlKey, Object control, String leaderKey, Object leader) { ControlPointWithLeader controlPoint = new ControlPointWithLeader(new Position(position, 0), this.controlPointWithLeaderAttributes, this.leaderAttributes, this); controlPoint.setValue(controlKey, control); controlPoint.setValue(leaderKey, leader); this.doAddControlPoint(controlPoint); } protected void doAddControlPoint(ControlPoint controlPoint) { this.controlPoints.add(controlPoint); this.controlPointsLayer.setRenderables(this.controlPoints); } public void updateAnnotation(Position pos) { if (pos == null) { this.annotation.getAttributes().setVisible(false); return; } String displayString = this.getDisplayString(pos); if (displayString == null) { this.annotation.getAttributes().setVisible(false); return; } this.annotation.setText(displayString); this.annotation.setPosition(pos); this.annotation.getAttributes().setVisible(true); } protected String getDisplayString(Position pos) { String displayString = null; if (pos != null) { if (this.measureShapeType.equals(SHAPE_CIRCLE) && this.shapeRectangle != null) { displayString = this.formatCircleMeasurements(pos); } else if (this.measureShapeType.equals(SHAPE_SQUARE) && this.shapeRectangle != null) { displayString = this.formatSquareMeasurements(pos); } else if (this.measureShapeType.equals(SHAPE_QUAD) && this.shapeRectangle != null) { displayString = this.formatQuadMeasurements(pos); } else if (this.measureShapeType.equals(SHAPE_ELLIPSE) && this.shapeRectangle != null) { displayString = this.formatEllipseMeasurements(pos); } else if (this.measureShapeType.equals(SHAPE_LINE) || this.measureShapeType.equals(SHAPE_PATH)) { displayString = this.formatLineMeasurements(pos); } else if (this.measureShapeType.equals(SHAPE_POLYGON)) { displayString = this.formatPolygonMeasurements(pos); } } return displayString; } protected String formatCircleMeasurements(Position pos) { StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.areaNL(this.getLabel(AREA_LABEL), this.getArea())); sb.append(this.unitsFormat.lengthNL(this.getLabel(PERIMETER_LABEL), this.getLength())); if (this.shapeRectangle != null) sb.append(this.unitsFormat.lengthNL(this.getLabel(RADIUS_LABEL), this.shapeRectangle.width / 2d)); if (this.getCenterPosition() != null && areLocationsRedundant(this.getCenterPosition(), pos)) { sb.append( this.unitsFormat.angleNL(this.getLabel(CENTER_LATITUDE_LABEL), this.getCenterPosition().getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL), this.getCenterPosition().getLongitude())); } if (!this.areLocationsRedundant(pos, this.getCenterPosition())) { sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); } return sb.toString(); } protected String formatEllipseMeasurements(Position pos) { StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.areaNL(this.getLabel(AREA_LABEL), this.getArea())); sb.append(this.unitsFormat.lengthNL(this.getLabel(PERIMETER_LABEL), this.getLength())); if (this.shapeRectangle != null) { sb.append(this.unitsFormat.lengthNL(this.getLabel(MAJOR_AXIS_LABEL), this.shapeRectangle.width)); sb.append(this.unitsFormat.lengthNL(this.getLabel(MINOR_AXIS_LABEL), this.shapeRectangle.height)); } if (this.getOrientation() != null) sb.append(this.unitsFormat.angleNL(this.getLabel(HEADING_LABEL), this.getOrientation())); if (this.getCenterPosition() != null && areLocationsRedundant(this.getCenterPosition(), pos)) { sb.append( this.unitsFormat.angleNL(this.getLabel(CENTER_LATITUDE_LABEL), this.getCenterPosition().getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL), this.getCenterPosition().getLongitude())); } if (!this.areLocationsRedundant(pos, this.getCenterPosition())) { sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); } return sb.toString(); } protected String formatSquareMeasurements(Position pos) { StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.areaNL(this.getLabel(AREA_LABEL), this.getArea())); sb.append(this.unitsFormat.lengthNL(this.getLabel(PERIMETER_LABEL), this.getLength())); if (this.shapeRectangle != null) sb.append(this.unitsFormat.lengthNL(this.getLabel(WIDTH_LABEL), this.shapeRectangle.width)); if (this.getOrientation() != null) sb.append(this.unitsFormat.angleNL(this.getLabel(HEADING_LABEL), this.getOrientation())); if (this.getCenterPosition() != null && areLocationsRedundant(this.getCenterPosition(), pos)) { sb.append( this.unitsFormat.angleNL(this.getLabel(CENTER_LATITUDE_LABEL), this.getCenterPosition().getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL), this.getCenterPosition().getLongitude())); } if (!this.areLocationsRedundant(pos, this.getCenterPosition())) { sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); } return sb.toString(); } protected String formatQuadMeasurements(Position pos) { StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.areaNL(this.getLabel(AREA_LABEL), this.getArea())); sb.append(this.unitsFormat.lengthNL(this.getLabel(PERIMETER_LABEL), this.getLength())); if (this.shapeRectangle != null) { sb.append(this.unitsFormat.lengthNL(this.getLabel(WIDTH_LABEL), this.shapeRectangle.width)); sb.append(this.unitsFormat.lengthNL(this.getLabel(HEIGHT_LABEL), this.shapeRectangle.height)); } if (this.getOrientation() != null) sb.append(this.unitsFormat.angleNL(this.getLabel(HEADING_LABEL), this.getOrientation())); if (this.getCenterPosition() != null && areLocationsRedundant(this.getCenterPosition(), pos)) { sb.append( this.unitsFormat.angleNL(this.getLabel(CENTER_LATITUDE_LABEL), this.getCenterPosition().getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL), this.getCenterPosition().getLongitude())); } if (!this.areLocationsRedundant(pos, this.getCenterPosition())) { sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); } return sb.toString(); } protected String formatPolygonMeasurements(Position pos) { StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.areaNL(this.getLabel(AREA_LABEL), this.getArea())); sb.append(this.unitsFormat.lengthNL(this.getLabel(PERIMETER_LABEL), this.getLength())); if (this.getCenterPosition() != null && areLocationsRedundant(this.getCenterPosition(), pos)) { sb.append( this.unitsFormat.angleNL(this.getLabel(CENTER_LATITUDE_LABEL), this.getCenterPosition().getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL), this.getCenterPosition().getLongitude())); } if (!this.areLocationsRedundant(pos, this.getCenterPosition())) { sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); } return sb.toString(); } protected String formatLineMeasurements(Position pos) { // TODO: Compute the heading of individual path segments StringBuilder sb = new StringBuilder(); sb.append(this.unitsFormat.lengthNL(this.getLabel(LENGTH_LABEL), this.getLength())); Double accumLength = this.computeAccumulatedLength(pos); if (accumLength != null && accumLength >= 1 && !lengthsEssentiallyEqual(this.getLength(), accumLength)) sb.append(this.unitsFormat.lengthNL(this.getLabel(ACCUMULATED_LABEL), accumLength)); if (this.getOrientation() != null) sb.append(this.unitsFormat.angleNL(this.getLabel(HEADING_LABEL), this.getOrientation())); sb.append(this.unitsFormat.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude())); sb.append(this.unitsFormat.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude())); return sb.toString(); } protected Double computeAccumulatedLength(LatLon pos) { if (this.positions.size() <= 2) return null; double radius = this.wwd.getModel().getGlobe().getRadius(); double distanceFromStart = 0; int segmentIndex = 0; LatLon pos1 = this.positions.get(segmentIndex); for (int i = 1; i < this.positions.size(); i++) { LatLon pos2 = this.positions.get(i); double segmentLength = LatLon.greatCircleDistance(pos1, pos2).radians * radius; // Check whether the position is inside the segment double length1 = LatLon.greatCircleDistance(pos1, pos).radians * radius; double length2 = LatLon.greatCircleDistance(pos2, pos).radians * radius; if (length1 <= segmentLength && length2 <= segmentLength) { // Compute portion of segment length distanceFromStart += length1 / (length1 + length2) * segmentLength; break; } else distanceFromStart += segmentLength; pos1 = pos2; } double gcPathLength = this.computePathLength(); return distanceFromStart < gcPathLength ? this.getLength() * (distanceFromStart / gcPathLength) : null; } protected double computePathLength() { double pathLengthRadians = 0; LatLon pos1 = null; for (LatLon pos2 : this.positions) { if (pos1 != null) pathLengthRadians += LatLon.greatCircleDistance(pos1, pos2).radians; pos1 = pos2; } return pathLengthRadians * this.wwd.getModel().getGlobe().getRadius(); } protected Angle computeAngleBetween(LatLon a, LatLon b, LatLon c) { Vec4 v0 = new Vec4( b.getLatitude().radians - a.getLatitude().radians, b.getLongitude().radians - a.getLongitude().radians, 0); Vec4 v1 = new Vec4( c.getLatitude().radians - b.getLatitude().radians, c.getLongitude().radians - b.getLongitude().radians, 0); return v0.angleBetween3(v1); } protected boolean lengthsEssentiallyEqual(Double l1, Double l2) { return Math.abs(l1 - l2) / l1 < 0.001; // equal to within a milimeter } protected boolean areLocationsRedundant(LatLon locA, LatLon locB) { if (locA == null || locB == null) return false; String aLat = this.unitsFormat.angleNL("", locA.getLatitude()); String bLat = this.unitsFormat.angleNL("", locB.getLatitude()); if (!aLat.equals(bLat)) return false; String aLon = this.unitsFormat.angleNL("", locA.getLongitude()); String bLon = this.unitsFormat.angleNL("", locB.getLongitude()); return aLon.equals(bLon); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy