/* 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.Earth; import gov.nasa.worldwind.avlist.AVList; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.geom.Sector; import gov.nasa.worldwind.layers.AbstractLayer; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.render.GeographicText; import gov.nasa.worldwind.render.Polyline; import gov.nasa.worldwind.render.UserFacingText; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.RestorableSupport; import gov.nasa.worldwind.view.OrbitView; import java.awt.*; import java.util.ArrayList; import java.util.Map; /** * Displays the UTM graticule. * @author Patrick Murris * @version $Id: UTMGraticuleLayer.java 4710 2008-03-16 22:02:20Z patrickmurris $ */ public class UTMGraticuleLayer extends AbstractLayer { public static final String GRATICULE_UTM = "Graticule.UTM"; /** * Solid line rendering style. This style specifies that a line will be drawn without any breaks. * <br> * <pre><code>_________</code></pre> * <br> * is an example of a solid line. */ public static final String LINE_STYLE_SOLID = GraticuleRenderingParams.VALUE_LINE_STYLE_SOLID; /** * Dashed line rendering style. This style specifies that a line will be drawn as a series of * long strokes, with space in between. * <br> * <pre><code>- - - - -</code></pre> * <br> * is an example of a dashed line. */ public static final String LINE_STYLE_DASHED = GraticuleRenderingParams.VALUE_LINE_STYLE_DASHED; /** * Dotted line rendering style. This style specifies that a line will be drawn as a series of * evenly spaced "square" dots. * <br> * <pre><code>. . . . .</code></pre> * is an example of a dotted line. */ public static final String LINE_STYLE_DOTTED = GraticuleRenderingParams.VALUE_LINE_STYLE_DOTTED; // Exceptions for some meridians. Values: longitude, min latitude, max latitude private static final int[][] specialMeridians = {{3, 56, 64}, {6, 64, 72}, {9, 72, 84}, {21, 72, 84}, {33, 72, 84}}; // Latitude bands letters - from south to north private static final String latBands = "CDEFGHJKLMNPQRSTUVWX"; private ArrayList<GridElement> gridElements; private GraticuleSupport graticuleSupport = new GraticuleSupport(); public UTMGraticuleLayer() { createUTMRenderables(); initRenderingParams(); this.setPickEnabled(false); this.setName(Logging.getMessage("layers.Earth.UTMGraticule.Name")); } /** * Returns whether or not graticule lines will be rendered. * * @return true if graticule lines will be rendered; false otherwise. */ public boolean isDrawGraticule() { return getUTMRenderingParams().isDrawLines(); } /** * Sets whether or not graticule lines will be rendered. * * @param drawGraticule true to render graticule lines; false to disable rendering. */ public void setDrawGraticule(boolean drawGraticule) { getUTMRenderingParams().setDrawLines(drawGraticule); } /** * Returns the graticule line Color. * * @return Color used to render graticule lines. */ public Color getGraticuleLineColor() { return getUTMRenderingParams().getLineColor(); } /** * Sets the graticule line Color. * * @param color Color that will be used to render graticule lines. * @throws IllegalArgumentException if <code>color</code> is null. */ public void setGraticuleLineColor(Color color) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getUTMRenderingParams().setLineColor(color); } /** * Returns the graticule line width. * * @return width of the graticule lines. */ public double getGraticuleLineWidth() { return getUTMRenderingParams().getLineWidth(); } /** * Sets the graticule line width. * * @param lineWidth width of the graticule lines. */ public void setGraticuleLineWidth(double lineWidth) { getUTMRenderingParams().setLineWidth(lineWidth); } /** * Returns the graticule line rendering style. * * @return rendering style of the graticule lines. */ public String getGraticuleLineStyle() { return getUTMRenderingParams().getLineStyle(); } /** * Sets the graticule line rendering style. * * @param lineStyle rendering style of the graticule lines. * One of LINE_STYLE_PLAIN, LINE_STYLE_DASHED, or LINE_STYLE_DOTTED. * @throws IllegalArgumentException if <code>lineStyle</code> is null. */ public void setGraticuleLineStyle(String lineStyle) { if (lineStyle == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getUTMRenderingParams().setLineStyle(lineStyle); } /** * Returns whether or not graticule labels will be rendered. * * @return true if graticule labels will be rendered; false otherwise. */ public boolean isDrawLabels() { return getUTMRenderingParams().isDrawLabels(); } /** * Sets whether or not graticule labels will be rendered. * * @param drawLabels true to render graticule labels; false to disable rendering. */ public void setDrawLabels(boolean drawLabels) { getUTMRenderingParams().setDrawLabels(drawLabels); } /** * Returns the graticule label Color. * * @return Color used to render graticule labels. */ public Color getLabelColor() { return getUTMRenderingParams().getLabelColor(); } /** * Sets the graticule label Color. * * @param color Color that will be used to render graticule labels. * @throws IllegalArgumentException if <code>color</code> is null. */ public void setLabelColor(Color color) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getUTMRenderingParams().setLabelColor(color); } /** * Returns the Font used for graticule labels. * * @return Font used to render graticule labels. */ public Font getLabelFont() { return getUTMRenderingParams().getLabelFont(); } /** * Sets the Font used for graticule labels. * * @param font Font that will be used to render graticule labels. * @throws IllegalArgumentException if <code>font</code> is null. */ public void setLabelFont(Font font) { if (font == null) { String message = Logging.getMessage("nullValue.FontIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getUTMRenderingParams().setLabelFont(font); } public String getRestorableState() { RestorableSupport rs = RestorableSupport.newRestorableSupport(); // Creating a new RestorableSupport failed. RestorableSupport logged the problem, so just return null. if (rs == null) return null; RestorableSupport.StateObject so = rs.addStateObject("renderingParams"); for (Map.Entry<String, GraticuleRenderingParams> entry : this.graticuleSupport.getAllRenderingParams()) { if (entry.getKey() != null && entry.getValue() != null) { RestorableSupport.StateObject eso = rs.addStateObject(so, entry.getKey()); makeRestorableState(entry.getValue(), rs, eso); } } return rs.getStateAsXml(); } private static void makeRestorableState(AVList params, RestorableSupport rs, RestorableSupport.StateObject context) { if (params != null && rs != null) { for (Map.Entry<String, Object> p : params.getEntries()) { if (p.getValue() instanceof Color) { rs.addStateValueAsInteger(context, p.getKey() + ".Red", ((Color) p.getValue()).getRed()); rs.addStateValueAsInteger(context, p.getKey() + ".Green", ((Color) p.getValue()).getGreen()); rs.addStateValueAsInteger(context, p.getKey() + ".Blue", ((Color) p.getValue()).getBlue()); rs.addStateValueAsInteger(context, p.getKey() + ".Alpha", ((Color) p.getValue()).getAlpha()); } else if (p.getValue() instanceof Font) { rs.addStateValueAsString(context, p.getKey() + ".Name", ((Font) p.getValue()).getName()); rs.addStateValueAsInteger(context, p.getKey() + ".Style", ((Font) p.getValue()).getStyle()); rs.addStateValueAsInteger(context, p.getKey() + ".Size", ((Font) p.getValue()).getSize()); } else { rs.addStateValueAsString(context, p.getKey(), p.getValue().toString()); } } } } public void restoreState(String stateInXml) { if (stateInXml == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } RestorableSupport rs; try { rs = RestorableSupport.parse(stateInXml); } catch (Exception e) { // Parsing the document specified by stateInXml failed. String message = Logging.getMessage("generic.ExceptionAttemptingToParseStateXml", stateInXml); Logging.logger().severe(message); throw new IllegalArgumentException(message, e); } RestorableSupport.StateObject so = rs.getStateObject("renderingParams"); if (so != null) { RestorableSupport.StateObject[] renderParams = rs.getAllStateObjects(so); for (RestorableSupport.StateObject rp : renderParams) { if (rp != null) { GraticuleRenderingParams params = getRenderingParams(rp.getName()); if (params == null) params = new GraticuleRenderingParams(); restorableStateToParams(params, rs, rp); setRenderingParams(rp.getName(), params); } } } } private static void restorableStateToParams(AVList params, RestorableSupport rs, RestorableSupport.StateObject context) { if (params != null && rs != null) { Boolean b = rs.getStateValueAsBoolean(context, GraticuleRenderingParams.KEY_DRAW_LINES); if (b != null) params.setValue(GraticuleRenderingParams.KEY_DRAW_LINES, b); Integer red = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LINE_COLOR + ".Red"); Integer green = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LINE_COLOR + ".Green"); Integer blue = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LINE_COLOR + ".Blue"); Integer alpha = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LINE_COLOR + ".Alpha"); if (red != null && green != null && blue != null && alpha != null) params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(red, green, blue, alpha)); Double d = rs.getStateValueAsDouble(context, GraticuleRenderingParams.KEY_LINE_WIDTH); if (d != null) params.setValue(GraticuleRenderingParams.KEY_LINE_WIDTH, d); String s = rs.getStateValueAsString(context, GraticuleRenderingParams.KEY_LINE_STYLE); if (s != null) params.setValue(GraticuleRenderingParams.KEY_LINE_STYLE, s); b = rs.getStateValueAsBoolean(context, GraticuleRenderingParams.KEY_DRAW_LABELS); if (b != null) params.setValue(GraticuleRenderingParams.KEY_DRAW_LABELS, b); red = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_COLOR + ".Red"); green = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_COLOR + ".Green"); blue = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_COLOR + ".Blue"); alpha = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_COLOR + ".Alpha"); if (red != null && green != null && blue != null && alpha != null) params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(red, green, blue, alpha)); String name = rs.getStateValueAsString(context, GraticuleRenderingParams.KEY_LABEL_FONT + ".Name"); Integer style = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_FONT + ".Style"); Integer size = rs.getStateValueAsInteger(context, GraticuleRenderingParams.KEY_LABEL_FONT + ".Size"); if (name != null && style != null && size != null) params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, new Font(name, style, size)); } } // --- Graticule Rendering -------------------------------------------------------------- private void initRenderingParams() { GraticuleRenderingParams params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(.8f, .8f, .8f, .5f)); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(1f, 1f, 1f, .8f)); params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-14")); params.setValue(GraticuleRenderingParams.KEY_DRAW_LABELS, Boolean.TRUE); setRenderingParams(GRATICULE_UTM, params); } private GraticuleRenderingParams getUTMRenderingParams() { return this.graticuleSupport.getRenderingParams(GRATICULE_UTM); } protected GraticuleRenderingParams getRenderingParams(String key) { if (key == null) { String message = Logging.getMessage("nullValue.KeyIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return this.graticuleSupport.getRenderingParams(key); } protected void setRenderingParams(String key, GraticuleRenderingParams renderingParams) { if (key == null) { String message = Logging.getMessage("nullValue.KeyIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.graticuleSupport.setRenderingParams(key, renderingParams); } protected void addRenderable(Object renderable, String paramsKey) { if (renderable == null) { String message = Logging.getMessage("nullValue.ObjectIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.graticuleSupport.addRenderable(renderable, paramsKey); } protected void removeAllRenderables() { this.graticuleSupport.removeAllRenderables(); } public void doRender(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } removeAllRenderables(); selectUTMRenderables(dc); renderGraticule(dc); } protected void renderGraticule(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.graticuleSupport.render(dc); } /** * Select the visible grid elements * @param dc the current <code>DrawContext</code>. */ protected void selectUTMRenderables(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } Sector vs = dc.getVisibleSector(); OrbitView view = (OrbitView)dc.getView(); // Compute labels offset from view center Position centerPos = view.getCenterPosition(); Double pixelSizeDegrees = Angle.fromRadians(view.computePixelSizeAtDistance(view.getZoom()) / dc.getGlobe().getEquatorialRadius()).degrees; Double labelOffsetDegrees = pixelSizeDegrees * view.getViewport().getWidth() / 4; Position labelPos = Position.fromDegrees(centerPos.getLatitude().degrees - labelOffsetDegrees, centerPos.getLongitude().degrees - labelOffsetDegrees, 0); Double labelLatDegrees = labelPos.getLatitude().normalizedLatitude().degrees; labelLatDegrees = Math.min(Math.max(labelLatDegrees, -76), 78); labelPos = new Position(Angle.fromDegrees(labelLatDegrees), labelPos.getLongitude().normalizedLongitude(), 0); int count = 0; if (vs != null) { for (GridElement ge : this.gridElements) { if (ge.isInView(dc)) { if (ge.renderable instanceof GeographicText) { GeographicText gt = (GeographicText) ge.renderable; if (labelPos.getLatitude().degrees < 72 || "*32*34*36*".indexOf("*" + gt.getText() + "*") == -1) { // Adjust label position according to eye position Position pos = gt.getPosition(); if (ge.type.equals(GridElement.TYPE_LATITUDE_LABEL)) pos = Position.fromDegrees(pos.getLatitude().degrees, labelPos.getLongitude().degrees, pos.getElevation()); else if (ge.type.equals(GridElement.TYPE_LONGITUDE_LABEL)) pos = Position.fromDegrees(labelPos.getLatitude().degrees, pos.getLongitude().degrees, pos.getElevation()); gt.setPosition(pos); } } this.graticuleSupport.addRenderable(ge.renderable, GRATICULE_UTM); count++; } } //System.out.println("Total elements: " + count + " visible sector: " + vs); } } /** * Create the graticule grid elements */ private void createUTMRenderables() { this.gridElements = new ArrayList<GridElement>(); ArrayList<Position> positions = new ArrayList<Position>(); // Generate meridians and zone labels int lon = -180; int zoneNumber = 1; int maxLat = 84; for (int i = 0; i < 60; i++) { Angle longitude = Angle.fromDegrees(lon); // Meridian positions.clear(); positions.add(new Position(Angle.fromDegrees(-80), longitude, 10e3)); positions.add(new Position(Angle.fromDegrees(-60), longitude, 10e3)); positions.add(new Position(Angle.fromDegrees(-30), longitude, 10e3)); positions.add(new Position(Angle.ZERO, longitude, 10e3)); positions.add(new Position(Angle.fromDegrees(30), longitude, 10e3)); if (lon < 6 || lon > 36) { // 'regular' UTM meridians maxLat = 84; positions.add(new Position(Angle.fromDegrees(60), longitude, 10e3)); positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3)); } else { // Exceptions: shorter meridians around and north-east of Norway if (lon == 6) { maxLat = 56; positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3)); } else { maxLat = 72; positions.add(new Position(Angle.fromDegrees(60), longitude, 10e3)); positions.add(new Position(Angle.fromDegrees(maxLat), longitude, 10e3)); } } Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); polyline.setTerrainConformance(50); Sector sector = Sector.fromDegrees(-80, maxLat, lon, lon); this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE)); // Zone label GeographicText text = new UserFacingText(zoneNumber + "", Position.fromDegrees(0, lon + 3, 0)); sector = Sector.fromDegrees(-90, 90, lon + 3, lon + 3); this.gridElements.add(new GridElement(sector, text, GridElement.TYPE_LONGITUDE_LABEL)); // Increase longitude and zone number lon += 6; zoneNumber++; } // Generate special meridian segments for exceptions around and north-east of Norway for (int i = 0; i < 5; i++) { positions.clear(); lon = this.specialMeridians[i][0]; positions.add(new Position(Angle.fromDegrees(this.specialMeridians[i][1]), Angle.fromDegrees(lon), 10e3)); positions.add(new Position(Angle.fromDegrees(this.specialMeridians[i][2]), Angle.fromDegrees(lon), 10e3)); Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); polyline.setTerrainConformance(50); Sector sector = Sector.fromDegrees(this.specialMeridians[i][1], this.specialMeridians[i][2], lon, lon); this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE)); } // Generate parallels - no exceptions int lat = -80; for (int i = 0; i < 21; i++) { Angle latitude = Angle.fromDegrees(lat); positions.clear(); positions.add(new Position(latitude, Angle.NEG180, 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(-150), 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(-120), 10e3)); positions.add(new Position(latitude, Angle.NEG90, 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(-60), 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(-30), 10e3)); positions.add(new Position(latitude, Angle.ZERO, 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(30), 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(60), 10e3)); positions.add(new Position(latitude, Angle.POS90, 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(120), 10e3)); positions.add(new Position(latitude, Angle.fromDegrees(150), 10e3)); positions.add(new Position(latitude, Angle.POS180, 10e3)); Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.LINEAR); polyline.setFollowTerrain(true); polyline.setTerrainConformance(20); Sector sector = Sector.fromDegrees(lat, lat, -180, 180); this.gridElements.add(new GridElement(sector, polyline, GridElement.TYPE_LINE)); // Latitude band label if (i < 20) { GeographicText text = new UserFacingText(this.latBands.charAt(i) + "", Position.fromDegrees(lat + 4, 0, 0)); sector = Sector.fromDegrees(lat + 4, lat + 4, -180, 180); this.gridElements.add(new GridElement(sector, text, GridElement.TYPE_LATITUDE_LABEL)); } // Increase latitude lat += lat < 72 ? 8 : 12; } } protected class GridElement { public final static String TYPE_LINE = "GridElement_Line"; public final static String TYPE_LINE_NORTH = "GridElement_LineNorth"; public final static String TYPE_LINE_SOUTH = "GridElement_LineSouth"; public final static String TYPE_LINE_WEST = "GridElement_LineWest"; public final static String TYPE_LINE_EAST = "GridElement_LineEast"; public final static String TYPE_LINE_NORTHING = "GridElement_LineNorthing"; public final static String TYPE_LINE_EASTING = "GridElement_LineEasting"; public final static String TYPE_GRIDZONE_LABEL = "GridElement_GridZoneLabel"; public final static String TYPE_LONGITUDE_LABEL = "GridElement_LongitudeLabel"; public final static String TYPE_LATITUDE_LABEL = "GridElement_LatitudeLabel"; protected final Sector sector; protected final Object renderable; protected final String type; protected double value; public GridElement(Sector sector, Object renderable, String type) { if (sector == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (renderable == null) { String message = Logging.getMessage("nullValue.ObjectIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (type == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.sector = sector; this.renderable = renderable; this.type = type; } public void setValue(double value) { this.value = value; } public boolean isInView(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return isInView(dc, dc.getVisibleSector()); } public boolean isInView(DrawContext dc, Sector vs) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (vs == null) { String message = Logging.getMessage("nullValue.SectorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (!this.sector.intersects(vs)) return false; return true; } } }