src.gov.nasa.worldwind.render.GlobeAnnotation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* 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.render;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.util.*;
import javax.media.opengl.GL;
import java.awt.*;
/**
* Represent a text label attached to a Position on the globe and its rendering attributes.
*
* @author Patrick Murris
* @version $Id: GlobeAnnotation.java 1171 2013-02-11 21:45:02Z dcollins $
* @see AbstractAnnotation
* @see AnnotationAttributes
*/
public class GlobeAnnotation extends AbstractAnnotation implements Locatable, Movable
{
protected Position position;
protected double heightInMeter = 0;
protected Integer altitudeMode;
/**
* Creates a GlobeAnnotation
with the given text, at the given globe Position
.
*
* @param text the annotation text.
* @param position the annotation Position
.
*/
public GlobeAnnotation(String text, Position position)
{
this.init(text, position, null, null);
}
/**
* Creates a GlobeAnnotation
with the given text, at the given globe Position
. Specify
* the Font
to be used.
*
* @param text the annotation text.
* @param position the annotation Position
.
* @param font the Font
to use.
*/
public GlobeAnnotation(String text, Position position, Font font)
{
this.init(text, position, font, null);
}
/**
* Creates a GlobeAnnotation
with the given text, at the given globe Position
. Specify
* the Font
and text Color
to be used.
*
* @param text the annotation text.
* @param position the annotation Position
.
* @param font the Font
to use.
* @param textColor the text Color
.
*/
public GlobeAnnotation(String text, Position position, Font font, Color textColor)
{
this.init(text, position, font, textColor);
}
/**
* Creates a GlobeAnnotation
with the given text, at the given globe Position
. Specify the
* default {@link AnnotationAttributes} set.
*
* @param text the annotation text.
* @param position the annotation Position
.
* @param defaults the default {@link AnnotationAttributes} set.
*/
public GlobeAnnotation(String text, Position position, AnnotationAttributes defaults)
{
if (text == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (defaults == null)
{
String message = Logging.getMessage("nullValue.AnnotationAttributesIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.setText(text);
this.position = position;
this.getAttributes().setDefaults(defaults);
}
private void init(String text, Position position, Font font, Color textColor)
{
if (text == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.setText(text);
this.position = position;
this.getAttributes().setFont(font);
this.getAttributes().setTextColor(textColor);
}
public Position getPosition()
{
return this.position;
}
public void setPosition(Position position)
{
this.position = position;
}
/**
* Get the annotation's altitude mode. The altitude mode may be null, indicating that the legacy altitude mode
* described below will be used.
*
* Legacy altitude mode
If the annotation Position elevation is lower then the highest elevation on the
* globe, the annotation will be drawn above the ground using its elevation as an offset, scaled by the current
* vertical exaggeration. Otherwise, the original elevation will be used. This functionality is supported for
* backward compatibility. New code that uses Globe Annotation should specify an altitude mode.
*
* @return The altitude mode, one of {@link WorldWind#CLAMP_TO_GROUND}, {@link WorldWind#RELATIVE_TO_GROUND}, {@link
* WorldWind#ABSOLUTE}, or {@code null}.
*
* @see #setAltitudeMode(Integer)
*/
public Integer getAltitudeMode()
{
return altitudeMode;
}
/**
* Set the annotation's altitude mode.
*
* @param altitudeMode The altitude mode, one of {@link WorldWind#CLAMP_TO_GROUND}, {@link
* WorldWind#RELATIVE_TO_GROUND}, or {@link WorldWind#ABSOLUTE}. {@code null} indicates that the
* legacy altitude mode should be used. See {@link #getAltitudeMode()} for details on this
* mode.
*
* @see #getAltitudeMode()
*/
public void setAltitudeMode(Integer altitudeMode)
{
this.altitudeMode = altitudeMode;
}
/**
* Returns the real world height of the annotation frame in meter. If this dimension is greater then zero, the
* annotation will be scaled so as to maintain this fixed dimension, which makes it appear as part of the
* surrounding terrain. This overrides min and max distance scaling - however min distance opacity is still accounted
* for.
*
* If this dimension is zero, the annotation always maintains the same apparent size with possible scaling relative
* to the viewport center point if min and max distance scale factors are not one.
*
* @return the real world height of the annotation frame in meter.
*/
public double getHeightInMeter()
{
return this.heightInMeter;
}
/**
* Set the real world height of the annotation frame in meter. If this dimension is greater then zero, the
* annotation will be scaled so as to maintain this fixed dimension, which makes it appear as part of the
* surrounding terrain. This overrides min and max distance scaling - however min distance opacity is still accounted
* for.
*
* If this dimension is zero, the annotation always maintains the same apparent size with possible scaling relative
* to the viewport center point if min and max distance scale factors are not one.
*
* @param meters the real world height of the annotation frame in meter.
*/
public void setHeightInMeter(double meters)
{
this.heightInMeter = meters;
}
public void move(Position position)
{
if (position == null)
{
String msg = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.position = this.position.add(position);
}
public void moveTo(Position position)
{
if (position == null)
{
String msg = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.position = position;
}
public Position getReferencePosition()
{
return this.position;
}
//**************************************************************//
//******************** Rendering *****************************//
//**************************************************************//
protected Rectangle computeBounds(DrawContext dc)
{
Vec4 point = this.getAnnotationDrawPoint(dc);
if (point == null)
return null;
Vec4 screenPoint = dc.getView().project(point);
if (screenPoint == null)
return null;
java.awt.Dimension size = this.getPreferredSize(dc);
double[] scaleAndOpacity = computeDistanceScaleAndOpacity(dc, point, size);
double finalScale = scaleAndOpacity[0] * this.computeScale(dc);
java.awt.Point offset = this.getAttributes().getDrawOffset();
double offsetX = offset.x * finalScale;
double offsetY = offset.y * finalScale;
double width = size.width * finalScale;
double height = size.height * finalScale;
double x = screenPoint.x - width / 2 + offsetX;
double y = screenPoint.y + offsetY; // use OGL coordinate system
Rectangle frameRect = new Rectangle((int) x, (int) y, (int) width, (int) height);
// Include reference point in bounds
return this.computeBoundingRectangle(frameRect, (int) screenPoint.x, (int) screenPoint.y);
}
protected void doRenderNow(DrawContext dc)
{
if (dc.isPickingMode() && this.getPickSupport() == null)
return;
Vec4 point = this.getAnnotationDrawPoint(dc);
if (point == null)
return;
if (dc.getView().getFrustumInModelCoordinates().getNear().distanceTo(point) < 0)
return;
Vec4 screenPoint = dc.getView().project(point);
if (screenPoint == null)
return;
java.awt.Dimension size = this.getPreferredSize(dc);
Position pos = dc.getGlobe().computePositionFromPoint(point);
// Scale and opacity depending on distance from eye
double[] scaleAndOpacity = computeDistanceScaleAndOpacity(dc, point, size);
this.setDepthFunc(dc, screenPoint);
this.drawTopLevelAnnotation(dc, (int) screenPoint.x, (int) screenPoint.y, size.width, size.height,
scaleAndOpacity[0], scaleAndOpacity[1], pos);
}
protected double[] computeDistanceScaleAndOpacity(DrawContext dc, Vec4 point, Dimension size)
{
double scale = 1, opacity = 1;
if (this.heightInMeter <= 0)
{
// Determine scale and opacity factors based on distance from eye vs the distance to the look at point.
Double lookAtDistance = this.computeLookAtDistance(dc);
if (lookAtDistance != null)
{
double eyeDistance = dc.getView().getEyePoint().distanceTo3(point);
double distanceFactor = Math.sqrt(lookAtDistance / eyeDistance);
scale = WWMath.clamp(distanceFactor,
this.attributes.getDistanceMinScale(), this.attributes.getDistanceMaxScale());
opacity = WWMath.clamp(distanceFactor,
this.attributes.getDistanceMinOpacity(), 1);
}
}
else
{
// Determine scale and opacity so as to maintain real world dimension
double distance = dc.getView().getEyePoint().distanceTo3(point);
double pixelSize = dc.getView().computePixelSizeAtDistance(distance);
double scaledHeight = this.heightInMeter / pixelSize;
scale = scaledHeight / size.height;
opacity = WWMath.clamp(scale, this.attributes.getDistanceMinOpacity(), 1);
}
return new double[] {scale, opacity};
}
protected Double computeLookAtDistance(DrawContext dc)
{
// TODO: Remove this method once the new mechanism for scaling and opacity is in place.
View view = dc.getView();
// Get point in the middle of the screen
Position groundPos = view.computePositionFromScreenPoint(
view.getViewport().getCenterX(), view.getViewport().getCenterY());
if (groundPos == null)
{
// Decrease the point's y coordinate until it intersects the globe.
Rectangle vp = view.getViewport();
double y = view.getViewport().getCenterY() + 1;
while (groundPos == null && y < vp.height - 1)
{
groundPos = view.computePositionFromScreenPoint(view.getViewport().getCenterX(), y++);
}
}
// Update look at distance if center point found
if (groundPos != null)
// Compute distance from eye to the position in the middle of the screen
return view.getEyePoint().distanceTo3(dc.getGlobe().computePointFromPosition(groundPos));
else
return null;
}
protected void setDepthFunc(DrawContext dc, Vec4 screenPoint)
{
GL gl = dc.getGL();
Position eyePos = dc.getView().getEyePosition();
if (eyePos == null)
{
gl.glDepthFunc(GL.GL_ALWAYS);
return;
}
double altitude = eyePos.getElevation();
if (altitude < (dc.getGlobe().getMaxElevation() * dc.getVerticalExaggeration()))
{
double depth = screenPoint.z - (8d * 0.00048875809d);
depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
gl.glDepthFunc(GL.GL_LESS);
gl.glDepthRange(depth, depth);
}
else if (screenPoint.z >= 1d)
{
gl.glDepthFunc(GL.GL_EQUAL);
gl.glDepthRange(1d, 1d);
}
else
{
gl.glDepthFunc(GL.GL_ALWAYS);
}
}
/**
* Get the final Vec4 point at which an annotation will be drawn. The altitude mode will be used to determine the
* annotation point, if an altitude mode has been set. If the altitude mode is null, then the legacy altitude mode
* described in {@link #getAltitudeMode()} will be used to determine the point.
*
* @param dc the current DrawContext.
*
* @return the annotation draw Cartesian point
*
* @see #getAltitudeMode()
*/
public Vec4 getAnnotationDrawPoint(DrawContext dc)
{
Vec4 drawPoint;
Position pos = this.getPosition();
Integer altitudeMode = this.getAltitudeMode();
if (altitudeMode == null)
{
drawPoint = getAnnotationDrawPointLegacy(dc);
}
else if (altitudeMode == WorldWind.CLAMP_TO_GROUND)
{
drawPoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
}
else if (altitudeMode == WorldWind.RELATIVE_TO_GROUND)
{
drawPoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), pos.getAltitude());
}
else // ABSOLUTE
{
double height = pos.getElevation() * dc.getVerticalExaggeration();
drawPoint = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height);
}
return drawPoint;
}
/**
* Compute the draw point using the legacy altitude mode. See {@link #getAltitudeMode()} for details on the legacy
* mode.
*
* @param dc the current DrawContext.
*
* @return the annotation draw Cartesian point
*
* @see #getAltitudeMode()
*/
protected Vec4 getAnnotationDrawPointLegacy(DrawContext dc)
{
Vec4 drawPoint = null;
Position pos = this.getPosition();
if (pos.getElevation() < dc.getGlobe().getMaxElevation())
drawPoint = dc.getSurfaceGeometry().getSurfacePoint(pos.getLatitude(), pos.getLongitude(),
pos.getElevation() * dc.getVerticalExaggeration());
if (drawPoint == null)
drawPoint = dc.getGlobe().computePointFromPosition(
dc.getVerticalExaggeration() == 1 ? pos
: new Position(pos, pos.getElevation() * dc.getVerticalExaggeration()));
return drawPoint;
}
//**************************************************************//
//******************** Restorable State **********************//
//**************************************************************//
/**
* Returns an XML state document String describing the public attributes of this GlobeAnnotation.
*
* @return XML state document string describing this GlobeAnnotation.
*/
public String getRestorableState()
{
RestorableSupport restorableSupport = null;
// Try to parse the superclass' xml state document, if it defined one.
String superStateInXml = super.getRestorableState();
if (superStateInXml != null)
{
try
{
restorableSupport = RestorableSupport.parse(superStateInXml);
}
catch (Exception e)
{
// Parsing the document specified by the superclass failed.
String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", superStateInXml);
Logging.logger().severe(message);
}
}
// Create our own state document from scratch.
if (restorableSupport == null)
restorableSupport = RestorableSupport.newRestorableSupport();
// Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null.
if (restorableSupport == null)
return null;
// Save the position property only if all parts (latitude, longitude, and elevation) can be saved.
// We will not save a partial position (for example, just the elevation).
if (this.position != null
&& this.position.getLatitude() != null
&& this.position.getLongitude() != null)
{
RestorableSupport.StateObject positionStateObj = restorableSupport.addStateObject("position");
if (positionStateObj != null)
{
restorableSupport.addStateValueAsDouble(positionStateObj, "latitude",
this.position.getLatitude().degrees);
restorableSupport.addStateValueAsDouble(positionStateObj, "longitude",
this.position.getLongitude().degrees);
restorableSupport.addStateValueAsDouble(positionStateObj, "elevation",
this.position.getElevation());
}
}
return restorableSupport.getStateAsXml();
}
/**
* Restores publicly settable attribute values found in the specified XML state document String. The document
* specified by stateInXml
must be a well formed XML document String, or this will throw an
* IllegalArgumentException. Unknown structures in stateInXml
are benign, because they will simply be
* ignored.
*
* @param stateInXml an XML document String describing a GlobeAnnotation.
*
* @throws IllegalArgumentException If stateInXml
is null, or if stateInXml
is not a well
* formed XML document String.
*/
public void restoreState(String stateInXml)
{
if (stateInXml == null)
{
String message = Logging.getMessage("nullValue.StringIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Allow the superclass to restore it's state.
try
{
super.restoreState(stateInXml);
}
catch (Exception e)
{
// Superclass will log the exception.
}
RestorableSupport restorableSupport;
try
{
restorableSupport = RestorableSupport.parse(stateInXml);
}
catch (Exception e)
{
// Parsing the document specified by stateInXml failed.
String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml);
Logging.logger().severe(message);
throw new IllegalArgumentException(message, e);
}
// Restore the position property only if all parts are available.
// We will not restore a partial position (for example, just the latitude).
RestorableSupport.StateObject positionStateObj = restorableSupport.getStateObject("position");
if (positionStateObj != null)
{
Double latitudeState = restorableSupport.getStateValueAsDouble(positionStateObj, "latitude");
Double longitudeState = restorableSupport.getStateValueAsDouble(positionStateObj, "longitude");
Double elevationState = restorableSupport.getStateValueAsDouble(positionStateObj, "elevation");
if (latitudeState != null && elevationState != null)
setPosition(Position.fromDegrees(latitudeState, longitudeState, elevationState));
}
}
}