1 package gov
.nasa
.worldwind
.layers
.Earth
;
3 import com
.sun
.opengl
.util
.j2d
.TextRenderer
;
4 import gov
.nasa
.worldwind
.WorldWindow
;
5 import gov
.nasa
.worldwind
.View
;
6 import gov
.nasa
.worldwind
.avlist
.AVKey
;
7 import gov
.nasa
.worldwind
.event
.*;
8 import gov
.nasa
.worldwind
.geom
.*;
9 import gov
.nasa
.worldwind
.layers
.AbstractLayer
;
10 import gov
.nasa
.worldwind
.pick
.PickSupport
;
11 import gov
.nasa
.worldwind
.render
.*;
12 import gov
.nasa
.worldwind
.util
.Logging
;
13 import gov
.nasa
.worldwind
.view
.*;
15 import javax
.media
.opengl
.GL
;
17 import java
.awt
.geom
.*;
18 import java
.beans
.PropertyChangeEvent
;
22 * Displays a terrain profile graph in a screen corner
24 * @author Patrick Murris
25 * @version $Id$ Usage: do setEventSource(wwd) to have the graph activated and updated with position changes See public
26 * properties for options: keepProportions, follow, unit, start and end latlon...
28 public class TerrainProfileLayer
extends AbstractLayer
implements PositionListener
31 // Positionning constants TODO: add north, east... center-north... center-screen.
32 public final static String NORTHWEST
= "gov.nasa.worldwind.TerrainProfileLayer.NorthWest";
33 public final static String SOUTHWEST
= "gov.nasa.worldwind.TerrainProfileLayer.SouthWest";
34 public final static String NORTHEAST
= "gov.nasa.worldwind.TerrainProfileLayer.NorthEast";
35 public final static String SOUTHEAST
= "gov.nasa.worldwind.TerrainProfileLayer.SouthEast";
36 // Stretching behavior constants
37 public final static String RESIZE_STRETCH
= "gov.nasa.worldwind.TerrainProfileLayer.Stretch";
38 public final static String RESIZE_SHRINK_ONLY
= "gov.nasa.worldwind.TerrainProfileLayer.ShrinkOnly";
39 public final static String RESIZE_KEEP_FIXED_SIZE
= "gov.nasa.worldwind.TerrainProfileLayer.FixedSize";
41 public final static String UNIT_METRIC
= "gov.nasa.worldwind.TerrainProfileLayer.Metric";
42 public final static String UNIT_IMPERIAL
= "gov.nasa.worldwind.TerrainProfileLayer.Imperial";
43 public final static double METER_TO_FEET
= 3.280839895;
45 public final static String FOLLOW_VIEW
= "gov.nasa.worldwind.TerrainProfileLayer.FollowView";
46 public final static String FOLLOW_EYE
= "gov.nasa.worldwind.TerrainProfileLayer.FollowEye";
47 public final static String FOLLOW_CURSOR
= "gov.nasa.worldwind.TerrainProfileLayer.FollowCursor";
48 public final static String FOLLOW_NONE
= "gov.nasa.worldwind.TerrainProfileLayer.FollowNone";
49 public final static String FOLLOW_OBJECT
= "gov.nasa.worldwind.TerrainProfileLayer.FollowObject";
51 // Display parameters - TODO: make configurable
52 private Dimension size
= new Dimension(250, 100);
53 private Color color
= Color
.white
;
54 private int borderWidth
= 20;
55 private String position
= SOUTHWEST
;
56 private String resizeBehavior
= RESIZE_SHRINK_ONLY
;
57 private String unit
= UNIT_METRIC
;
58 private Font defaultFont
= Font
.decode("Arial-12-PLAIN");
59 private double toViewportScale
= 1;
60 private Point locationCenter
= null;
61 private TextRenderer textRenderer
= null;
62 private PickSupport pickSupport
= new PickSupport();
63 private boolean initialized
= false;
64 private boolean update
= true; // Recompute profile if true
66 private int pickedSample
= -1; // Picked sample number if not -1
67 Polyline selectionShape
; // Shape showing section on the ground
68 Polyline pickedShape
; // Shape showing actual pick position on the ground
69 private boolean keepProportions
= false; // Keep graph distance/elevation proportions
70 private boolean zeroBased
= true; // Pad graph elevation scale to include sea level if true
71 private String follow
= FOLLOW_VIEW
; // Profile position follow behavior
72 private boolean showEyePosition
= false; // When FOLLOW_EYE, draw the eye position on graph when true
73 private double profileLengthFactor
= 1; // Applied to default profile length (zoom on profile)
74 private LatLon startLatLon
; // Section start lat/lon when FOLLOW_NONE
75 private LatLon endLatLon
; // Section end lat/lon when FOLLOW_NONE
76 private Position objectPosition
; // Object position if FOLLOW_OBJECT
77 private Angle objectHeading
; // Object heading if FOLLOW_OBJECT
80 // Terrain profile data
81 private int samples
= 250; // Number of position samples
82 private double minElevation
; // Minimum elevation along the profile
83 private double maxElevation
; // Maximum elevation along the profile
84 private double length
; // Profile length along great circle in meter
85 private Position positions
[]; // Position list
88 private WorldWindow wwd
;
90 // Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things.
91 // TODO: Add general support for this common pattern.
92 private OrderedIcon orderedImage
= new OrderedIcon();
94 private class OrderedIcon
implements OrderedRenderable
96 public double getDistanceFromEye()
101 public void pick(DrawContext dc
, Point pickPoint
)
103 TerrainProfileLayer
.this.drawProfile(dc
);
106 public void render(DrawContext dc
)
108 TerrainProfileLayer
.this.drawProfile(dc
);
113 * Renders a terrain profile graphic in a screen corner
115 public TerrainProfileLayer()
117 this.setName(Logging
.getMessage("layers.Earth.TerrainProfileLayer.Name"));
120 // ** Public properties ************************************************************
123 * Get whether the profile should be recomputed
125 * @return true if the profile should be recomputed
127 public boolean getUpdate()
133 * Set wheter the profile should be recomputed
135 * @param state true if the profile should be recomputed
137 public void setUpdate(boolean state
)
143 * Get the graphic Dimension (in pixels)
145 * @return the scalebar graphic Dimension
147 public Dimension
getSize()
153 * Set the graphic Dimenion (in pixels)
155 * @param size the graphic Dimension
157 public void setSize(Dimension size
)
161 String message
= Logging
.getMessage("nullValue.DimensionIsNull");
162 Logging
.logger().severe(message
);
163 throw new IllegalArgumentException(message
);
169 * Get the graphic color
171 * @return the graphic Color
173 public Color
getColor()
179 * Set the graphic Color
181 * @param color the graphic Color
183 public void setColor(Color color
)
187 String msg
= Logging
.getMessage("nullValue.ColorIsNull");
188 Logging
.logger().severe(msg
);
189 throw new IllegalArgumentException(msg
);
195 * Returns the graphic-to-viewport scale factor.
197 * @return the graphic-to-viewport scale factor
199 public double getToViewportScale()
201 return toViewportScale
;
205 * Sets the scale factor applied to the viewport size to determine the displayed size of the graphic. This scale factor
206 * is used only when the layer's resize behavior is {@link #RESIZE_STRETCH} or {@link #RESIZE_SHRINK_ONLY}. The
207 * graphic's width is adjusted to occupy the proportion of the viewport's width indicated by this factor. The graphic's
208 * height is adjusted to maintain the graphic's Dimension aspect ratio.
210 * @param toViewportScale the graphic to viewport scale factor
212 public void setToViewportScale(double toViewportScale
)
214 this.toViewportScale
= toViewportScale
;
217 public String
getPosition()
219 return this.position
;
223 * Sets the relative viewport location to display the graphic. Can be one of {@link #NORTHEAST} (the default), {@link
224 * #NORTHWEST}, {@link #SOUTHEAST}, or {@link #SOUTHWEST}. These indicate the corner of the viewport.
226 * @param position the desired graphic position
228 public void setPosition(String position
)
230 if (position
== null)
232 String msg
= Logging
.getMessage("nullValue.PositionIsNull");
233 Logging
.logger().severe(msg
);
234 throw new IllegalArgumentException(msg
);
236 this.position
= position
;
240 * Get the screen location of the graph center if set (can be null)
242 * @return the screen location of the graph center if set (can be null)
244 public Point
getLocationCenter()
246 return this.locationCenter
;
250 * Set the screen location of the graph center - overrides SetPosition if not null
252 * @param point the screen location of the graph center (can be null)
254 public void setLocationCenter(Point point
)
256 this.locationCenter
= point
;
260 * Returns the layer's resize behavior.
262 * @return the layer's resize behavior
264 public String
getResizeBehavior()
266 return resizeBehavior
;
270 * Sets the behavior the layer uses to size the graphic when the viewport size changes, typically when the World Wind
271 * window is resized. If the value is {@link #RESIZE_KEEP_FIXED_SIZE}, the graphic size is kept to the size specified
272 * in its Dimension scaled by the layer's current icon scale. If the value is {@link #RESIZE_STRETCH}, the graphic is
273 * resized to have a constant size relative to the current viewport size. If the viewport shrinks the graphic size
274 * decreases; if it expands then the graphic enlarges. If the value is {@link #RESIZE_SHRINK_ONLY} (the default),
275 * graphic sizing behaves as for {@link #RESIZE_STRETCH} but it will not grow larger than the size specified in its
278 * @param resizeBehavior the desired resize behavior
280 public void setResizeBehavior(String resizeBehavior
)
282 this.resizeBehavior
= resizeBehavior
;
285 public int getBorderWidth()
291 * Sets the graphic offset from the viewport border.
293 * @param borderWidth the number of pixels to offset the graphic from the borders indicated by {@link
294 * #setPosition(String)}.
296 public void setBorderWidth(int borderWidth
)
298 this.borderWidth
= borderWidth
;
301 public String
getUnit()
307 * Sets the unit the graphic uses to display distances and elevations. Can be one of {@link #UNIT_METRIC}
308 * (the default), or {@link #UNIT_IMPERIAL}.
310 * @param unit the desired unit
312 public void setUnit(String unit
)
318 * Get the graphic legend Font
320 * @return the graphic legend Font
322 public Font
getFont()
324 return this.defaultFont
;
328 * Set the graphic legend Font
330 * @param font the graphic legend Font
332 public void setFont(Font font
)
336 String msg
= Logging
.getMessage("nullValue.FontIsNull");
337 Logging
.logger().severe(msg
);
338 throw new IllegalArgumentException(msg
);
340 this.defaultFont
= font
;
344 * Get whether distance/elevation proportions are maintained
346 * @return true if the graph maintains distance/elevation proportions
348 public boolean getKeepProportions()
350 return this.keepProportions
;
354 * Set whether distance/elevation proportions are maintained
356 * @param state true if the graph should maintains distance/elevation proportions
358 public void setKeepProportions(boolean state
)
360 this.keepProportions
= state
;
364 * Get whether the graph center point follows the mouse cursor
366 * @return true if the graph center point follows the mouse cursor
368 public boolean getFollowCursor()
370 return this.follow
.compareToIgnoreCase(FOLLOW_CURSOR
) == 0;
374 * Set whether the graph center point should follows the mouse cursor
376 * @param state true if the graph center point should follows the mouse cursor
378 public void setFollowCursor(boolean state
)
380 this.setFollow(state ? FOLLOW_CURSOR
: FOLLOW_VIEW
);
384 * Get the graph center point placement behavior
386 * @return the graph center point placement behavior
388 public String
getFollow()
394 * Set the graph center point placement behavior. Can be one of {@link #FOLLOW_VIEW} (the default),
395 * {@link #FOLLOW_CURSOR}, {@link #FOLLOW_EYE} or {@link #FOLLOW_NONE}. If {@link #FOLLOW_NONE} the profile will
396 * be computed between startLatLon and endLatLon.
398 * @param behavior the graph center point placement behavior
400 public void setFollow(String behavior
)
402 this.follow
= behavior
;
403 this.setUpdate(true);
407 * Get whether the eye position is shown on the graph when {@link #FOLLOW_EYE}
409 * @return true if the eye position is shown on the grap
411 public boolean getShowEyePosition()
413 return this.showEyePosition
;
417 * Set whether the eye position is shown on the graph when {@link #FOLLOW_EYE}
419 * @param state if true the eye position is shown on the graph
421 public void setShowEyePosition(Boolean state
)
423 this.showEyePosition
= state
;
424 if (this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0)
425 this.setUpdate(true);
429 * Set the profile length factor - has no effect if {@link #FOLLOW_NONE}
431 * @param factor the new factor
433 public void setProfileLengthFactor(double factor
)
435 this.profileLengthFactor
= factor
;
436 this.setUpdate(true);
440 * Get the profile length factor
442 * @return the profile length factor
444 public double getProfileLenghtFactor()
446 return this.profileLengthFactor
;
450 * Get the profile start position lat/lon when {@link #FOLLOW_NONE}
452 * @return the profile start position lat/lon
454 public LatLon
getStartLatLon()
456 return this.startLatLon
;
460 * Set the profile start position lat/lon when {@link #FOLLOW_NONE}
462 * @param latLon the profile start position lat/lon
464 public void setStartLatLon(LatLon latLon
)
468 String msg
= Logging
.getMessage("nullValue.LatLonIsNull");
469 Logging
.logger().severe(msg
);
470 throw new IllegalArgumentException(msg
);
472 this.startLatLon
= latLon
;
473 if (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) == 0)
474 this.setUpdate(true);
478 * Get the profile end position lat/lon when {@link #FOLLOW_NONE}
480 * @return the profile end position lat/lon
482 public LatLon
getEndLatLon()
484 return this.endLatLon
;
488 * Set the profile end position lat/lon when {@link #FOLLOW_NONE}
490 * @param latLon the profile end position lat/lon
492 public void setEndLatLon(LatLon latLon
)
496 String msg
= Logging
.getMessage("nullValue.LatLonIsNull");
497 Logging
.logger().severe(msg
);
498 throw new IllegalArgumentException(msg
);
500 this.endLatLon
= latLon
;
501 if (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) == 0)
502 this.setUpdate(true);
506 * Get the number of elevation samples in the profile
508 * @return the number of elevation samples in the profile
510 public int getSamples()
516 * Set the number of elevation samples in the profile
518 * @param number the number of elevation samples in the profile
520 public void setSamples(int number
)
522 this.samples
= Math
.abs(number
);
523 this.setUpdate(true);
527 * Get whether the profile graph should include sea level
529 * @return whether the profile graph should include sea level
531 public boolean getZeroBased()
533 return this.zeroBased
;
537 * Set whether the profile graph should include sea level. True is the default.
539 * @param state true if the profile graph should include sea level
541 public void setZeroBased(boolean state
)
543 this.zeroBased
= state
;
544 this.setUpdate(true);
547 public Position
getObjectPosition()
549 return this.objectPosition
;
552 public void setObjectPosition(Position pos
)
554 this.objectPosition
= pos
;
555 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
556 this.setUpdate(true);
559 public Angle
getObjectHeading()
561 return this.objectHeading
;
564 public void setObjectHeading(Angle heading
)
566 this.objectHeading
= heading
;
567 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
568 this.setUpdate(true);
571 // ** Rendering ************************************************************
573 public void doRender(DrawContext dc
)
575 // Delegate graph rendering to OrderedRenderable list
576 dc
.addOrderedRenderable(this.orderedImage
);
577 // Render section line on the ground now
578 if (this.positions
!= null && this.selectionShape
!= null)
580 this.selectionShape
.render(dc
);
581 // If picking in progress, render pick indicator
582 if (this.pickedSample
!= -1 && this.pickedShape
!= null)
583 this.pickedShape
.render(dc
);
588 public void doPick(DrawContext dc
, Point pickPoint
)
590 // No picking at the graph if selection follows the mouse cursor
591 if (this.follow
.compareToIgnoreCase(FOLLOW_CURSOR
) != 0)
592 // Delegate drawing to the ordered renderable list
593 dc
.addOrderedRenderable(this.orderedImage
);
596 private void initialize()
598 if (this.initialized
|| this.positions
!= null)
601 if (this.wwd
!= null)
602 this.computeProfile();
604 if (this.positions
!= null)
605 this.initialized
= true;
608 // Profile graph rendering - ortho
609 public void drawProfile(DrawContext dc
)
612 this.computeProfile();
614 if ((this.positions
== null || (this.minElevation
== 0 && this.maxElevation
== 0))
615 && !this.initialized
)
618 if (this.positions
== null || (this.minElevation
== 0 && this.maxElevation
== 0))
623 boolean attribsPushed
= false;
624 boolean modelviewPushed
= false;
625 boolean projectionPushed
= false;
629 gl
.glPushAttrib(GL
.GL_DEPTH_BUFFER_BIT
630 | GL
.GL_COLOR_BUFFER_BIT
633 | GL
.GL_TRANSFORM_BIT
635 | GL
.GL_CURRENT_BIT
);
636 attribsPushed
= true;
638 gl
.glDisable(GL
.GL_TEXTURE_2D
); // no textures
640 gl
.glEnable(GL
.GL_BLEND
);
641 gl
.glBlendFunc(GL
.GL_SRC_ALPHA
, GL
.GL_ONE_MINUS_SRC_ALPHA
);
642 gl
.glDisable(GL
.GL_DEPTH_TEST
);
644 double width
= this.size
.width
;
645 double height
= this.size
.height
;
647 // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
648 // into the GL projection matrix.
649 java
.awt
.Rectangle viewport
= dc
.getView().getViewport();
650 gl
.glMatrixMode(javax
.media
.opengl
.GL
.GL_PROJECTION
);
652 projectionPushed
= true;
654 double maxwh
= width
> height ? width
: height
;
655 gl
.glOrtho(0d
, viewport
.width
, 0d
, viewport
.height
, -0.6 * maxwh
, 0.6 * maxwh
);
657 gl
.glMatrixMode(GL
.GL_MODELVIEW
);
659 modelviewPushed
= true;
662 // Scale to a width x height space
663 // located at the proper position on screen
664 double scale
= this.computeScale(viewport
);
665 Vec4 locationSW
= this.computeLocation(viewport
, scale
);
666 gl
.glTranslated(locationSW
.x(), locationSW
.y(), locationSW
.z());
667 gl
.glScaled(scale
, scale
, 1d
);
669 if (!dc
.isPickingMode())
671 // Draw grid - Set color using current layer opacity
672 this.drawGrid(dc
, this.size
);
674 // Draw profile graph
675 this.drawGraph(dc
, this.size
);
678 String label
= String
.format("min %.0fm max %.0fm", this.minElevation
, this.maxElevation
);
679 if (this.unit
.equals(UNIT_IMPERIAL
))
680 label
= String
.format("min %.0fft max %.0fft", this.minElevation
* METER_TO_FEET
,
681 this.maxElevation
* METER_TO_FEET
);
683 gl
.glDisable(GL
.GL_CULL_FACE
);
684 drawLabel(label
, locationSW
.add3(new Vec4(0, -12, 0)), -1); // left aligned
685 if (this.pickedSample
!= -1)
687 double pickedElevation
= positions
[this.pickedSample
].getElevation();
688 label
= String
.format("%.0fm", pickedElevation
);
689 if (this.unit
.equals(UNIT_IMPERIAL
))
690 label
= String
.format("%.0fft", pickedElevation
* METER_TO_FEET
);
691 drawLabel(label
, locationSW
.add3(new Vec4(width
, -12, 0)), 1); // right aligned
697 this.pickSupport
.clearPickList();
698 this.pickSupport
.beginPicking(dc
);
699 // Where in the world are we picking ?
700 Position pickPosition
=
701 computePickPosition(dc
, locationSW
, new Dimension((int) (width
* scale
), (int) (height
* scale
)));
702 // Draw unique color across the rectangle
703 Color color
= dc
.getUniquePickColor();
704 int colorCode
= color
.getRGB();
705 // Add our object(s) to the pickable list
706 this.pickSupport
.addPickableObject(colorCode
, this, pickPosition
, false);
707 gl
.glScaled(width
, height
, 1d
);
708 gl
.glColor3ub((byte) color
.getRed(), (byte) color
.getGreen(), (byte) color
.getBlue());
709 gl
.glBegin(GL
.GL_POLYGON
);
710 gl
.glVertex3d(0, 0, 0);
711 gl
.glVertex3d(1, 0, 0);
712 gl
.glVertex3d(1, 1, 0);
713 gl
.glVertex3d(0, 1, 0);
714 gl
.glVertex3d(0, 0, 0);
717 this.pickSupport
.endPicking(dc
);
718 this.pickSupport
.resolvePick(dc
, dc
.getPickPoint(), this);
724 if (projectionPushed
)
726 gl
.glMatrixMode(GL
.GL_PROJECTION
);
731 gl
.glMatrixMode(GL
.GL_MODELVIEW
);
740 private void drawGrid(DrawContext dc
, Dimension dimension
)
743 Color backColor
= getBackgroundColor(this.color
);
744 drawFilledRectangle(dc
, new Vec4(0, 0, 0), dimension
, new Color(backColor
.getRed(),
745 backColor
.getGreen(), backColor
.getBlue(), (int) (backColor
.getAlpha() * .5))); // Increased transparency
747 float[] colorRGB
= this.color
.getRGBColorComponents(null);
748 dc
.getGL().glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity());
749 drawVerticalLine(dc
, dimension
, 0);
750 drawVerticalLine(dc
, dimension
, dimension
.getWidth());
751 drawHorizontalLine(dc
, dimension
, 0);
754 // Draw profile graphic
755 private void drawGraph(DrawContext dc
, Dimension dimension
)
758 // Adjust min/max elevation for the graph
759 double min
= this.minElevation
;
760 double max
= this.maxElevation
;
761 if (this.showEyePosition
&& this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0)
762 max
= Math
.max(max
, dc
.getView().getEyePosition().getElevation());
763 if (this.showEyePosition
&& this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0 && this.objectPosition
!= null)
764 max
= Math
.max(max
, this.objectPosition
.getElevation());
767 if (min
> 0) min
= 0;
768 if (max
< 0) max
= 0;
771 double stepX
= dimension
.getWidth() / (this.length
);
772 double stepY
= dimension
.getHeight() / (max
- min
);
773 if (this.keepProportions
)
775 stepX
= Math
.min(stepX
, stepY
);
778 double lengthStep
= this.length
/ (this.samples
- 1);
781 gl
.glColor4ub((byte) this.color
.getRed(), (byte) this.color
.getGreen(),
782 (byte) this.color
.getBlue(), (byte) 100);
783 gl
.glBegin(GL
.GL_TRIANGLE_STRIP
);
784 for (i
= 0; i
< this.samples
; i
++)
786 x
= i
* lengthStep
* stepX
;
787 y
= (this.positions
[i
].getElevation() - min
) * stepY
;
788 gl
.glVertex3d(x
, 0, 0);
789 gl
.glVertex3d(x
, y
, 0);
793 float[] colorRGB
= this.color
.getRGBColorComponents(null);
794 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity());
795 gl
.glBegin(GL
.GL_LINE_STRIP
);
796 for (i
= 0; i
< this.samples
; i
++)
798 x
= i
* lengthStep
* stepX
;
799 y
= (this.positions
[i
].getElevation() - min
) * stepY
;
800 gl
.glVertex3d(x
, y
, 0);
803 // Middle vertical line
804 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity() * .3); // increased transparency here
805 drawVerticalLine(dc
, dimension
, x
/ 2);
807 if ((this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0 ||
808 (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0 && this.objectPosition
!= null))
809 && this.showEyePosition
)
811 double eyeY
= (dc
.getView().getEyePosition().getElevation() - min
) * stepY
;
812 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
813 eyeY
= (this.objectPosition
.getElevation() - min
) * stepY
;
814 this.drawFilledRectangle(dc
, new Vec4(x
/ 2 - 2, eyeY
- 2, 0), new Dimension(5, 5), this.color
);
816 // Selected/picked vertical and horizontal lines
817 if (this.pickedSample
!= -1)
819 double pickedX
= this.pickedSample
* lengthStep
* stepX
;
820 double pickedY
= (positions
[this.pickedSample
].getElevation() - min
) * stepY
;
821 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2] * .5, this.getOpacity() * .8); // yellower color
822 drawVerticalLine(dc
, dimension
, pickedX
);
823 drawHorizontalLine(dc
, dimension
, pickedY
);
824 // Eye or object - picked position line
825 if ((this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0 ||
826 (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0 && this.objectPosition
!= null))
827 && this.showEyePosition
)
830 double eyeY
= (dc
.getView().getEyePosition().getElevation() - min
) * stepY
;
831 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
832 eyeY
= (this.objectPosition
.getElevation() - min
) * stepY
;
833 drawLine(dc
, pickedX
, pickedY
, x
/ 2, eyeY
);
835 double distance
= dc
.getView().getEyePoint().distanceTo3(
836 dc
.getGlobe().computePointFromPosition(positions
[this.pickedSample
]));
837 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
838 distance
= dc
.getGlobe().computePointFromPosition(this.objectPosition
).distanceTo3(
839 dc
.getGlobe().computePointFromPosition(positions
[this.pickedSample
]));
840 String label
= String
.format("Dist %.0fm", distance
);
841 if (this.unit
.equals(UNIT_IMPERIAL
))
842 label
= String
.format("Dist %.0fft", distance
* METER_TO_FEET
);
843 drawLabel(label
, new Vec4(pickedX
+ 5, pickedY
- 12, 0), -1); // left aligned
846 // Min elevation horizontal line
847 if (this.minElevation
!= min
)
849 y
= (this.minElevation
- min
) * stepY
;
850 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity() * .5); // medium transparency
851 drawHorizontalLine(dc
, dimension
, y
);
853 // Max elevation horizontal line
854 if (this.maxElevation
!= max
)
856 y
= (this.maxElevation
- min
) * stepY
;
857 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity() * .5); // medium transparency
858 drawHorizontalLine(dc
, dimension
, y
);
860 // Sea level in between positive elevations only (not across land)
861 if (min
< 0 && max
>= 0)
863 gl
.glColor4d(colorRGB
[0] * .7, colorRGB
[1] * .7, colorRGB
[2], this.getOpacity() * .5); // bluer color
864 y
= -this.minElevation
* stepY
;
865 double previousX
= -1;
866 for (i
= 0; i
< this.samples
; i
++)
868 x
= i
* lengthStep
* stepX
;
869 if (this.positions
[i
].getElevation() > 0 || i
== this.samples
- 1)
873 gl
.glBegin(GL
.GL_LINE_STRIP
);
874 gl
.glVertex3d(previousX
, y
, 0);
875 gl
.glVertex3d(x
, y
, 0);
881 previousX
= previousX
< 0 ? x
: previousX
;
886 private void drawHorizontalLine(DrawContext dc
, Dimension dimension
, double y
)
888 drawLine(dc
, 0, y
, dimension
.getWidth(), y
);
891 private void drawVerticalLine(DrawContext dc
, Dimension dimension
, double x
)
893 drawLine(dc
, x
, 0, x
, dimension
.getHeight());
896 private void drawFilledRectangle(DrawContext dc
, Vec4 origin
, Dimension dimension
, Color color
)
899 gl
.glColor4ub((byte) color
.getRed(), (byte) color
.getGreen(),
900 (byte) color
.getBlue(), (byte) color
.getAlpha());
901 gl
.glDisable(GL
.GL_TEXTURE_2D
); // no textures
902 gl
.glBegin(GL
.GL_POLYGON
);
903 gl
.glVertex3d(origin
.x
, origin
.y
, 0);
904 gl
.glVertex3d(origin
.x
+ dimension
.getWidth(), origin
.y
, 0);
905 gl
.glVertex3d(origin
.x
+ dimension
.getWidth(), origin
.y
+ dimension
.getHeight(), 0);
906 gl
.glVertex3d(origin
.x
, origin
.y
+ dimension
.getHeight(), 0);
907 gl
.glVertex3d(origin
.x
, origin
.y
, 0);
912 private void drawLine(DrawContext dc
, double x1
, double y1
, double x2
, double y2
)
915 gl
.glBegin(GL
.GL_LINE_STRIP
);
916 gl
.glVertex3d(x1
, y1
, 0);
917 gl
.glVertex3d(x2
, y2
, 0);
922 // Align = -1: left, 0: center and 1: right
923 private void drawLabel(String text
, Vec4 screenPoint
, int align
)
925 if (this.textRenderer
== null)
927 this.textRenderer
= new TextRenderer(this.defaultFont
, true, true);
930 Rectangle2D nameBound
= this.textRenderer
.getBounds(text
);
931 int x
= (int) screenPoint
.x(); // left
932 if (align
== 0) x
= (int) (screenPoint
.x() - nameBound
.getWidth() / 2d
); // centered
933 if (align
> 0) x
= (int) (screenPoint
.x() - nameBound
.getWidth()); // right
934 int y
= (int) screenPoint
.y();
936 this.textRenderer
.begin3DRendering();
938 this.textRenderer
.setColor(this.getBackgroundColor(this.color
));
939 this.textRenderer
.draw(text
, x
+ 1, y
- 1);
940 this.textRenderer
.setColor(this.color
);
941 this.textRenderer
.draw(text
, x
, y
);
943 this.textRenderer
.end3DRendering();
947 // Compute background color for best contrast
948 private Color
getBackgroundColor(Color color
)
950 float[] compArray
= new float[4];
951 Color
.RGBtoHSB(color
.getRed(), color
.getGreen(), color
.getBlue(), compArray
);
952 if (compArray
[2] > 0.5)
953 return new Color(0, 0, 0, (int) (this.color
.getAlpha() * 0.7f
));
955 return new Color(255, 255, 255, (int) (this.color
.getAlpha() * 0.7f
));
958 // ** Dimensions and positionning ************************************************************
960 private double computeScale(java
.awt
.Rectangle viewport
)
962 if (this.resizeBehavior
.equals(RESIZE_SHRINK_ONLY
))
964 return Math
.min(1d
, (this.toViewportScale
) * viewport
.width
/ this.size
.width
);
966 else if (this.resizeBehavior
.equals(RESIZE_STRETCH
))
968 return (this.toViewportScale
) * viewport
.width
/ this.size
.width
;
970 else if (this.resizeBehavior
.equals(RESIZE_KEEP_FIXED_SIZE
))
980 private Vec4
computeLocation(java
.awt
.Rectangle viewport
, double scale
)
982 double scaledWidth
= scale
* this.size
.width
;
983 double scaledHeight
= scale
* this.size
.height
;
988 if (this.locationCenter
!= null)
990 x
= this.locationCenter
.x
- scaledWidth
/ 2;
991 y
= this.locationCenter
.y
- scaledHeight
/ 2;
993 else if (this.position
.equals(NORTHEAST
))
995 x
= viewport
.getWidth() - scaledWidth
- this.borderWidth
;
996 y
= viewport
.getHeight() - scaledHeight
- this.borderWidth
;
998 else if (this.position
.equals(SOUTHEAST
))
1000 x
= viewport
.getWidth() - scaledWidth
- this.borderWidth
;
1001 y
= 0d
+ this.borderWidth
;
1003 else if (this.position
.equals(NORTHWEST
))
1005 x
= 0d
+ this.borderWidth
;
1006 y
= viewport
.getHeight() - scaledHeight
- this.borderWidth
;
1008 else if (this.position
.equals(SOUTHWEST
))
1010 x
= 0d
+ this.borderWidth
;
1011 y
= 0d
+ this.borderWidth
;
1013 else // use North East
1015 x
= viewport
.getWidth() - scaledWidth
/ 2 - this.borderWidth
;
1016 y
= viewport
.getHeight() - scaledHeight
/ 2 - this.borderWidth
;
1019 return new Vec4(x
, y
, 0);
1022 private Position
computeGroundPosition(DrawContext dc
, View view
)
1027 Position groundPos
= view
.computePositionFromScreenPoint(
1028 view
.getViewport().getWidth() / 2, view
.getViewport().getHeight() / 2);
1029 if (groundPos
== null)
1032 double elevation
= dc
.getGlobe().getElevation(groundPos
.getLatitude(), groundPos
.getLongitude());
1033 return new Position(
1034 groundPos
.getLatitude(),
1035 groundPos
.getLongitude(),
1036 elevation
* dc
.getVerticalExaggeration());
1040 * Computes the Position of the pickPoint over the graph and updates pickedSample indice
1042 * @param dc the current DrawContext
1043 * @param locationSW the screen location of the bottom left corner of the graph
1044 * @param mapSize the graph screen dimension in pixels
1045 * @return the picked Position
1047 private Position
computePickPosition(DrawContext dc
, Vec4 locationSW
, Dimension mapSize
)
1049 Position pickPosition
= null;
1050 this.pickedSample
= -1;
1051 Point pickPoint
= dc
.getPickPoint();
1052 if (pickPoint
!= null && this.positions
!= null)
1054 Rectangle viewport
= dc
.getView().getViewport();
1055 // Check if pickpoint is inside the graph
1056 if (pickPoint
.getX() >= locationSW
.getX()
1057 && pickPoint
.getX() < locationSW
.getX() + mapSize
.width
1058 && viewport
.height
- pickPoint
.getY() >= locationSW
.getY()
1059 && viewport
.height
- pickPoint
.getY() < locationSW
.getY() + mapSize
.height
)
1061 // Find sample - Note: only works when graph expends over the full width
1062 int sample
= (int) (((double) (pickPoint
.getX() - locationSW
.getX()) / mapSize
.width
) * this.samples
);
1063 if (sample
>= 0 && sample
< this.samples
)
1065 pickPosition
= this.positions
[sample
];
1066 this.pickedSample
= sample
;
1067 // Update polyline indicator
1068 ArrayList
<Position
> posList
= new ArrayList
<Position
>();
1069 posList
.add(positions
[sample
]);
1070 posList
.add(new Position(positions
[sample
].getLatitude(), positions
[sample
].getLongitude(),
1071 positions
[sample
].getElevation() + this.length
/ 10));
1072 if (this.pickedShape
== null)
1074 this.pickedShape
= new Polyline(posList
);
1075 this.pickedShape
.setPathType(Polyline
.LINEAR
);
1076 this.pickedShape
.setLineWidth(2);
1077 this.pickedShape
.setColor(new Color(this.color
.getRed(),
1078 this.color
.getGreen(), (int) (this.color
.getBlue() * .8), (int) (255 * .8)));
1081 this.pickedShape
.setPositions(posList
);
1085 return pickPosition
;
1088 // ** Position listener impl. ************************************************************
1090 public void moved(PositionEvent event
)
1092 if (this.wwd
!= null && this.isEnabled())
1094 // Update profile if FOLLOW_CURSOR
1095 if (this.follow
.compareToIgnoreCase(FOLLOW_CURSOR
) == 0)
1096 this.setUpdate(true);
1099 this.positions
= null;
1102 // ** Property change listener ***********************************************************
1104 public void propertyChange(PropertyChangeEvent propertyChangeEvent
)
1106 if (this.wwd
!= null && this.isEnabled())
1108 // Update profile if FOLLOW_VIEW or FOLLOW_EYE or terrain elevations changed
1109 if (this.follow
.compareToIgnoreCase(FOLLOW_VIEW
) == 0
1110 || this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0
1111 || propertyChangeEvent
.getPropertyName().compareToIgnoreCase(AVKey
.ELEVATION_MODEL
) == 0)
1112 this.setUpdate(true);
1115 this.positions
= null;
1118 // Sets the wwd local reference and add us to the position listeners
1119 // the view and elevation model property change listener
1120 public void setEventSource(WorldWindow wwd
)
1122 if (this.wwd
!= null)
1124 this.wwd
.removePositionListener(this);
1125 this.wwd
.getView().removePropertyChangeListener(this);
1126 this.wwd
.getModel().getGlobe().getElevationModel().removePropertyChangeListener(this);
1129 if (this.wwd
!= null)
1131 this.wwd
.addPositionListener(this);
1132 this.wwd
.getView().addPropertyChangeListener(this);
1133 this.wwd
.getModel().getGlobe().getElevationModel().addPropertyChangeListener(this);
1137 // ** Profile data collection ************************************************************
1140 * If {@link #FOLLOW_VIEW ], {@link #FOLLOW_EYE] or {@link #FOLLOW_CURSOR] collects terrain profile data along a
1141 * great circle line centered at the current position (view, eye or cursor) and perpendicular to the view heading.
1142 * If {@link #FOLLOW_NONE] the profile is computed in between start and end latlon
1144 public void computeProfile()
1146 if (this.wwd
== null)
1150 // Find center position
1151 OrbitView view
= (OrbitView
) this.wwd
.getView(); // TODO: check for OrbitView instance first
1152 Position groundPos
= view
.computePositionFromScreenPoint(
1153 view
.getViewport().getWidth() / 2, view
.getViewport().getHeight() / 2);
1154 if (view
.getLookAtLatitude() != null && view
.getLookAtLongitude() != null)
1155 groundPos
= new Position(view
.getLookAtLatitude(), view
.getLookAtLongitude(), 0);
1156 if (this.follow
.compareToIgnoreCase(FOLLOW_CURSOR
) == 0)
1157 groundPos
= this.wwd
.getCurrentPosition();
1158 if (this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0)
1159 groundPos
= view
.getEyePosition();
1160 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
1161 groundPos
= this.objectPosition
;
1162 if ((this.follow
.compareToIgnoreCase(FOLLOW_VIEW
) == 0 && groundPos
!= null ) ||
1163 (this.follow
.compareToIgnoreCase(FOLLOW_EYE
) == 0 && groundPos
!= null ) ||
1164 (this.follow
.compareToIgnoreCase(FOLLOW_CURSOR
) == 0 && groundPos
!= null ) ||
1165 (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) == 0 && this.startLatLon
!= null && this.endLatLon
!= null) ||
1166 (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0 && this.objectPosition
!= null && this.objectHeading
!= null))
1168 this.positions
= new Position
[samples
];
1169 this.minElevation
= Double
.MAX_VALUE
;
1170 this.maxElevation
= -1e6
; // Note: Double.MIN_VALUE would fail below min-max tests for some reason
1171 this.length
= Math
.min(view
.getZoom() * .8 * this.profileLengthFactor
,
1172 this.wwd
.getModel().getGlobe().getRadius() * Math
.PI
);
1173 if (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) == 0)
1174 this.length
= LatLon
.sphericalDistance(this.startLatLon
, this.endLatLon
).radians
1175 * this.wwd
.getModel().getGlobe().getRadius();
1176 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
1177 this.length
= Math
.min(this.objectPosition
.getElevation() * .8 * this.profileLengthFactor
,
1178 this.wwd
.getModel().getGlobe().getRadius() * Math
.PI
);
1179 double lengthRadian
= this.length
/ this.wwd
.getModel().getGlobe().getRadius();
1181 LatLon centerLatLon
= new LatLon(groundPos
.getLatitude(), groundPos
.getLongitude());
1182 // Iterate on both sides of the center point
1184 double step
= lengthRadian
/ (samples
- 1);
1185 for (i
= 0; i
< this.samples
; i
++)
1187 LatLon latLon
= null;
1188 if (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) != 0)
1190 // Compute segments perpendicular to view or object heading
1191 double azimuth
= view
.getHeading().subtract(Angle
.POS90
).radians
;
1192 if (this.follow
.compareToIgnoreCase(FOLLOW_OBJECT
) == 0)
1193 azimuth
= this.objectHeading
.subtract(Angle
.POS90
).radians
;
1194 if (i
> (float) (this.samples
- 1) / 2f
)
1196 //azimuth = view.getHeading().subtract(Angle.NEG90).radians;
1199 double distance
= Math
.abs(((double) i
- ((double) (this.samples
- 1) / 2d
)) * step
);
1200 latLon
= LatLon
.endPosition(centerLatLon
, azimuth
, distance
);
1202 else if (this.follow
.compareToIgnoreCase(FOLLOW_NONE
) == 0 && this.startLatLon
!= null
1203 && this.endLatLon
!= null)
1205 // Compute segments between start and end positions latlon
1206 latLon
= LatLon
.interpolate((double) i
/ (this.samples
- 1), this.startLatLon
, this.endLatLon
);
1209 this.wwd
.getModel().getGlobe().getElevation(latLon
.getLatitude(), latLon
.getLongitude());
1210 this.minElevation
= elevation
< this.minElevation ? elevation
: this.minElevation
;
1211 this.maxElevation
= elevation
> this.maxElevation ? elevation
: this.maxElevation
;
1212 // Add position to the list
1213 positions
[i
] = new Position(latLon
.getLatitude(), latLon
.getLongitude(), elevation
);
1215 // Update shape on ground
1216 if (this.selectionShape
== null)
1218 this.selectionShape
= new Polyline(Arrays
.asList(this.positions
));
1219 this.selectionShape
.setLineWidth(2);
1220 this.selectionShape
.setFollowTerrain(true);
1221 this.selectionShape
.setColor(new Color(this.color
.getRed(),
1222 this.color
.getGreen(), (int) (this.color
.getBlue() * .5), (int) (255 * .8)));
1225 this.selectionShape
.setPositions(Arrays
.asList(this.positions
));
1229 // Off globe or something missing
1230 this.positions
= null;
1235 this.positions
= null;
1237 // Clear update flag
1238 this.update
= false;
1241 public void dispose()
1243 if (this.textRenderer
!= null)
1245 this.textRenderer
.dispose();
1246 this.textRenderer
= null;
1251 public String
toString()
1253 return this.getName();