/*
* Copyright (C) 2011-2013 GeoForge Project
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package _org.geoforge.amd.wwwx.examples.measuretool;
import gov.nasa.worldwind.Disposable;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.AnnotationAttributes;
import gov.nasa.worldwind.render.BasicShapeAttributes;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.render.Material;
import gov.nasa.worldwind.render.Polyline;
import gov.nasa.worldwind.render.PreRenderable;
import gov.nasa.worldwind.render.Renderable;
import gov.nasa.worldwind.render.ScreenAnnotation;
import gov.nasa.worldwind.render.ShapeAttributes;
import gov.nasa.worldwind.render.SurfaceShape;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.UnitsFormat;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author Amadeus.Sowerby
*
* email: Amadeus.Sowerby_AT_gmail.com ... please remove "_AT_" from the above
* string to get the right email address
*/
final public class GfrMeasureTool extends AVListImpl implements Disposable
{
public static final String SHAPE_LINE = "GfrMeasureTool.ShapeLine";
public static final String SHAPE_PATH = "GfrMeasureTool.ShapePath";
public static final String EVENT_POSITION_ADD = "GfrMeasureTool.AddPosition";
public static final String EVENT_POSITION_REMOVE = "GfrMeasureTool.RemovePosition";
public static final String EVENT_POSITION_REPLACE = "GfrMeasureTool.ReplacePosition";
public static final String EVENT_METRIC_CHANGED = "GfrMeasureTool.MetricChanged";
public static final String EVENT_ARMED = "GfrMeasureTool.Armed";
public static final String EVENT_RUBBERBAND_START = "GfrMeasureTool.RubberBandStart";
public static final String EVENT_RUBBERBAND_STOP = "GfrMeasureTool.RubberBandStop";
public static final String ANGLE_LABEL = "GfrMeasureTool.AngleLabel";
public static final String AREA_LABEL = "GfrMeasureTool.AreaLabel";
public static final String LENGTH_LABEL = "GfrMeasureTool.LengthLabel";
public static final String PERIMETER_LABEL = "GfrMeasureTool.PerimeterLabel";
public static final String RADIUS_LABEL = "GfrMeasureTool.RadiusLabel";
public static final String HEIGHT_LABEL = "GfrMeasureTool.HeightLabel";
public static final String WIDTH_LABEL = "GfrMeasureTool.WidthLabel";
public static final String HEADING_LABEL = "GfrMeasureTool.HeadingLabel";
public static final String CENTER_LATITUDE_LABEL = "GfrMeasureTool.CenterLatitudeLabel";
public static final String CENTER_LONGITUDE_LABEL = "GfrMeasureTool.CenterLongitudeLabel";
public static final String LATITUDE_LABEL = "GfrMeasureTool.LatitudeLabel";
public static final String LONGITUDE_LABEL = "GfrMeasureTool.LongitudeLabel";
public static final String ACCUMULATED_LABEL = "GfrMeasureTool.AccumulatedLabel";
public static final String MAJOR_AXIS_LABEL = "GfrMeasureTool.MajorAxisLabel";
public static final String MINOR_AXIS_LABEL = "GfrMeasureTool.MinorAxisLabel";
public static final String CONTROL_TYPE_LOCATION_INDEX = "GfrMeasureTool.ControlTypeLocationIndex";
public static final String CONTROL_TYPE_REGULAR_SHAPE = "GfrMeasureTool.ControlTypeRegularShape";
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";
protected final WorldWindow wwd;
protected GfrMeasureToolController controller;
protected ArrayList<Position> positions = new ArrayList<Position>();
protected ArrayList<Renderable> controlPoints = new ArrayList<Renderable>();
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 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 showAnnotation = true;
protected UnitsFormat unitsFormat = new UnitsFormat();
// Rectangle enclosed regular shapes attributes
protected Angle shapeOrientation = null;
/**
* Construct a new measure tool drawing events from the specified <code>WorldWindow</code>.
*
* @param wwd the <code>WorldWindow</code> to draw events from.
*/
public GfrMeasureTool(final WorldWindow wwd)
{
this(wwd, null);
}
/**
* Construct a new measure tool drawing events from the specified <code>WorldWindow</code> and using the given
* <code>RenderableLayer</code>.
*
* @param wwd the <code>WorldWindow</code> to draw events from.
* @param applicationLayer the <code>RenderableLayer</code> to use. May be null. If specified, the caller is
* responsible for adding the layer to the model and enabling it.
*/
public GfrMeasureTool(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(true);
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.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);
}
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(GfrMeasureToolController 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 <code>GfrMeasureToolController</code> for this measure tool.
*
* @return the <code>GfrMeasureToolController</code> for this measure tool.
*/
public GfrMeasureToolController 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 applilcation 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;
}
/**
* Get the list of positions that define the current measure shape.
*
* @return the list of positions that define the current measure shape.
*/
public ArrayList<? extends Position> 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<? extends Position> 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<Renderable> 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 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);
this.wwd.redraw();
}
@SuppressWarnings(
{
"StringEquality"
})
protected static int polylinePathTypeFromKey(String type)
{
if (type != null && type.equals(AVKey.GREAT_CIRCLE))
{
return Polyline.GREAT_CIRCLE;
}
else if (type != null && (type.equals(AVKey.RHUMB_LINE) || type.equals(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 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.shapeOrientation = 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 the measure shape to an existing <code>Polyline</code>.
*
* @param line a <code>Polyline</code> 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
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<? extends Position> positions)
{
return positions.size() > 2 ? SHAPE_PATH : SHAPE_LINE;
}
// *** 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 Angle getOrientation()
{
return this.shapeOrientation;
}
// *** Editing shapes ***
/** Add a control point to the current measure shape at the cuurrent WorldWindow position. */
public void addControlPoint()
{
Position curPos = this.wwd.getCurrentPosition();
if (curPos == null)
return;
// 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_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));
}
// Update screen shapes
updateMeasureShape();
this.firePropertyChange(EVENT_POSITION_ADD, null, curPos);
this.wwd.redraw();
}
/** Remove the last control point from the current measure shape. */
public void removeControlPoint()
{
Position currentLastPosition = null;
if (this.positions.isEmpty())
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 (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_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 <code>Angle</code> for a given
* distance <code>Angle</code>.
*
* @param azimuth the azimuth <code>Angle</code>.
* @param distance the distance <code>Angle</code>.
*/
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())//always false
{
// 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();
}
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 ControlPoint getOppositeControl(String control)
{
if (this.controlPoints.isEmpty())
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 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;
}
@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));
return azimuth != null ? computeNormalizedHeading(azimuth) : 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);
}
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 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(true);
this.line.setLineWidth(this.getLineWidth());
this.line.setColor(this.getLineColor());
this.line.setPathType(polylinePathTypeFromKey(this.getPathType()));
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
}
@Override
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?
}
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 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);
Vec4 screenPoint = this.computeAnnotationPosition(pos);
if (screenPoint != null)
this.annotation.setScreenPoint(new Point((int) screenPoint.x, (int) screenPoint.y));
this.annotation.getAttributes().setVisible(true);
}
protected String getDisplayString(Position pos)
{
String displayString = null;
if (pos != null)
{
if (this.measureShapeType.equals(SHAPE_LINE) || this.measureShapeType.equals(SHAPE_PATH))
{
displayString = this.formatLineMeasurements(pos);
}
}
return displayString;
}
protected Vec4 computeAnnotationPosition(Position pos)
{
Vec4 surfacePoint = this.wwd.getSceneController().getTerrain().getSurfacePoint(
pos.getLatitude(), pos.getLongitude());
if (surfacePoint == null)
{
Globe globe = this.wwd.getModel().getGlobe();
surfacePoint = globe.computePointFromPosition(pos.getLatitude(), pos.getLongitude(),
globe.getElevation(pos.getLatitude(), pos.getLongitude()));
}
return this.wwd.getView().project(surfacePoint);
}
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 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);
}
}