gov.nasa.worldwind.render.PointPlacemark 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.render;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.drag.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe2D;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.ogc.kml.KMLConstants;
import gov.nasa.worldwind.ogc.kml.impl.KMLExportUtil;
import gov.nasa.worldwind.pick.*;
import gov.nasa.worldwind.util.*;
import com.jogamp.opengl.*;
import javax.xml.stream.*;
import java.awt.*;
import java.awt.geom.*;
import java.io.*;
import java.net.URL;
import java.util.*;
import static gov.nasa.worldwind.ogc.kml.impl.KMLExportUtil.kmlBoolean;
/**
* Represents a point placemark consisting of an image, an optional line linking the image to a corresponding point on
* the terrain, and an optional label. The image and the label are displayed in the plane of the screen.
*
* Point placemarks have separate attributes for normal rendering and highlighted rendering. If highlighting is
* requested but no highlight attributes are specified, the normal attributes are used. If the normal attributes are not
* specified, default attributes are used. See {@link #getDefaultAttributes()}.
*
* This class implements and extends the functionality of a KML Point.
*
* Point placemarks can participate in global text decluttering by setting their decluttering-enabled flag to {@code
* true}. See {@link #setEnableDecluttering(boolean)}. The default for this flag is {@code false}. When participating in
* decluttering, only the point placemark's label is considered when determining interference with other text.
*
* When the label of a point placemark is picked, the associated {@link gov.nasa.worldwind.pick.PickedObject} contains
* the key {@link AVKey#LABEL}
*
* @author tag
* @version $Id: PointPlacemark.java 3028 2015-04-17 00:10:19Z tgaskins $
*/
public class PointPlacemark extends WWObjectImpl
implements SelfOrderedRenderable, Locatable, Movable, Highlightable, Exportable, Draggable
{
//--- SelfOrderedRenderable implementation
private SelfOrderedRenderable next = null;
public void setNext (SelfOrderedRenderable n) { next = n; }
public SelfOrderedRenderable getNext() { return next; }
private boolean isBehind = false;
public boolean isBehind() { return isBehind; }
public void setBehind (boolean cond) { isBehind = cond; }
/**
* An interface to enable application selection of placemark level of detail.
*/
public interface LODSelector
{
/**
* Modifies the placemark's attributes and properties to achieve a desired level of detail during rendering. This
* method is called during rendering in order to provide the application an opportunity to adjust the placemark's
* attributes and properties to achieve a level of detail based on the placemark's distance from the view's eye
* point or other criteria.
*
* @param dc the current draw context.
* @param placemark the placemark about to be rendered.
* @param eyeDistance the distance in meters from the view's eye point to the placemark's geographic position.
*/
void selectLOD(DrawContext dc, PointPlacemark placemark, double eyeDistance);
}
/** The scale to use when highlighting if no highlight attributes are specified. */
protected static final Double DEFAULT_HIGHLIGHT_SCALE = 1.3;
/** The label offset to use if none is specified but an image has been specified. */
protected static final Offset DEFAULT_LABEL_OFFSET_IF_UNSPECIFIED = new Offset(1d, 0.6d, AVKey.FRACTION,
AVKey.FRACTION);
/** The point size to use when none is specified. */
protected static final Double DEFAULT_POINT_SIZE = 5d;
/**
* The address of the transparent image used when attributes.isDrawImage is false.
*/
protected static final String TRANSPARENT_IMAGE_ADDRESS = "images/transparent2x2.png";
// Label picking needs to add padding around the label to make it easier to pick.
protected static final int PICK_Y_OFFSET = -5;
protected static final int PICK_Y_SIZE_DELTA = 2;
/** The attributes used if attributes are not specified. */
protected static final PointPlacemarkAttributes defaultAttributes = new PointPlacemarkAttributes();
static
{
defaultAttributes.setImageAddress(PointPlacemarkAttributes.DEFAULT_IMAGE_PATH);
defaultAttributes.setImageOffset(PointPlacemarkAttributes.DEFAULT_IMAGE_OFFSET);
defaultAttributes.setLabelOffset(PointPlacemarkAttributes.DEFAULT_LABEL_OFFSET);
defaultAttributes.setScale(PointPlacemarkAttributes.DEFAULT_IMAGE_SCALE);
defaultAttributes.setLabelScale(PointPlacemarkAttributes.DEFAULT_LABEL_SCALE);
}
protected Position position;
protected String labelText;
protected PointPlacemarkAttributes normalAttrs;
protected PointPlacemarkAttributes highlightAttrs;
protected PointPlacemarkAttributes activeAttributes = new PointPlacemarkAttributes(); // re-determined each frame
protected Map textures = new HashMap(); // holds the textures created
protected WWTexture activeTexture; // determined each frame
protected boolean highlighted;
protected boolean dragEnabled = true;
protected DraggableSupport draggableSupport = null;
protected boolean visible = true;
protected int altitudeMode = WorldWind.CLAMP_TO_GROUND;
protected boolean lineEnabled;
protected boolean applyVerticalExaggeration = true;
protected int linePickWidth = 10;
protected boolean enableBatchRendering = true;
protected boolean enableBatchPicking = true;
protected Object delegateOwner;
protected boolean clipToHorizon = true;
protected boolean enableDecluttering = false;
protected boolean enableLabelPicking = false;
protected boolean alwaysOnTop = false;
protected LODSelector LODSelector = null;
// Values computed once per frame and reused during the frame as needed.
protected long frameNumber = -1; // identifies frame used to calculate these values
protected Vec4 placePoint; // the Cartesian point corresponding to the placemark position
protected Vec4 terrainPoint; // point on the terrain extruded from the placemark position.
protected Vec4 screenPoint; // the projection of the place-point in the viewport (on the screen)
protected double eyeDistance; // used to order the placemark as an ordered renderable
protected Rectangle imageBounds; // set during 1st render pass
protected boolean isRenderable = false; // PCM - result of makeOrderedRenderable
protected double dx; // offsets needed to position image relative to the placemark position
protected double dy;
protected Layer pickLayer; // shape's layer when ordered renderable was created
protected PickSupport pickSupport = new PickSupport();
/**
* Construct a point placemark.
*
* @param position the placemark position.
*
* @throws IllegalArgumentException if the position is null.
*/
public PointPlacemark(Position position)
{
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.position = position;
}
public Layer getPickLayer() { return pickLayer; }
public Vec4 getScreenPoint() { return screenPoint; }
public Rectangle2D getBounds(DrawContext dc) { return getLabelBounds(dc); }
/**
* Sets the placemark's position.
*
* @param position the placemark position.
*
* @throws IllegalArgumentException if the position is null.
*/
public void setPosition(Position position)
{
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.position = position;
}
/**
* Returns the placemark's position.
*
* @return the placemark's position.
*/
public Position getPosition()
{
return this.position;
}
/**
* Indicates whether the placemark is drawn when in view.
*
* @return true if the placemark is drawn when in view, otherwise false.
*/
public boolean isVisible()
{
return this.visible;
}
/**
* Specifies whether the placemark is drawn when in view.
*
* @param visible true if the placemark is drawn when in view, otherwise false.
*/
public void setVisible(boolean visible)
{
this.visible = visible;
}
/**
* Returns the placemark's altitude mode. See {@link #setAltitudeMode(int)} for a description of the modes.
*
* @return the placemark's altitude mode.
*/
public int getAltitudeMode()
{
return this.altitudeMode;
}
/**
* Specifies the placemark's altitude mode. Recognized modes are: - @link WorldWind#CLAMP_TO_GROUND}
* -- the point is placed on the terrain at the latitude and longitude of its position.
- @link
* WorldWind#RELATIVE_TO_GROUND} -- the point is placed above the terrain at the latitude and longitude of its
* position and the distance specified by its elevation.
- {@link WorldWind#ABSOLUTE} -- the point is
* placed at its specified position.
*
* @param altitudeMode the altitude mode
*/
public void setAltitudeMode(int altitudeMode)
{
this.altitudeMode = altitudeMode;
}
/**
* Returns the distance from the current view's eye point to the placemark.
*
* @return the distance from the placemark to the current view's eye point.
*/
public double getDistanceFromEye()
{
return this.eyeDistance;
}
/**
* Indicates whether a line from the placemark point to the corresponding position on the terrain is drawn.
*
* @return true if the line is drawn, otherwise false.
*/
public boolean isLineEnabled()
{
return lineEnabled;
}
/**
* Specifies whether a line from the placemark point to the corresponding position on the terrain is drawn.
*
* @param lineEnabled true if the line is drawn, otherwise false.
*/
public void setLineEnabled(boolean lineEnabled)
{
this.lineEnabled = lineEnabled;
}
/**
* Specifies the attributes used when the placemark is drawn normally, not highlighted.
*
* @param attrs the attributes to use in normal mode. May be null to indicate use of default attributes.
*/
public void setAttributes(PointPlacemarkAttributes attrs)
{
if (this.normalAttrs != null && this.normalAttrs.getImageAddress() != null)
this.textures.remove(this.normalAttrs.getImageAddress());
this.normalAttrs = attrs;
}
/**
* Returns the attributes used when the placemark is drawn normally, not highlighted.
*
* @return the attributes used in normal mode. May be null to indicate use of default attributes.
*/
public PointPlacemarkAttributes getAttributes()
{
return this.normalAttrs;
}
/**
* Specifies the attributes used to draw the placemark when it's highlighted.
*
* @param attrs the attributes to use in normal mode. May be null to indicate use of the normal attributes.
*/
public void setHighlightAttributes(PointPlacemarkAttributes attrs)
{
if (this.highlightAttrs != null && this.highlightAttrs.getImageAddress() != null)
this.textures.remove(this.highlightAttrs.getImageAddress());
this.highlightAttrs = attrs;
}
/**
* Returns the attributes used to draw the placemark when it's highlighted.
*
* @return the attributes used in normal mode. May be null to indicate use of the normal attributes.
*/
public PointPlacemarkAttributes getHighlightAttributes()
{
return this.highlightAttrs;
}
/**
* Returns the attributes used if normal attributes are not specified.
*
* @return the default attributes.
*/
public PointPlacemarkAttributes getDefaultAttributes()
{
return defaultAttributes;
}
/**
* Indicates whether the placemark is drawn highlighted.
*
* @return true if the placemark is drawn highlighted, otherwise false.
*/
public boolean isHighlighted()
{
return this.highlighted;
}
/**
* Specfies whether the placemark is drawn highlighted.
*
* @param highlighted true if the placemark is drawn highlighted, otherwise false.
*/
public void setHighlighted(boolean highlighted)
{
this.highlighted = highlighted;
}
/**
* Returns the placemark's label text.
*
* @return the placemark's label next, which man be null.
*/
public String getLabelText()
{
return labelText;
}
/**
* Specifies the placemark's label text that is displayed alongside the placemark.
*
* @param labelText the placemark label text. If null, no label is displayed.
*/
public void setLabelText(String labelText)
{
this.labelText = labelText != null ? labelText.trim() : null;
}
public boolean isApplyVerticalExaggeration()
{
return applyVerticalExaggeration;
}
public void setApplyVerticalExaggeration(boolean applyVerticalExaggeration)
{
this.applyVerticalExaggeration = applyVerticalExaggeration;
}
public int getLinePickWidth()
{
return linePickWidth;
}
public void setLinePickWidth(int linePickWidth)
{
this.linePickWidth = linePickWidth;
}
/**
* Indicates whether batch rendering is enabled.
*
* @return true if batch rendering is enabled, otherwise false.
*
* @see #setEnableBatchRendering(boolean).
*/
public boolean isEnableBatchRendering()
{
return enableBatchRendering;
}
/**
* Specifies whether adjacent PointPlacemarks in the ordered renderable list may be rendered together if they are
* contained in the same layer. This increases performance and there is seldom a reason to disable it.
*
* @param enableBatchRendering true to enable batch rendering, otherwise false.
*/
public void setEnableBatchRendering(boolean enableBatchRendering)
{
this.enableBatchRendering = enableBatchRendering;
}
/**
* Indicates whether batch picking is enabled.
*
* @return true if batch rendering is enabled, otherwise false.
*
* @see #setEnableBatchPicking(boolean).
*/
public boolean isEnableBatchPicking()
{
return enableBatchPicking;
}
/**
* Returns the delegate owner of this placemark. If non-null, the returned object replaces the placemark as the
* pickable object returned during picking. If null, the placemark itself is the pickable object returned during
* picking.
*
* @return the object used as the pickable object returned during picking, or null to indicate the the placemark is
* returned during picking.
*/
public Object getDelegateOwner()
{
return this.delegateOwner;
}
/**
* Specifies the delegate owner of this placemark. If non-null, the delegate owner replaces the placemark as the
* pickable object returned during picking. If null, the placemark itself is the pickable object returned during
* picking.
*
* @param owner the object to use as the pickable object returned during picking, or null to return the placemark.
*/
public void setDelegateOwner(Object owner)
{
this.delegateOwner = owner;
}
protected PointPlacemarkAttributes getActiveAttributes()
{
return this.activeAttributes;
}
/**
* Specifies whether adjacent PointPlacemarks in the ordered renderable list may be pick-tested together if they are
* contained in the same layer. This increases performance but allows only the top-most of the placemarks to be
* reported in a {@link gov.nasa.worldwind.event.SelectEvent} even if several of the placemarks are at the pick
* position.
*
* Batch rendering ({@link #setEnableBatchRendering(boolean)}) must be enabled in order for batch picking to occur.
*
* @param enableBatchPicking true to enable batch rendering, otherwise false.
*/
public void setEnableBatchPicking(boolean enableBatchPicking)
{
this.enableBatchPicking = enableBatchPicking;
}
/**
* Indicates whether this placemark is shown if it is beyond the horizon.
*
* @return the value of the clip-to-horizon flag. {@code true} if horizon clipping is enabled, otherwise {@code
* false}. The default value is {@code true}.
*/
public boolean isClipToHorizon()
{
return clipToHorizon;
}
/**
* Specifies whether this placemark is shown if it is beyond the horizon.
*
* @param clipToHorizon {@code true} if this placemark should not be shown when beyond the horizon, otherwise {@code
* false}.
*/
public void setClipToHorizon(boolean clipToHorizon)
{
this.clipToHorizon = clipToHorizon;
}
/**
* Indicates whether this placemark participates in global text decluttering.
*
* @return {@code true} if this placemark participates in global text decluttering, otherwise false. The default
* value is {@code false}. Only the placemark's label is considered during decluttering.
*/
public boolean isEnableDecluttering()
{
return enableDecluttering;
}
/**
* Specifies whether this placemark participates in globe text decluttering.
*
* @param enableDecluttering {@code true} if the placemark participates in global text decluttering, otherwise
* {@code false}. The default value is {@code false}. Only the placemark lable is
* considered during decluttering.
*/
public void setEnableDecluttering(boolean enableDecluttering)
{
this.enableDecluttering = enableDecluttering;
}
/**
* Indicates whether picking considers this placemark's label.
*
* @return true
if this placemark's label is considered during picking, otherwise false
.
*/
public boolean isEnableLabelPicking()
{
return enableLabelPicking;
}
/**
* Specifies whether this placemark's label should be considered during picking. The default is not to consider the
* placemark's label during picking.
*
* @param enableLabelPicking true
to consider the label during picking, otherwise false
.
*/
public void setEnableLabelPicking(boolean enableLabelPicking)
{
this.enableLabelPicking = enableLabelPicking;
}
/**
* Indicates the state of this placemark's always-on-top flag.
*
* @return true
if the always-on-top flag is set, otherwise false
.
*/
public boolean isAlwaysOnTop()
{
return alwaysOnTop;
}
/**
* Specifies whether this placemark should appear on top of other placemarks and shapes in the scene. If the flag is
* true
, this placemark's eye distance is set to 0 so that it will appear visually above other shapes
* whose eye distance is greater than 0.
*
* @param alwaysOnTop true
if the placemark should appear always on top, otherwise false
.
*/
public void setAlwaysOnTop(boolean alwaysOnTop)
{
this.alwaysOnTop = alwaysOnTop;
}
/**
* Indicates this placemark's level of detail selector.
*
* @return this placemark's level of detail selector, or null if one has not been specified.
*/
public LODSelector getLODSelector()
{
return this.LODSelector;
}
/**
* Specifies this placemark's level of detail selector.
*
* @param LODSelector the level of detail selector. May be null, the default, to indicate no level of detail
* selector.
*/
public void setLODSelector(LODSelector LODSelector)
{
this.LODSelector = LODSelector;
}
/**
* Indicates whether a point should be drawn when the active texture is null.
*
* @param dc the current draw context.
*
* @return true if a point should be drawn, otherwise false.
*/
@SuppressWarnings({"UnusedParameters"})
protected boolean isDrawPoint(DrawContext dc)
{
return this.activeTexture == null && this.getActiveAttributes().isUsePointAsDefaultImage()
&& this.getActiveAttributes().isDrawImage();
}
public void pick(DrawContext dc, Point pickPoint) {
// This method is called only when ordered renderables are being drawn.
this.pickSupport.clearPickList();
try
{
this.pickSupport.beginPicking(dc);
this.drawOrderedRenderable(dc);
} finally {
this.pickSupport.endPicking(dc);
this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer);
}
}
// PCM - be aware this is is called in three different contexts:
// (1) to order renderables depending on their eye distance - within a layer context
// (2) to pick render with unique color code (invisible) - layer global
// (3) to render the visible object - layer global
public void render(DrawContext dc)
{
// This render method is called twice during frame generation. It's first called as a {@link Renderable}
// during Renderable
picking. It's called again during normal rendering. These two calls determine
// whether to add the placemark and its optional line to the ordered renderable list during pick and render.
if (dc == null)
{
String msg = Logging.getMessage("nullValue.DrawContextIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
if (dc.getSurfaceGeometry() == null)
return;
if (!this.isVisible())
return;
if (dc.is2DGlobe())
{
Sector limits = ((Globe2D) dc.getGlobe()).getProjection().getProjectionLimits();
if (limits != null && !limits.contains(this.getPosition()))
return;
}
//if (dc.getCurrentLayer() != null) {
if (dc.isOrderedRenderingMode()) {
// this draws the ordered renderable during pick/display.
// we don't need to check frustum because we only get here if
// the object was previously added to the dc (in the branch below)
drawOrderedRenderable(dc);
} else {
// this is during the Renderable ordering phase
isRenderable = this.makeOrderedRenderable(dc);
}
}
/**
* If the scene controller is rendering ordered renderables, this method draws this placemark's image as an ordered
* renderable. Otherwise the method determines whether this instance should be added to the ordered renderable
* list.
*
* The Cartesian and screen points of the placemark are computed during the first call per frame and re-used in
* subsequent calls of that frame.
*
* @param dc the current draw context.
*/
protected boolean makeOrderedRenderable (DrawContext dc) {
long currentFrameTimeStamp = dc.getFrameTimeStamp();
if (currentFrameTimeStamp != this.frameNumber || dc.isContinuous2DGlobe()) {
this.computePlacemarkPoints(dc);
if (placePoint == null || screenPoint == null) {
return false;
}
if (this.getLODSelector() != null)
this.getLODSelector().selectLOD(dc, this, placePoint.distanceTo3(dc.getView().getEyePoint()));
this.determineActiveAttributes();
if (this.activeTexture == null && !this.getActiveAttributes().isUsePointAsDefaultImage()) {
return false;
}
this.computeImageOffset(dc); // calculates offsets to align the image with the hotspot
this.frameNumber = currentFrameTimeStamp;
if (this.isClipToHorizon() && !dc.is2DGlobe()) {
// Don't draw if beyond the horizon.
double horizon = dc.getView().getHorizonDistance();
if (this.eyeDistance > horizon) {
return false;
}
}
this.computeImageBounds(dc);
if (dc.isPickingMode()) {
this.pickLayer = dc.getCurrentLayer();
}
// note - we can't check for frustum here because there are different ones
// in pick and draw mode
if (intersectsFrustum(dc)) {
dc.addOrderedRenderable(this);
}
return true;
} else { // we looked at this frame before, no need to recompute coords/bounds
// note that the frustum changes between pick/draw mode
if (isRenderable && intersectsFrustum(dc)) {
dc.addOrderedRenderable(this);
}
return isRenderable;
}
}
/**
* Determines whether the placemark image intersects the view frustum.
*
* @param dc the current draw context.
*
* @return true if the image intersects the frustum, otherwise false.
*/
protected boolean intersectsFrustum(DrawContext dc) {
View view = dc.getView();
// Test the placemark's model coordinate point against the near and far clipping planes.
if (placePoint != null
&& (view.getFrustumInModelCoordinates().getNear().distanceTo(placePoint) < 0
|| view.getFrustumInModelCoordinates().getFar().distanceTo(placePoint) < 0))
{
return false;
}
Rectangle rect = imageBounds;
if (dc.isPickingMode()) {
if (this.isEnableDecluttering()) {
// If decluttering then we need everything within the viewport drawn.
return view.getViewport().intersects(rect);
} else {
// Test image rect against pick frustums.
if (dc.getPickFrustums().intersectsAny(rect))
return true;
if (this.getLabelText() != null && this.isEnableLabelPicking()) {
rect = this.getLabelBounds(dc);
rect = new Rectangle(rect.x, rect.y + PICK_Y_OFFSET, rect.width, rect.height + PICK_Y_SIZE_DELTA);
if (dc.getPickFrustums().intersectsAny(rect))
return true;
}
}
}
else if (rect.getWidth() > 0) {
return view.getViewport().intersects(rect);
} else if (mustDrawLabel()) {
// We are drawing a label but not an image. Determine if the placemark point is visible. This case comes up
// when the image scale is zero and the label scale is non-zero.
return view.getViewport().contains((int) screenPoint.x, (int) screenPoint.y);
}
return false;
}
/**
* Establish the OpenGL state needed to draw Paths.
*
* @param dc the current draw context.
*/
protected void beginDrawing(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
int attrMask =
GL2.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func
| GL2.GL_TRANSFORM_BIT // for modelview and perspective
| GL2.GL_VIEWPORT_BIT // for depth range
| GL2.GL_CURRENT_BIT // for current color
| GL2.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
| GL2.GL_DEPTH_BUFFER_BIT // for depth func
| GL2.GL_ENABLE_BIT // for enable/disable changes
| GL2.GL_HINT_BIT | GL2.GL_LINE_BIT; // for antialiasing and line attrs
gl.glPushAttrib(attrMask);
if (!dc.isPickingMode())
{
gl.glEnable(GL.GL_BLEND);
OGLUtil.applyBlending(gl, false);
}
}
/**
* Pop the state set in beginDrawing.
*
* @param dc the current draw context.
*/
protected void endDrawing(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
gl.glPopAttrib();
}
/**
* Draws the path as an ordered renderable.
*
* @param dc the current draw context.
*/
protected void drawOrderedRenderable(DrawContext dc) {
this.beginDrawing(dc);
try {
this.doDrawOrderedRenderable(dc, this.pickSupport);
if (this.isEnableBatchRendering())
this.drawBatched(dc);
} finally {
this.endDrawing(dc);
}
}
/**
* Draws this ordered renderable and all subsequent PointPlacemark ordered renderables in the ordered renderable
* list.
*
* @param dc the current draw context.
*/
protected void drawBatched(DrawContext dc)
{
// Draw as many as we can in a batch to save ogl state switching.
Object nextItem = dc.peekOrderedRenderables();
if (!dc.isPickingMode())
{
while (nextItem != null && nextItem instanceof PointPlacemark)
{
PointPlacemark opm = (PointPlacemark) nextItem;
if (!opm.isEnableBatchRendering())
break;
dc.pollOrderedRenderables(); // take it off the queue
opm.doDrawOrderedRenderable(dc, this.pickSupport);
nextItem = dc.peekOrderedRenderables();
}
}
else if (this.isEnableBatchPicking())
{
while (nextItem != null && nextItem instanceof PointPlacemark)
{
PointPlacemark opm = (PointPlacemark) nextItem;
if (!opm.isEnableBatchRendering() || !opm.isEnableBatchPicking())
break;
if (opm.pickLayer != this.pickLayer) // batch pick only within a single layer
break;
dc.pollOrderedRenderables(); // take it off the queue
opm.doDrawOrderedRenderable(dc, this.pickSupport);
nextItem = dc.peekOrderedRenderables();
}
}
}
/**
* Draw this placemark as an ordered renderable. If in picking mode, add it to the picked object list of specified
* {@link PickSupport}. The PickSupport
may not be the one associated with this instance. During batch
* picking the PickSupport
of the instance initiating the batch picking is used so that all shapes
* rendered in batch are added to the same pick list.
*
* @param dc the current draw context.
* @param pickCandidates a pick support holding the picked object list to add this shape to.
*/
protected void doDrawOrderedRenderable(DrawContext dc, PickSupport pickCandidates)
{
if (this.isDrawLine(dc))
this.drawLine(dc, pickCandidates);
if (this.activeTexture == null)
{
if (this.isDrawPoint(dc))
this.drawPoint(dc, pickCandidates);
return;
}
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler osh = new OGLStackHandler();
try
{
if (dc.isPickingMode())
{
// Set up to replace the non-transparent texture colors with the single pick color.
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_REPLACE);
Color pickColor = dc.getUniquePickColor();
pickCandidates.addPickableObject(this.createPickedObject(dc, pickColor));
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
}
else
{
gl.glEnable(GL.GL_TEXTURE_2D);
Color color = this.getActiveAttributes().getImageColor();
if (color == null)
color = PointPlacemarkAttributes.DEFAULT_IMAGE_COLOR;
gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
(byte) color.getAlpha());
}
// The image is drawn using a parallel projection.
osh.pushProjectionIdentity(gl);
gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -1d, 1d);
// Apply the depth buffer but don't change it (for screen-space shapes).
if ((!dc.isDeepPickingEnabled()))
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
// Suppress any fully transparent image pixels.
gl.glEnable(GL2.GL_ALPHA_TEST);
gl.glAlphaFunc(GL2.GL_GREATER, 0.001f);
// Adjust depth of image to bring it slightly forward
double depth = screenPoint.z - (8d * 0.00048875809d);
depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
gl.glDepthFunc(GL.GL_LESS);
gl.glDepthRange(depth, depth);
// The image is drawn using a translated and scaled unit quad.
// Translate to screen point and adjust to align hot spot.
osh.pushModelviewIdentity(gl);
gl.glTranslated(screenPoint.x + this.dx, screenPoint.y + this.dy, 0);
// Compute the scale
double xscale;
Double scale = this.getActiveAttributes().getScale();
if (scale != null)
xscale = scale * this.activeTexture.getWidth(dc);
else
xscale = this.activeTexture.getWidth(dc);
double yscale;
if (scale != null)
yscale = scale * this.activeTexture.getHeight(dc);
else
yscale = this.activeTexture.getHeight(dc);
Double heading = getActiveAttributes().getHeading();
Double pitch = getActiveAttributes().getPitch();
// Adjust heading to be relative to globe or screen
if (heading != null)
{
if (AVKey.RELATIVE_TO_GLOBE.equals(this.getActiveAttributes().getHeadingReference()))
heading = dc.getView().getHeading().degrees - heading;
else
heading = -heading;
}
// Apply the heading and pitch if specified.
if (heading != null || pitch != null)
{
gl.glTranslated(xscale / 2, yscale / 2, 0);
if (pitch != null)
gl.glRotated(pitch, 1, 0, 0);
if (heading != null)
gl.glRotated(heading, 0, 0, 1);
gl.glTranslated(-xscale / 2, -yscale / 2, 0);
}
// Scale the unit quad
gl.glScaled(xscale, yscale, 1);
if (this.activeTexture.bind(dc))
dc.drawUnitQuad(activeTexture.getTexCoords());
gl.glDepthRange(0, 1); // reset depth range to the OGL default
//
// gl.glDisable(GL.GL_TEXTURE_2D);
// dc.drawUnitQuadOutline(); // for debugging label placement
if (this.mustDrawLabel())
{
if (!dc.isPickingMode() || this.isEnableLabelPicking())
this.drawLabel(dc, pickCandidates);
}
}
finally
{
if (dc.isPickingMode())
{
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, OGLUtil.DEFAULT_TEX_ENV_MODE);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, OGLUtil.DEFAULT_SRC0_RGB);
gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, OGLUtil.DEFAULT_COMBINE_RGB);
}
gl.glDisable(GL.GL_TEXTURE_2D);
osh.pop(gl);
}
}
/**
* Create a {@link PickedObject} for this placemark. The PickedObject returned by this method will be added to the
* pick list to represent the current placemark.
*
* @param dc Active draw context.
* @param pickColor Unique color for this PickedObject.
*
* @return A new picked object.
*/
protected PickedObject createPickedObject(DrawContext dc, Color pickColor)
{
Object delegateOwner = this.getDelegateOwner();
return new PickedObject(pickColor.getRGB(), delegateOwner != null ? delegateOwner : this);
}
/**
* Determines if the placemark label will be rendered.
*
* @return True if the label must be drawn.
*/
protected boolean mustDrawLabel()
{
return this.getLabelText() != null && this.getActiveAttributes().isDrawLabel();
}
/**
* Determines the screen coordinate boundaries of this placemark's label.
*
* @param dc the current draw context.
*
* @return the label bounds, in lower-left origin screen coordinates, or null if there is no label.
*/
protected Rectangle getLabelBounds(DrawContext dc) {
if (this.getLabelText() == null)
return null;
Vec4 labelPoint = this.computeLabelPoint(dc);
Font font = this.getActiveAttributes().getLabelFont();
if (font == null) {
font = PointPlacemarkAttributes.DEFAULT_LABEL_FONT;
}
TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font);
Rectangle2D bounds = textRenderer.getBounds(this.getLabelText());
double width = bounds.getWidth();
double height = bounds.getHeight();
Double labelScale = this.getActiveAttributes().getLabelScale();
if (labelScale != null) {
width *= labelScale;
height *= labelScale;
}
return new Rectangle((int) labelPoint.x, (int) labelPoint.getY(), (int) Math.ceil(width),
(int) Math.ceil(height));
}
/**
* Draws the placemark's label if a label is specified.
*
* @param dc the current draw context.
*/
protected void drawLabel(DrawContext dc, PickSupport pickCandidates) {
String labelText = this.getLabelText();
if (labelText == null)
return;
Color color = this.getActiveAttributes().getLabelColor();
// Use the default color if the active attributes do not specify one.
if (color == null)
color = PointPlacemarkAttributes.DEFAULT_LABEL_COLOR;
// If the label color's alpha component is 0 or less, then the label is completely transparent. Exit
// immediately; the label does not need to be rendered.
if (color.getAlpha() <= 0)
return;
// Apply the label color's alpha component to the background color. This causes both the label foreground and
// background to blend evenly into the frame. If the alpha component is 255 we just use the pre-defined constant
// for BLACK to avoid creating a new background color every frame.
Color backgroundColor = (color.getAlpha() < 255 ? new Color(0, 0, 0, color.getAlpha()) : Color.BLACK);
Vec4 labelPoint = this.computeLabelPoint(dc);
float x = (float) labelPoint.x;
float y = (float) labelPoint.y;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glMatrixMode(GL2.GL_MODELVIEW);
gl.glLoadIdentity();
Double labelScale = this.getActiveAttributes().getLabelScale();
if (labelScale != null) {
gl.glTranslatef(x, y, 0); // Assumes matrix mode is MODELVIEW
gl.glScaled(labelScale, labelScale, 1);
gl.glTranslatef(-x, -y, 0);
}
// Do not depth buffer the label. (Placemarks beyond the horizon are culled above.)
gl.glDisable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
Font font = this.getActiveAttributes().getLabelFont();
if (font == null) {
font = PointPlacemarkAttributes.DEFAULT_LABEL_FONT;
}
if (dc.isPickingMode()) {
// Pick the text box, not just the text.
Rectangle textBounds = this.getLabelBounds(dc);
Color pickColor = dc.getUniquePickColor();
PickedObject po = this.createPickedObject(dc, pickColor);
po.setValue(AVKey.PICKED_OBJECT_ID, AVKey.LABEL);
pickCandidates.addPickableObject(po);
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
gl.glTranslated(textBounds.getX(), textBounds.getY() + PICK_Y_OFFSET, 0);
gl.glScaled(textBounds.getWidth(), textBounds.getHeight() + PICK_Y_SIZE_DELTA, 1);
gl.glDisable(GL.GL_TEXTURE_2D);
dc.drawUnitQuad();
} else {
renderLabelText(dc, labelText, x, y, font, color, backgroundColor);
}
}
protected void renderLabelText (DrawContext dc, String labelText, float x, float y, Font font, Color color, Color backgroundColor) {
TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font);
try
{
textRenderer.begin3DRendering();
textRenderer.setColor(backgroundColor);
textRenderer.draw3D(labelText, x + 1, y - 1, 0, 1);
textRenderer.setColor(color);
textRenderer.draw3D(labelText, x, y, 0, 1);
}
finally
{
textRenderer.end3DRendering();
}
}
/**
* Draws the placemark's line.
*
* @param dc the current draw context.
* @param pickCandidates the pick support object to use when adding this as a pick candidate.
*/
protected void drawLine(DrawContext dc, PickSupport pickCandidates) {
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if ((!dc.isDeepPickingEnabled())) {
gl.glEnable(GL.GL_DEPTH_TEST);
}
gl.glDepthFunc(GL.GL_LEQUAL);
gl.glDepthMask(true);
try {
dc.getView().pushReferenceCenter(dc, placePoint); // draw relative to the place point
this.setLineWidth(dc);
this.setLineColor(dc, pickCandidates);
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(Vec4.ZERO.x, Vec4.ZERO.y, Vec4.ZERO.z);
gl.glVertex3d(terrainPoint.x - placePoint.x,
terrainPoint.y - placePoint.y,
terrainPoint.z - placePoint.z);
gl.glEnd();
} finally {
dc.getView().popReferenceCenter(dc);
}
}
/**
* Draws the placemark's point.
*
* @param dc the current draw context.
* @param pickCandidates the pick support object to use when adding this as a pick candidate.
*/
protected void drawPoint(DrawContext dc, PickSupport pickCandidates) {
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler osh = new OGLStackHandler();
try
{
osh.pushAttrib(gl, GL2.GL_POINT_BIT);
this.setLineColor(dc, pickCandidates);
this.setPointSize(dc);
// The point is drawn using a parallel projection.
osh.pushProjectionIdentity(gl);
gl.glOrtho(0d, dc.getView().getViewport().width, 0d, dc.getView().getViewport().height, -1d, 1d);
osh.pushModelviewIdentity(gl);
// Apply the depth buffer but don't change it (for screen-space shapes).
if ((!dc.isDeepPickingEnabled()))
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthMask(false);
// Suppress any fully transparent pixels.
gl.glEnable(GL2.GL_ALPHA_TEST);
gl.glAlphaFunc(GL2.GL_GREATER, 0.001f);
// Adjust depth of point to bring it slightly forward
double depth = screenPoint.z - (8d * 0.00048875809d);
depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
gl.glDepthFunc(GL.GL_LESS);
gl.glDepthRange(depth, depth);
gl.glBegin(GL2.GL_POINTS);
gl.glVertex3d(screenPoint.x, screenPoint.y, 0);
gl.glEnd();
gl.glDepthRange(0, 1); // reset depth range to the OGL default
if (this.mustDrawLabel()) {
if (!dc.isPickingMode() || this.isEnableLabelPicking())
this.drawLabel(dc, pickCandidates);
}
}
finally
{
osh.pop(gl);
}
}
/**
* Determines whether the placemark's optional line should be drawn and whether it intersects the view frustum.
*
* @param dc the current draw context.
*
* @return true if the line should be drawn and it intersects the view frustum, otherwise false.
*/
protected boolean isDrawLine(DrawContext dc) {
if (!this.isLineEnabled() || dc.is2DGlobe() ||
this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND || terrainPoint == null)
return false;
if (dc.isPickingMode()) {
return dc.getPickFrustums().intersectsAny(placePoint, terrainPoint);
} else {
return dc.getView().getFrustumInModelCoordinates().intersectsSegment(placePoint, terrainPoint);
}
}
/**
* Sets the width of the placemark's line during rendering.
*
* @param dc the current draw context.
*/
protected void setLineWidth(DrawContext dc)
{
Double lineWidth = this.getActiveAttributes().getLineWidth();
if (lineWidth != null)
{
GL gl = dc.getGL();
if (dc.isPickingMode())
{
gl.glLineWidth(lineWidth.floatValue() + this.getLinePickWidth());
}
else
gl.glLineWidth(lineWidth.floatValue());
if (!dc.isPickingMode())
{
gl.glHint(GL.GL_LINE_SMOOTH_HINT, this.getActiveAttributes().getAntiAliasHint());
gl.glEnable(GL.GL_LINE_SMOOTH);
}
}
}
/**
* Sets the width of the placemark's point during rendering.
*
* @param dc the current draw context.
*/
protected void setPointSize(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
Double scale = this.getActiveAttributes().getScale();
if (scale == null)
scale = DEFAULT_POINT_SIZE;
if (dc.isPickingMode())
gl.glPointSize(scale.floatValue() + this.getLinePickWidth());
else
gl.glPointSize(scale.floatValue());
if (!dc.isPickingMode())
{
gl.glEnable(GL2.GL_POINT_SMOOTH);
gl.glHint(GL2.GL_POINT_SMOOTH_HINT, GL2.GL_NICEST);
}
}
/**
* Sets the color of the placemark's line during rendering.
*
* @param dc the current draw context.
* @param pickCandidates the pick support object to use when adding this as a pick candidate.
*/
protected void setLineColor(DrawContext dc, PickSupport pickCandidates)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
if (!dc.isPickingMode())
{
Color color = this.getActiveAttributes().getLineColor();
if (color == null)
color = PointPlacemarkAttributes.DEFAULT_LINE_COLOR;
gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(),
(byte) color.getAlpha());
}
else
{
Color pickColor = dc.getUniquePickColor();
Object delegateOwner = this.getDelegateOwner();
pickCandidates.addPickableObject(pickColor.getRGB(), delegateOwner != null ? delegateOwner : this,
this.getPosition());
gl.glColor3ub((byte) pickColor.getRed(), (byte) pickColor.getGreen(), (byte) pickColor.getBlue());
}
}
/** Determines which attributes -- normal, highlight or default -- to use each frame. */
protected void determineActiveAttributes()
{
PointPlacemarkAttributes actAttrs = this.getActiveAttributes();
if (this.isHighlighted())
{
if (this.getHighlightAttributes() != null)
{
actAttrs.copy(this.getHighlightAttributes());
// Even though there are highlight attributes, there may not be an image for them, so use the normal image.
if (WWUtil.isEmpty(actAttrs.getImageAddress())
&& this.getAttributes() != null && !WWUtil.isEmpty(this.getAttributes().getImageAddress()))
{
actAttrs.setImageAddress(this.getAttributes().getImageAddress());
if (this.getAttributes().getScale() != null)
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE * this.getAttributes().getScale());
else
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE);
}
}
else
{
// If no highlight attributes have been specified we need to use the normal attributes but adjust them
// for highlighting.
if (this.getAttributes() != null)
{
actAttrs.copy(this.getAttributes());
if (getAttributes().getScale() != null)
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE * this.getAttributes().getScale());
else
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE);
}
else
{
actAttrs.copy(defaultAttributes);
if (defaultAttributes.getScale() != null)
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE * defaultAttributes.getScale());
else
actAttrs.setScale(DEFAULT_HIGHLIGHT_SCALE);
}
}
}
else if (this.getAttributes() != null)
{
actAttrs.copy(this.getAttributes());
}
else
{
actAttrs.copy(defaultAttributes);
if (this.activeTexture == null && actAttrs.isUsePointAsDefaultImage())
{
actAttrs.setImageAddress(null);
actAttrs.setScale(DEFAULT_POINT_SIZE);
}
}
this.activeTexture = this.chooseTexture(actAttrs);
if (this.activeTexture == null && actAttrs.isUsePointAsDefaultImage())
{
actAttrs.setImageAddress(null);
actAttrs.setImageOffset(null);
if (actAttrs.getScale() == null)
actAttrs.setScale(DEFAULT_POINT_SIZE);
}
}
/**
* Determines the appropriate texture for the current availability.
*
* @param attrs the attributes specifying the placemark image and properties.
*
* @return the appropriate texture, or null if an image is not available.
*/
protected WWTexture chooseTexture(PointPlacemarkAttributes attrs)
{
if (!attrs.isDrawImage())
{
WWTexture texture = this.textures.get(TRANSPARENT_IMAGE_ADDRESS);
if (texture == null)
{
URL localUrl = WorldWind.getDataFileStore().requestFile(TRANSPARENT_IMAGE_ADDRESS);
if (localUrl != null)
{
texture = new BasicWWTexture(localUrl, true);
this.textures.put(TRANSPARENT_IMAGE_ADDRESS, texture);
}
}
return texture;
}
if (!WWUtil.isEmpty(attrs.getImageAddress()))
{
WWTexture texture = this.textures.get(attrs.getImageAddress());
if (texture != null)
return texture;
texture = this.initializeTexture(attrs.getImageAddress());
if (texture != null)
{
this.textures.put(attrs.getImageAddress(), texture);
return texture;
}
}
if (this.getActiveAttributes().usePointAsDefaultImage)
return null;
// Use the default image if no other is defined or it's not yet available.
WWTexture texture = this.textures.get(defaultAttributes.getImageAddress());
this.getActiveAttributes().setImageOffset(defaultAttributes.getImageOffset());
if (attrs.getScale() != null)
this.getActiveAttributes().setScale(defaultAttributes.getScale() * attrs.getScale());
else
this.getActiveAttributes().setScale(defaultAttributes.getScale());
if (texture == null)
{
URL localUrl = WorldWind.getDataFileStore().requestFile(defaultAttributes.getImageAddress());
if (localUrl != null)
{
texture = new BasicWWTexture(localUrl, true);
this.textures.put(defaultAttributes.getImageAddress(), texture);
}
}
return texture;
}
/**
* Load a texture. If the texture source is not available locally, this method requests the texture source and
* returns null.
*
* @param address Path or URL to the image to load into a texture.
*
* @return The new texture, or null if the texture could not be created because the resource is not yet available
* locally.
*/
protected WWTexture initializeTexture(String address)
{
if (this.getActiveAttributes().getImage() != null)
{
// App has specified a buffered image.
return new BasicWWTexture(this.getActiveAttributes().getImage(), true);
}
URL localUrl = WorldWind.getDataFileStore().requestFile(address);
if (localUrl != null)
{
return new BasicWWTexture(localUrl, true);
}
return null;
}
/**
* Computes and stores the placemark's Cartesian location, the Cartesian location of the corresponding point on the
* terrain (if the altitude mode requires it), and the screen-space projection of the placemark's point. Applies the
* placemark's altitude mode when computing the points.
*
* @param dc the current draw context.
*/
protected void computePlacemarkPoints(DrawContext dc) {
placePoint = null;
terrainPoint = null;
screenPoint = null;
Position pos = this.getPosition();
if (pos == null)
return;
if (this.altitudeMode == WorldWind.CLAMP_TO_GROUND || dc.is2DGlobe()) {
placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
} else if (this.altitudeMode == WorldWind.RELATIVE_TO_GROUND) {
placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), pos.getAltitude());
} else {// ABSOLUTE
double height = pos.getElevation()
* (this.isApplyVerticalExaggeration() ? dc.getVerticalExaggeration() : 1);
placePoint = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height);
}
if (placePoint != null) {
// Compute a terrain point if needed.
if (this.isLineEnabled() && this.altitudeMode != WorldWind.CLAMP_TO_GROUND && !dc.is2DGlobe())
terrainPoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0);
// Compute the placemark point's screen location.
screenPoint = dc.getView().project(placePoint);
eyeDistance = this.isAlwaysOnTop() ? 0 : placePoint.distanceTo3(dc.getView().getEyePoint());
}
}
/**
* Computes the screen-space rectangle bounding the placemark image.
*
* @param dc the current draw context.
*
* @return the bounding rectangle.
*/
protected void computeImageBounds(DrawContext dc)
{
double s = this.getActiveAttributes().getScale() != null ? this.getActiveAttributes().getScale() : 1;
double width = s * (this.activeTexture != null ? this.activeTexture.getWidth(dc) : 1);
double height = s * (this.activeTexture != null ? this.activeTexture.getHeight(dc) : 1);
double x = screenPoint.x + (this.isDrawPoint(dc) ? -0.5 * s : this.dx);
double y = screenPoint.y + (this.isDrawPoint(dc) ? -0.5 * s : this.dy);
imageBounds = new Rectangle((int) x, (int) y, (int) Math.ceil(width), (int) Math.ceil(height));
}
/**
* Computes the screen coordinate (lower-left origin) location of this placemark's label.
*
* @param dc the current draw context.
*
* @return the 2D label location, or null if there is no label.
*/
protected Vec4 computeLabelPoint(DrawContext dc) {
if (this.getLabelText() == null)
return null;
float x = (float) (screenPoint.x + this.dx);
float y = (float) (screenPoint.y + this.dy);
Double imageScale = this.getActiveAttributes().getScale();
Offset os = this.getActiveAttributes().getLabelOffset();
if (os == null)
os = DEFAULT_LABEL_OFFSET_IF_UNSPECIFIED;
double w = this.activeTexture != null ? this.activeTexture.getWidth(dc) : 1;
double h = this.activeTexture != null ? this.activeTexture.getHeight(dc) : 1;
Point.Double offset = os.computeOffset(w, h, imageScale, imageScale);
x += offset.x;
y += offset.y;
return new Vec4(x, y);
}
protected void computeImageOffset(DrawContext dc)
{
// Determine the screen-space offset needed to align the image hot spot with the placemark point.
this.dx = 0;
this.dy = 0;
if (this.isDrawPoint(dc))
return;
Offset os = this.getActiveAttributes().getImageOffset();
if (os == null)
return;
double w = this.activeTexture != null ? this.activeTexture.getWidth(dc) : 1;
double h = this.activeTexture != null ? this.activeTexture.getHeight(dc) : 1;
Point.Double offset = os.computeOffset(w, h,
this.getActiveAttributes().getScale(), this.getActiveAttributes().getScale());
this.dx = -offset.x;
this.dy = -offset.y;
}
/** {@inheritDoc} */
public String isExportFormatSupported(String format)
{
if (KMLConstants.KML_MIME_TYPE.equalsIgnoreCase(format))
return Exportable.FORMAT_SUPPORTED;
else
return Exportable.FORMAT_NOT_SUPPORTED;
}
/** {@inheritDoc} */
public Position getReferencePosition()
{
return this.getPosition();
}
/** {@inheritDoc} */
public void move(Position delta)
{
if (delta == null)
{
String msg = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
Position refPos = this.getReferencePosition();
// The reference position is null if this shape has positions. With PointPlacemark, this should never happen
// because its position must always be non-null. We check and this case anyway to handle a subclass overriding
// getReferencePosition and returning null. In this case moving the shape by a relative delta is meaningless
// because the shape has no geographic location. Therefore we fail softly by exiting and doing nothing.
if (refPos == null)
return;
this.moveTo(refPos.add(delta));
}
/** {@inheritDoc} */
public void moveTo(Position position)
{
if (position == null)
{
String msg = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.setPosition(position);
}
@Override
public boolean isDragEnabled()
{
return this.dragEnabled;
}
@Override
public void setDragEnabled(boolean enabled)
{
this.dragEnabled = enabled;
}
@Override
public void drag(DragContext dragContext)
{
if (!this.dragEnabled)
return;
if (this.draggableSupport == null)
this.draggableSupport = new DraggableSupport(this, this.getAltitudeMode());
this.doDrag(dragContext);
}
protected void doDrag(DragContext dragContext)
{
this.draggableSupport.dragScreenSizeConstant(dragContext);
}
/**
* Export the Placemark. The {@code output} object will receive the exported data. The type of this object depends
* on the export format. The formats and object types supported by this class are:
*
*
* Format Supported output object types
* ================================================================================
* KML (application/vnd.google-earth.kml+xml) java.io.Writer
* java.io.OutputStream
* javax.xml.stream.XMLStreamWriter
*
*
* @param mimeType MIME type of desired export format.
* @param output An object that will receive the exported data. The type of this object depends on the export
* format (see above).
*
* @throws IOException If an exception occurs writing to the output object.
*/
public void export(String mimeType, Object output) throws IOException
{
if (mimeType == null)
{
String message = Logging.getMessage("nullValue.Format");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (output == null)
{
String message = Logging.getMessage("nullValue.OutputBufferIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (KMLConstants.KML_MIME_TYPE.equalsIgnoreCase(mimeType))
{
try
{
exportAsKML(output);
}
catch (XMLStreamException e)
{
Logging.logger().throwing(getClass().getName(), "export", e);
throw new IOException(e);
}
}
else
{
String message = Logging.getMessage("Export.UnsupportedFormat", mimeType);
Logging.logger().warning(message);
throw new UnsupportedOperationException(message);
}
}
/**
* Export the placemark to KML as a {@code } element. The {@code output} object will receive the data.
* This object must be one of: java.io.Writer java.io.OutputStream javax.xml.stream.XMLStreamWriter
*
* @param output Object to receive the generated KML.
*
* @throws XMLStreamException If an exception occurs while writing the KML
* @throws IOException if an exception occurs while exporting the data.
* @see #export(String, Object)
*/
protected void exportAsKML(Object output) throws IOException, XMLStreamException
{
XMLStreamWriter xmlWriter = null;
XMLOutputFactory factory = XMLOutputFactory.newInstance();
boolean closeWriterWhenFinished = true;
if (output instanceof XMLStreamWriter)
{
xmlWriter = (XMLStreamWriter) output;
closeWriterWhenFinished = false;
}
else if (output instanceof Writer)
{
xmlWriter = factory.createXMLStreamWriter((Writer) output);
}
else if (output instanceof OutputStream)
{
xmlWriter = factory.createXMLStreamWriter((OutputStream) output);
}
if (xmlWriter == null)
{
String message = Logging.getMessage("Export.UnsupportedOutputObject");
Logging.logger().warning(message);
throw new IllegalArgumentException(message);
}
xmlWriter.writeStartElement("Placemark");
xmlWriter.writeStartElement("name");
xmlWriter.writeCharacters(this.getLabelText());
xmlWriter.writeEndElement();
xmlWriter.writeStartElement("visibility");
xmlWriter.writeCharacters(kmlBoolean(this.isVisible()));
xmlWriter.writeEndElement();
String shortDescription = (String) getValue(AVKey.SHORT_DESCRIPTION);
if (shortDescription != null)
{
xmlWriter.writeStartElement("Snippet");
xmlWriter.writeCharacters(shortDescription);
xmlWriter.writeEndElement();
}
String description = (String) getValue(AVKey.BALLOON_TEXT);
if (description != null)
{
xmlWriter.writeStartElement("description");
xmlWriter.writeCharacters(description);
xmlWriter.writeEndElement();
}
final PointPlacemarkAttributes normalAttributes = getAttributes();
final PointPlacemarkAttributes highlightAttributes = getHighlightAttributes();
// Write style map
if (normalAttributes != null || highlightAttributes != null)
{
xmlWriter.writeStartElement("StyleMap");
exportAttributesAsKML(xmlWriter, KMLConstants.NORMAL, normalAttributes);
exportAttributesAsKML(xmlWriter, KMLConstants.HIGHLIGHT, highlightAttributes);
xmlWriter.writeEndElement(); // StyleMap
}
// Write geometry
xmlWriter.writeStartElement("Point");
xmlWriter.writeStartElement("extrude");
xmlWriter.writeCharacters(kmlBoolean(isLineEnabled()));
xmlWriter.writeEndElement();
final String altitudeMode = KMLExportUtil.kmlAltitudeMode(getAltitudeMode());
xmlWriter.writeStartElement("altitudeMode");
xmlWriter.writeCharacters(altitudeMode);
xmlWriter.writeEndElement();
final String coordString = String.format(Locale.US, "%f,%f,%f",
position.getLongitude().getDegrees(),
position.getLatitude().getDegrees(),
position.getElevation());
xmlWriter.writeStartElement("coordinates");
xmlWriter.writeCharacters(coordString);
xmlWriter.writeEndElement();
xmlWriter.writeEndElement(); // Point
xmlWriter.writeEndElement(); // Placemark
xmlWriter.flush();
if (closeWriterWhenFinished)
xmlWriter.close();
}
/**
* Export PointPlacemarkAttributes as KML Style element.
*
* @param xmlWriter Writer to receive the Style element.
* @param styleType The type of style: normal or highlight. Value should match either {@link KMLConstants#NORMAL}
* or {@link KMLConstants#HIGHLIGHT}
* @param attributes Attributes to export. The method takes no action if this parameter is null.
*
* @throws XMLStreamException if exception occurs writing XML.
* @throws IOException if exception occurs exporting data.
*/
private void exportAttributesAsKML(XMLStreamWriter xmlWriter, String styleType, PointPlacemarkAttributes attributes)
throws XMLStreamException, IOException
{
if (attributes != null)
{
xmlWriter.writeStartElement("Pair");
xmlWriter.writeStartElement("key");
xmlWriter.writeCharacters(styleType);
xmlWriter.writeEndElement();
attributes.export(KMLConstants.KML_MIME_TYPE, xmlWriter);
xmlWriter.writeEndElement(); // Pair
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy