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

src.gov.nasa.worldwind.render.PointPlacemark Maven / Gradle / Ivy

Go to download

World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.

There is a newer version: 2.0.0-986
Show 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 com.jogamp.opengl.util.awt.TextRenderer;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
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 javax.media.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. * * @author tag * @version $Id: PointPlacemark.java 1942 2014-04-16 02:21:09Z tgaskins $ */ public class PointPlacemark extends WWObjectImpl implements OrderedRenderable, Locatable, Movable, Highlightable, Exportable, Declutterable { /** 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 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 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; // 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 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 Rectangle2D labelBounds; // cached label bounds protected Font boundsFont; // the font associated with the cached label bounds 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; } /** * 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; } /** {@inheritDoc} * */ 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 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(); } public void pick(DrawContext dc, Point pickPoint) { // This method is called only when ordered renderables are being drawn. // Arg checked within call to render. this.pickSupport.clearPickList(); try { this.pickSupport.beginPicking(dc); this.render(dc); } finally { this.pickSupport.endPicking(dc); this.pickSupport.resolvePick(dc, pickPoint, this.pickLayer); } } public void render(DrawContext dc) { // This render method is called three times during frame generation. It's first called as a {@link Renderable} // during Renderable picking. It's called again during normal rendering. And it's called a third // time as an OrderedRenderable. The first two calls determine whether to add the placemark and its optional // line to the ordered renderable list during pick and render. The third call just draws the ordered renderable. 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.isOrderedRenderingMode()) this.drawOrderedRenderable(dc); else 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 void makeOrderedRenderable(DrawContext dc) { // The rest of the code in this method determines whether to queue an ordered renderable for the placemark // and its optional line. // Re-use values already calculated this frame. if (dc.getFrameTimeStamp() != this.frameNumber) { this.computePlacemarkPoints(dc); if (this.placePoint == null || this.screenPoint == null) return; this.determineActiveAttributes(); if (this.activeTexture == null && !this.getActiveAttributes().isUsePointAsDefaultImage()) return; this.computeImageOffset(dc); // calculates offsets to align the image with the hotspot this.frameNumber = dc.getFrameTimeStamp(); } if (this.isClipToHorizon()) { // Don't draw if beyond the horizon. double horizon = dc.getView().getHorizonDistance(); if (this.eyeDistance > horizon) return; } if (this.intersectsFrustum(dc) || this.isDrawLine(dc)) dc.addOrderedRenderable(this); // add the image ordered renderable if (dc.isPickingMode()) this.pickLayer = dc.getCurrentLayer(); } /** * 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 (this.placePoint != null && (view.getFrustumInModelCoordinates().getNear().distanceTo(this.placePoint) < 0 || view.getFrustumInModelCoordinates().getFar().distanceTo(this.placePoint) < 0)) { return false; } Rectangle rect = this.computeImageRectangle(dc); if (dc.isPickingMode()) { // Test image rect against pick frustums. Note that we do this even when the label is visible and the image // is not because we do not support picking via the label. return dc.getPickFrustums().intersectsAny(rect); } else if (rect.width > 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) this.screenPoint.x, (int) this.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 p = (PointPlacemark) nextItem; if (!p.isEnableBatchRendering()) break; dc.pollOrderedRenderables(); // take it off the queue p.doDrawOrderedRenderable(dc, this.pickSupport); nextItem = dc.peekOrderedRenderables(); } } else if (this.isEnableBatchPicking()) { while (nextItem != null && nextItem instanceof PointPlacemark) { PointPlacemark p = (PointPlacemark) nextItem; if (!p.isEnableBatchRendering() || !p.isEnableBatchPicking()) break; if (p.pickLayer != this.pickLayer) // batch pick only within a single layer break; dc.pollOrderedRenderables(); // take it off the queue p.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() && !dc.isPickingMode()) // don't pick via the label this.drawLabel(dc); } 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.labelText != null; } @Override public Rectangle2D getBounds(DrawContext dc) { return this.getLabelBounds(dc); } /** * 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. */ protected Rectangle2D getLabelBounds(DrawContext dc) { if (this.labelText == null) return null; double x = (float) (this.screenPoint.x + this.dx); double y = (float) (this.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; Font font = this.getActiveAttributes().getLabelFont(); if (font == null) font = PointPlacemarkAttributes.DEFAULT_LABEL_FONT; Rectangle2D bounds; if (this.labelBounds != null && font == this.boundsFont) { bounds = new Rectangle.Double(x, y, this.labelBounds.getWidth(), this.labelBounds.getHeight()); } else { TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font); bounds = textRenderer.getBounds(this.labelText); this.boundsFont = font; this.labelBounds = bounds; } Double labelScale = this.getActiveAttributes().getLabelScale(); if (labelScale != null) { double tw = labelScale * bounds.getWidth(); double th = labelScale * bounds.getHeight(); bounds = new Rectangle2D.Double(x, y, tw, th); } else { bounds = new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight()); } return bounds; } /** * Draws the placemark's label if a label is specified. * * @param dc the current draw context. */ protected void drawLabel(DrawContext dc) { if (this.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); Font font = this.getActiveAttributes().getLabelFont(); if (font == null) font = PointPlacemarkAttributes.DEFAULT_LABEL_FONT; float x = (float) (this.screenPoint.x + this.dx); float y = (float) (this.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; 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); TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font); try { textRenderer.begin3DRendering(); textRenderer.setColor(backgroundColor); textRenderer.draw3D(this.labelText, x + 1, y - 1, 0, 1); textRenderer.setColor(color); textRenderer.draw3D(this.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, this.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(this.terrainPoint.x - this.placePoint.x, this.terrainPoint.y - this.placePoint.y, this.terrainPoint.z - this.placePoint.z); gl.glEnd(); } finally { dc.getView().popReferenceCenter(dc); } } /** * 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 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 = this.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(this.screenPoint.x, this.screenPoint.y, 0); gl.glEnd(); gl.glDepthRange(0, 1); // reset depth range to the OGL default if (!dc.isPickingMode()) // don't pick via the label this.drawLabel(dc); } 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() || this.getAltitudeMode() == WorldWind.CLAMP_TO_GROUND || this.terrainPoint == null) return false; if (dc.isPickingMode()) return dc.getPickFrustums().intersectsAny(this.placePoint, this.terrainPoint); else return dc.getView().getFrustumInModelCoordinates().intersectsSegment(this.placePoint, this.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(); pickCandidates.addPickableObject(pickColor.getRGB(), 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 (!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) { URL localUrl = WorldWind.getDataFileStore().requestFile(address); // WWJ-434 (http://issues.worldwind.arc.nasa.gov/jira/browse/WWJ-434) includes a stack trace showing HTTP // access from BasicWWTexture on the EDT. This should not occur and I (tag) can find no path through the // code that would cause it. Nevertheless, I've added guard code here to prevent anything other than a file // based URL to be passed to the BasicWWTexture constructor. if (localUrl != null) { if (!"file".equals(localUrl.getProtocol())) { Logging.logger().warning(Logging.getMessage("generic.URLProtocolNotFile", localUrl)); return 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) { this.placePoint = null; this.terrainPoint = null; this.screenPoint = null; Position pos = this.getPosition(); if (pos == null) return; if (this.altitudeMode == WorldWind.CLAMP_TO_GROUND) { this.placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0); } else if (this.altitudeMode == WorldWind.RELATIVE_TO_GROUND) { this.placePoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), pos.getAltitude()); } else // ABSOLUTE { double height = pos.getElevation() * (this.isApplyVerticalExaggeration() ? dc.getVerticalExaggeration() : 1); this.placePoint = dc.getGlobe().computePointFromPosition(pos.getLatitude(), pos.getLongitude(), height); } if (this.placePoint == null) return; // Compute a terrain point if needed. if (this.isLineEnabled() && this.altitudeMode != WorldWind.CLAMP_TO_GROUND) this.terrainPoint = dc.computeTerrainPoint(pos.getLatitude(), pos.getLongitude(), 0); // Compute the placemark point's screen location. this.screenPoint = dc.getView().project(this.placePoint); this.eyeDistance = this.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 Rectangle computeImageRectangle(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 = this.screenPoint.x + (this.isDrawPoint(dc) ? -0.5 * s : this.dx); double y = this.screenPoint.y + (this.isDrawPoint(dc) ? -0.5 * s : this.dy); return new Rectangle((int) x, (int) y, (int) Math.ceil(width), (int) Math.ceil(height)); } 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); } /** * 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