package worldwinddemo; import gov.nasa.worldwind.*; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.layers.*; import javax.media.opengl.GL; import com.sun.opengl.util.j2d.TextRenderer; import java.awt.Color; import java.awt.Font; import java.awt.geom.Rectangle2D; import java.awt.Dimension; /** * Renders a graphic scalebar in a screen corner * @author Patrick Murris * @version 0.1 - 20070608 - First version - not very smart yet */ public class ScalebarLayer extends RenderableLayer { // The layer public ScalebarLayer() { this.setName("Scalebar"); this.addRenderable(new Scale(this)); } // The renderable private static class Scale implements Renderable, Disposable { // Positionning constants public final static String NORTHWEST = "NW"; public final static String SOUTHWEST = "SW"; public final static String NORTHEAST = "NE"; public final static String SOUTHEAST = "SE"; // Stretching behavior constants public final static String RESIZE_STRETCH = "Stretch"; public final static String RESIZE_SHRINK_ONLY = "ShrinkOnly"; public final static String RESIZE_KEEP_FIXED_SIZE = "FixedSize"; // Units constants public final static String UNIT_METRIC = "Metric"; public final static String UNIT_IMPERIAL = "Imperial"; // Display parameters - TODO: make configurable private Dimension size = new Dimension(150, 10); protected Color color = Color.white; private int borderWidth = 20; private String position = SOUTHEAST; private String resizeBehavior = RESIZE_SHRINK_ONLY; protected String unit = UNIT_METRIC; private Font defaultFont = Font.decode("Arial-12-PLAIN"); private double toViewportScale = 0.2; protected Layer layer; private Point locationCenter = null; private TextRenderer textRenderer = null; // Constructor public Scale(Layer layer) { this.layer = layer; } // Public properties public Dimension getSize() { return this.size; } public void setSize(Dimension size) { this.size = size; } public Color getColor() { return this.color; } public void setColor(Color color) { this.color = color; } public String getPosition() { return this.position; } public void setPosition(String position) { this.position = position; } public String getResizeBehavior() { return resizeBehavior; } public void setResizeBehavior(String resizeBehavior) { this.resizeBehavior = resizeBehavior; } public int getBorderWidth() { return borderWidth; } public void setBorderWidth(int borderWidth) { this.borderWidth = borderWidth; } public String getUnit() { return this.unit; } public void setUnit(String unit) { this.unit = unit; } public Font getFont() { return this.defaultFont; } public void setFont(Font font) { this.defaultFont = font; } // Rendering public void render(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 0..1 space stretched over the widht/height area // located at the proper position on screen // TODO: implement a variable length scale with size and // subdivisions rounded like 10 Km, 20Km, 1000Km... etc double scale = this.computeScale(viewport); Point locationSW = this.computeLocation(viewport, scale); gl.glTranslated(locationSW.x(), locationSW.y(), locationSW.z()); gl.glScaled(scale, scale, 1); gl.glScaled(width, height, 1d); // Set color float[] colorRGB = this.color.getRGBColorComponents(null); gl.glColor4d(colorRGB[0], colorRGB[1], colorRGB[2], this.layer.getOpacity()); // Draw scale // TODO: add shadow for better contrast gl.glBegin(GL.GL_LINE_STRIP); gl.glVertex3d(0, 1 ,0); gl.glVertex3d(0, 0 ,0); gl.glVertex3d(1, 0 ,0); gl.glVertex3d(1, 1 ,0); gl.glEnd(); gl.glBegin(GL.GL_LINE_STRIP); gl.glVertex3d(0.5, 0 ,0); gl.glVertex3d(0.5, 0.5 ,0); gl.glEnd(); // Compute label Point groundTarget = dc.getGlobe().computePointFromPosition(dc.getView().getPosition()); Double distance = dc.getView().getEyePoint().distanceTo(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)"; } } String label = String.format("%.2f ", scaleSize) + unitLabel; // Draw label gl.glLoadIdentity(); gl.glDisable(GL.GL_CULL_FACE); drawLabel(dc, label, locationSW.add(new Point(width * scale / 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 the scale label private void drawLabel(DrawContext dc, String text, Point 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.setBackgroundColor(this.textRenderer, 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 text background color for best contrast private void setBackgroundColor(TextRenderer textRenderer, Color color) { Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), compArray); if (compArray[2] > 0.5) textRenderer.setColor(0, 0, 0, 0.7f); else textRenderer.setColor(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 Point 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 gov.nasa.worldwind.geom.Point(x, y, 0); } public void dispose() { } } // Scale renderable @Override public String toString() { return this.getName(); } } // ScaleLayer renderable layer