gov.nasa.worldwind.layers.WorldMapLayer 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 com.jogamp.opengl.util.texture.*;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;
import com.jogamp.opengl.*;
import java.awt.*;
import java.io.*;
import java.util.ArrayList;
/**
* Displays a world map overlay with a current-position crosshair in a screen corner.
*
* A {@link gov.nasa.worldwindx.examples.ClickAndGoSelectListener} can be used in conjunction with this layer to move
* the view to a selected location when that location is clicked within the layer's map. Specify
* WorldMapLayer.class
when constructing the ClickAndGoSelectListener
.
*
* Note: This layer may not be shared among multiple {@link WorldWindow}s.
*
* @author Patrick Murris
* @version $Id: WorldMapLayer.java 2230 2014-08-14 18:19:48Z tgaskins $
*/
public class WorldMapLayer extends AbstractLayer
{
protected String iconFilePath;
protected double toViewportScale = 0.2;
protected double iconScale = 0.5;
protected int borderWidth = 20;
protected String position = AVKey.NORTHWEST;
protected String resizeBehavior = AVKey.RESIZE_SHRINK_ONLY;
protected int iconWidth;
protected int iconHeight;
protected Vec4 locationCenter = null;
protected Vec4 locationOffset = null;
protected Color color = Color.white;
protected Color backColor = new Color(0f, 0f, 0f, 0.4f);
protected boolean showFootprint = true;
protected ArrayList extends LatLon> footPrintPositions;
protected PickSupport pickSupport = new PickSupport();
protected long frameStampForPicking;
protected long frameStampForDrawing;
// 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)
{
WorldMapLayer.this.drawIcon(dc);
}
public void render(DrawContext dc)
{
WorldMapLayer.this.drawIcon(dc);
}
}
/** Displays a world map overlay with a current position crosshair in a screen corner */
public WorldMapLayer()
{
this.setOpacity(0.6);
this.setIconFilePath(Configuration.getStringValue(AVKey.WORLD_MAP_IMAGE_PATH));
}
/**
* Displays a world map overlay with a current position crosshair in a screen corner
*
* @param iconFilePath the world map image path and filename
*/
public WorldMapLayer(String iconFilePath)
{
this.setOpacity(0.6);
this.setIconFilePath(iconFilePath);
}
// Public properties
/**
* Returns the layer's current icon file path.
*
* @return the icon file path
*/
public String getIconFilePath()
{
return iconFilePath;
}
/**
* Sets the world map icon's image location. The layer first searches for this location in the current Java
* classpath. If not found then the specified path is assumed to refer to the local file system. found there then
* the
*
* @param iconFilePath the path to the icon's image file
*/
public void setIconFilePath(String iconFilePath)
{
if (iconFilePath == null || iconFilePath.length() == 0)
{
String message = Logging.getMessage("nullValue.FilePathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.iconFilePath = iconFilePath;
}
/**
* Returns the layer's world map-to-viewport scale factor.
*
* @return the world map-to-viewport scale factor
*/
public double getToViewportScale()
{
return toViewportScale;
}
/**
* Sets the scale factor applied to the viewport size to determine the displayed size of the world map icon. This
* scale factor is used only when the layer's resize behavior is AVKey.RESIZE_STRETCH or AVKey.RESIZE_SHRINK_ONLY.
* The icon's width is adjusted to occupy the proportion of the viewport's width indicated by this factor. The
* icon's height is adjusted to maintain the world map image's native aspect ratio.
*
* @param toViewportScale the world map to viewport scale factor
*/
public void setToViewportScale(double toViewportScale)
{
this.toViewportScale = toViewportScale;
}
/**
* Returns the icon scale factor. See {@link #setIconScale(double)} for a description of the scale factor.
*
* @return the current icon scale
*/
public double getIconScale()
{
return iconScale;
}
/**
* Sets the scale factor defining the displayed size of the world map icon relative to the icon's width and height
* in its image file. Values greater than 1 magify the image, values less than one minify it. If the layer's resize
* behavior is other than AVKey.RESIZE_KEEP_FIXED_SIZE, the icon's displayed sized is further affected by the value
* specified by {@link #setToViewportScale(double)} and the current viewport size.
*
* @param iconScale the icon scale factor
*/
public void setIconScale(double iconScale)
{
this.iconScale = iconScale;
}
/**
* Returns the world map icon's resize behavior.
*
* @return the icon's resize behavior
*/
public String getResizeBehavior()
{
return resizeBehavior;
}
/**
* Sets the behavior the layer uses to size the world map icon when the viewport size changes, typically when the
* World Wind window is resized. If the value is AVKey.RESIZE_KEEP_FIXED_SIZE, the icon size is kept to the size
* specified in its image file scaled by the layer's current icon scale. If the value is AVKey.RESIZE_STRETCH, the
* icon is resized to have a constant size relative to the current viewport size. If the viewport shrinks the icon
* size decreases; if it expands then the icon file enlarges. The relative size is determined by the current world
* map-to-viewport scale and by the icon's image file size scaled by the current icon scale. If the value is
* AVKey.RESIZE_SHRINK_ONLY (the default), icon sizing behaves as for AVKey.RESIZE_STRETCH but the icon will not
* grow larger than the size specified in its image file scaled by the current icon scale.
*
* @param resizeBehavior the desired resize behavior
*/
public void setResizeBehavior(String resizeBehavior)
{
this.resizeBehavior = resizeBehavior;
}
public int getBorderWidth()
{
return borderWidth;
}
/**
* Sets the world map icon offset from the viewport border.
*
* @param borderWidth the number of pixels to offset the world map icon from the borders indicated by {@link
* #setPosition(String)}.
*/
public void setBorderWidth(int borderWidth)
{
this.borderWidth = borderWidth;
}
/**
* Returns the current relative world map icon position.
*
* @return the current world map position
*/
public String getPosition()
{
return position;
}
/**
* Sets the relative viewport location to display the world map icon. Can be one of AVKey.NORTHEAST, AVKey.NORTHWEST
* (the default), AVKey.SOUTHEAST, or SOUTHWEST. These indicate the corner of the viewport to place the icon.
*
* @param position the desired world map position
*/
public void setPosition(String position)
{
if (position == null)
{
String message = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.position = position;
}
/**
* Returns the current worldmap image location.
*
* @return the current location center. May be null.
*/
public Vec4 getLocationCenter()
{
return locationCenter;
}
/**
* Specifies the screen location of the worldmap image, relative to the image's center. May be null. If this value
* is non-null, it overrides the position specified by #setPosition. The location is specified in pixels. The origin
* is the window's lower left corner. Positive X values are to the right of the origin, positive Y values are
* upwards from the origin. The final image location will be affected by the currently specified location offset if
* a non-null location offset has been specified (see #setLocationOffset).
*
* @param locationCenter the location center. May be null.
*
* @see #locationCenter the screen location at which to place the map.
*/
public void setLocationCenter(Vec4 locationCenter)
{
this.locationCenter = locationCenter;
}
/**
* Returns the current location offset. See #setLocationOffset for a description of the offset and its values.
*
* @return the location offset. Will be null if no offset has been specified.
*/
public Vec4 getLocationOffset()
{
return locationOffset;
}
/**
* Specifies a placement offset from the worldmap's position on the screen.
*
* @param locationOffset the number of pixels to shift the worldmap 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(gov.nasa.worldwind.geom.Vec4)
* @see #setPosition(String)
*/
public void setLocationOffset(Vec4 locationOffset)
{
this.locationOffset = locationOffset;
}
public Color getBackgrounColor()
{
return this.backColor;
}
public void setBackgroundColor(Color color)
{
if (color == null)
{
String msg = Logging.getMessage("nullValue.ColorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.backColor = color;
}
public boolean getShowFootprint()
{
return this.showFootprint;
}
public void setShowFootprint(boolean state)
{
this.showFootprint = state;
}
/**
* Get the current view footprint position list. May be null if no footprint is displayed or none has been
* computed.
*
* @return the current view footprint position list - may be null.
*/
public java.util.List extends LatLon> getFootPrintPositions()
{
return this.footPrintPositions;
}
@Override
public void doRender(DrawContext dc)
{
// Ensure that this shape isn't added to the ordered renderable list more than once per frame.
if (dc.isContinuous2DGlobe() && this.frameStampForDrawing == dc.getFrameTimeStamp())
return;
// Delegate drawing to the ordered renderable list
dc.addOrderedRenderable(this.orderedImage);
this.frameStampForDrawing = dc.getFrameTimeStamp();
}
@Override
public void doPick(DrawContext dc, Point pickPoint)
{
// Ensure that this shape isn't added to the ordered renderable list more than once per frame.
if (dc.isContinuous2DGlobe() && this.frameStampForPicking == dc.getFrameTimeStamp())
return;
// Delegate drawing to the ordered renderable list
dc.addOrderedRenderable(this.orderedImage);
this.frameStampForPicking = dc.getFrameTimeStamp();
}
protected void drawIcon(DrawContext dc)
{
if (this.getIconFilePath() == null)
return;
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler ogsh = new OGLStackHandler();
try
{
// Initialize texture if necessary
Texture iconTexture = dc.getTextureCache().getTexture(this.getIconFilePath());
if (iconTexture == null)
{
this.initializeTexture(dc);
iconTexture = dc.getTextureCache().getTexture(this.getIconFilePath());
if (iconTexture == null)
{
String msg = Logging.getMessage("generic.ImageReadFailed");
Logging.logger().finer(msg);
return;
}
}
gl.glDisable(GL.GL_DEPTH_TEST);
// Need to assign the width and height here to address the case in which the texture was already
// loaded into the cache by another layer or a previous instance of this one.
this.iconWidth = iconTexture.getWidth();
this.iconHeight = iconTexture.getHeight();
double width = this.getScaledIconWidth();
double height = this.getScaledIconHeight();
// Load a parallel projection with xy dimensions (viewportWidth, viewportHeight)
// into the GL projection matrix.
java.awt.Rectangle viewport = dc.getView().getViewport();
ogsh.pushProjectionIdentity(gl);
double maxwh = width > height ? width : height;
gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh);
// Translate and scale
ogsh.pushModelviewIdentity(gl);
double scale = this.computeScale(viewport);
Vec4 locationSW = this.computeLocation(viewport, scale);
gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
// Scale to 0..1 space
gl.glScaled(scale, scale, 1);
gl.glScaled(width, height, 1d);
if (!dc.isPickingMode())
{
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
// Draw background color behind the map
gl.glColor4ub((byte) this.backColor.getRed(), (byte) this.backColor.getGreen(),
(byte) this.backColor.getBlue(), (byte) (this.backColor.getAlpha() * this.getOpacity()));
dc.drawUnitQuad();
// Draw world map icon
gl.glColor4d(1d, 1d, 1d, this.getOpacity());
gl.glEnable(GL.GL_TEXTURE_2D);
iconTexture.bind(gl);
TextureCoords texCoords = iconTexture.getImageTexCoords();
dc.drawUnitQuad(texCoords);
gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
gl.glDisable(GL.GL_TEXTURE_2D);
// Draw crosshair for current location
gl.glLoadIdentity();
gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
// Scale to width x height space
gl.glScaled(scale, scale, 1);
// Set color
float[] colorRGB = this.color.getRGBColorComponents(null);
gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity());
// Draw crosshair
Position groundPos = this.computeGroundPosition(dc, dc.getView());
if (groundPos != null)
{
int x = (int) (width * (groundPos.getLongitude().degrees + 180) / 360);
int y = (int) (height * (groundPos.getLatitude().degrees + 90) / 180);
int w = 10; // cross branch length
// Draw
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(x - w, y, 0);
gl.glVertex3d(x + w + 1, y, 0);
gl.glEnd();
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(x, y - w, 0);
gl.glVertex3d(x, y + w + 1, 0);
gl.glEnd();
}
// Draw view footprint in map icon space
if (!dc.is2DGlobe() && this.showFootprint)
{
this.footPrintPositions = this.computeViewFootPrint(dc, 32);
if (this.footPrintPositions != null)
{
gl.glBegin(GL2.GL_LINE_STRIP);
LatLon p1 = this.footPrintPositions.get(0);
for (LatLon p2 : this.footPrintPositions)
{
int x = (int) (width * (p2.getLongitude().degrees + 180) / 360);
int y = (int) (height * (p2.getLatitude().degrees + 90) / 180);
// Draw
if (LatLon.locationsCrossDateline(p1, p2))
{
int y1 = (int) (height * (p1.getLatitude().degrees + 90) / 180);
gl.glVertex3d(x < width / 2 ? width : 0, (y1 + y) / 2, 0);
gl.glEnd();
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(x < width / 2 ? 0 : width, (y1 + y) / 2, 0);
}
gl.glVertex3d(x, y, 0);
p1 = p2;
}
gl.glEnd();
}
}
// Draw 1px border around and inside the map
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(0, 0, 0);
gl.glVertex3d(width, 0, 0);
gl.glVertex3d(width, height - 1, 0);
gl.glVertex3d(0, height - 1, 0);
gl.glVertex3d(0, 0, 0);
gl.glEnd();
}
else
{
// Picking
this.pickSupport.clearPickList();
this.pickSupport.beginPicking(dc);
// Where in the world are we picking ?
Position pickPosition =
computePickPosition(dc, locationSW, new Dimension((int) (width * scale), (int) (height * scale)));
Color color = dc.getUniquePickColor();
int colorCode = color.getRGB();
this.pickSupport.addPickableObject(colorCode, this, pickPosition, false);
gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
dc.drawUnitQuad();
this.pickSupport.endPicking(dc);
this.pickSupport.resolvePick(dc, dc.getPickPoint(), this);
}
}
finally
{
dc.restoreDefaultDepthTesting();
dc.restoreDefaultCurrentColor();
if (dc.isPickingMode())
dc.restoreDefaultBlending();
ogsh.pop(gl);
}
}
protected double computeScale(java.awt.Rectangle viewport)
{
if (this.resizeBehavior.equals(AVKey.RESIZE_SHRINK_ONLY))
{
return Math.min(1d, (this.toViewportScale) * viewport.width / this.getScaledIconWidth());
}
else if (this.resizeBehavior.equals(AVKey.RESIZE_STRETCH))
{
return (this.toViewportScale) * viewport.width / this.getScaledIconWidth();
}
else if (this.resizeBehavior.equals(AVKey.RESIZE_KEEP_FIXED_SIZE))
{
return 1d;
}
else
{
return 1d;
}
}
protected double getScaledIconWidth()
{
return this.iconWidth * this.iconScale;
}
protected double getScaledIconHeight()
{
return this.iconHeight * this.iconScale;
}
protected Vec4 computeLocation(java.awt.Rectangle viewport, double scale)
{
double width = this.getScaledIconWidth();
double height = this.getScaledIconHeight();
double scaledWidth = scale * width;
double scaledHeight = scale * 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);
}
protected void initializeTexture(DrawContext dc)
{
Texture iconTexture = dc.getTextureCache().getTexture(this.getIconFilePath());
if (iconTexture != null)
return;
GL gl = dc.getGL();
try
{
InputStream iconStream = this.getClass().getResourceAsStream("/" + this.getIconFilePath());
if (iconStream == null)
{
File iconFile = new File(this.iconFilePath);
if (iconFile.exists())
{
iconStream = new FileInputStream(iconFile);
}
}
TextureData textureData = OGLUtil.newTextureData(gl.getGLProfile(), iconStream, false);
iconTexture = TextureIO.newTexture(textureData);
iconTexture.bind(gl);
this.iconWidth = iconTexture.getWidth();
this.iconHeight = iconTexture.getHeight();
dc.getTextureCache().put(this.getIconFilePath(), iconTexture);
}
catch (IOException e)
{
String msg = Logging.getMessage("layers.IOExceptionDuringInitialization");
Logging.logger().severe(msg);
throw new WWRuntimeException(msg, e);
}
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);//_MIPMAP_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE);
// Enable texture anisotropy, improves "tilted" world map quality.
int[] maxAnisotropy = new int[1];
gl.glGetIntegerv(GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy, 0);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy[0]);
}
/**
* Compute the lat/lon position of the view center
*
* @param dc the current DrawContext
* @param view the current View
*
* @return the ground position of the view center or null
*/
protected Position computeGroundPosition(DrawContext dc, View view)
{
if (view == null)
return null;
Position groundPos = view.computePositionFromScreenPoint(
view.getViewport().getWidth() / 2, view.getViewport().getHeight() / 2);
if (groundPos == null)
return null;
double elevation = dc.getGlobe().getElevation(groundPos.getLatitude(), groundPos.getLongitude());
return new Position(
groundPos.getLatitude(),
groundPos.getLongitude(),
elevation * dc.getVerticalExaggeration());
}
/**
* Computes the lat/lon of the pickPoint over the world map
*
* @param dc the current DrawContext
* @param locationSW the screen location of the bottom left corner of the map
* @param mapSize the world map screen dimension in pixels
*
* @return the picked Position
*/
protected Position computePickPosition(DrawContext dc, Vec4 locationSW, Dimension mapSize)
{
Position pickPosition = null;
Point pickPoint = dc.getPickPoint();
if (pickPoint != null)
{
Rectangle viewport = dc.getView().getViewport();
// Check if pickpoint is inside the map
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)
{
double lon = (pickPoint.getX() - locationSW.getX()) / mapSize.width * 360 - 180;
double lat = (viewport.height - pickPoint.getY() - locationSW.getY()) / mapSize.height * 180 - 90;
double pickAltitude = 1000e3;
pickPosition = new Position(Angle.fromDegrees(lat), Angle.fromDegrees(lon), pickAltitude);
}
}
return pickPosition;
}
/**
* Compute the view range footprint on the globe.
*
* @param dc the current DrawContext
* @param steps the number of steps.
*
* @return an array list of LatLon
forming a closed shape.
*/
protected ArrayList computeViewFootPrint(DrawContext dc, int steps)
{
ArrayList positions = new ArrayList();
Position eyePos = dc.getView().getEyePosition();
Angle distance = Angle.fromRadians(
Math.asin(dc.getView().getFarClipDistance() / (dc.getGlobe().getRadius() + eyePos.getElevation())));
if (distance.degrees > 10)
{
double headStep = 360d / steps;
Angle heading = Angle.ZERO;
for (int i = 0; i <= steps; i++)
{
LatLon p = LatLon.greatCircleEndPosition(eyePos, heading, distance);
positions.add(p);
heading = heading.addDegrees(headStep);
}
return positions;
}
else
return null;
}
@Override
public String toString()
{
return Logging.getMessage("layers.Earth.WorldMapLayer.Name");
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy