src.gov.nasa.worldwind.layers.ScalebarLayer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwindx Show documentation
Show all versions of worldwindx Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* 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.awt.TextRenderer;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;
import javax.media.opengl.*;
import java.awt.*;
import java.awt.geom.*;
/**
* Renders a scalebar graphic in a screen corner.
*
* @author Patrick Murris
* @version $Id: ScalebarLayer.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class ScalebarLayer extends AbstractLayer
{
// Units constants
public final static String UNIT_METRIC = "gov.nasa.worldwind.ScalebarLayer.Metric";
public final static String UNIT_IMPERIAL = "gov.nasa.worldwind.ScalebarLayer.Imperial";
public final static String UNIT_NAUTICAL = "gov.nasa.worldwind.ScalebarLayer.Nautical";
// Display parameters - TODO: make configurable
private Dimension size = new Dimension(150, 10);
private Color color = Color.white;
private int borderWidth = 20;
private String position = AVKey.SOUTHEAST;
private String resizeBehavior = AVKey.RESIZE_SHRINK_ONLY;
private String unit = UNIT_METRIC;
private Font defaultFont = Font.decode("Arial-PLAIN-12");
private double toViewportScale = 0.2;
private PickSupport pickSupport = new PickSupport();
private Vec4 locationCenter = null;
private Vec4 locationOffset = null;
private double pixelSize;
// Draw it as ordered with an eye distance of 0 so that it shows up in front of most other things.
// TODO: Add general support for this common pattern.
private OrderedIcon orderedImage = new OrderedIcon();
private class OrderedIcon implements OrderedRenderable
{
public double getDistanceFromEye()
{
return 0;
}
public void pick(DrawContext dc, Point pickPoint)
{
ScalebarLayer.this.draw(dc);
}
public void render(DrawContext dc)
{
ScalebarLayer.this.draw(dc);
}
}
/** Renders a scalebar graphic in a screen corner */
public ScalebarLayer()
{
setPickEnabled(false);
}
// Public properties
/**
* Get the apparent pixel size in meter at the reference position.
*
* @return the apparent pixel size in meter at the reference position.
*/
public double getPixelSize()
{
return this.pixelSize;
}
/**
* Get the scalebar graphic Dimension (in pixels)
*
* @return the scalebar graphic Dimension
*/
public Dimension getSize()
{
return this.size;
}
/**
* Set the scalebar graphic Dimenion (in pixels)
*
* @param size the scalebar graphic Dimension
*/
public void setSize(Dimension size)
{
if (size == null)
{
String message = Logging.getMessage("nullValue.DimensionIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.size = size;
}
/**
* Get the scalebar color
*
* @return the scalebar Color
*/
public Color getColor()
{
return this.color;
}
/**
* Set the scalbar Color
*
* @param color the scalebar Color
*/
public void setColor(Color color)
{
if (color == null)
{
String msg = Logging.getMessage("nullValue.ColorIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.color = color;
}
/**
* Returns the scalebar-to-viewport scale factor.
*
* @return the scalebar-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 scalebar. This scale
* factor is used only when the layer's resize behavior is AVKey.RESIZE_STRETCH or AVKey.RESIZE_SHRINK_ONLY. The
* scalebar's width is adjusted to occupy the proportion of the viewport's width indicated by this factor. The
* scalebar's height is adjusted to maintain the scalebar's Dimension aspect ratio.
*
* @param toViewportScale the scalebar to viewport scale factor
*/
public void setToViewportScale(double toViewportScale)
{
this.toViewportScale = toViewportScale;
}
public String getPosition()
{
return this.position;
}
/**
* Sets the relative viewport location to display the scalebar. Can be one of AVKey.NORTHEAST, AVKey.NORTHWEST,
* AVKey.SOUTHEAST (the default), or AVKey.SOUTHWEST. These indicate the corner of the viewport.
*
* @param position the desired scalebar position
*/
public void setPosition(String position)
{
if (position == null)
{
String msg = Logging.getMessage("nullValue.PositionIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.position = position;
}
/**
* Returns the current scalebar center location.
*
* @return the current location center. May be null.
*/
public Vec4 getLocationCenter()
{
return locationCenter;
}
/**
* Specifies the screen location of the scalebar 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 scalebar location will be affected by the currently specified location offset if a non-null location offset
* has been specified (see #setLocationOffset).
*
* @param locationCenter the scalebar center. May be null.
*
* @see #setPosition
* @see #setLocationOffset
*/
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 scalebar's position on the screen.
*
* @param locationOffset the number of pixels to shift the scalebar from its specified screen position. A positive X
* value shifts the image to the right. A positive Y value shifts the image up. If null, no
* offset is applied. The default offset is null.
*
* @see #setLocationCenter
* @see #setPosition
*/
public void setLocationOffset(Vec4 locationOffset)
{
this.locationOffset = locationOffset;
}
/**
* Returns the layer's resize behavior.
*
* @return the layer's resize behavior
*/
public String getResizeBehavior()
{
return resizeBehavior;
}
/**
* Sets the behavior the layer uses to size the scalebar when the viewport size changes, typically when the World
* Wind window is resized. If the value is AVKey.RESIZE_KEEP_FIXED_SIZE, the scalebar size is kept to the size
* specified in its Dimension scaled by the layer's current icon scale. If the value is AVKey.RESIZE_STRETCH, the
* scalebar is resized to have a constant size relative to the current viewport size. If the viewport shrinks the
* scalebar size decreases; if it expands then the scalebar enlarges. If the value is AVKey.RESIZE_SHRINK_ONLY (the
* default), scalebar sizing behaves as for AVKey.RESIZE_STRETCH but it will not grow larger than the size specified
* in its Dimension.
*
* @param resizeBehavior the desired resize behavior
*/
public void setResizeBehavior(String resizeBehavior)
{
this.resizeBehavior = resizeBehavior;
}
public int getBorderWidth()
{
return borderWidth;
}
/**
* Sets the scalebar offset from the viewport border.
*
* @param borderWidth the number of pixels to offset the scalebar from the borders indicated by {@link
* #setPosition(String)}.
*/
public void setBorderWidth(int borderWidth)
{
this.borderWidth = borderWidth;
}
public String getUnit()
{
return this.unit;
}
/**
* Sets the unit the scalebar uses to display distances. Can be one of {@link #UNIT_METRIC} (the default), or {@link
* #UNIT_IMPERIAL}.
*
* @param unit the desired unit
*/
public void setUnit(String unit)
{
this.unit = unit;
}
/**
* Get the scalebar legend Fon
*
* @return the scalebar legend Font
*/
public Font getFont()
{
return this.defaultFont;
}
/**
* Set the scalebar legend Fon
*
* @param font the scalebar legend Font
*/
public void setFont(Font font)
{
if (font == null)
{
String msg = Logging.getMessage("nullValue.FontIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.defaultFont = font;
}
// Rendering
@Override
public void doRender(DrawContext dc)
{
dc.addOrderedRenderable(this.orderedImage);
}
@Override
public void doPick(DrawContext dc, Point pickPoint)
{
// Delegate drawing to the ordered renderable list
dc.addOrderedRenderable(this.orderedImage);
}
// Rendering
public void draw(DrawContext dc)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
OGLStackHandler ogsh = new OGLStackHandler();
try
{
ogsh.pushAttrib(gl, GL2.GL_TRANSFORM_BIT);
gl.glDisable(GL.GL_DEPTH_TEST);
double width = this.size.width;
double height = this.size.height;
// 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);
ogsh.pushModelviewIdentity(gl);
// Scale to a width x height space
// located at the proper position on screen
double scale = this.computeScale(viewport);
Vec4 locationSW = this.computeLocation(viewport, scale);
gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z());
gl.glScaled(scale, scale, 1);
// Compute scale size in real world
Position referencePosition = dc.getViewportCenterPosition();
if (referencePosition != null)
{
Vec4 groundTarget = dc.getGlobe().computePointFromPosition(referencePosition);
Double distance = dc.getView().getEyePoint().distanceTo3(groundTarget);
this.pixelSize = dc.getView().computePixelSizeAtDistance(distance);
Double scaleSize = this.pixelSize * width * scale; // meter
String unitLabel = "m";
if (this.unit.equals(UNIT_METRIC))
{
if (scaleSize > 10000)
{
scaleSize /= 1000;
unitLabel = "Km";
}
}
else if (this.unit.equals(UNIT_IMPERIAL))
{
scaleSize *= 3.280839895; // feet
unitLabel = "ft";
if (scaleSize > 5280)
{
scaleSize /= 5280;
unitLabel = "mile(s)";
}
}
else if (this.unit.equals(UNIT_NAUTICAL))
{
scaleSize *= 3.280839895; // feet
unitLabel = "ft";
if (scaleSize > 6076)
{
scaleSize /= 6076;
unitLabel = "Nautical mile(s)";
}
}
// Rounded division size
int pot = (int) Math.floor(Math.log10(scaleSize));
if (!Double.isNaN(pot))
{
int digit = Integer.parseInt(String.format("%.0f", scaleSize).substring(0, 1));
double divSize = digit * Math.pow(10, pot);
if (digit >= 5)
divSize = 5 * Math.pow(10, pot);
else if (digit >= 2)
divSize = 2 * Math.pow(10, pot);
double divWidth = width * divSize / scaleSize;
// Draw scale
if (!dc.isPickingMode())
{
gl.glEnable(GL.GL_BLEND);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
// Set color using current layer opacity
Color backColor = this.getBackgroundColor(this.color);
float[] colorRGB = backColor.getRGBColorComponents(null);
gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], (double) backColor.getAlpha() / 255d
* this.getOpacity());
gl.glTranslated((width - divWidth) / 2, 0d, 0d);
this.drawScale(dc, divWidth, height);
colorRGB = this.color.getRGBColorComponents(null);
gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.getOpacity());
gl.glTranslated(-1d / scale, 1d / scale, 0d);
this.drawScale(dc, divWidth, height);
// Draw label
String label = String.format("%.0f ", divSize) + unitLabel;
gl.glLoadIdentity();
gl.glDisable(GL.GL_CULL_FACE);
drawLabel(dc, label,
locationSW.add3(
new Vec4(divWidth * scale / 2 + (width - divWidth) / 2, height * scale, 0)));
}
else
{
// Picking
this.pickSupport.clearPickList();
this.pickSupport.beginPicking(dc);
// Draw unique color across the map
Color color = dc.getUniquePickColor();
int colorCode = color.getRGB();
// Add our object(s) to the pickable list
this.pickSupport.addPickableObject(colorCode, this, referencePosition, false);
gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue());
gl.glTranslated((width - divWidth) / 2, 0d, 0d);
this.drawRectangle(dc, divWidth, height);
// Done picking
this.pickSupport.endPicking(dc);
this.pickSupport.resolvePick(dc, dc.getPickPoint(), this);
}
}
}
}
finally
{
gl.glColor4d(1d, 1d, 1d, 1d); // restore the default OpenGL color
gl.glEnable(GL.GL_DEPTH_TEST);
if (!dc.isPickingMode())
{
gl.glBlendFunc(GL.GL_ONE, GL.GL_ZERO); // restore to default blend function
gl.glDisable(GL.GL_BLEND); // restore to default blend state
}
ogsh.pop(gl);
}
}
// Draw scale rectangle
private void drawRectangle(DrawContext dc, double width, double height)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glBegin(GL2.GL_POLYGON);
gl.glVertex3d(0, height, 0);
gl.glVertex3d(0, 0, 0);
gl.glVertex3d(width, 0, 0);
gl.glVertex3d(width, height, 0);
gl.glVertex3d(0, height, 0);
gl.glEnd();
}
// Draw scale graphic
private void drawScale(DrawContext dc, double width, double height)
{
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(0, height, 0);
gl.glVertex3d(0, 0, 0);
gl.glVertex3d(width, 0, 0);
gl.glVertex3d(width, height, 0);
gl.glEnd();
gl.glBegin(GL2.GL_LINE_STRIP);
gl.glVertex3d(width / 2, 0, 0);
gl.glVertex3d(width / 2, height / 2, 0);
gl.glEnd();
}
// Draw the scale label
private void drawLabel(DrawContext dc, String text, Vec4 screenPoint)
{
TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(),
this.defaultFont);
Rectangle2D nameBound = textRenderer.getBounds(text);
int x = (int) (screenPoint.x() - nameBound.getWidth() / 2d);
int y = (int) screenPoint.y();
textRenderer.begin3DRendering();
textRenderer.setColor(this.getBackgroundColor(this.color));
textRenderer.draw(text, x + 1, y - 1);
textRenderer.setColor(this.color);
textRenderer.draw(text, x, y);
textRenderer.end3DRendering();
}
private final float[] compArray = new float[4];
// Compute background color for best contrast
private Color getBackgroundColor(Color color)
{
Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), compArray);
if (compArray[2] > 0.5)
return new Color(0, 0, 0, 0.7f);
else
return new Color(1, 1, 1, 0.7f);
}
private double computeScale(java.awt.Rectangle viewport)
{
if (this.resizeBehavior.equals(AVKey.RESIZE_SHRINK_ONLY))
{
return Math.min(1d, (this.toViewportScale) * viewport.width / this.size.width);
}
else if (this.resizeBehavior.equals(AVKey.RESIZE_STRETCH))
{
return (this.toViewportScale) * viewport.width / this.size.width;
}
else if (this.resizeBehavior.equals(AVKey.RESIZE_KEEP_FIXED_SIZE))
{
return 1d;
}
else
{
return 1d;
}
}
private Vec4 computeLocation(java.awt.Rectangle viewport, double scale)
{
double scaledWidth = scale * this.size.width;
double scaledHeight = scale * this.size.height;
double x;
double y;
if (this.locationCenter != null)
{
x = this.locationCenter.x - scaledWidth / 2;
y = this.locationCenter.y - scaledHeight / 2;
}
else if (this.position.equals(AVKey.NORTHEAST))
{
x = viewport.getWidth() - scaledWidth - this.borderWidth;
y = viewport.getHeight() - scaledHeight - this.borderWidth;
}
else if (this.position.equals(AVKey.SOUTHEAST))
{
x = viewport.getWidth() - scaledWidth - this.borderWidth;
y = 0d + this.borderWidth;
}
else if (this.position.equals(AVKey.NORTHWEST))
{
x = 0d + this.borderWidth;
y = viewport.getHeight() - scaledHeight - this.borderWidth;
}
else if (this.position.equals(AVKey.SOUTHWEST))
{
x = 0d + this.borderWidth;
y = 0d + this.borderWidth;
}
else // use North East
{
x = viewport.getWidth() - scaledWidth / 2 - this.borderWidth;
y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth;
}
if (this.locationOffset != null)
{
x += this.locationOffset.x;
y += this.locationOffset.y;
}
return new Vec4(x, y, 0);
}
@Override
public String toString()
{
return Logging.getMessage("layers.Earth.ScalebarLayer.Name");
}
}