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

gov.nasa.worldwind.layers.TerrainProfileLayer 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.layers;

import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.event.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.view.orbit.OrbitView;

import com.jogamp.opengl.*;
import java.awt.*;
import java.awt.geom.*;
import java.beans.PropertyChangeEvent;
import java.util.*;
import java.util.List;

/**
 * Displays a terrain profile graph in a screen corner. 

Usage: do setEventSource(wwd) to have the graph * activated and updated with position changes. See public properties for options: keepProportions, follow, unit, start * and end latlon...

* * @author Patrick Murris * @version $Id: TerrainProfileLayer.java 2053 2014-06-10 20:16:57Z tgaskins $ */ public class TerrainProfileLayer extends AbstractLayer implements PositionListener, SelectListener { // Units constants public final static String UNIT_METRIC = "gov.nasa.worldwind.TerrainProfileLayer.Metric"; public final static String UNIT_IMPERIAL = "gov.nasa.worldwind.TerrainProfileLayer.Imperial"; public final static double METER_TO_FEET = 3.280839895; // Follow constants public final static String FOLLOW_VIEW = "gov.nasa.worldwind.TerrainProfileLayer.FollowView"; public final static String FOLLOW_EYE = "gov.nasa.worldwind.TerrainProfileLayer.FollowEye"; public final static String FOLLOW_CURSOR = "gov.nasa.worldwind.TerrainProfileLayer.FollowCursor"; public final static String FOLLOW_NONE = "gov.nasa.worldwind.TerrainProfileLayer.FollowNone"; public final static String FOLLOW_OBJECT = "gov.nasa.worldwind.TerrainProfileLayer.FollowObject"; public final static String FOLLOW_PATH = "gov.nasa.worldwind.TerrainProfileLayer.FollowPath"; // Graph size when minimized protected final static int MINIMIZED_SIZE = 32; // GUI pickable objects protected final String buttonMinimize = "gov.nasa.worldwind.TerrainProfileLayer.ButtonMinimize"; protected final String buttonMaximize = "gov.nasa.worldwind.TerrainProfileLayer.ButtonMaximize"; // Display parameters protected Dimension size = new Dimension(250, 100); protected Color color = Color.white; protected int borderWidth = 20; protected String position = AVKey.SOUTHWEST; protected String resizeBehavior = AVKey.RESIZE_SHRINK_ONLY; protected String unit = UNIT_METRIC; protected Font defaultFont = Font.decode("Arial-PLAIN-12"); protected double toViewportScale = 1; protected Point locationCenter = null; protected Vec4 locationOffset = null; protected PickSupport pickSupport = new PickSupport(); protected boolean initialized = false; protected boolean isMinimized = false; // True when graph is minimized to an icon protected boolean isMaximized = false; // True when graph is 'full screen' protected boolean showProfileLine = true; // True to show the profile path line on the ground protected boolean showPickedLine = true; // True to show the picked position line on the terrain protected int pickedSample = -1; // Picked sample number if not -1 Polyline selectionShape; // Shape showing section on the ground Polyline pickedShape; // Shape showing actual pick position on the ground protected boolean keepProportions = false; // Keep graph distance/elevation proportions protected boolean zeroBased = true; // Pad graph elevation scale to include sea level if true protected String follow = FOLLOW_VIEW; // Profile position follow behavior protected boolean showEyePosition = false; // When FOLLOW_EYE, draw the eye position on graph when true protected double profileLengthFactor = 1; // Applied to default profile length (zoom on profile) protected LatLon startLatLon; // Section start lat/lon when FOLLOW_NONE protected LatLon endLatLon; // Section end lat/lon when FOLLOW_NONE protected Position objectPosition; // Object position if FOLLOW_OBJECT protected Angle objectHeading; // Object heading if FOLLOW_OBJECT protected ArrayList pathPositions; // Path position list if FOLLOW_PATH protected int pathType = Polyline.GREAT_CIRCLE; // Terrain profile data protected int samples = 250; // Number of position samples protected double minElevation; // Minimum elevation along the profile protected double maxElevation; // Maximum elevation along the profile protected double length; // Profile length along great circle in meter protected Position positions[]; // Position list // Worldwind protected WorldWindow wwd; // Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things. protected OrderedIcon orderedImage = new OrderedIcon(); protected class OrderedIcon implements OrderedRenderable { public double getDistanceFromEye() { return 0; } public void pick(DrawContext dc, Point pickPoint) { TerrainProfileLayer.this.drawProfile(dc); } public void render(DrawContext dc) { TerrainProfileLayer.this.drawProfile(dc); } } /** Renders a terrain profile graphic in a screen corner. */ public TerrainProfileLayer() { } // ** Public properties ************************************************************ /** * Get whether the profile graph is minimized. * * @return true if the profile graph is minimized. */ @SuppressWarnings({"UnusedDeclaration"}) public boolean getIsMinimized() { return this.isMinimized; } /** * Set whether the profile graph should be minimized.

Note that the graph can be both minimized and maximized at * the same time. The minimized state will take precedence and the graph will display as an icon. When * 'un-minimized' it will display as maximized.

* * @param state true if the profile should be minimized. */ public void setIsMinimized(boolean state) { this.isMinimized = state; this.pickedSample = -1; // Clear picked position } /** * Get whether the profile graph is maximized - displays over the whole viewport. * * @return true if the profile graph is maximized. */ @SuppressWarnings({"UnusedDeclaration"}) public boolean getIsMaximized() { return this.isMaximized; } /** * Set wheter the profile graph should be maximized - displays over the whole viewport. * * @param state true if the profile should be maximized. */ public void setIsMaximized(boolean state) { this.isMaximized = state; } /** * Get the graphic Dimension (in pixels). * * @return the scalebar graphic Dimension. */ public Dimension getSize() { return this.size; } /** * Set the graphic Dimension (in pixels). * * @param size the graphic Dimension. */ public void setSize(Dimension size) { if (size == null) { String message = Logging.getMessage("nullValue.DimensionIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.size = size; } /** * Get the graphic color. * * @return the graphic Color. */ public Color getColor() { return this.color; } /** * Set the graphic Color. * * @param color the graphic Color. */ public void setColor(Color color) { if (color == null) { String msg = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.color = color; } /** * Sets the layer opacity, which is applied to the layer's chart. The opacity is modified slightly for various * elements of the chart to distinguish among them. * * @param opacity the current opacity value, which is ignored by this layer. * * @see #setColor */ @Override public void setOpacity(double opacity) { super.setOpacity(opacity); } /** * Returns the layer's opacity value. The opacity is modified slightly for various elements of the chart to * distinguish among them. * * @return The layer opacity, a value between 0 and 1. * * @see #getColor */ @Override public double getOpacity() { return super.getOpacity(); } /** * Returns the graphic-to-viewport scale factor. * * @return the graphic-to-viewport scale factor. */ @SuppressWarnings({"UnusedDeclaration"}) public double getToViewportScale() { return toViewportScale; } /** * Sets the scale factor applied to the viewport size to determine the displayed size of the graphic. This scale * factor is used only when the layer's resize behavior is {@link AVKey#RESIZE_STRETCH} or {@link * AVKey#RESIZE_SHRINK_ONLY}. The graphic's width is adjusted to occupy the proportion of the viewport's width * indicated by this factor. The graphic's height is adjusted to maintain the graphic's Dimension aspect ratio. * * @param toViewportScale the graphic to viewport scale factor. */ @SuppressWarnings({"UnusedDeclaration"}) public void setToViewportScale(double toViewportScale) { this.toViewportScale = toViewportScale; } public String getPosition() { return this.position; } /** * Sets the relative viewport location to display the graphic. Can be one of {@link AVKey#NORTHEAST}, {@link * AVKey#NORTHWEST}, {@link AVKey#SOUTHEAST}, or {@link AVKey#SOUTHWEST} (the default). These indicate the corner of * the viewport. * * @param position the desired graphic position. */ public void setPosition(String position) { if (position == null) { String msg = Logging.getMessage("nullValue.PositionIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.position = position; } /** * Get the screen location of the graph center if set (can be null). * * @return the screen location of the graph center if set (can be null). */ @SuppressWarnings({"UnusedDeclaration"}) public Point getLocationCenter() { return this.locationCenter; } /** * Set the screen location of the graph center - overrides {@link #setPosition} if not null. * * @param point the screen location of the graph center (can be null). */ public void setLocationCenter(Point point) { this.locationCenter = point; } /** * Returns the current location offset. See {@link #setLocationOffset} for a description of the offset and its * values. * * @return the location offset. Will be null if no offset has been specified. */ @SuppressWarnings({"UnusedDeclaration"}) public Vec4 getLocationOffset() { return locationOffset; } /** * Specifies a placement offset from the layer position on the screen. * * @param locationOffset the number of pixels to shift the layer image from its specified screen position. A * positive X value shifts the image to the right. A positive Y value shifts the image up. If * null, no offset is applied. The default offset is null. * * @see #setLocationCenter * @see #setPosition */ public void setLocationOffset(Vec4 locationOffset) { this.locationOffset = locationOffset; } /** * Returns the layer's resize behavior. * * @return the layer's resize behavior. */ @SuppressWarnings({"UnusedDeclaration"}) public String getResizeBehavior() { return resizeBehavior; } /** * Sets the behavior the layer uses to size the graphic when the viewport size changes, typically when the World * Wind window is resized. If the value is {@link AVKey#RESIZE_KEEP_FIXED_SIZE}, the graphic size is kept to the * size specified in its Dimension scaled by the layer's current icon scale. If the value is {@link * AVKey#RESIZE_STRETCH}, the graphic is resized to have a constant size relative to the current viewport size. If * the viewport shrinks the graphic size decreases; if it expands then the graphic enlarges. If the value is {@link * AVKey#RESIZE_SHRINK_ONLY} (the default), graphic sizing behaves as for {@link AVKey#RESIZE_STRETCH} but it will * not grow larger than the size specified in its Dimension. * * @param resizeBehavior the desired resize behavior */ @SuppressWarnings({"UnusedDeclaration"}) public void setResizeBehavior(String resizeBehavior) { this.resizeBehavior = resizeBehavior; } public int getBorderWidth() { return borderWidth; } /** * Sets the graphic offset from the viewport border. * * @param borderWidth the number of pixels to offset the graphic from the borders indicated by {@link * #setPosition(String)}. */ public void setBorderWidth(int borderWidth) { this.borderWidth = borderWidth; } @SuppressWarnings({"UnusedDeclaration"}) public String getUnit() { return this.unit; } /** * Sets the unit the graphic uses to display distances and elevations. Can be one of {@link #UNIT_METRIC} (the * default), or {@link #UNIT_IMPERIAL}. * * @param unit the desired unit. */ public void setUnit(String unit) { this.unit = unit; } /** * Get the graphic legend Font. * * @return the graphic legend Font. */ public Font getFont() { return this.defaultFont; } /** * Set the graphic legend Font. * * @param font the graphic legend Font. */ public void setFont(Font font) { if (font == null) { String msg = Logging.getMessage("nullValue.FontIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.defaultFont = font; } /** * Get whether distance/elevation proportions are maintained. * * @return true if the graph maintains distance/elevation proportions. */ public boolean getKeepProportions() { return this.keepProportions; } /** * Set whether distance/elevation proportions are maintained. * * @param state true if the graph should maintains distance/elevation proportions. */ public void setKeepProportions(boolean state) { this.keepProportions = state; } /** * Get the graph center point placement behavior. * * @return the graph center point placement behavior. */ public String getFollow() { return this.follow; } /** * Set the graph center point placement behavior. Can be one of {@link #FOLLOW_VIEW} (the default), {@link * #FOLLOW_CURSOR}, {@link #FOLLOW_EYE}, {@link #FOLLOW_OBJECT}, {@link #FOLLOW_NONE} or {@link #FOLLOW_PATH}. If * {@link #FOLLOW_NONE} the profile will be computed between startLatLon and endLatLon. If {@link #FOLLOW_PATH} the * profile will be computed along the path defined with {@link #setPathPositions(java.util.ArrayList)}. * * @param behavior the graph center point placement behavior. */ public void setFollow(String behavior) { this.follow = behavior; } /** * Get whether the eye or object position is shown on the graph when {@link #FOLLOW_EYE} or {@link #FOLLOW_OBJECT}. * * @return true if the eye or object position is shown on the graph. */ public boolean getShowEyePosition() { return this.showEyePosition; } /** * Set whether the eye or object position should be shown on the graph when {@link #FOLLOW_EYE} or {@link * #FOLLOW_OBJECT}. * * @param state if true the eye or object position should be shown on the graph. */ public void setShowEyePosition(Boolean state) { this.showEyePosition = state; } /** * Set the profile length factor - has no effect if {@link #FOLLOW_NONE} or {@link #FOLLOW_PATH} * * @param factor the new profile length factor. */ public void setProfileLengthFactor(double factor) { this.profileLengthFactor = factor; } /** * Get the profile length factor. * * @return the profile length factor. */ public double getProfileLenghtFactor() { return this.profileLengthFactor; } /** * Get the profile start position lat/lon when {@link #FOLLOW_NONE}. * * @return the profile start position lat/lon. */ @SuppressWarnings({"UnusedDeclaration"}) public LatLon getStartLatLon() { return this.startLatLon; } /** * Set the profile start position lat/lon when {@link #FOLLOW_NONE}. * * @param latLon the profile start position lat/lon. */ public void setStartLatLon(LatLon latLon) { if (latLon == null) { String msg = Logging.getMessage("nullValue.LatLonIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.startLatLon = latLon; } /** * Get the profile end position lat/lon when {@link #FOLLOW_NONE}. * * @return the profile end position lat/lon. */ @SuppressWarnings({"UnusedDeclaration"}) public LatLon getEndLatLon() { return this.endLatLon; } /** * Set the profile end position lat/lon when {@link #FOLLOW_NONE}. * * @param latLon the profile end position lat/lon. */ public void setEndLatLon(LatLon latLon) { if (latLon == null) { String msg = Logging.getMessage("nullValue.LatLonIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.endLatLon = latLon; } /** * Get the number of elevation samples in the profile. * * @return the number of elevation samples in the profile. */ @SuppressWarnings({"UnusedDeclaration"}) public int getSamples() { return this.samples; } /** * Set the number of elevation samples in the profile. * * @param number the number of elevation samples in the profile. */ @SuppressWarnings({"UnusedDeclaration"}) public void setSamples(int number) { this.samples = Math.abs(number); } /** * Get whether the profile graph should include sea level. * * @return whether the profile graph should include sea level. */ public boolean getZeroBased() { return this.zeroBased; } /** * Set whether the profile graph should include sea level. True is the default. * * @param state true if the profile graph should include sea level. */ public void setZeroBased(boolean state) { this.zeroBased = state; } /** * Get the object position the graph follows when it is set to {@link #FOLLOW_OBJECT}. * * @return the object position the graph follows. */ @SuppressWarnings({"UnusedDeclaration"}) public Position getObjectPosition() { return this.objectPosition; } /** * Set the object position the graph follows when it is set to {@link #FOLLOW_OBJECT}. * * @param pos the object position the graph follows. */ public void setObjectPosition(Position pos) { this.objectPosition = pos; } /** * Get the object heading the graph follows when it is set to {@link #FOLLOW_OBJECT}. The profile graph will be * computed perpendicular to the object heading direction. * * @return the object heading the graph follows. */ @SuppressWarnings({"UnusedDeclaration"}) public Angle getObjectHeading() { return this.objectHeading; } /** * Set the object heading the graph follows when it is set to {@link #FOLLOW_OBJECT}. The profile graph will be * computed perpendicular to the object heading direction. * * @param heading the object heading the graph follows. */ public void setObjectHeading(Angle heading) { this.objectHeading = heading; } /** * Get the path positions that the profile follows if set to {@link #FOLLOW_PATH}. * * @return the path positions that the profile follows. */ @SuppressWarnings({"UnusedDeclaration"}) public List getPathPositions() { return this.pathPositions; } /** * Set the path positions that the profile should follow if {@link #FOLLOW_PATH}. * * @param positions the path positions that the profile should follow. */ public void setPathPositions(ArrayList positions) { if (positions == null) { String msg = Logging.getMessage("nullValue.PositionsListIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } this.pathPositions = positions; } @SuppressWarnings({"UnusedDeclaration"}) public int getPathType() { return this.pathType; } /** * Sets the type of path to follow, one of {@link Polyline#GREAT_CIRCLE}, which computes each segment of the path as * a great circle, or {@link Polyline#RHUMB_LINE}, which computes each segment of the path as a line of constant * heading. * * @param type the type of path to follow. */ public void setPathType(int type) { this.pathType = type; } /** * Get the Polyline used to render the profile line on the ground. * * @return the Polyline used to render the profile line on the ground. */ @SuppressWarnings({"UnusedDeclaration"}) public Polyline getProfileLine() { return this.selectionShape; } /** * Get the Polyline used to render the picked position on the terrain. * * @return the Polyline used to render the picked position on the terrain. */ @SuppressWarnings({"UnusedDeclaration"}) public Polyline getPickedLine() { return this.selectionShape; } /** * Get whether the profile line is displayed over the ground. * * @return true is the profile line is displayed over the ground. */ @SuppressWarnings({"UnusedDeclaration"}) public boolean isShowProfileLine() { return this.showProfileLine; } /** * Set whether the profile line should be displayed over the terrain. * * @param state true if the profile line should be displayed over the terrain. */ public void setShowProfileLine(boolean state) { this.showProfileLine = state; } /** * Get whether the picked line is displayed over the ground. * * @return true if the picked line is displayed over the ground. */ @SuppressWarnings({"UnusedDeclaration"}) public boolean isShowPickedLine() { return this.showPickedLine; } /** * Set whether the picked line should be displayed over the ground. * * @param state if the picked line should be displayed over the ground. */ @SuppressWarnings({"UnusedDeclaration"}) public void setShowPickedLine(boolean state) { this.showPickedLine = state; } // ** Rendering ************************************************************ @Override public void doRender(DrawContext dc) { // Delegate graph rendering to OrderedRenderable list dc.addOrderedRenderable(this.orderedImage); // Render section line on the ground now if (!isMinimized && this.positions != null && this.selectionShape != null) { if (this.showProfileLine) this.selectionShape.render(dc); // If picking in progress, render pick indicator if (this.showPickedLine && this.pickedSample != -1 && this.pickedShape != null) this.pickedShape.render(dc); } } @Override public void doPick(DrawContext dc, Point pickPoint) { dc.addOrderedRenderable(this.orderedImage); } protected void initialize(DrawContext dc) { if (this.initialized || this.positions != null) return; if (this.wwd != null) { this.computeProfile(dc); // this.expirySupport.restart(dc); } if (this.positions != null) this.initialized = true; } // Profile graph rendering - ortho public void drawProfile(DrawContext dc) { this.computeProfile(dc); if ((this.positions == null || (this.minElevation == 0 && this.maxElevation == 0)) && !this.initialized) this.initialize(dc); if (this.positions == null || (this.minElevation == 0 && this.maxElevation == 0)) return; GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. boolean attribsPushed = false; boolean modelviewPushed = false; boolean projectionPushed = false; try { gl.glPushAttrib(GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_COLOR_BUFFER_BIT | GL2.GL_ENABLE_BIT | GL2.GL_TRANSFORM_BIT | GL2.GL_VIEWPORT_BIT | GL2.GL_CURRENT_BIT); attribsPushed = true; gl.glDisable(GL.GL_TEXTURE_2D); // no textures gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glDisable(GL.GL_DEPTH_TEST); Rectangle viewport = dc.getView().getViewport(); Dimension drawSize = isMinimized ? new Dimension(MINIMIZED_SIZE, MINIMIZED_SIZE) : isMaximized ? new Dimension(viewport.width - this.borderWidth * 2, viewport.height * 2 / 3 - this.borderWidth * 2) : this.size; double width = drawSize.width; double height = drawSize.height; // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight) // into the GL projection matrix. gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPushMatrix(); projectionPushed = true; gl.glLoadIdentity(); double maxwh = width > height ? width : height; gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh); gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPushMatrix(); modelviewPushed = true; gl.glLoadIdentity(); // Scale to a width x height space // located at the proper position on screen double scale = this.computeScale(viewport); Vec4 locationSW = this.computeLocation(viewport, scale); gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z()); gl.glScaled(scale, scale, 1d); if (!dc.isPickingMode()) { // Draw grid - Set color using current layer opacity this.drawGrid(dc, drawSize); // Draw profile graph this.drawGraph(dc, drawSize); if (!isMinimized) { // Draw GUI buttons drawGUI(dc, drawSize); // Draw labels String label = String.format("min %.0fm max %.0fm", this.minElevation, this.maxElevation); if (this.unit.equals(UNIT_IMPERIAL)) label = String.format("min %.0fft max %.0fft", this.minElevation * METER_TO_FEET, this.maxElevation * METER_TO_FEET); gl.glLoadIdentity(); gl.glDisable(GL.GL_CULL_FACE); drawLabel(dc, label, locationSW.add3(new Vec4(0, -12, 0)), -1); // left aligned if (this.pickedSample != -1) { double pickedElevation = positions[this.pickedSample].getElevation(); label = String.format("%.0fm", pickedElevation); if (this.unit.equals(UNIT_IMPERIAL)) label = String.format("%.0fft", pickedElevation * METER_TO_FEET); drawLabel(dc, label, locationSW.add3(new Vec4(width, -12, 0)), 1); // right aligned } } } else { // Picking this.pickSupport.clearPickList(); this.pickSupport.beginPicking(dc); // Draw unique color across the rectangle Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); if (!isMinimized) { // Update graph pick point computePickPosition(dc, locationSW, new Dimension((int) (width * scale), (int) (height * scale))); // Draw GUI buttons drawGUI(dc, drawSize); } else { // Add graph to the pickable list for 'un-minimize' click this.pickSupport.addPickableObject(colorCode, this); gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); gl.glBegin(GL2.GL_POLYGON); gl.glVertex3d(0, 0, 0); gl.glVertex3d(width, 0, 0); gl.glVertex3d(width, height, 0); gl.glVertex3d(0, height, 0); gl.glVertex3d(0, 0, 0); gl.glEnd(); } // Done picking this.pickSupport.endPicking(dc); this.pickSupport.resolvePick(dc, dc.getPickPoint(), this); } } catch (Exception e) { //e.printStackTrace(); } finally { if (projectionPushed) { gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPopMatrix(); } if (modelviewPushed) { gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPopMatrix(); } if (attribsPushed) gl.glPopAttrib(); } } // Draw grid graphic protected void drawGrid(DrawContext dc, Dimension dimension) { // Background color Color backColor = getBackgroundColor(this.color); drawFilledRectangle(dc, new Vec4(0, 0, 0), dimension, new Color(backColor.getRed(), backColor.getGreen(), backColor.getBlue(), (int) (backColor.getAlpha() * .5))); // Increased transparency // Grid - minimal float[] colorRGB = this.color.getRGBColorComponents(null); GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity()); drawVerticalLine(dc, dimension, 0); drawVerticalLine(dc, dimension, dimension.getWidth()); drawHorizontalLine(dc, dimension, 0); } // Draw profile graphic protected void drawGraph(DrawContext dc, Dimension dimension) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. // Adjust min/max elevation for the graph double min = this.minElevation; double max = this.maxElevation; if (this.showEyePosition && this.follow.equals(FOLLOW_EYE)) max = Math.max(max, dc.getView().getEyePosition().getElevation()); if (this.showEyePosition && (this.follow.equals(FOLLOW_OBJECT) || this.follow.equals(FOLLOW_PATH)) && this.objectPosition != null) max = Math.max(max, this.objectPosition.getElevation()); if (this.zeroBased) { if (min > 0) min = 0; if (max < 0) max = 0; } int i; double stepX = dimension.getWidth() / this.length; double stepY = dimension.getHeight() / (max - min); if (this.keepProportions) { stepX = Math.min(stepX, stepY); //noinspection SuspiciousNameCombination stepY = stepX; } double lengthStep = this.length / (this.samples - 1); double x = 0, y; // Filled graph gl.glColor4ub((byte) this.color.getRed(), (byte) this.color.getGreen(), (byte) this.color.getBlue(), (byte) 100); gl.glBegin(GL2.GL_TRIANGLE_STRIP); for (i = 0; i < this.samples; i++) { x = i * lengthStep * stepX; y = (this.positions[i].getElevation() - min) * stepY; gl.glVertex3d(x, 0, 0); gl.glVertex3d(x, y, 0); } gl.glEnd(); // Line graph float[] colorRGB = this.color.getRGBColorComponents(null); gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity()); gl.glBegin(GL2.GL_LINE_STRIP); for (i = 0; i < this.samples; i++) { x = i * lengthStep * stepX; y = (this.positions[i].getElevation() - min) * stepY; gl.glVertex3d(x, y, 0); } gl.glEnd(); // Middle vertical line gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity() * .3); // increased transparency here if (!this.follow.equals(FOLLOW_PATH)) drawVerticalLine(dc, dimension, x / 2); // Eye or object position double eyeX = -1, eyeY = -1; if ((this.follow.equals(FOLLOW_EYE) || (this.follow.equals(FOLLOW_OBJECT) && this.objectPosition != null) || (this.follow.equals(FOLLOW_PATH) && this.objectPosition != null)) && this.showEyePosition) { eyeX = x / 2; eyeY = (dc.getView().getEyePosition().getElevation() - min) * stepY; if (this.follow.equals(FOLLOW_PATH)) eyeX = computeObjectSample(this.objectPosition) * lengthStep * stepX; if (this.follow.equals(FOLLOW_OBJECT) || this.follow.equals(FOLLOW_PATH)) eyeY = (this.objectPosition.getElevation() - min) * stepY; if (eyeX >= 0 && eyeY >= 0) this.drawFilledRectangle(dc, new Vec4(eyeX - 2, eyeY - 2, 0), new Dimension(5, 5), this.color); // Vertical line at object position when follow path if (this.follow.equals(FOLLOW_PATH) && eyeX >= 0) drawVerticalLine(dc, dimension, eyeX); } // Selected/picked vertical and horizontal lines if (this.pickedSample != -1) { double pickedX = this.pickedSample * lengthStep * stepX; double pickedY = (positions[this.pickedSample].getElevation() - min) * stepY; gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2] * .5, this.getOpacity() * .8); // yellower color drawVerticalLine(dc, dimension, pickedX); drawHorizontalLine(dc, dimension, pickedY); // Eye or object - picked position line if (eyeX >= 0 && eyeY >= 0) { // Line drawLine(dc, pickedX, pickedY, eyeX, eyeY); // Distance label double distance = dc.getView().getEyePoint().distanceTo3( dc.getGlobe().computePointFromPosition(positions[this.pickedSample])); if (this.follow.equals(FOLLOW_OBJECT) || this.follow.equals(FOLLOW_PATH)) distance = dc.getGlobe().computePointFromPosition(this.objectPosition).distanceTo3( dc.getGlobe().computePointFromPosition(positions[this.pickedSample])); String label = String.format("Dist %.0fm", distance); if (this.unit.equals(UNIT_IMPERIAL)) label = String.format("Dist %.0fft", distance * METER_TO_FEET); drawLabel(dc, label, new Vec4(pickedX + 5, pickedY - 12, 0), -1); // left aligned } } // Min elevation horizontal line if (this.minElevation != min) { y = (this.minElevation - min) * stepY; gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity() * .5); // medium transparency drawHorizontalLine(dc, dimension, y); } // Max elevation horizontal line if (this.maxElevation != max) { y = (this.maxElevation - min) * stepY; gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity() * .5); // medium transparency drawHorizontalLine(dc, dimension, y); } // Sea level in between positive elevations only (not across land) if (min < 0 && max >= 0) { gl.glColor4d(colorRGB[0] * .7, colorRGB[1] * .7, colorRGB[2], this.getOpacity() * .5); // bluer color y = -this.minElevation * stepY; double previousX = -1; for (i = 0; i < this.samples; i++) { x = i * lengthStep * stepX; if (this.positions[i].getElevation() > 0 || i == this.samples - 1) { if (previousX >= 0) { gl.glBegin(GL2.GL_LINE_STRIP); gl.glVertex3d(previousX, y, 0); gl.glVertex3d(x, y, 0); gl.glEnd(); previousX = -1; } } else previousX = previousX < 0 ? x : previousX; } } } protected void drawGUI(DrawContext dc, Dimension dimension) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. int buttonSize = 16; int hs = buttonSize / 2; int buttonBorder = 4; Dimension buttonDimension = new Dimension(buttonSize, buttonSize); Color highlightColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) (color.getAlpha() * .5)); Color backColor = getBackgroundColor(this.color); backColor = new Color(backColor.getRed(), backColor.getGreen(), backColor.getBlue(), (int) (backColor.getAlpha() * .5)); // Increased transparency Color drawColor; int y = dimension.height - buttonDimension.height - buttonBorder; int x = dimension.width; Object pickedObject = dc.getPickedObjects() != null ? dc.getPickedObjects().getTopObject() : null; // Maximize button if (!isMaximized) { x -= buttonDimension.width + buttonBorder; if (dc.isPickingMode()) { drawColor = dc.getUniquePickColor(); int colorCode = drawColor.getRGB(); this.pickSupport.addPickableObject(colorCode, this.buttonMaximize, null, false); } else drawColor = this.buttonMaximize == pickedObject ? highlightColor : backColor; drawFilledRectangle(dc, new Vec4(x, y, 0), buttonDimension, drawColor); if (!dc.isPickingMode()) { gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(), (byte) color.getAlpha()); // Draw '+' drawLine(dc, x + 3, y + hs, x + buttonDimension.width - 3, y + hs); // Horizontal line drawLine(dc, x + hs, y + 3, x + hs, y + buttonDimension.height - 3); // Vertical line } } // Minimize button x -= buttonDimension.width + buttonBorder; if (dc.isPickingMode()) { drawColor = dc.getUniquePickColor(); int colorCode = drawColor.getRGB(); this.pickSupport.addPickableObject(colorCode, this.buttonMinimize, null, false); } else drawColor = this.buttonMinimize == pickedObject ? highlightColor : backColor; drawFilledRectangle(dc, new Vec4(x, y, 0), buttonDimension, drawColor); if (!dc.isPickingMode()) { gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(), (byte) color.getAlpha()); // Draw '-' drawLine(dc, x + 3, y + hs, x + buttonDimension.width - 3, y + hs); // Horizontal line } } protected void drawHorizontalLine(DrawContext dc, Dimension dimension, double y) { drawLine(dc, 0, y, dimension.getWidth(), y); } protected void drawVerticalLine(DrawContext dc, Dimension dimension, double x) { drawLine(dc, x, 0, x, dimension.getHeight()); } protected void drawFilledRectangle(DrawContext dc, Vec4 origin, Dimension dimension, Color color) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glColor4ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue(), (byte) color.getAlpha()); gl.glDisable(GL.GL_TEXTURE_2D); // no textures gl.glBegin(GL2.GL_POLYGON); gl.glVertex3d(origin.x, origin.y, 0); gl.glVertex3d(origin.x + dimension.getWidth(), origin.y, 0); gl.glVertex3d(origin.x + dimension.getWidth(), origin.y + dimension.getHeight(), 0); gl.glVertex3d(origin.x, origin.y + dimension.getHeight(), 0); gl.glVertex3d(origin.x, origin.y, 0); gl.glEnd(); } protected void drawLine(DrawContext dc, double x1, double y1, double x2, double y2) { GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility. gl.glBegin(GL2.GL_LINE_STRIP); gl.glVertex3d(x1, y1, 0); gl.glVertex3d(x2, y2, 0); gl.glEnd(); } // Draw a text label // Align = -1: left, 0: center and 1: right protected void drawLabel(DrawContext dc, String text, Vec4 screenPoint, int align) { TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), this.defaultFont); Rectangle2D nameBound = textRenderer.getBounds(text); int x = (int) screenPoint.x(); // left if (align == 0) x = (int) (screenPoint.x() - nameBound.getWidth() / 2d); // centered if (align > 0) x = (int) (screenPoint.x() - nameBound.getWidth()); // right int y = (int) screenPoint.y(); textRenderer.begin3DRendering(); textRenderer.setColor(this.getBackgroundColor(this.color)); textRenderer.draw(text, x + 1, y - 1); textRenderer.setColor(this.color); textRenderer.draw(text, x, y); textRenderer.end3DRendering(); } // Compute background color for best contrast protected Color getBackgroundColor(Color color) { float[] compArray = new float[4]; Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), compArray); if (compArray[2] > 0.5) return new Color(0, 0, 0, (int) (this.color.getAlpha() * 0.7f)); else return new Color(255, 255, 255, (int) (this.color.getAlpha() * 0.7f)); } // ** Dimensions and positionning ************************************************************ protected double computeScale(java.awt.Rectangle viewport) { if (this.resizeBehavior.equals(AVKey.RESIZE_SHRINK_ONLY)) { return Math.min(1d, (this.toViewportScale) * viewport.width / this.size.width); } else if (this.resizeBehavior.equals(AVKey.RESIZE_STRETCH)) { return (this.toViewportScale) * viewport.width / this.size.width; } else if (this.resizeBehavior.equals(AVKey.RESIZE_KEEP_FIXED_SIZE)) { return 1d; } else { return 1d; } } protected Vec4 computeLocation(java.awt.Rectangle viewport, double scale) { double scaledWidth = scale * (isMinimized ? MINIMIZED_SIZE : isMaximized ? viewport.width - this.borderWidth * 2 : this.size.width); double scaledHeight = scale * (isMinimized ? MINIMIZED_SIZE : isMaximized ? viewport.height * 2 / 3 - this.borderWidth * 2 : this.size.height); double x; double y; if (this.locationCenter != null) { x = this.locationCenter.x - scaledWidth / 2; y = this.locationCenter.y - scaledHeight / 2; } else if (this.position.equals(AVKey.NORTHEAST)) { x = viewport.getWidth() - scaledWidth - this.borderWidth; y = viewport.getHeight() - scaledHeight - this.borderWidth; } else if (this.position.equals(AVKey.SOUTHEAST)) { x = viewport.getWidth() - scaledWidth - this.borderWidth; y = 0d + this.borderWidth; } else if (this.position.equals(AVKey.NORTHWEST)) { x = 0d + this.borderWidth; y = viewport.getHeight() - scaledHeight - this.borderWidth; } else if (this.position.equals(AVKey.SOUTHWEST)) { x = 0d + this.borderWidth; y = 0d + this.borderWidth; } else // use North East { x = viewport.getWidth() - scaledWidth / 2 - this.borderWidth; y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth; } if (this.locationOffset != null) { x += this.locationOffset.x; y += this.locationOffset.y; } return new Vec4(x, y, 0); } /** * Computes the Position of the pickPoint over the graph and updates pickedSample indice * * @param dc the current DrawContext * @param locationSW the screen location of the bottom left corner of the graph * @param mapSize the graph screen dimension in pixels * * @return the picked Position */ protected Position computePickPosition(DrawContext dc, Vec4 locationSW, Dimension mapSize) { Position pickPosition = null; this.pickedSample = -1; Point pickPoint = dc.getPickPoint(); if (pickPoint != null && this.positions != null && !this.follow.equals(FOLLOW_CURSOR)) { Rectangle viewport = dc.getView().getViewport(); // Check if pickpoint is inside the graph if (pickPoint.getX() >= locationSW.getX() && pickPoint.getX() < locationSW.getX() + mapSize.width && viewport.height - pickPoint.getY() >= locationSW.getY() && viewport.height - pickPoint.getY() < locationSW.getY() + mapSize.height) { // Find sample - Note: only works when graph expends over the full width int sample = (int) (((double) (pickPoint.getX() - locationSW.getX()) / mapSize.width) * this.samples); if (sample >= 0 && sample < this.samples) { pickPosition = this.positions[sample]; this.pickedSample = sample; // Update polyline indicator ArrayList posList = new ArrayList(); posList.add(positions[sample]); posList.add(new Position(positions[sample].getLatitude(), positions[sample].getLongitude(), positions[sample].getElevation() + this.length / 10)); if (this.pickedShape == null) { this.pickedShape = new Polyline(posList); this.pickedShape.setPathType(Polyline.LINEAR); this.pickedShape.setLineWidth(2); this.pickedShape.setColor(new Color(this.color.getRed(), this.color.getGreen(), (int) (this.color.getBlue() * .8), (int) (255 * .8))); } else this.pickedShape.setPositions(posList); } } } return pickPosition; } /** * Compute the sample number along the path closest to the given LatLon. * * @param pos the object LatLon * * @return the sample number or -1 if not found. */ protected int computeObjectSample(LatLon pos) { if (this.pathPositions.size() < 2) return -1; double radius = this.wwd.getModel().getGlobe().getRadius(); double maxDistanceFromPath = 1000; // meters double distanceFromStart = 0; int segmentIndex = 0; LatLon pos1 = this.pathPositions.get(segmentIndex); for (int i = 1; i < this.pathPositions.size(); i++) { LatLon pos2 = this.pathPositions.get(i); double segmentLength = LatLon.greatCircleDistance(pos1, pos2).radians * radius; // Check wether the position is close to the segment line Vec4 v0 = new Vec4(pos.getLatitude().radians, pos.getLongitude().radians, 0, 0); Vec4 v1 = new Vec4(pos1.getLatitude().radians, pos1.getLongitude().radians, 0, 0); Vec4 v2 = new Vec4(pos2.getLatitude().radians, pos2.getLongitude().radians, 0, 0); Line line = new Line(v1, v2.subtract3(v1)); if (line.distanceTo(v0) * radius <= maxDistanceFromPath) { // Check whether the position is inside the segment double length1 = LatLon.greatCircleDistance(pos1, pos).radians * radius; double length2 = LatLon.greatCircleDistance(pos2, pos).radians * radius; if (length1 <= segmentLength && length2 <= segmentLength) { // Compute portion of segment length distanceFromStart += length1 / (length1 + length2) * segmentLength; break; } else distanceFromStart += segmentLength; } else distanceFromStart += segmentLength; // Next segment pos1 = pos2; } double pathLength = this.computePathLength(); return distanceFromStart < pathLength ? (int) (distanceFromStart / pathLength * this.samples) : -1; } // ** Position listener impl. ************************************************************ public void moved(PositionEvent event) { this.positions = null; } // ** Select listener impl. ************************************************************ public void selected(SelectEvent event) { if (event.hasObjects() && event.getEventAction().equals(SelectEvent.LEFT_CLICK)) { if (event.getMouseEvent() != null && event.getMouseEvent().isConsumed()) return; Object o = event.getTopObject(); if (o == this.buttonMinimize) { if (this.isMaximized) this.setIsMaximized(false); else this.setIsMinimized(true); } else if (o == this.buttonMaximize) { this.setIsMaximized(true); } else if (o == this && this.isMinimized) { this.setIsMinimized(false); } } } // ** Property change listener *********************************************************** public void propertyChange(PropertyChangeEvent propertyChangeEvent) { this.positions = null; } // Sets the wwd local reference and add us to the position listeners // the view and elevation model property change listener public void setEventSource(WorldWindow wwd) { if (this.wwd != null) { this.wwd.removePositionListener(this); this.wwd.getView().removePropertyChangeListener(this); // this.wwd.getModel().getGlobe().getElevationModel().removePropertyChangeListener(this); this.wwd.removeSelectListener(this); } this.wwd = wwd; if (this.wwd != null) { this.wwd.addPositionListener(this); this.wwd.getView().addPropertyChangeListener(this); // this.wwd.getModel().getGlobe().getElevationModel().addPropertyChangeListener(this); this.wwd.addSelectListener(this); } } // ** Profile data collection ************************************************************ /** * Compute the terrain profile.

If {@link #FOLLOW_VIEW}, {@link #FOLLOW_EYE}, {@link #FOLLOW_CURSOR} or {@link * #FOLLOW_OBJECT}, collects terrain profile data along a great circle line centered at the current position (view, * eye, cursor or object) and perpendicular to the view heading - or object heading if {@link #FOLLOW_OBJECT}.

* If {@link #FOLLOW_NONE} the profile is computed in between start and end latlon.

If {@link #FOLLOW_PATH} the * profile is computed along the path provided with {@link #setPathPositions(ArrayList)}

* * @param dc the current DrawContext */ protected void computeProfile(DrawContext dc) { if (this.wwd == null) return; try { // Find center position View view = this.wwd.getView(); Position groundPos = null; if (this.follow.equals(FOLLOW_VIEW)) groundPos = this.computeViewCenterPosition(dc); else if (this.follow.equals(FOLLOW_CURSOR)) groundPos = this.computeCursorPosition(dc); else if (this.follow.equals(FOLLOW_EYE)) groundPos = view.getEyePosition(); else if (this.follow.equals(FOLLOW_OBJECT)) groundPos = this.objectPosition; // Compute profile if we can if ((this.follow.equals(FOLLOW_VIEW) && groundPos != null) || (this.follow.equals(FOLLOW_EYE) && groundPos != null) || (this.follow.equals(FOLLOW_CURSOR) && groundPos != null) || (this.follow.equals(FOLLOW_NONE) && this.startLatLon != null && this.endLatLon != null) || (this.follow.equals(FOLLOW_OBJECT) && this.objectPosition != null && this.objectHeading != null) || (this.follow.equals(FOLLOW_PATH) && this.pathPositions != null && this.pathPositions.size() >= 2)) { this.positions = new Position[samples]; this.minElevation = Double.MAX_VALUE; this.maxElevation = -Double.MAX_VALUE; // Compute profile positions if (this.follow.equals(FOLLOW_PATH)) { computePathPositions(); } else { computeMirroredPositions(groundPos); } // Update shape on ground if (this.selectionShape == null) { this.selectionShape = new Polyline(Arrays.asList(this.positions)); this.selectionShape.setLineWidth(2); this.selectionShape.setFollowTerrain(true); this.selectionShape.setColor(new Color(this.color.getRed(), this.color.getGreen(), (int) (this.color.getBlue() * .5), (int) (255 * .8))); } else this.selectionShape.setPositions(Arrays.asList(this.positions)); } else { // Off globe or something missing this.positions = null; } } catch (Exception e) { this.positions = null; } } protected Position computeViewCenterPosition(DrawContext dc) { View view = dc.getView(); Line ray = view.computeRayFromScreenPoint(view.getViewport().getWidth() / 2, view.getViewport().getHeight() / 2); Intersection[] inters = dc.getSurfaceGeometry().intersect(ray); if (inters.length > 0) return dc.getGlobe().computePositionFromPoint(inters[0].getIntersectionPoint()); return null; } protected void computeMirroredPositions(LatLon centerLatLon) { View view = this.wwd.getView(); // Compute profile length if (view instanceof OrbitView) { this.length = Math.min(((OrbitView) view).getZoom() * .8 * this.profileLengthFactor, this.wwd.getModel().getGlobe().getRadius() * Math.PI); } else { this.length = Math.min(Math.abs(view.getEyePosition().getElevation()) * .8 * this.profileLengthFactor, this.wwd.getModel().getGlobe().getRadius() * Math.PI); } if (this.follow.equals(FOLLOW_NONE)) { this.length = LatLon.greatCircleDistance(this.startLatLon, this.endLatLon).radians * this.wwd.getModel().getGlobe().getRadius(); } else if (this.follow.equals(FOLLOW_OBJECT)) { this.length = Math.min(Math.abs(this.objectPosition.getElevation()) * .8 * this.profileLengthFactor, this.wwd.getModel().getGlobe().getRadius() * Math.PI); } double lengthRadian = this.length / this.wwd.getModel().getGlobe().getRadius(); // Iterate on both sides of the center point int i; double step = lengthRadian / (samples - 1); for (i = 0; i < this.samples; i++) { LatLon latLon = null; if (!this.follow.equals(FOLLOW_NONE)) { // Compute segments perpendicular to view or object heading double azimuth = view.getHeading().subtract(Angle.POS90).radians; if (this.follow.equals(FOLLOW_OBJECT)) azimuth = this.objectHeading.subtract(Angle.POS90).radians; if (i > (float) (this.samples - 1) / 2f) { //azimuth = view.getHeading().subtract(Angle.NEG90).radians; azimuth += Math.PI; } double distance = Math.abs(((double) i - ((double) (this.samples - 1) / 2d)) * step); latLon = LatLon.greatCircleEndPosition(centerLatLon, azimuth, distance); } else if (this.follow.equals(FOLLOW_NONE) && this.startLatLon != null && this.endLatLon != null) { // Compute segments between start and end positions latlon latLon = LatLon.interpolate((double) i / (this.samples - 1), this.startLatLon, this.endLatLon); } this.setPosition(i, latLon); } } protected void computePathPositions() { this.length = computePathLength(); double lengthRadians = this.length / this.wwd.getModel().getGlobe().getRadius(); double step = lengthRadians / (this.samples - 1); int segmentIndex = 0; LatLon latLon = this.pathPositions.get(segmentIndex); // Set first sample at path start this.setPosition(0, latLon); // Compute in between samples double distance, azimuth; for (int i = 1; i < this.samples - 1; i++) { double stepToGo = step; while (stepToGo > 0) { if (this.pathType == Polyline.RHUMB_LINE) distance = LatLon.rhumbDistance(latLon, this.pathPositions.get(segmentIndex + 1)).radians; else distance = LatLon.greatCircleDistance(latLon, this.pathPositions.get(segmentIndex + 1)).radians; if (distance >= stepToGo) { if (this.pathType == Polyline.RHUMB_LINE) { azimuth = LatLon.rhumbAzimuth(latLon, this.pathPositions.get(segmentIndex + 1)).radians; latLon = LatLon.rhumbEndPosition(latLon, azimuth, stepToGo); } else { azimuth = LatLon.greatCircleAzimuth(latLon, this.pathPositions.get(segmentIndex + 1)).radians; latLon = LatLon.greatCircleEndPosition(latLon, azimuth, stepToGo); } this.setPosition(i, latLon); stepToGo = 0; } else { segmentIndex++; latLon = this.pathPositions.get(segmentIndex); stepToGo -= distance; } } } // Set last sample at path end this.setPosition(this.samples - 1, this.pathPositions.get(this.pathPositions.size() - 1)); } protected double computePathLength() { double pathLengthRadians = 0; LatLon pos1 = null; for (LatLon pos2 : this.pathPositions) { if (pos1 != null) pathLengthRadians += LatLon.greatCircleDistance(pos1, pos2).radians; pos1 = pos2; } return pathLengthRadians * this.wwd.getModel().getGlobe().getRadius(); } protected void setPosition(int index, LatLon latLon) { Double elevation = this.wwd.getModel().getGlobe().getElevation(latLon.getLatitude(), latLon.getLongitude()); this.minElevation = elevation < this.minElevation ? elevation : this.minElevation; this.maxElevation = elevation > this.maxElevation ? elevation : this.maxElevation; // Add position to the list positions[index] = new Position(latLon, elevation); } protected Position computeCursorPosition(DrawContext dc) { Position pos = this.wwd.getCurrentPosition(); if (pos == null && dc.getPickPoint() != null) { // Current pos is null, try intersection with terrain geometry Line ray = dc.getView().computeRayFromScreenPoint(dc.getPickPoint().x, dc.getPickPoint().y); Intersection[] inter = dc.getSurfaceGeometry().intersect(ray); if (inter != null && inter.length > 0) pos = dc.getGlobe().computePositionFromPoint(inter[0].getIntersectionPoint()); } if (pos == null && dc.getPickPoint() != null) { // Position is still null, fallback on intersection with ellipsoid. pos = dc.getView().computePositionFromScreenPoint(dc.getPickPoint().x, dc.getPickPoint().y); } return pos; } @Override public String toString() { return Logging.getMessage("layers.Earth.TerrainProfileLayer.Name"); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy