2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
7 package gov
.nasa
.worldwind
.layers
.Earth
;
9 import com
.sun
.opengl
.util
.texture
.*;
10 import gov
.nasa
.worldwind
.exception
.WWRuntimeException
;
11 import gov
.nasa
.worldwind
.geom
.*;
12 import gov
.nasa
.worldwind
.layers
.RenderableLayer
;
13 import gov
.nasa
.worldwind
.pick
.PickSupport
;
14 import gov
.nasa
.worldwind
.render
.*;
15 import gov
.nasa
.worldwind
.util
.Logging
;
16 import gov
.nasa
.worldwind
.View
;
17 import gov
.nasa
.worldwind
.avlist
.AVKey
;
18 import gov
.nasa
.worldwind
.Configuration
;
20 import javax
.media
.opengl
.GL
;
25 * Displays a world map overlay with a current position crosshair in a screen corner. Supports picking at a position on
28 * @author Patrick Murris
31 public class WorldMapLayer
extends RenderableLayer
33 // Positionning constants
34 public final static String NORTHWEST
= "gov.nasa.worldwind.WorldmapLayer.NorthWest";
35 public final static String SOUTHWEST
= "gov.nasa.worldwind.WorldmapLayer.SouthWest";
36 public final static String NORTHEAST
= "gov.nasa.worldwind.WorldmapLayer.NorthEast";
37 public final static String SOUTHEAST
= "gov.nasa.worldwind.WorldmapLayer.SouthEast";
38 // Stretching behavior constants
39 public final static String RESIZE_STRETCH
= "gov.nasa.worldwind.WorldmapLayer.Stretch";
40 public final static String RESIZE_SHRINK_ONLY
= "gov.nasa.worldwind.WorldmapLayer.ShrinkOnly";
41 public final static String RESIZE_KEEP_FIXED_SIZE
= "gov.nasa.worldwind.WorldmapLayer.FixedSize";
43 private String iconFilePath
;
44 private double toViewportScale
= 0.2; // TODO: make configurable
45 private double iconScale
= 0.5;
46 private int borderWidth
= 20; // TODO: make configurable
47 private String position
= NORTHWEST
; // TODO: make configurable
48 private String resizeBehavior
= RESIZE_SHRINK_ONLY
;
49 private int iconWidth
;
50 private int iconHeight
;
51 private Vec4 locationCenter
= null;
52 private Color color
= Color
.white
;
53 private Color backColor
= new Color(0f
, 0f
, 0f
, 0.4f
);
54 private PickSupport pickSupport
= new PickSupport();
55 private double pickAltitude
= 1000e3
; // Altitude for picked position
57 // Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things.
58 private OrderedIcon orderedImage
= new OrderedIcon();
60 private class OrderedIcon
implements OrderedRenderable
62 public double getDistanceFromEye()
67 public void pick(DrawContext dc
, Point pickPoint
)
69 WorldMapLayer
.this.drawIcon(dc
);
72 public void render(DrawContext dc
)
74 WorldMapLayer
.this.drawIcon(dc
);
79 * Displays a world map overlay with a current position crosshair in a screen corner
81 public WorldMapLayer()
83 this.setName(Logging
.getMessage("layers.Earth.WorldMapLayer.Name"));
85 this.setIconFilePath(Configuration
.getStringValue(AVKey
.WORLD_MAP_IMAGE_PATH
));
89 * Displays a world map overlay with a current position crosshair in a screen corner
91 * @param iconFilePath the world map image path and filename
93 public WorldMapLayer(String iconFilePath
)
95 this.setName(Logging
.getMessage("layers.Earth.WorldMapLayer.Name"));
97 this.setIconFilePath(iconFilePath
);
103 * Returns the layer's current icon file path.
105 * @return the icon file path
107 public String
getIconFilePath()
113 * Sets the world map icon's image location. The layer first searches for this location in the current Java classpath.
114 * If not found then the specified path is assumed to refer to the local file system. found there then the
116 * @param iconFilePath the path to the icon's image file
118 public void setIconFilePath(String iconFilePath
)
120 if (iconFilePath
== null || iconFilePath
.length() == 0)
122 String message
= Logging
.getMessage("nullValue.FilePathIsNull");
123 Logging
.logger().severe(message
);
124 throw new IllegalArgumentException(message
);
126 this.iconFilePath
= iconFilePath
;
130 * Returns the layer's world map-to-viewport scale factor.
132 * @return the world map-to-viewport scale factor
134 public double getToViewportScale()
136 return toViewportScale
;
140 * Sets the scale factor applied to the viewport size to determine the displayed size of the world map icon. This scale
141 * factor is used only when the layer's resize behavior is {@link #RESIZE_STRETCH} or {@link #RESIZE_SHRINK_ONLY}. The
142 * icon's width is adjusted to occupy the proportion of the viewport's width indicated by this factor. The icon's
143 * height is adjusted to maintain the world map image's native aspect ratio.
145 * @param toViewportScale the world map to viewport scale factor
147 public void setToViewportScale(double toViewportScale
)
149 this.toViewportScale
= toViewportScale
;
153 * Returns the icon scale factor. See {@link #setIconScale(double)} for a description of the scale factor.
155 * @return the current icon scale
157 public double getIconScale()
163 * Sets the scale factor defining the displayed size of the world map icon relative to the icon's width and height in
164 * its image file. Values greater than 1 magify the image, values less than one minify it. If the layer's resize
165 * behavior is other than {@link #RESIZE_KEEP_FIXED_SIZE}, the icon's displayed sized is further affected by the value
166 * specified by {@link #setToViewportScale(double)} and the current viewport size.
168 * @param iconScale the icon scale factor
170 public void setIconScale(double iconScale
)
172 this.iconScale
= iconScale
;
176 * Returns the world map icon's resize behavior.
178 * @return the icon's resize behavior
180 public String
getResizeBehavior()
182 return resizeBehavior
;
186 * Sets the behavior the layer uses to size the world map icon when the viewport size changes, typically when the World
187 * Wind window is resized. If the value is {@link #RESIZE_KEEP_FIXED_SIZE}, the icon size is kept to the size specified
188 * in its image file scaled by the layer's current icon scale. If the value is {@link #RESIZE_STRETCH}, the icon is
189 * resized to have a constant size relative to the current viewport size. If the viewport shrinks the icon size
190 * decreases; if it expands then the icon file enlarges. The relative size is determined by the current world
191 * map-to-viewport scale and by the icon's image file size scaled by the current icon scale. If the value is {@link
192 * #RESIZE_SHRINK_ONLY} (the default), icon sizing behaves as for {@link #RESIZE_STRETCH} but the icon will not grow
193 * larger than the size specified in its image file scaled by the current icon scale.
195 * @param resizeBehavior the desired resize behavior
197 public void setResizeBehavior(String resizeBehavior
)
199 this.resizeBehavior
= resizeBehavior
;
202 public int getBorderWidth()
208 * Sets the world map icon offset from the viewport border.
210 * @param borderWidth the number of pixels to offset the world map icon from the borders indicated by {@link
211 * #setPosition(String)}.
213 public void setBorderWidth(int borderWidth
)
215 this.borderWidth
= borderWidth
;
219 * Returns the current relative world map icon position.
221 * @return the current world map position
223 public String
getPosition()
229 * Sets the relative viewport location to display the world map icon. Can be one of {@link #NORTHEAST} (the default),
230 * {@link #NORTHWEST}, {@link #SOUTHEAST}, or {@link #SOUTHWEST}. These indicate the corner of the viewport to place
233 * @param position the desired world map position
235 public void setPosition(String position
)
237 if (position
== null)
239 String message
= Logging
.getMessage("nullValue.PositionIsNull");
240 Logging
.logger().severe(message
);
241 throw new IllegalArgumentException(message
);
243 this.position
= position
;
246 public Vec4
getLocationCenter()
248 return locationCenter
;
251 public void setLocationCenter(Vec4 locationCenter
)
253 this.locationCenter
= locationCenter
;
256 public Color
getBackgrounColor()
258 return this.backColor
;
261 public void setBackgroundColor(Color color
)
265 String msg
= Logging
.getMessage("nullValue.ColorIsNull");
266 Logging
.logger().severe(msg
);
267 throw new IllegalArgumentException(msg
);
269 this.backColor
= color
;
273 public void doRender(DrawContext dc
)
275 // Delegate drawing to the ordered renderable list
276 dc
.addOrderedRenderable(this.orderedImage
);
280 public void doPick(DrawContext dc
, Point pickPoint
)
282 // Delegate drawing to the ordered renderable list
283 dc
.addOrderedRenderable(this.orderedImage
);
286 private void drawIcon(DrawContext dc
)
288 if (this.iconFilePath
== null)
293 boolean attribsPushed
= false;
294 boolean modelviewPushed
= false;
295 boolean projectionPushed
= false;
299 gl
.glPushAttrib(GL
.GL_DEPTH_BUFFER_BIT
300 | GL
.GL_COLOR_BUFFER_BIT
303 | GL
.GL_TRANSFORM_BIT
305 | GL
.GL_CURRENT_BIT
);
306 attribsPushed
= true;
308 // Initialize texture if not done yet
309 Texture iconTexture
= dc
.getTextureCache().get(this);
310 if (iconTexture
== null)
312 this.initializeTexture(dc
);
313 iconTexture
= dc
.getTextureCache().get(this);
314 if (iconTexture
== null)
321 gl
.glEnable(GL
.GL_BLEND
);
322 gl
.glBlendFunc(GL
.GL_SRC_ALPHA
, GL
.GL_ONE_MINUS_SRC_ALPHA
);
323 gl
.glDisable(GL
.GL_DEPTH_TEST
);
325 double width
= this.getScaledIconWidth();
326 double height
= this.getScaledIconHeight();
328 // Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
329 // into the GL projection matrix.
330 java
.awt
.Rectangle viewport
= dc
.getView().getViewport();
331 gl
.glMatrixMode(javax
.media
.opengl
.GL
.GL_PROJECTION
);
333 projectionPushed
= true;
335 double maxwh
= width
> height ? width
: height
;
336 gl
.glOrtho(0d
, viewport
.width
, 0d
, viewport
.height
, -0.6 * maxwh
, 0.6 * maxwh
);
338 gl
.glMatrixMode(GL
.GL_MODELVIEW
);
340 modelviewPushed
= true;
343 // Translate and scale
344 double scale
= this.computeScale(viewport
);
345 Vec4 locationSW
= this.computeLocation(viewport
, scale
);
346 gl
.glTranslated(locationSW
.x(), locationSW
.y(), locationSW
.z());
347 // Scale to 0..1 space
348 gl
.glScaled(scale
, scale
, 1);
349 gl
.glScaled(width
, height
, 1d
);
351 if (!dc
.isPickingMode())
353 // Draw background color behind the map
354 gl
.glColor4ub((byte) this.backColor
.getRed(), (byte) this.backColor
.getGreen(),
355 (byte) this.backColor
.getBlue(), (byte) this.backColor
.getAlpha());
356 gl
.glDisable(GL
.GL_TEXTURE_2D
); // no textures
357 gl
.glBegin(GL
.GL_POLYGON
);
358 gl
.glVertex3d(0, 0, 0);
359 gl
.glVertex3d(1, 0, 0);
360 gl
.glVertex3d(1, 1, 0);
361 gl
.glVertex3d(0, 1, 0);
362 gl
.glVertex3d(0, 0, 0);
365 // Draw world map icon
366 gl
.glColor4d(1d
, 1d
, 1d
, this.getOpacity());
367 gl
.glEnable(GL
.GL_TEXTURE_2D
);
370 TextureCoords texCoords
= iconTexture
.getImageTexCoords();
371 dc
.drawUnitQuad(texCoords
);
373 // Draw crosshair for current location
375 gl
.glTranslated(locationSW
.x(), locationSW
.y(), locationSW
.z());
376 // Scale to width x height space
377 gl
.glScaled(scale
, scale
, 1);
379 float[] colorRGB
= this.color
.getRGBColorComponents(null);
380 gl
.glColor4d(colorRGB
[0], colorRGB
[1], colorRGB
[2], this.getOpacity());
381 gl
.glDisable(GL
.GL_TEXTURE_2D
); // no textures
383 // Find coordinates in map icon space
384 Position groundPos
= this.computeGroundPosition(dc
, dc
.getView());
385 if (groundPos
!= null)
387 int x
= (int) (width
* (groundPos
.getLongitude().degrees
+ 180) / 360);
388 int y
= (int) (height
* (groundPos
.getLatitude().degrees
+ 90) / 180);
389 int w
= 10; // cross branch length
391 gl
.glBegin(GL
.GL_LINE_STRIP
);
392 gl
.glVertex3d(x
- w
, y
, 0);
393 gl
.glVertex3d(x
+ w
+ 1, y
, 0);
395 gl
.glBegin(GL
.GL_LINE_STRIP
);
396 gl
.glVertex3d(x
, y
- w
, 0);
397 gl
.glVertex3d(x
, y
+ w
+ 1, 0);
400 // Draw 1px border around and inside the map
401 gl
.glBegin(GL
.GL_LINE_STRIP
);
402 gl
.glVertex3d(0, 0, 0);
403 gl
.glVertex3d(width
, 0, 0);
404 gl
.glVertex3d(width
, height
- 1, 0);
405 gl
.glVertex3d(0, height
- 1, 0);
406 gl
.glVertex3d(0, 0, 0);
412 this.pickSupport
.clearPickList();
413 this.pickSupport
.beginPicking(dc
);
414 // Where in the world are we picking ?
415 Position pickPosition
=
416 computePickPosition(dc
, locationSW
, new Dimension((int) (width
* scale
), (int) (height
* scale
)));
417 // Draw unique color across the map
418 Color color
= dc
.getUniquePickColor();
419 int colorCode
= color
.getRGB();
420 // Add our object(s) to the pickable list
421 this.pickSupport
.addPickableObject(colorCode
, this, pickPosition
, false);
422 gl
.glColor3ub((byte) color
.getRed(), (byte) color
.getGreen(), (byte) color
.getBlue());
423 gl
.glBegin(GL
.GL_POLYGON
);
424 gl
.glVertex3d(0, 0, 0);
425 gl
.glVertex3d(1, 0, 0);
426 gl
.glVertex3d(1, 1, 0);
427 gl
.glVertex3d(0, 1, 0);
428 gl
.glVertex3d(0, 0, 0);
431 this.pickSupport
.endPicking(dc
);
432 this.pickSupport
.resolvePick(dc
, dc
.getPickPoint(), this);
438 if (projectionPushed
)
440 gl
.glMatrixMode(GL
.GL_PROJECTION
);
445 gl
.glMatrixMode(GL
.GL_MODELVIEW
);
453 private double computeScale(java
.awt
.Rectangle viewport
)
455 if (this.resizeBehavior
.equals(RESIZE_SHRINK_ONLY
))
457 return Math
.min(1d
, (this.toViewportScale
) * viewport
.width
/ this.getScaledIconWidth());
459 else if (this.resizeBehavior
.equals(RESIZE_STRETCH
))
461 return (this.toViewportScale
) * viewport
.width
/ this.getScaledIconWidth();
463 else if (this.resizeBehavior
.equals(RESIZE_KEEP_FIXED_SIZE
))
473 private double getScaledIconWidth()
475 return this.iconWidth
* this.iconScale
;
478 private double getScaledIconHeight()
480 return this.iconHeight
* this.iconScale
;
483 private Vec4
computeLocation(java
.awt
.Rectangle viewport
, double scale
)
485 double width
= this.getScaledIconWidth();
486 double height
= this.getScaledIconHeight();
488 double scaledWidth
= scale
* width
;
489 double scaledHeight
= scale
* height
;
494 if (this.locationCenter
!= null)
496 x
= viewport
.getWidth() - scaledWidth
/ 2 - this.borderWidth
;
497 y
= viewport
.getHeight() - scaledHeight
/ 2 - this.borderWidth
;
499 else if (this.position
.equals(NORTHEAST
))
501 x
= viewport
.getWidth() - scaledWidth
- this.borderWidth
;
502 y
= viewport
.getHeight() - scaledHeight
- this.borderWidth
;
504 else if (this.position
.equals(SOUTHEAST
))
506 x
= viewport
.getWidth() - scaledWidth
- this.borderWidth
;
507 y
= 0d
+ this.borderWidth
;
509 else if (this.position
.equals(NORTHWEST
))
511 x
= 0d
+ this.borderWidth
;
512 y
= viewport
.getHeight() - scaledHeight
- this.borderWidth
;
514 else if (this.position
.equals(SOUTHWEST
))
516 x
= 0d
+ this.borderWidth
;
517 y
= 0d
+ this.borderWidth
;
519 else // use North East
521 x
= viewport
.getWidth() - scaledWidth
/ 2 - this.borderWidth
;
522 y
= viewport
.getHeight() - scaledHeight
/ 2 - this.borderWidth
;
525 return new Vec4(x
, y
, 0);
528 private void initializeTexture(DrawContext dc
)
530 Texture iconTexture
= dc
.getTextureCache().get(this);
531 if (iconTexture
!= null)
536 InputStream iconStream
= this.getClass().getResourceAsStream("/" + this.iconFilePath
);
537 if (iconStream
== null)
539 File iconFile
= new File(this.iconFilePath
);
540 if (iconFile
.exists())
542 iconStream
= new FileInputStream(iconFile
);
546 iconTexture
= TextureIO
.newTexture(iconStream
, true, null);
548 this.iconWidth
= iconTexture
.getWidth();
549 this.iconHeight
= iconTexture
.getHeight();
550 dc
.getTextureCache().put(this, iconTexture
);
552 catch (IOException e
)
554 String msg
= Logging
.getMessage("layers.IOExceptionDuringInitialization");
555 Logging
.logger().severe(msg
);
556 throw new WWRuntimeException(msg
, e
);
560 gl
.glTexEnvf(GL
.GL_TEXTURE_ENV
, GL
.GL_TEXTURE_ENV_MODE
, GL
.GL_MODULATE
);
561 gl
.glTexParameteri(GL
.GL_TEXTURE_2D
, GL
.GL_TEXTURE_MIN_FILTER
, GL
.GL_LINEAR_MIPMAP_LINEAR
);
562 gl
.glTexParameteri(GL
.GL_TEXTURE_2D
, GL
.GL_TEXTURE_MAG_FILTER
, GL
.GL_LINEAR
);
563 gl
.glTexParameteri(GL
.GL_TEXTURE_2D
, GL
.GL_TEXTURE_WRAP_S
, GL
.GL_CLAMP_TO_EDGE
);
564 gl
.glTexParameteri(GL
.GL_TEXTURE_2D
, GL
.GL_TEXTURE_WRAP_T
, GL
.GL_CLAMP_TO_EDGE
);
565 // Enable texture anisotropy, improves "tilted" world map quality.
566 int[] maxAnisotropy
= new int[1];
567 gl
.glGetIntegerv(GL
.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
, maxAnisotropy
, 0);
568 gl
.glTexParameteri(GL
.GL_TEXTURE_2D
, GL
.GL_TEXTURE_MAX_ANISOTROPY_EXT
, maxAnisotropy
[0]);
572 * Compute the lat/lon position of the view center
574 * @param dc the current DrawContext
575 * @param view the current View
576 * @return the ground position of the view center or null
578 private Position
computeGroundPosition(DrawContext dc
, View view
)
583 Position groundPos
= view
.computePositionFromScreenPoint(
584 view
.getViewport().getWidth() / 2, view
.getViewport().getHeight() / 2);
585 if (groundPos
== null)
588 double elevation
= dc
.getGlobe().getElevation(groundPos
.getLatitude(), groundPos
.getLongitude());
590 groundPos
.getLatitude(),
591 groundPos
.getLongitude(),
592 elevation
* dc
.getVerticalExaggeration());
596 * Computes the lat/lon of the pickPoint over the world map
598 * @param dc the current DrawContext
599 * @param locationSW the screen location of the bottom left corner of the map
600 * @param mapSize the world map screen dimension in pixels
601 * @return the picked Position
603 private Position
computePickPosition(DrawContext dc
, Vec4 locationSW
, Dimension mapSize
)
605 Position pickPosition
= null;
606 Point pickPoint
= dc
.getPickPoint();
607 if (pickPoint
!= null)
609 Rectangle viewport
= dc
.getView().getViewport();
610 // Check if pickpoint is inside the map
611 if (pickPoint
.getX() >= locationSW
.getX()
612 && pickPoint
.getX() < locationSW
.getX() + mapSize
.width
613 && viewport
.height
- pickPoint
.getY() >= locationSW
.getY()
614 && viewport
.height
- pickPoint
.getY() < locationSW
.getY() + mapSize
.height
)
616 double lon
= (pickPoint
.getX() - locationSW
.getX()) / mapSize
.width
* 360 - 180;
617 double lat
= (viewport
.height
- pickPoint
.getY() - locationSW
.getY()) / mapSize
.height
* 180 - 90;
618 pickPosition
= new Position(Angle
.fromDegrees(lat
), Angle
.fromDegrees(lon
), pickAltitude
);
624 public void dispose()
626 // TODO: dispose of the icon texture
631 public String
toString()
633 return this.getName();