package org.geoforge.worldwind.builder.editor;
/* Copyright (C) 2001, 2011 United States Government as represented by
the Administrator of the National Aeronautics and Space Administration.
All Rights Reserved.
*/
import gov.nasa.worldwind.View;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.WorldWindow;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.LatLon;
import gov.nasa.worldwind.geom.Line;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.layers.AbstractLayer;
import gov.nasa.worldwind.pick.PickedObjectList;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.render.airspaces.editor.AirspaceEditorUtil;
import gov.nasa.worldwind.render.markers.BasicMarkerAttributes;
import gov.nasa.worldwind.render.markers.Marker;
import gov.nasa.worldwind.render.markers.MarkerRenderer;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.UnitsFormat;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import org.geoforge.java.awt.font.GfrUtilFont;
import org.geoforge.lang.handler.IGfrHandlerLifeCycleObject;
import org.geoforge.worldwind.builder.marker.GfrMrkMoveAbs;
import org.geoforge.worldwind.builder.marker.GfrMrkMoveHoriz;
import org.geoforge.worldwind.builder.marker.GfrMrkMoveVert;
/**
* An abstract class defining common functionality and fields for editors used in the RigidShapeBuilder example. These
* include field variables and getters and setters for the shape's annotations that are displayed during editing (and
* related labels), references to the current WorldWindow and mouse location, flags indicating whether the editor is
* currently _blnArmed and whether annotations should be shown, as well as fields indicating the current action being
* performed, the current _strModeEdit, and the current _intModeAltitude.
* <p/>
* In addition, the class contains several helper functions related to displaying annotations, which all editors should
* be able to do.
*
* @author ccrick
* @version $Id: AbstractShapeEditor.java 1 2011-07-16 23:22:47Z dcollins $
*
* modified: bantchao
*/
abstract public class GfrEditorObjShpPlnAbs extends AbstractLayer implements
IGfrHandlerLifeCycleObject,
MouseListener,
MouseMotionListener
{
// BEG PLINE
final static protected String MOVE_POLYGON_ACTION = "EditorShapePolylineAbs.MovePolygonAction";
protected GfrMrkMoveAbs _cpmActive_;
protected AbstractShape _pol_ = null;
protected MarkerRenderer _mrr;
protected java.util.List<Marker> _lstMarkerControlPoints;
protected BasicMarkerAttributes _bmaControlVertex;
protected BasicMarkerAttributes _bmaControlHeight;
protected int _intIndexControlPointActive;
abstract protected boolean removeVertex(GfrMrkMoveHoriz vertexToRemove);
abstract protected void setHeightPolyline(Point previousMousePoint, Point mousePoint);
abstract protected void addVertex(Point mousePoint);
abstract protected void _assembleVertexControlPoints_(DrawContext dc);
abstract protected void moveControlPoint(GfrMrkMoveHoriz controlPoint, Point lastMousePoint, Point moveToPoint);
public void setPolyline(AbstractShape shp)
{
if (shp == null)
{
String message = "nullValue.Shape";
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this._pol_ = shp;
}
// END PLINE
/**
* Labels used in the annotations which are displayed during editing to show the current value of various shape
* parameters. Actual label values are retrieved from the World Wind message resource bundle.
*/
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 TILT_LABEL = "MeasureTool.TiltLabel";
public static final String ROLL_LABEL = "MeasureTool.RollLabel";
public static final String EAST_SKEW_LABEL = "MeasureTool.EastSkewLabel";
public static final String NORTH_SKEW_LABEL = "MeasureTool.NorthSkewLabel";
public static final String CENTER_LATITUDE_LABEL = "MeasureTool.CenterLatitudeLabel";
public static final String CENTER_LONGITUDE_LABEL = "MeasureTool.CenterLongitudeLabel";
public static final String CENTER_ALTITUDE_LABEL = "MeasureTool.CenterAltitudeLabel";
public static final String LATITUDE_LABEL = "MeasureTool.LatitudeLabel";
public static final String LONGITUDE_LABEL = "MeasureTool.LongitudeLabel";
public static final String ALTITUDE_LABEL = "MeasureTool.AltitudeLabel";
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";
protected WorldWindow _wwd;
protected Point _pntMouse;
protected ScreenAnnotation _san; // an _san for displaying current values of various shape parameters
protected AnnotationAttributes _aas; // attributes controlling the look-and-feel of the _san
protected UnitsFormat _uft; // class for uniformly formatting the units used in _san values
protected boolean _blnArmed;
protected boolean _blnShowAnnotation = true;
protected boolean _blnAboveGround = false;
protected long _lngFrameTimestamp = -1;
protected String _strActionActive;
protected String _strModeEdit;
protected int _intModeAltitude = WorldWind.ABSOLUTE;
protected GfrEditorObjShpPlnAbs()
{
super();
// BEG PLINE
this._mrr = new MarkerRenderer();
this._mrr.setKeepSeparated(false);
this._mrr.setOverrideMarkerElevation(false);
this._mrr.setEnablePickSizeReturn(true);
// END PLINE
}
@Override
public void mouseMoved(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public boolean init()
{
// BEG PLINE
this.assembleMarkerAttributes();
this._initializeAnnotation();
this._uft = new UnitsFormat();
// END PLINE
return true;
}
@Override
public void destroy()
{
// BEG PLINE
if (this._mrr != null)
{
this._mrr = null;
}
// END PLINE
}
public String getEditMode()
{
return this._strModeEdit;
}
public WorldWindow getWorldWindow()
{
return this._wwd;
}
public void setWorldWindow(WorldWindow wwd)
{
if (this._wwd == wwd)
return;
if (this._wwd != null)
{
this._wwd.getInputHandler().removeMouseListener(this);
this._wwd.getInputHandler().removeMouseMotionListener(this);
}
this._wwd = wwd;
if (this._wwd != null)
{
this._wwd.getInputHandler().addMouseListener(this);
this._wwd.getInputHandler().addMouseMotionListener(this);
}
}
public boolean isArmed()
{
return this._blnArmed;
}
public void setArmed(boolean armed)
{
this._blnArmed = armed;
}
public int getAltitudeMode()
{
return this._intModeAltitude;
}
public void setAltitudeMode(int altitudeMode)
{
this._intModeAltitude = altitudeMode;
}
public boolean isShowAnnotation()
{
return this._blnShowAnnotation;
}
public void setShowAnnotation(boolean state)
{
this._blnShowAnnotation = state;
}
public boolean isAboveGround()
{
return this._blnAboveGround;
}
public void setAboveGround(boolean state)
{
this._blnAboveGround = state;
}
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);
if (label != null)
return label;
else
{
if (this._uft == null)
this._uft = new UnitsFormat();
return this._uft.getStringValue(labelName);
}
}
public void setLabel(String labelName, String label)
{
if (labelName != null && labelName.length() > 0)
this.setValue(labelName, label);
}
protected void _setAnnotationAttributes(AnnotationAttributes attributes)
{
this._aas = attributes;
}
protected AnnotationAttributes _getAnnotationAttributes()
{
return this._aas;
}
protected void _initializeAnnotation()
{
// Annotation attributes
this._setInitialLabels();
this._aas = new AnnotationAttributes();
this._aas.setFrameShape(AVKey.SHAPE_NONE);
this._aas.setInsets(new Insets(0, 0, 0, 0));
this._aas.setDrawOffset(new Point(0, 10));
this._aas.setTextAlign(AVKey.CENTER);
this._aas.setEffect(AVKey.TEXT_EFFECT_OUTLINE);
Font fntAas = GfrUtilFont.s_get(Font.BOLD, 14);
this._aas.setFont(fntAas);
this._aas.setTextColor(Color.WHITE);
this._aas.setBackgroundColor(Color.BLACK);
this._aas.setSize(new Dimension(220, 0));
this._san = new ScreenAnnotation("", new Point(0, 0), this._aas);
this._san.getAttributes().setVisible(false);
this._san.getAttributes().setDrawOffset(null); // use defaults
}
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(CENTER_ALTITUDE_LABEL, Logging.getMessage(CENTER_ALTITUDE_LABEL));
this.setLabel(HEADING_LABEL, Logging.getMessage(HEADING_LABEL));
this.setLabel(TILT_LABEL, Logging.getMessage(TILT_LABEL));
this.setLabel(ROLL_LABEL, Logging.getMessage(ROLL_LABEL));
this.setLabel(EAST_SKEW_LABEL, Logging.getMessage(EAST_SKEW_LABEL));
this.setLabel(NORTH_SKEW_LABEL, Logging.getMessage(NORTH_SKEW_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(ALTITUDE_LABEL, Logging.getMessage(ALTITUDE_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));
}
protected boolean _arePositionsRedundant(Position posA, Position posB)
{
if (posA == null || posB == null)
return false;
String aLat = this._uft.angleNL("", posA.getLatitude());
String bLat = this._uft.angleNL("", posB.getLatitude());
if (!aLat.equals(bLat))
return false;
String aLon = this._uft.angleNL("", posA.getLongitude());
String bLon = this._uft.angleNL("", posB.getLongitude());
if (!aLon.equals(bLon))
return false;
String aAlt = this._uft.lengthNL("", posA.getAltitude());
String bAlt = this._uft.lengthNL("", posB.getAltitude());
return aAlt.equals(bAlt);
}
// BEG PLINE
//*************************************************************
// ***************** Polygon manipulation *********************
//*************************************************************
protected void movePolyline(Point previousMousePoint, Point mousePoint)
{
// Intersect a ray through each mouse point, with a geoid passing through the reference elevation.
// If either ray fails to intersect the geoid, then ignore this event. Use the difference between the two
// intersected positions to move the control point's location.
View view = this._wwd.getView();
Globe globe = this._wwd.getModel().getGlobe();
Position refPos = this._pol_.getReferencePosition();
if (refPos == null)
return;
Line ray = view.computeRayFromScreenPoint(mousePoint.getX(), mousePoint.getY());
Line previousRay = view.computeRayFromScreenPoint(previousMousePoint.getX(), previousMousePoint.getY());
Vec4 vec = AirspaceEditorUtil.intersectGlobeAt(this._wwd, refPos.getElevation(), ray);
Vec4 previousVec = AirspaceEditorUtil.intersectGlobeAt(this._wwd, refPos.getElevation(), previousRay);
if (vec == null || previousVec == null)
{
return;
}
Position pos = globe.computePositionFromPoint(vec);
Position previousPos = globe.computePositionFromPoint(previousVec);
LatLon change = pos.subtract(previousPos);
this._pol_.move(new Position(change.getLatitude(), change.getLongitude(), 0.0));
}
// ----
protected String formatMeasurements(Position pos)
{
StringBuilder sb = new StringBuilder();
// if "activeControlPoint" is in fact one of the control points
if (!this._arePositionsRedundant(pos, this._pol_.getReferencePosition()))
{
sb.append(this._uft.angleNL(this.getLabel(LATITUDE_LABEL), pos.getLatitude()));
sb.append(this._uft.angleNL(this.getLabel(LONGITUDE_LABEL), pos.getLongitude()));
sb.append(this._uft.lengthNL(this.getLabel(ALTITUDE_LABEL), pos.getAltitude()));
}
// if "activeControlPoint" is the shape itself
if (this._pol_.getReferencePosition() != null)
{
sb.append(this._uft.angleNL(this.getLabel(CENTER_LATITUDE_LABEL),
this._pol_.getReferencePosition().getLatitude()));
sb.append(this._uft.angleNL(this.getLabel(CENTER_LONGITUDE_LABEL),
this._pol_.getReferencePosition().getLongitude()));
sb.append(this._uft.lengthNL(this.getLabel(CENTER_ALTITUDE_LABEL),
this._pol_.getReferencePosition().getAltitude()));
}
return sb.toString();
}
protected void _assembleHeightControlPoints_()
{
if (this._lstMarkerControlPoints.size() < 2)
return;
// Add one control point for the height between the first and second vertices.
// TODO: ensure that this control point is visible
Position firstVertex = this._lstMarkerControlPoints.get(0).getPosition();
Position secondVertex = this._lstMarkerControlPoints.get(1).getPosition();
Globe globe = this._wwd.getModel().getGlobe();
// Get cartesian points for the vertices
Vec4 firstPoint = globe.computePointFromPosition(firstVertex);
Vec4 secondPoint = globe.computePointFromPosition(secondVertex);
// Find the midpoint of the line segment that connects the vertices
Vec4 halfwayPoint = firstPoint.add3(secondPoint).divide3(2.0);
Position halfwayPosition = globe.computePositionFromPoint(halfwayPoint);
this._lstMarkerControlPoints.add(new GfrMrkMoveVert(halfwayPosition, halfwayPoint,
this._bmaControlHeight, this._lstMarkerControlPoints.size()));
}
public void setEditMode(String editMode)
{
// void
}
public void clearIt()
{
this._lstMarkerControlPoints.clear();
}
protected void _assembleControlPoints_(DrawContext dc)
{
// Control points are re-computed each frame
if (this._lstMarkerControlPoints!=null && this._lstMarkerControlPoints.size() > 0)
{
// TODO: clean-up
this._lstMarkerControlPoints.clear();
}
this._lstMarkerControlPoints = new ArrayList<Marker>();
this._assembleVertexControlPoints_(dc);
this._assembleHeightControlPoints_();
}
@Override
protected void doRender(DrawContext dc)
{
if (this._lngFrameTimestamp != dc.getFrameTimeStamp())
{
this._assembleControlPoints_(dc);
this._lngFrameTimestamp = dc.getFrameTimeStamp();
}
this._mrr.render(dc, this._lstMarkerControlPoints);
if (this._san != null && isShowAnnotation())
{
this._san.render(dc);
}
}
@Override
protected void doPick(DrawContext dc, Point point)
{
this.doRender(dc); // Same logic for picking and rendering
}
protected void assembleMarkerAttributes()
{
this._bmaControlVertex = new BasicMarkerAttributes();
this._bmaControlVertex.setMaterial(Material.ORANGE);
this._bmaControlHeight = new BasicMarkerAttributes();
this._bmaControlHeight.setMaterial(Material.RED);
}
//*******************************************************
// ***************** Event handling *********************
//*******************************************************
@Override
public void mousePressed(MouseEvent e)
{
this._pntMouse = e.getPoint();
Object topObject = null;
PickedObjectList pickedObjects = this._wwd.getObjectsAtCurrentPosition();
if (pickedObjects != null)
topObject = pickedObjects.getTopObject();
if (topObject instanceof GfrMrkMoveAbs)
{
this._cpmActive_ = (GfrMrkMoveAbs) topObject;
this._strActionActive = this._cpmActive_.getType();
setShowAnnotation(true);
updateAnnotation(this._cpmActive_.getPosition());
// update controlPointIndex;
int i = 0;
for (Marker controlPoint : this._lstMarkerControlPoints)
{
if (controlPoint.equals(topObject))
break;
i++;
}
this._intIndexControlPointActive = i;
e.consume();
}
else if (topObject == this._pol_)
{
this._strActionActive = MOVE_POLYGON_ACTION;
// set the shape to be the "active control point"
this._intIndexControlPointActive = -1;
setShowAnnotation(true);
updateAnnotation(this._pol_.getReferencePosition());
e.consume();
}
}
@Override
public void mouseDragged(MouseEvent e)
{
Point lastMousePoint = this._pntMouse;
this._pntMouse = e.getPoint();
if (lastMousePoint == null)
lastMousePoint = this._pntMouse;
// update _san
if (isShowAnnotation())
{
if (this._intIndexControlPointActive < 0)
updateAnnotation(this._pol_.getReferencePosition());
else if (this._lstMarkerControlPoints != null)
updateAnnotation(this._lstMarkerControlPoints.get(this._intIndexControlPointActive).getPosition());
}
if (GfrMrkMoveHoriz.MOVE_VERTEX_ACTION.equals(this._strActionActive))
{
if (this._cpmActive_ instanceof GfrMrkMoveHoriz)
{
this.moveControlPoint((GfrMrkMoveHoriz) this._cpmActive_, lastMousePoint, this._pntMouse);
e.consume();
}
}
else if (GfrMrkMoveVert.CHANGE_HEIGHT_ACTION.equals(this._strActionActive))
{
if (this._cpmActive_ instanceof GfrMrkMoveVert)
{
this.setHeightPolyline(lastMousePoint, this._pntMouse);
e.consume();
}
}
else if (MOVE_POLYGON_ACTION.equals(this._strActionActive))
{
this.movePolyline(lastMousePoint, this._pntMouse);
e.consume();
}
}
@Override
public void mouseReleased(MouseEvent e)
{
this._strActionActive = null;
setShowAnnotation(false);
updateAnnotation(null);
e.consume();
this._cpmActive_ = null;
}
/**
* Determine the point at which a ray intersects a the globe at the elevation of the polygon.
*
* @param ray Ray to intersect with the globe.
*
* @return The point at which the ray intersects the globe at the elevation of the polygon.
*/
protected Vec4 _intersectPolygonAltitudeAt(Line ray)
{
// If there are control points computed, use the elevation of the first control point as the polygon elevation.
// Otherwise, if there are no control points, intersect the globe at sea level
double elevation = 0.0;
if (this._lstMarkerControlPoints.size() > 0)
{
elevation = this._lstMarkerControlPoints.get(0).getPosition().getElevation();
}
return AirspaceEditorUtil.intersectGlobeAt(this._wwd, elevation, ray);
}
public void updateAnnotation(Position pos)
{
if (pos == null)
{
if (this._san != null)
this._san.getAttributes().setVisible(false);
return;
}
String displayString = this._getDisplayString_(pos);
if (displayString == null)
{
this._san.getAttributes().setVisible(false);
return;
}
this._san.setText(displayString);
Vec4 screenPoint = this._computeAnnotationPosition_(pos);
if (screenPoint != null)
this._san.setScreenPoint(new Point((int) screenPoint.x, (int) screenPoint.y));
this._san.getAttributes().setVisible(true);
}
//*******************************************************
// ***************** Event handling *********************
//*******************************************************
@Override
public void mouseClicked(MouseEvent e)
{
if (this.isArmed())
{
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2)
{
Object topObject = null;
PickedObjectList pickedObjects = this._wwd.getObjectsAtCurrentPosition();
if (pickedObjects != null)
topObject = pickedObjects.getTopObject();
if (topObject instanceof GfrMrkMoveHoriz)
{
if (this.removeVertex((GfrMrkMoveHoriz) topObject))
e.consume();
}
// !!!!
else if (topObject instanceof GfrMrkMoveVert)
{
// -----
System.out.println("topObject instanceof MrkMoveVert, preventing remove");
}
else
{
this.addVertex(e.getPoint());
e.consume();
}
}
}
}
// ----
// beg private
private 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);
}
private String _getDisplayString_(Position pos)
{
String displayString = null;
if (pos != null)
{
displayString = this.formatMeasurements(pos);
}
return displayString;
}
// END PLINE
}