Update to Worldwind release 0.4.0
[worldwind-tracker.git] / gov / nasa / worldwind / layers / Earth / WorldMapLayer.java
blobef2f3dada039a1d9d33091cbd3222d7a23cfc9a5
1 /*
2 Copyright (C) 2001, 2006 United States Government
3 as represented by the Administrator of the
4 National Aeronautics and Space Administration.
5 All Rights Reserved.
6 */
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;
21 import java.awt.*;
22 import java.io.*;
24 /**
25 * Displays a world map overlay with a current position crosshair in a screen corner. Supports picking at a position on
26 * the map.
28 * @author Patrick Murris
29 * @version $Id$
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()
64 return 0;
67 public void pick(DrawContext dc, Point pickPoint)
69 WorldMapLayer.this.drawIcon(dc);
72 public void render(DrawContext dc)
74 WorldMapLayer.this.drawIcon(dc);
78 /**
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"));
84 this.setOpacity(0.6);
85 this.setIconFilePath(Configuration.getStringValue(AVKey.WORLD_MAP_IMAGE_PATH));
88 /**
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"));
96 this.setOpacity(0.6);
97 this.setIconFilePath(iconFilePath);
100 // Public properties
103 * Returns the layer's current icon file path.
105 * @return the icon file path
107 public String getIconFilePath()
109 return iconFilePath;
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()
159 return iconScale;
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()
204 return borderWidth;
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()
225 return position;
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
231 * the icon.
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)
263 if (color == null)
265 String msg = Logging.getMessage("nullValue.ColorIsNull");
266 Logging.logger().severe(msg);
267 throw new IllegalArgumentException(msg);
269 this.backColor = color;
272 @Override
273 public void doRender(DrawContext dc)
275 // Delegate drawing to the ordered renderable list
276 dc.addOrderedRenderable(this.orderedImage);
279 @Override
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)
289 return;
291 GL gl = dc.getGL();
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
301 | GL.GL_ENABLE_BIT
302 | GL.GL_TEXTURE_BIT
303 | GL.GL_TRANSFORM_BIT
304 | GL.GL_VIEWPORT_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)
316 // TODO: log warning
317 return;
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);
332 gl.glPushMatrix();
333 projectionPushed = true;
334 gl.glLoadIdentity();
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);
339 gl.glPushMatrix();
340 modelviewPushed = true;
341 gl.glLoadIdentity();
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);
363 gl.glEnd();
365 // Draw world map icon
366 gl.glColor4d(1d, 1d, 1d, this.getOpacity());
367 gl.glEnable(GL.GL_TEXTURE_2D);
368 iconTexture.bind();
370 TextureCoords texCoords = iconTexture.getImageTexCoords();
371 dc.drawUnitQuad(texCoords);
373 // Draw crosshair for current location
374 gl.glLoadIdentity();
375 gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
376 // Scale to width x height space
377 gl.glScaled(scale, scale, 1);
378 // Set color
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
390 // Draw
391 gl.glBegin(GL.GL_LINE_STRIP);
392 gl.glVertex3d(x - w, y, 0);
393 gl.glVertex3d(x + w + 1, y, 0);
394 gl.glEnd();
395 gl.glBegin(GL.GL_LINE_STRIP);
396 gl.glVertex3d(x, y - w, 0);
397 gl.glVertex3d(x, y + w + 1, 0);
398 gl.glEnd();
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);
407 gl.glEnd();
409 else
411 // Picking
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);
429 gl.glEnd();
430 // Done picking
431 this.pickSupport.endPicking(dc);
432 this.pickSupport.resolvePick(dc, dc.getPickPoint(), this);
436 finally
438 if (projectionPushed)
440 gl.glMatrixMode(GL.GL_PROJECTION);
441 gl.glPopMatrix();
443 if (modelviewPushed)
445 gl.glMatrixMode(GL.GL_MODELVIEW);
446 gl.glPopMatrix();
448 if (attribsPushed)
449 gl.glPopAttrib();
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))
465 return 1d;
467 else
469 return 1d;
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;
491 double x;
492 double y;
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)
532 return;
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);
547 iconTexture.bind();
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);
559 GL gl = dc.getGL();
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)
580 if (view == null)
581 return null;
583 Position groundPos = view.computePositionFromScreenPoint(
584 view.getViewport().getWidth() / 2, view.getViewport().getHeight() / 2);
585 if (groundPos == null)
586 return null;
588 double elevation = dc.getGlobe().getElevation(groundPos.getLatitude(), groundPos.getLongitude());
589 return new Position(
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);
621 return pickPosition;
624 public void dispose()
626 // TODO: dispose of the icon texture
630 @Override
631 public String toString()
633 return this.getName();