/* Copyright (C) 2001, 2007 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.sun.opengl.util.j2d.TextRenderer; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.layers.RenderableLayer; import gov.nasa.worldwind.render.*; import gov.nasa.worldwind.util.Logging; import javax.media.opengl.GL; import java.awt.*; import java.awt.geom.*; /** * Renders a scalebar graphic in a screen corner. * * @author Patrick Murris * @version $Id: ScalebarLayer.java 5178 2008-04-25 21:51:20Z patrickmurris $ */ public class ScalebarLayer extends RenderableLayer { // Positionning constants public final static String NORTHWEST = "gov.nasa.worldwind.ScalebarLayer.NorthWest"; public final static String SOUTHWEST = "gov.nasa.worldwind.ScalebarLayer.SouthWest"; public final static String NORTHEAST = "gov.nasa.worldwind.ScalebarLayer.NorthEast"; public final static String SOUTHEAST = "gov.nasa.worldwind.ScalebarLayer.SouthEast"; // Stretching behavior constants public final static String RESIZE_STRETCH = "gov.nasa.worldwind.ScalebarLayer.Stretch"; public final static String RESIZE_SHRINK_ONLY = "gov.nasa.worldwind.ScalebarLayer.ShrinkOnly"; public final static String RESIZE_KEEP_FIXED_SIZE = "gov.nasa.worldwind.ScalebarLayer.FixedSize"; // Units constants public final static String UNIT_METRIC = "gov.nasa.worldwind.ScalebarLayer.Metric"; public final static String UNIT_IMPERIAL = "gov.nasa.worldwind.ScalebarLayer.Imperial"; // Display parameters - TODO: make configurable private Dimension size = new Dimension(150, 10); private Color color = Color.white; private int borderWidth = 20; private String position = SOUTHEAST; private String resizeBehavior = RESIZE_SHRINK_ONLY; private String unit = UNIT_METRIC; private Font defaultFont = Font.decode("Arial-PLAIN-12"); private double toViewportScale = 0.2; private Vec4 locationCenter = null; private TextRenderer textRenderer = null; // 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) { } public void render(DrawContext dc) { ScalebarLayer.this.draw(dc); } } /** * Renders a scalebar graphic in a screen corner */ public ScalebarLayer() { this.setName(Logging.getMessage("layers.Earth.ScalebarLayer.Name")); } // Public properties /** * 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 {@link #RESIZE_STRETCH} or {@link * #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 {@link #NORTHEAST} (the default), * {@link #NORTHWEST}, {@link #SOUTHEAST}, or {@link #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 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 {@link #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 {@link #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 * {@link #RESIZE_SHRINK_ONLY} (the default), scalebar sizing behaves as for {@link #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); } // Rendering public void draw(DrawContext dc) { GL gl = dc.getGL(); boolean attribsPushed = false; boolean modelviewPushed = false; boolean projectionPushed = false; try { gl.glPushAttrib(GL.GL_DEPTH_BUFFER_BIT | GL.GL_COLOR_BUFFER_BIT | GL.GL_ENABLE_BIT | GL.GL_TEXTURE_BIT | GL.GL_TRANSFORM_BIT | GL.GL_VIEWPORT_BIT | GL.GL_CURRENT_BIT); attribsPushed = true; gl.glDisable(GL.GL_TEXTURE_2D); // no textures gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); 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(); gl.glMatrixMode(javax.media.opengl.GL.GL_PROJECTION); gl.glPushMatrix(); projectionPushed = true; gl.glLoadIdentity(); double maxwh = width > height ? width : height; gl.glOrtho(0d, viewport.width, 0d, viewport.height, -0.6 * maxwh, 0.6 * maxwh); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); modelviewPushed = true; gl.glLoadIdentity(); // 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 groundPos = dc.getViewportCenterPosition(); if(groundPos != null) { Vec4 groundTarget = dc.getGlobe().computePointFromPosition(groundPos); Double distance = dc.getView().getEyePoint().distanceTo3(groundTarget); Double pixelSize = dc.getView().computePixelSizeAtDistance(distance); Double scaleSize = 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)"; } } // Rounded division size int pot = (int)Math.floor(Math.log10(scaleSize)); int digit = Integer.parseInt(scaleSize.toString().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 // 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(label, locationSW.add3(new Vec4(divWidth * scale / 2 + (width - divWidth) / 2, height * scale, 0))); } } finally { if (projectionPushed) { gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); } if (modelviewPushed) { gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); } if (attribsPushed) gl.glPopAttrib(); } } // Draw scale graphic private void drawScale(DrawContext dc, double width, double height) { GL gl = dc.getGL(); gl.glBegin(GL.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(GL.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(String text, Vec4 screenPoint) { if (this.textRenderer == null) { this.textRenderer = new TextRenderer(this.defaultFont, true, true); } Rectangle2D nameBound = this.textRenderer.getBounds(text); int x = (int) (screenPoint.x() - nameBound.getWidth() / 2d); int y = (int) screenPoint.y(); this.textRenderer.begin3DRendering(); this.textRenderer.setColor(this.getBackgroundColor(this.color)); this.textRenderer.draw(text, x + 1, y - 1); this.textRenderer.setColor(this.color); this.textRenderer.draw(text, x, y); this.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(RESIZE_SHRINK_ONLY)) { return Math.min(1d, (this.toViewportScale) * viewport.width / this.size.width); } else if (this.resizeBehavior.equals(RESIZE_STRETCH)) { return (this.toViewportScale) * viewport.width / this.size.width; } else if (this.resizeBehavior.equals(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 = viewport.getWidth() - scaledWidth / 2 - this.borderWidth; y = viewport.getHeight() - scaledHeight / 2 - this.borderWidth; } else if (this.position.equals(NORTHEAST)) { x = viewport.getWidth() - scaledWidth - this.borderWidth; y = viewport.getHeight() - scaledHeight - this.borderWidth; } else if (this.position.equals(SOUTHEAST)) { x = viewport.getWidth() - scaledWidth - this.borderWidth; y = 0d + this.borderWidth; } else if (this.position.equals(NORTHWEST)) { x = 0d + this.borderWidth; y = viewport.getHeight() - scaledHeight - this.borderWidth; } else if (this.position.equals(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; } return new Vec4(x, y, 0); } public void dispose() { if (this.textRenderer != null) { this.textRenderer.dispose(); this.textRenderer = null; } } @Override public String toString() { return this.getName(); } }