/* 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.View; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.Globe; 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.geom.coords.MGRSCoord; import gov.nasa.worldwind.geom.coords.UTMCoord; import gov.nasa.worldwind.view.OrbitView; import java.awt.*; import java.awt.geom.Rectangle2D; import java.util.ArrayList; /** * @author Patrick Murris * @version $Id: MGRSGraticuleLayer.java 5175 2008-04-25 21:12:21Z patrickmurris $ */ public class MGRSGraticuleLayer extends UTMGraticuleLayer { /** * Graticule for the UTM grid. */ public static final String GRATICULE_UTM_GRID = "Graticule.UTM.Grid"; /** * Graticule for the 100,000 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_100000M = "Graticule.100000m"; /** * Graticule for the 10,000 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_10000M = "Graticule.10000m"; /** * Graticule for the 1,000 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_1000M = "Graticule.1000m"; /** * Graticule for the 100 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_100M = "Graticule.100m"; /** * Graticule for the 10 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_10M = "Graticule.10m"; /** * Graticule for the 1 meter grid, nested inside the UTM grid. */ public static final String GRATICULE_1M = "Graticule.1m"; private GridZone[][] gridZones = new GridZone[20][60]; // row/col private double zoneMaxAltitude = 5000e3; private double squareMaxAltitude = 3000e3; private MetricScaleSupport metricScaleSupport = new MetricScaleSupport(); private int renderablesCount = 0; private int visibleCellsCount = 0; private long frameCount = 0; private double polylineTerrainConformance = 50; private Frustum viewFrustum; private Vec4 lastEyePoint; private double lastViewHeading = 0; private double lastViewPitch = 0; private double lastViewFOV = 0; private double lastVerticalExaggeration = 1; private Globe globe; /** * Creates a new <code>MGRSGraticuleLayer</code>, with default graticule attributes. */ public MGRSGraticuleLayer() { initRenderingParams(); this.setName(Logging.getMessage("layers.Earth.MGRSGraticule.Name")); } /** * Returns the maxiumum resolution graticule that will be rendered, or null if no graticules will be rendered. * By default, all graticules are rendered, and this will return GRATICULE_1M. * * @return maximum resolution rendered. */ public String getMaximumGraticuleResolution() { String maxTypeDrawn = null; String[] orderedTypeList = getOrderedTypes(); for (String type : orderedTypeList) { GraticuleRenderingParams params = getRenderingParams(type); if (params.isDrawLines()) { maxTypeDrawn = type; } } return maxTypeDrawn; } /** * Sets the maxiumum resolution graticule that will be rendered. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setMaximumGraticuleResolution(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } boolean pastTarget = false; String[] orderedTypeList = getOrderedTypes(); for (String type : orderedTypeList) { // Enable all graticulte BEFORE and INCLUDING the target. // Disable all graticules AFTER the target. GraticuleRenderingParams params = getRenderingParams(type); params.setDrawLines(!pastTarget); if (!pastTarget && type.equals(graticuleType)) { pastTarget = true; } } } /** * Returns the line color of the specified graticule. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return Color of the the graticule line. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public Color getGraticuleLineColor(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).getLineColor(); } /** * Sets the line rendering color for the specified graticule. * * @param color the line color for the specified graticule. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>color</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineColor(Color color, String graticuleType) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setLineColor(color); } /** * Sets the line rendering color for the specified graticules. * * @param color the line color for the specified graticules. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>color</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineColor(Color color, Iterable<String> graticuleType) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setGraticuleLineColor(color, type); } } /** * Sets the line rendering color for all graticules. * * @param color the line color. * @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); } String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setGraticuleLineColor(color, type); } } /** * Returns the line width of the specified graticule. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return width of the graticule line. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public double getGraticuleLineWidth(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).getLineWidth(); } /** * Sets the line rendering width for the specified graticule. * * @param lineWidth the line rendering width for the specified graticule. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineWidth(double lineWidth, String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setLineWidth(lineWidth); } /** * Sets the line rendering width for the specified graticules. * * @param lineWidth the line rendering width for the specified graticules. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineWidth(double lineWidth, Iterable<String> graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setGraticuleLineWidth(lineWidth, type); } } /** * Sets the line rendering width for all graticules. * * @param lineWidth the line rendering width. */ public void setGraticuleLineWidth(double lineWidth) { String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setGraticuleLineWidth(lineWidth, type); } } /** * Returns the line rendering style of the specified graticule. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return line rendering style of the graticule. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public String getGraticuleLineStyle(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).getLineStyle(); } /** * Sets the line rendering style for the specified graticule. * * @param lineStyle the line rendering style for the specified graticule. * One of LINE_STYLE_PLAIN, LINE_STYLE_DASHED, or LINE_STYLE_DOTTED. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M * @throws IllegalArgumentException if <code>lineStyle</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineStyle(String lineStyle, String graticuleType) { if (lineStyle == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setLineStyle(lineStyle); } /** * Sets the line rendering style for the specified graticules. * * @param lineStyle the line rendering style for the specified graticules. * One of LINE_STYLE_PLAIN, LINE_STYLE_DASHED, or LINE_STYLE_DOTTED. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M * @throws IllegalArgumentException if <code>lineStyle</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setGraticuleLineStyle(String lineStyle, Iterable<String> graticuleType) { if (lineStyle == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setGraticuleLineStyle(lineStyle, type); } } /** * Sets the line rendering style for all graticules. * * @param lineStyle the line rendering style. * 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); } String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setGraticuleLineStyle(lineStyle, type); } } /** * Returns whether specified graticule labels will be rendered. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return true if graticule labels are will be rendered; false otherwise. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public boolean isDrawLabels(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).isDrawLabels(); } /** * Sets whether the specified graticule labels will be rendered. * If true, the graticule labels will be rendered. * Otherwise, the graticule labels will not be rendered, but other graticules will not be affected. * * @param drawLabels true to render graticule labels; false to disable rendering. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setDrawLabels(boolean drawLabels, String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setDrawLabels(drawLabels); } /** * Sets whether the specified graticule labels will be rendered. * If true, the graticule labels will be rendered. * Otherwise, the graticule labels will not be rendered, but other graticules will not be affected. * * @param drawLabels true to render graticule labels; false to disable rendering. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setDrawLabels(boolean drawLabels, Iterable<String> graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setDrawLabels(drawLabels, type); } } /** * Sets whether all graticule labels will be rendered. * If true, all graticule labels will be rendered. * Otherwise, all graticule labels will not be rendered. * * @param drawLabels true to render all graticule labels; false to disable rendering. */ public void setDrawLabels(boolean drawLabels) { String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setDrawLabels(drawLabels, type); } } /** * Returns the label color of the specified graticule. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return Color of the the graticule label. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public Color getLabelColor(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).getLabelColor(); } /** * Sets the label rendering color for the specified graticule. * * @param color the label color for the specified graticule. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>color</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setLabelColor(Color color, String graticuleType) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setLabelColor(color); } /** * Sets the label rendering color for the specified graticules. * * @param color the label color for the specified graticules. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>color</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setLabelColor(Color color, Iterable<String> graticuleType) { if (color == null) { String message = Logging.getMessage("nullValue.ColorIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setLabelColor(color, type); } } /** * Sets the label rendering color for all graticules. * * @param color the label color. * @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); } String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setLabelColor(color, type); } } /** * Returns the label font of the specified graticule. * * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @return Font of the graticule label. * @throws IllegalArgumentException if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public Font getLabelFont(String graticuleType) { if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } return getRenderingParams(graticuleType).getLabelFont(); } /** * Sets the label rendering font for the specified graticule. * * @param font the label font for the specified graticule. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>font</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setLabelFont(Font font, String graticuleType) { if (font == null) { String message = Logging.getMessage("nullValue.FontIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.StringIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } getRenderingParams(graticuleType).setLabelFont(font); } /** * Sets the label rendering font for the specified graticules. * * @param font the label font for the specified graticules. * @param graticuleType one of GRATICULE_UTM, GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, * GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, or GRATICULE_1M. * @throws IllegalArgumentException if<code>font</code> is null, * if <code>graticuleType</code> is null, * or if <code>graticuleType</code> is not a valid type. */ public void setLabelFont(Font font, Iterable<String> graticuleType) { if (font == null) { String message = Logging.getMessage("nullValue.FontIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (graticuleType == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } for (String type : graticuleType) { setLabelFont(font, type); } } /** * Sets the label rendering font for all graticules. * * @param font the label font. * @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); } String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { setLabelFont(font, type); } } private void initRenderingParams() { GraticuleRenderingParams params; // UTM graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.YELLOW); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.YELLOW); params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-16")); setRenderingParams(GRATICULE_UTM_GRID, params); // 100,000 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.GREEN); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.GREEN); params.setValue(GraticuleRenderingParams.KEY_LABEL_FONT, Font.decode("Arial-Bold-14")); setRenderingParams(GRATICULE_100000M, params); // 10,000 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 102, 255)); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 102, 255)); setRenderingParams(GRATICULE_10000M, params); // 1,000 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, Color.CYAN); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, Color.CYAN); setRenderingParams(GRATICULE_1000M, params); // 100 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(0, 153, 153)); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(0, 153, 153)); setRenderingParams(GRATICULE_100M, params); // 10 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(102, 255, 204)); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(102, 255, 204)); setRenderingParams(GRATICULE_10M, params); // 1 meter graticule params = new GraticuleRenderingParams(); params.setValue(GraticuleRenderingParams.KEY_LINE_COLOR, new Color(153, 153, 255)); params.setValue(GraticuleRenderingParams.KEY_LABEL_COLOR, new Color(153, 153, 255)); setRenderingParams(GRATICULE_1M, params); } private String[] getOrderedTypes() { return new String[] { GRATICULE_UTM_GRID, GRATICULE_100000M, GRATICULE_10000M, GRATICULE_1000M, GRATICULE_100M, GRATICULE_10M, GRATICULE_1M, }; } private String getTypeFor(int resolution) { String graticuleType = null; switch (resolution) { case 100000: // 100,000 meters graticuleType = GRATICULE_100000M; break; case 10000: // 10,000 meters graticuleType = GRATICULE_10000M; break; case 1000: // 1000 meters graticuleType = GRATICULE_1000M; break; case 100: // 100 meters graticuleType = GRATICULE_100M; break; case 10: // 10 meters graticuleType = GRATICULE_10M; break; case 1: // 1 meter graticuleType = GRATICULE_1M; break; } return graticuleType; } // --- Renderable layer -------------------------------------------------------------- public void doRender(DrawContext dc) { if (dc == null) { String message = Logging.getMessage("nullValue.DrawContextIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.needsToUpdate(dc)) { // Init frame removeAllRenderables(); this.renderablesCount = 0; this.visibleCellsCount = 0; this.frameCount++; this.polylineTerrainConformance = computeTerrainConformance(dc); applyTerrainConformance(); this.viewFrustum = dc.getView().getFrustumInModelCoordinates(); this.lastEyePoint = dc.getView().getEyePoint(); this.lastViewFOV = dc.getView().getFieldOfView().degrees; if (dc.getView() instanceof OrbitView) { this.lastViewHeading = ((OrbitView)dc.getView()).getHeading().degrees; this.lastViewPitch = ((OrbitView)dc.getView()).getPitch().degrees; } this.lastVerticalExaggeration = dc.getVerticalExaggeration(); this.globe = dc.getGlobe(); this.metricScaleSupport.clear(); this.metricScaleSupport.computeZone(dc); Sector vs = computeVisibleSector(dc); // Select renderables if (dc.getView().getEyePosition().getElevation() <= this.zoneMaxAltitude) { selectMGRSRenderables(dc, vs); this.metricScaleSupport.selectRenderables(dc); //System.out.println("MGRS visible: " + visibleCellsCount + " renderables: " + this.renderablesCount + " vs: " + vs); } else { super.selectUTMRenderables(dc); } } // Render super.renderGraticule(dc); } private void applyTerrainConformance() { String[] graticuleType = getOrderedTypes(); for (String type : graticuleType) { getRenderingParams(type).setValue( GraticuleRenderingParams.KEY_LINE_CONFORMANCE, this.polylineTerrainConformance); } } /** * Determines whether the grid should be updated. It returns true if: * <ul> * <li>the eye has moved more than 1% of its altitude above ground * <li>the view FOV, heading or pitch have changed more than 1 degree * <li>vertical exaggeration has changed * </ul * @param dc * @return true if the graticule should be updated. */ private boolean needsToUpdate(DrawContext dc) { if (this.lastEyePoint == null) return true; View view = dc.getView(); double altitudeAboveGround = computeAltitudeAboveGround(dc); if (view.getEyePoint().distanceTo3(this.lastEyePoint) > altitudeAboveGround / 100) // 1% of AAG return true; if (this.lastVerticalExaggeration != dc.getVerticalExaggeration()) return true; if (view instanceof OrbitView) { if (Math.abs(this.lastViewHeading - ((OrbitView)view).getHeading().degrees) > 1) return true; if (Math.abs(this.lastViewPitch - ((OrbitView)view).getPitch().degrees) > 1) return true; } if (Math.abs(this.lastViewFOV - view.getFieldOfView().degrees) > 1) return true; return false; } private double computeTerrainConformance(DrawContext dc) { int value = 100; double alt = dc.getView().getEyePosition().getElevation(); if (alt < 10e3) value = 20; else if (alt < 50e3) value = 30; else if (alt < 100e3) value = 40; else if (alt < 1000e3) value = 60; return value; } private Sector computeVisibleSector(DrawContext dc) { return dc.getVisibleSector(); } private void selectMGRSRenderables(DrawContext dc, Sector vs) { ArrayList<GridZone> zoneList = getVisibleZones(dc); if (zoneList.size() > 0) { for (GridZone gz : zoneList) { // Select visible grid zones elements gz.selectRenderables(dc, vs, this); } } } private ArrayList<GridZone> getVisibleZones(DrawContext dc) { ArrayList<GridZone> zoneList = new ArrayList<GridZone>(); Sector vs = dc.getVisibleSector(); if (vs != null) { Rectangle2D gridRectangle = getGridRectangleForSector(vs); if (gridRectangle != null) { for (int row = (int)gridRectangle.getY(); row <= gridRectangle.getY() + gridRectangle.getHeight(); row++) { for (int col = (int)gridRectangle.getX(); col <= gridRectangle.getX() + gridRectangle.getWidth(); col++) { if (row != 19 || (col != 31 && col != 33 && col != 35)) // ignore X32, 34 and 36 { if (gridZones[row][col] == null) gridZones[row][col] = new GridZone(getGridSector(row, col)); if (gridZones[row][col].isInView(dc)) zoneList.add(gridZones[row][col]); else gridZones[row][col].clearRenderables(); } } } } } return zoneList; } private Rectangle2D getGridRectangleForSector(Sector sector) { Rectangle2D rectangle = null; if (sector.getMinLatitude().degrees < 84 && sector.getMaxLatitude().degrees > -80) { Sector gridSector = Sector.fromDegrees( Math.max(sector.getMinLatitude().degrees, -80), Math.min(sector.getMaxLatitude().degrees, 84), sector.getMinLongitude().degrees, sector.getMaxLongitude().degrees); int x1 = getGridColumn(gridSector.getMinLongitude().degrees); int x2 = getGridColumn(gridSector.getMaxLongitude().degrees); int y1 = getGridRow(gridSector.getMinLatitude().degrees); int y2 = getGridRow(gridSector.getMaxLatitude().degrees); // Adjust rectangle to include special zones if (y1 <= 17 && y2 >= 17 && x2 == 30) // 32V Norway x2 = 31; if (y1 <= 19 && y2 >= 19) // X band { if (x1 == 31) // 31X x1 = 30; if (x2 == 31) // 33X x2 = 32; if (x1 == 33) // 33X x1 = 32; if (x2 == 33) // 35X x2 = 34; if (x1 == 35) // 35X x1 = 34; if (x2 == 35) // 37X x2 = 36; } rectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); } return rectangle; } private int getGridColumn(Double longitude) { int col = (int)Math.floor((longitude + 180) / 6d); return Math.min(col, 59); } private int getGridRow(Double latitude) { int row = (int)Math.floor((latitude + 80) / 8d); return Math.min(row, 19); } private Sector getGridSector(int row, int col) { int minLat = -80 + row * 8; int maxLat = minLat + (minLat != 72 ? 8 : 12); int minLon = -180 + col * 6; int maxLon = minLon + 6; // Special sectors if (row == 17 && col == 30) // 31V maxLon -= 3; else if (row == 17 && col == 31) // 32V minLon -= 3; else if (row == 19 && col == 30 ) // 31X maxLon += 3; else if (row == 19 && col == 31 ) // 32X does not exist {minLon += 3; maxLon -= 3;} else if (row == 19 && col == 32 ) // 33X {minLon -= 3; maxLon += 3;} else if (row == 19 && col == 33 ) // 34X does not exist {minLon += 3; maxLon -= 3;} else if (row == 19 && col == 34 ) // 35X {minLon -= 3; maxLon += 3;} else if (row == 19 && col == 35 ) // 36X does not exist {minLon += 3; maxLon -= 3;} else if (row == 19 && col == 36 ) // 37X minLon -= 3; return Sector.fromDegrees(minLat, maxLat, minLon, maxLon); } private boolean isNorthNeighborInView(GridZone gz, DrawContext dc) { int row = getGridRow(gz.sector.getCentroid().getLatitude().degrees); int col = getGridColumn(gz.sector.getCentroid().getLongitude().degrees); GridZone neighbor = row + 1 <= 19 ? this.gridZones[row + 1][col] : null; return neighbor != null ? neighbor.isInView(dc) : false; } private boolean isEastNeighborInView(GridZone gz, DrawContext dc) { int row = getGridRow(gz.sector.getCentroid().getLatitude().degrees); int col = getGridColumn(gz.sector.getCentroid().getLongitude().degrees); GridZone neighbor = col + 1 <= 59 ? this.gridZones[row][col + 1] : null; return neighbor != null ? neighbor.isInView(dc) : false; } private Vec4 getSurfacePoint(DrawContext dc, Angle latitude, Angle longitude) { Vec4 surfacePoint = dc.getSurfaceGeometry().getSurfacePoint(latitude, longitude); if (surfacePoint == null) surfacePoint = dc.getGlobe().computePointFromPosition(new Position(latitude, longitude, dc.getGlobe().getElevation(latitude, longitude))); return surfacePoint; } private double computeAltitudeAboveGround(DrawContext dc) { View view = dc.getView(); Position eyePosition = view.getEyePosition(); Vec4 surfacePoint = getSurfacePoint(dc, eyePosition.getLatitude(), eyePosition.getLongitude()); return view.getEyePoint().distanceTo3(surfacePoint); } //--- Metric scale support ----------------------------------------------------------- private class MetricScaleSupport { private int zone; private double offsetFactorX = -.5; private double offsetFactorY = -.5; private double visibleDistanceFactor = 10; // 5 levels 100km to 10m UTMExtremes[] extremes = new UTMExtremes[5]; private class UTMExtremes { protected double minX, maxX, minY, maxY; protected char minYHemisphere, maxYHemisphere; public UTMExtremes() { this.clear(); } public void clear() { minX = 1e6; maxX = 0; minY = 10e6; minYHemisphere = 'N'; maxY = 0; maxYHemisphere = 'S'; } } public int getZone() { return this.zone; } private void computeZone(DrawContext dc) { this.zone = 0; try { Position centerPos = ((OrbitView)dc.getView()).getCenterPosition(); if (centerPos != null) { UTMCoord UTM = UTMCoord.fromLatLon(centerPos.getLatitude(), centerPos.getLongitude(), dc.getGlobe()); this.zone = UTM.getZone(); } } catch (Exception ex) { this.zone = 0; } } public void clear() { for (int i = 0; i < 5; i++) { if (this.extremes[i] == null) this.extremes[i] = new UTMExtremes(); this.extremes[i].clear(); } } public void computeMetricScaleExtremes(GridZone gz, GridElement ge, double size) { if (gz.UTMZone != this.zone) return; if (size < 1 || size > 100e3) return; UTMExtremes levelExtremes = this.extremes[(int)Math.log10(size) - 1]; if (ge.type.equals(GridElement.TYPE_LINE_EASTING) || ge.type.equals(GridElement.TYPE_LINE_EAST) || ge.type.equals(GridElement.TYPE_LINE_WEST)) { levelExtremes.minX = ge.value < levelExtremes.minX ? ge.value : levelExtremes.minX; levelExtremes.maxX = ge.value > levelExtremes.maxX ? ge.value : levelExtremes.maxX; } else if (ge.type.equals(GridElement.TYPE_LINE_NORTHING) || ge.type.equals(GridElement.TYPE_LINE_SOUTH) || ge.type.equals(GridElement.TYPE_LINE_NORTH)) { if (gz.hemisphere == levelExtremes.minYHemisphere) levelExtremes.minY = ge.value < levelExtremes.minY ? ge.value : levelExtremes.minY; else if (gz.hemisphere == 'S') { levelExtremes.minY = ge.value; levelExtremes.minYHemisphere = gz.hemisphere; } if (gz.hemisphere == levelExtremes.maxYHemisphere) levelExtremes.maxY = ge.value > levelExtremes.maxY ? ge.value : levelExtremes.maxY; else if (gz.hemisphere == 'N') { levelExtremes.maxY = ge.value; levelExtremes.maxYHemisphere = gz.hemisphere; } } } public void selectRenderables(DrawContext dc) { try { OrbitView view = (OrbitView)dc.getView(); // Compute easting and northing label offsets Double pixelSize = view.computePixelSizeAtDistance(view.getZoom()); Double eastingOffset = view.getViewport().width * pixelSize * offsetFactorX / 2; Double northingOffset = view.getViewport().height * pixelSize * offsetFactorY / 2; // Derive labels center pos from the view center Position centerPos = view.getCenterPosition(); UTMCoord UTM = UTMCoord.fromLatLon(centerPos.getLatitude(), centerPos.getLongitude(), dc.getGlobe()); double labelEasting = UTM.getEasting() + eastingOffset; double labelNorthing = UTM.getNorthing() + northingOffset; char labelHemisphere = UTM.getHemisphere(); for (int i = 0; i < 5; i++) { UTMExtremes levelExtremes = this.extremes[i]; double gridStep = Math.pow(10, i); double gridStepTimesTen = gridStep * 10; String graticuleType = getTypeFor((int) gridStep); if (levelExtremes.minX <= levelExtremes.maxX) { // Process easting scale labels for this level for (double easting = levelExtremes.minX; easting <= levelExtremes.maxX; easting += gridStep) { if (i == 4 || easting % gridStepTimesTen != 0) { try { UTM = UTMCoord.fromUTM(this.zone, labelHemisphere, easting, labelNorthing, dc.getGlobe()); Angle lat = UTM.getLatitude(); Angle lon = UTM.getLongitude(); Vec4 surfacePoint = getSurfacePoint(dc, lat, lon); if(viewFrustum.contains(surfacePoint) && isPointInRange(dc, surfacePoint)) { String text = String.valueOf((int)(easting % 100e3)); GeographicText gt = new UserFacingText(text, new Position(lat, lon, 0)); addRenderable(gt, graticuleType); renderablesCount++; } } catch (IllegalArgumentException ignore) {} } } } if (!(levelExtremes.maxYHemisphere == 'S' && levelExtremes.maxY == 0)) { // Process northing scale labels for this level char currentHemisphere = levelExtremes.minYHemisphere; for (double northing = levelExtremes.minY; (northing <= levelExtremes.maxY) || (currentHemisphere != levelExtremes.maxYHemisphere); northing += gridStep) { if (i == 4 || northing % gridStepTimesTen != 0) { try { UTM = UTMCoord.fromUTM(this.zone, currentHemisphere, labelEasting, northing, dc.getGlobe()); Angle lat = UTM.getLatitude(); Angle lon = UTM.getLongitude(); Vec4 surfacePoint = getSurfacePoint(dc, lat, lon); if(viewFrustum.contains(surfacePoint) && isPointInRange(dc, surfacePoint)) { String text = String.valueOf((int)(northing % 100e3)); GeographicText gt = new UserFacingText(text, new Position(lat, lon, 0)); addRenderable(gt, graticuleType); renderablesCount++; } } catch (IllegalArgumentException ignore) {} if (currentHemisphere != levelExtremes.maxYHemisphere && northing >= 10e6 - gridStep) { // Switch hemisphere currentHemisphere = levelExtremes.maxYHemisphere; northing = -gridStep; } } } } // end northing } // for levels } catch (IllegalArgumentException ignore) {} } private boolean isPointInRange(DrawContext dc, Vec4 point) { double altitudeAboveGround = computeAltitudeAboveGround(dc); return dc.getView().getEyePoint().distanceTo3(point) < altitudeAboveGround * this.visibleDistanceFactor; } public String toString() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append("level "); sb.append(String.valueOf(i)); sb.append(" : "); UTMExtremes levelExtremes = this.extremes[i]; if (levelExtremes.minX < levelExtremes.maxX || !(levelExtremes.maxYHemisphere == 'S' && levelExtremes.maxY == 0)) { sb.append(levelExtremes.minX); sb.append(", "); sb.append(levelExtremes.maxX); sb.append(" - "); sb.append(levelExtremes.minY); sb.append(levelExtremes.minYHemisphere); sb.append(", "); sb.append(levelExtremes.maxY); sb.append(levelExtremes.maxYHemisphere); } else { sb.append("empty"); } sb.append("\n"); } return sb.toString(); } } //--- Grid zone ---------------------------------------------------------------------- /** * Represent a UTM zone / latitude band intersection */ private class GridZone { private static final double ONEHT = 100e3; private Sector sector; private String name = ""; private int UTMZone = 0; private char hemisphere = ' '; private Extent extent; private double extentVerticalExaggeration = 1; private ArrayList<GridElement> gridElements; private ArrayList<SquareZone> squares; public GridZone(Sector sector) { this.sector = sector; try { MGRSCoord MGRS = MGRSCoord.fromLatLon(sector.getCentroid().getLatitude(), sector.getCentroid().getLongitude(), globe); this.name = MGRS.toString().substring(0, 3); UTMCoord UTM = UTMCoord.fromLatLon(sector.getCentroid().getLatitude(), sector.getCentroid().getLongitude(), globe); this.UTMZone = UTM.getZone(); this.hemisphere = UTM.getHemisphere(); } catch (IllegalArgumentException ignore) {} } public Extent getExtent(Globe globe, double ve) { if (this.extent == null || ve != this.extentVerticalExaggeration) { this.extent = globe.computeBoundingCylinder(ve, this.sector); this.extentVerticalExaggeration = ve; } return this.extent; } public boolean isInView(DrawContext dc) { return viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration())); } public void selectRenderables(DrawContext dc, Sector vs, MGRSGraticuleLayer layer) { // Select zone elements if (this.gridElements == null) createRenderables(); for (GridElement ge : this.gridElements) { if (ge.isInView(dc, vs)) { if (ge.type.equals(GridElement.TYPE_LINE_NORTH) && isNorthNeighborInView(this, dc)) continue; if (ge.type.equals(GridElement.TYPE_LINE_EAST) && isEastNeighborInView(this, dc)) continue; layer.addRenderable(ge.renderable, GRATICULE_UTM_GRID); renderablesCount++; } } if (dc.getView().getEyePosition().getElevation() > MGRSGraticuleLayer.this.squareMaxAltitude) return; // Select 100km squares elements if (this.squares == null) createSquares(); for (SquareZone sz : this.squares) { if (sz.isInView(dc)) { sz.selectRenderables(dc, vs, layer); visibleCellsCount++; } else sz.clearRenderables(); } } public void clearRenderables() { if (this.gridElements != null) { this.gridElements.clear(); this.gridElements = null; } if (this.squares != null) { for (SquareZone sz : this.squares) sz.clearRenderables(); this.squares.clear(); this.squares = null; } } private void createSquares() { this.squares = new ArrayList<SquareZone>(); try { // Find grid zone easting and northing boundaries UTMCoord UTM; UTM = UTMCoord.fromLatLon(this.sector.getMinLatitude(), this.sector.getCentroid().getLongitude(), globe); double minNorthing = UTM.getNorthing(); UTM = UTMCoord.fromLatLon(this.sector.getMaxLatitude(), this.sector.getCentroid().getLongitude(), globe); double maxNorthing = UTM.getNorthing(); maxNorthing = maxNorthing == 0 ? 10e6 : maxNorthing; UTM = UTMCoord.fromLatLon(this.sector.getMinLatitude(), this.sector.getMinLongitude(), globe); double minEasting = UTM.getEasting(); UTM = UTMCoord.fromLatLon(this.sector.getMaxLatitude(), this.sector.getMinLongitude(), globe); minEasting = UTM.getEasting() < minEasting ? UTM.getEasting() : minEasting; double maxEasting = 1e6 - minEasting; // Compensate for some distorted zones if (this.name.equals("32V")) // catch KS and LS in 32V maxNorthing += 20e3; if (this.name.equals("31X")) // catch GA and GV in 31X maxEasting += ONEHT; // Create squares //int count = 0; double startEasting = Math.floor(minEasting / ONEHT) * ONEHT; double startNorthing = Math.floor(minNorthing / ONEHT) * ONEHT; int cols = (int)Math.ceil((maxEasting - startEasting) / ONEHT); int rows = (int)Math.ceil((maxNorthing - startNorthing) / ONEHT); SquareZone[][] squaresArray = new SquareZone[rows][cols]; int col = 0; for (double easting = startEasting; easting < maxEasting; easting += ONEHT) { int row = 0; for (double northing = startNorthing; northing < maxNorthing; northing += ONEHT) { SquareZone sz = new SquareZone(this, easting, northing, ONEHT); if (sz.boundingSector != null && !sz.isOutsideGridZone()) { this.squares.add(sz); squaresArray[row][col] = sz; //count++; } row++; } col++; } // Keep track of neighbors for (col = 0; col < cols; col++) for (int row = 0; row < rows; row++) { SquareZone sz = squaresArray[row][col]; if (sz != null) { sz.setNorthNeighbor(row + 1 < rows ? squaresArray[row + 1][col] : null); sz.setEastNeighbor(col + 1 < cols ? squaresArray[row][col + 1] : null); } } } catch (IllegalArgumentException ignore) {} } private void createRenderables() { this.gridElements = new ArrayList<GridElement>(); ArrayList<Position> positions = new ArrayList<Position>(); // left meridian segment positions.clear(); //for (double lat = this.sector.getMinLatitude().degrees; lat <= this.sector.getMaxLatitude().degrees; lat += this.sector.getDeltaLat().degrees / 5d) // positions.add(new Position(Angle.fromDegrees(lat), this.sector.getMinLongitude(), 10e3)); positions.add(new Position(this.sector.getMinLatitude(), this.sector.getMinLongitude(), 10e3)); positions.add(new Position(this.sector.getMaxLatitude(), this.sector.getMinLongitude(), 10e3)); Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.LINEAR); polyline.setFollowTerrain(true); polyline.setTerrainConformance(50); Sector lineSector = new Sector(this.sector.getMinLatitude(), this.sector.getMaxLatitude(), this.sector.getMinLongitude(), this.sector.getMinLongitude()); this.gridElements.add(new GridElement(lineSector, polyline, GridElement.TYPE_LINE_WEST)); // right meridian segment positions.clear(); //for (double lat = this.sector.getMinLatitude().degrees; lat <= this.sector.getMaxLatitude().degrees; lat += this.sector.getDeltaLat().degrees / 5d) // positions.add(new Position(Angle.fromDegrees(lat), this.sector.getMaxLongitude(), 10e3)); positions.add(new Position(this.sector.getMinLatitude(), this.sector.getMaxLongitude(), 10e3)); positions.add(new Position(this.sector.getMaxLatitude(), this.sector.getMaxLongitude(), 10e3)); polyline = new Polyline(positions); polyline.setPathType(Polyline.LINEAR); polyline.setFollowTerrain(true); polyline.setTerrainConformance(50); lineSector = new Sector(this.sector.getMinLatitude(), this.sector.getMaxLatitude(), this.sector.getMaxLongitude(), this.sector.getMaxLongitude()); this.gridElements.add(new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EAST)); // bottom parallel segment positions.clear(); //for (double lon = this.sector.getMinLongitude().degrees; lon <= this.sector.getMaxLongitude().degrees; lon += this.sector.getDeltaLon().degrees / 5d) // positions.add(new Position(this.sector.getMinLatitude(), Angle.fromDegrees(lon), 10e3)); positions.add(new Position(this.sector.getMinLatitude(), this.sector.getMinLongitude(), 10e3)); positions.add(new Position(this.sector.getMinLatitude(), this.sector.getMaxLongitude(), 10e3)); polyline = new Polyline(positions); polyline.setPathType(Polyline.LINEAR); polyline.setFollowTerrain(true); polyline.setTerrainConformance(20); lineSector = new Sector(this.sector.getMinLatitude(), this.sector.getMinLatitude(), this.sector.getMinLongitude(), this.sector.getMaxLongitude()); this.gridElements.add(new GridElement(lineSector, polyline, GridElement.TYPE_LINE_SOUTH)); // top parallel segment positions.clear(); //for (double lon = this.sector.getMinLongitude().degrees; lon <= this.sector.getMaxLongitude().degrees; lon += this.sector.getDeltaLon().degrees / 5d) // positions.add(new Position(this.sector.getMaxLatitude(), Angle.fromDegrees(lon), 10e3)); positions.add(new Position(this.sector.getMaxLatitude(), this.sector.getMinLongitude(), 10e3)); positions.add(new Position(this.sector.getMaxLatitude(), this.sector.getMaxLongitude(), 10e3)); polyline = new Polyline(positions); polyline.setPathType(Polyline.LINEAR); polyline.setFollowTerrain(true); polyline.setTerrainConformance(20); lineSector = new Sector(this.sector.getMaxLatitude(), this.sector.getMaxLatitude(), this.sector.getMinLongitude(), this.sector.getMaxLongitude()); this.gridElements.add(new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTH)); // Label GeographicText text = new UserFacingText(this.name, new Position(this.sector.getCentroid(), 0)); this.gridElements.add(new GridElement(this.sector, text, GridElement.TYPE_GRIDZONE_LABEL)); } public boolean isPositionInside(Position position) { return position != null ? this.sector.contains(position.getLatLon()) : false; } /** * Computes the intersection point position between a great circle segment and a meridian. * @param p1 the great circle segment start position. * @param p2 the great circle segment end position. * @param longitude the meridian longitude <code>Angle</code> * @return the intersection <code>Position</code> or null if there was no intersection found. */ public LatLon greatCircleIntersectionAtLongitude(LatLon p1, LatLon p2, Angle longitude) { if (p1.getLongitude().degrees == longitude.degrees) return p1; if (p2.getLongitude().degrees == longitude.degrees) return p2; LatLon pos = null; Double deltaLon = getDeltaLongitude(p1, p2.getLongitude()).degrees; if (getDeltaLongitude(p1, longitude).degrees < deltaLon && getDeltaLongitude(p2, longitude).degrees < deltaLon) { int count = 0; double precision = 1d / 6378137d; // 1m angle in radians LatLon a = p1; LatLon b = p2; LatLon midPoint = greatCircleMidPoint(a, b); while (getDeltaLongitude(midPoint, longitude).radians > precision && count <= 20) { count++; if (getDeltaLongitude(a, longitude).degrees < getDeltaLongitude(b, longitude).degrees) b = midPoint; else a = midPoint; midPoint = greatCircleMidPoint(a, b); } pos = midPoint; if (count >= 20) System.out.println("Warning dichotomy loop aborted: " + p1 + " - " + p2 + " for lon " + longitude + " = " + pos); } // Adjust final longitude for an exact match if (pos != null) pos = new LatLon(pos.getLatitude(), longitude); return pos; } /** * Computes the intersection point position between a great circle segment and a parallel. * @param p1 the great circle segment start position. * @param p2 the great circle segment end position. * @param latitude the parallel latitude <code>Angle</code> * @return the intersection <code>Position</code> or null if there was no intersection found. */ public LatLon greatCircleIntersectionAtLatitude(LatLon p1, LatLon p2, Angle latitude) { LatLon pos = null; if (Math.signum(p1.getLatitude().degrees - latitude.degrees) != Math.signum(p2.getLatitude().degrees - latitude.degrees)) { int count = 0; double precision = 1d / 6378137d; // 1m angle in radians LatLon a = p1; LatLon b = p2; LatLon midPoint = greatCircleMidPoint(a, b); while (Math.abs(midPoint.getLatitude().radians - latitude.radians) > precision && count <= 20) { count++; if (Math.signum(a.getLatitude().degrees - latitude.degrees) != Math.signum(midPoint.getLatitude().degrees - latitude.degrees)) b = midPoint; else a = midPoint; midPoint = greatCircleMidPoint(a, b); } pos = midPoint; if (count >= 20) System.out.println("Warning dichotomy loop aborted: " + p1 + " - " + p2 + " for lat " + latitude + " = " + pos); } // Adjust final latitude for an exact match if (pos != null) pos = new LatLon(latitude, pos.getLongitude()); return pos; } public LatLon greatCircleMidPoint(LatLon p1, LatLon p2) { Angle azimuth = LatLon.greatCircleAzimuth(p1, p2); Angle distance = LatLon.greatCircleDistance(p1, p2); return LatLon.greatCircleEndPosition(p1, azimuth.radians, distance.radians / 2); } public Angle getDeltaLongitude(LatLon p1, Angle longitude) { double deltaLon = Math.abs(p1.getLongitude().degrees - longitude.degrees); return Angle.fromDegrees(deltaLon < 180 ? deltaLon : 360 - deltaLon); } } // --- UTM square zone ------------------------------------------------------------------ /** * Represent a generic UTM square area */ private class SquareSector { public static final int MIN_CELL_SIZE_PIXELS = 50; protected GridZone gridZone; protected double SWEasting; protected double SWNorthing; protected double size; protected Position sw, se, nw, ne; // Four corners position protected Sector boundingSector; protected LatLon centroid; protected LatLon squareCenter; protected Vec4 centerPoint; private Extent extent; private double extentVerticalExaggeration = 1; protected boolean isTruncated = false; public SquareSector(GridZone parent, double SWEasting, double SWNorthing, double size) { this.gridZone = parent; this.SWEasting = SWEasting; this.SWNorthing = SWNorthing; this.size = size; // Compute corners positions this.sw = computePositionFromUTM(parent.UTMZone, parent.hemisphere, SWEasting, SWNorthing); this.se = computePositionFromUTM(parent.UTMZone, parent.hemisphere, SWEasting + size, SWNorthing); this.nw = computePositionFromUTM(parent.UTMZone, parent.hemisphere, SWEasting, SWNorthing + size); this.ne = computePositionFromUTM(parent.UTMZone, parent.hemisphere, SWEasting + size, SWNorthing + size); // Compute approximate bounding sector and center point if (this.sw != null && this.se != null && this.nw != null && this.ne != null) { this.boundingSector = Sector.boundingSector(sw, ne).union(Sector.boundingSector(nw, se)); this.squareCenter = this.boundingSector.getCentroid(); this.boundingSector = this.gridZone.sector.intersection(this.boundingSector); } if (this.boundingSector != null) this.centroid = this.boundingSector.getCentroid(); // Check whether this square is truncated by the grid zone boundary this.isTruncated = !isInsideGridZone(); } public Position computePositionFromUTM(int zone, char hemisphere, double easting, double northing) { try { UTMCoord UTM = UTMCoord.fromUTM(zone, hemisphere, easting, northing, globe); return new Position(Angle.fromRadiansLatitude(UTM.getLatitude().radians), Angle.fromRadiansLongitude(UTM.getLongitude().radians), 10e3); } catch (IllegalArgumentException e) { return null; } } public Extent getExtent(Globe globe, double ve) { if (this.extent == null || ve != this.extentVerticalExaggeration) { this.extent = globe.computeBoundingCylinder(ve, this.boundingSector); this.extentVerticalExaggeration = ve; } return this.extent; } public boolean isInView(DrawContext dc) { if (!viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration()))) return false; // Check apparent size if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS) return false; return true; } /** * Determines whether this square is fully inside its parent grid zone. * @return true if this square is totaly inside its parent grid zone. */ public boolean isInsideGridZone() { if (!this.gridZone.isPositionInside(this.nw)) return false; if (!this.gridZone.isPositionInside(this.ne)) return false; if (!this.gridZone.isPositionInside(this.sw)) return false; if (!this.gridZone.isPositionInside(this.se)) return false; return true; } /** * Determines whether this square is fully outside its parent grid zone. * @return true is this square is totaly outside its parent grid zone. */ public boolean isOutsideGridZone() { if (this.gridZone.isPositionInside(this.nw)) return false; if (this.gridZone.isPositionInside(this.ne)) return false; if (this.gridZone.isPositionInside(this.sw)) return false; if (this.gridZone.isPositionInside(this.se)) return false; return true; } public double getSizeInPixels(DrawContext dc) { View view = dc.getView(); if(this.centerPoint == null || frameCount % 10 == 0) this.centerPoint = getSurfacePoint(dc, this.centroid.getLatitude(), this.centroid.getLongitude()); Double distance = view.getEyePoint().distanceTo3(this.centerPoint); return this.size / view.computePixelSizeAtDistance(distance); } public boolean isUTMPositionInsideZone(int zone, char hemisphere, double easting, double northing) { return gridZone.isPositionInside(computePositionFromUTM(zone, hemisphere, easting, northing)); } public void computeTruncatedSegment(Position p1, Position p2, ArrayList<Position> positions) { boolean p1In = this.gridZone.isPositionInside(p1); boolean p2In = this.gridZone.isPositionInside(p2); if (!p1In && !p2In) // whole segment is (likely) outside return; if (p1In && p2In) { // whole segment is (likely) inside positions.add(p1); positions.add(p2); } else { // segment does cross the boundary Position outPoint = !p1In ? p1 : p2; Position inPoint = p1In ? p1 : p2; for (int i = 1; i <= 2; i++) // there may be two intersections { LatLon intersection = null; if (outPoint.getLongitude().degrees > this.gridZone.sector.getMaxLongitude().degrees || (this.gridZone.sector.getMaxLongitude().degrees == 180 && outPoint.getLongitude().degrees < 0)) { // intersect with east meridian intersection = this.gridZone.greatCircleIntersectionAtLongitude( inPoint.getLatLon(), outPoint.getLatLon(), this.gridZone.sector.getMaxLongitude()); } else if (outPoint.getLongitude().degrees < this.gridZone.sector.getMinLongitude().degrees || (this.gridZone.sector.getMinLongitude().degrees == -180 && outPoint.getLongitude().degrees > 0)) { // intersect with west meridian intersection = this.gridZone.greatCircleIntersectionAtLongitude( inPoint.getLatLon(), outPoint.getLatLon(), this.gridZone.sector.getMinLongitude()); } else if (outPoint.getLatitude().degrees > this.gridZone.sector.getMaxLatitude().degrees) { // intersect with top parallel intersection = this.gridZone.greatCircleIntersectionAtLatitude( inPoint.getLatLon(), outPoint.getLatLon(), this.gridZone.sector.getMaxLatitude()); } else if (outPoint.getLatitude().degrees < this.gridZone.sector.getMinLatitude().degrees) { // intersect with bottom parallel intersection = this.gridZone.greatCircleIntersectionAtLatitude( inPoint.getLatLon(), outPoint.getLatLon(), this.gridZone.sector.getMinLatitude()); } if (intersection != null) outPoint = new Position(intersection, outPoint.getElevation()); else break; } positions.add(inPoint); positions.add(outPoint); } } } /** * Represent a 100km square zone inside a <code>GridZone</code>. */ private class SquareZone extends SquareSector { protected String name; protected SquareGrid squareGrid; protected ArrayList<GridElement> gridElements; private SquareZone northNeighbor, eastNeighbor; public SquareZone(GridZone parent, double SWEasting, double SWNorthing, double size) { super(parent, SWEasting, SWNorthing, size); // Find out name double tenMeterRadian = 10d / 6378137d; try { MGRSCoord MGRS = null; if (this.gridZone.isPositionInside(this.sw)) MGRS = MGRSCoord.fromLatLon( Angle.fromRadiansLatitude(this.sw.getLatitude().radians + tenMeterRadian), Angle.fromRadiansLongitude(this.sw.getLongitude().radians + tenMeterRadian), globe); else if (this.gridZone.isPositionInside(this.se)) MGRS = MGRSCoord.fromLatLon( Angle.fromRadiansLatitude(this.se.getLatitude().radians + tenMeterRadian), Angle.fromRadiansLongitude(this.se.getLongitude().radians - tenMeterRadian), globe); else if (this.gridZone.isPositionInside(this.nw)) MGRS = MGRSCoord.fromLatLon( Angle.fromRadiansLatitude(this.nw.getLatitude().radians - tenMeterRadian), Angle.fromRadiansLongitude(this.nw.getLongitude().radians + tenMeterRadian), globe); else if (this.gridZone.isPositionInside(this.ne)) MGRS = MGRSCoord.fromLatLon( Angle.fromRadiansLatitude(this.ne.getLatitude().radians - tenMeterRadian), Angle.fromRadiansLongitude(this.ne.getLongitude().radians - tenMeterRadian), globe); if (MGRS != null) this.name = MGRS.toString().substring(3, 5); } catch (IllegalArgumentException e) { this.name = ""; } } public void setNorthNeighbor(SquareZone sz) { this.northNeighbor = sz; } public void setEastNeighbor(SquareZone sz) { this.eastNeighbor = sz; } public void selectRenderables(DrawContext dc, Sector vs, MGRSGraticuleLayer layer) { // Select our renderables if (this.gridElements == null) createRenderables(dc); boolean drawMetricLabels = getSizeInPixels(dc) > MIN_CELL_SIZE_PIXELS * 4 * 1.7; for (GridElement ge : this.gridElements) { if (ge.isInView(dc, vs)) { if (ge.type.equals(GridElement.TYPE_LINE_NORTH) && this.isNorthNeighborInView(dc)) continue; if (ge.type.equals(GridElement.TYPE_LINE_EAST) && this.isEastNeighborInView(dc)) continue; if (drawMetricLabels) metricScaleSupport.computeMetricScaleExtremes(this.gridZone, ge, this.size); layer.addRenderable(ge.renderable, GRATICULE_100000M); renderablesCount++; } } if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 2) return; // Select grid renderables if (this.squareGrid == null) this.squareGrid = new SquareGrid(this.gridZone, this.SWEasting, this.SWNorthing, this.size); if (this.squareGrid.isInView(dc)) { this.squareGrid.selectRenderables(dc, vs, layer); visibleCellsCount++; } else this.squareGrid.clearRenderables(); } private boolean isNorthNeighborInView(DrawContext dc) { return this.northNeighbor != null ? this.northNeighbor.isInView(dc) : false; } private boolean isEastNeighborInView(DrawContext dc) { return this.eastNeighbor != null ? this.eastNeighbor.isInView(dc) : false; } public void clearRenderables() { if (this.gridElements != null) { this.gridElements.clear(); this.gridElements = null; } if (this.squareGrid != null) { this.squareGrid.clearRenderables(); this.squareGrid = null; } } public void createRenderables(DrawContext dc) { this.gridElements = new ArrayList<GridElement>(); ArrayList<Position> positions = new ArrayList<Position>(); Position p1, p2; Polyline polyline; Sector lineSector; Globe globe = dc.getGlobe(); // left segment positions.clear(); if (this.isTruncated) { this.computeTruncatedSegment(sw, nw, positions); } else { positions.add(sw); positions.add(nw); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_WEST); ge.setValue(this.SWEasting); this.gridElements.add(ge); } // right segment positions.clear(); if (this.isTruncated) { this.computeTruncatedSegment(se, ne, positions); } else { positions.add(se); positions.add(ne); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EAST); ge.setValue(this.SWEasting + this.size); this.gridElements.add(ge); } // bottom segment positions.clear(); if (this.isTruncated) { this.computeTruncatedSegment(sw, se, positions); } else { positions.add(sw); positions.add(se); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_SOUTH); ge.setValue(this.SWNorthing); this.gridElements.add(ge); } // top segment positions.clear(); if (this.isTruncated) { this.computeTruncatedSegment(nw, ne, positions); } else { positions.add(nw); positions.add(ne); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTH); ge.setValue(this.SWNorthing + this.size); this.gridElements.add(ge); } // Label if (this.gridZone.isPositionInside(new Position(this.squareCenter, 0))) { GeographicText text = new UserFacingText(this.name, new Position(this.squareCenter, 0)); this.gridElements.add(new GridElement(this.boundingSector, text, GridElement.TYPE_GRIDZONE_LABEL)); } else if (this.squareCenter.getLatitude().degrees <= this.gridZone.sector.getMaxLatitude().degrees && this.squareCenter.getLatitude().degrees >= this.gridZone.sector.getMinLatitude().degrees && this.boundingSector.getDeltaLon().degrees * Math.cos(this.centroid.getLatitude().radians) > .3) { GeographicText text = new UserFacingText(this.name, new Position(this.centroid, 0)); this.gridElements.add(new GridElement(this.boundingSector, text, GridElement.TYPE_GRIDZONE_LABEL)); } } } /** * Represent a square 10x10 grid and recursive tree in TM coordinates */ private class SquareGrid extends SquareSector { private ArrayList<GridElement> gridElements; private ArrayList<SquareGrid> subGrids; public SquareGrid(GridZone parent, double SWEasting, double SWNorthing, double size) { super(parent, SWEasting, SWNorthing, size); } public boolean isInView(DrawContext dc) { if (!viewFrustum.intersects(this.getExtent(dc.getGlobe(), dc.getVerticalExaggeration()))) return false; // Check apparent size if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 4) return false; return true; } public void selectRenderables(DrawContext dc, Sector vs, MGRSGraticuleLayer layer) { // Select our renderables if (this.gridElements == null) createRenderables(dc); int gridStep = (int) this.size / 10; boolean drawMetricLabels = getSizeInPixels(dc) > MIN_CELL_SIZE_PIXELS * 4 * 1.7; String graticuleType = getTypeFor(gridStep); for (GridElement ge : this.gridElements) { if (ge.isInView(dc, vs)) { if (drawMetricLabels) metricScaleSupport.computeMetricScaleExtremes(this.gridZone, ge, this.size); layer.addRenderable(ge.renderable, graticuleType); renderablesCount++; } } if (getSizeInPixels(dc) <= MIN_CELL_SIZE_PIXELS * 4 * 2) return; // Select sub grids renderables if (this.subGrids == null) createSubGrids(); for (SquareGrid sg : this.subGrids) { if (sg.isInView(dc)) { sg.selectRenderables(dc, vs, layer); visibleCellsCount++; } else sg.clearRenderables(); } } public void clearRenderables() { if (this.gridElements != null) { this.gridElements.clear(); this.gridElements = null; } if (this.subGrids != null) { for (SquareGrid sg : this.subGrids) sg.clearRenderables(); this.subGrids.clear(); this.subGrids = null; } } public void createSubGrids() { this.subGrids = new ArrayList<SquareGrid>(); // TODO: round value to int to avoid precision drift from double operations double gridStep = this.size / 10; for (int i = 0; i < 10; i++) { double easting = this.SWEasting + gridStep * i; for (int j = 0; j < 10; j++) { double northing = this.SWNorthing + gridStep * j; SquareGrid sg = new SquareGrid(this.gridZone, easting, northing, gridStep); if (!sg.isOutsideGridZone()) this.subGrids.add(sg); } } } public void createRenderables(DrawContext dc) { this.gridElements = new ArrayList<GridElement>(); double gridStep = this.size / 10; Position p1, p2; Globe globe = dc.getGlobe(); ArrayList<Position> positions = new ArrayList<Position>(); // South-North lines for (int i = 1; i <= 9; i++) { double easting = this.SWEasting + gridStep * i; positions.clear(); p1 = this.computePositionFromUTM(this.gridZone.UTMZone, this.gridZone.hemisphere, easting, SWNorthing); p2 = this.computePositionFromUTM(this.gridZone.UTMZone, this.gridZone.hemisphere, easting, SWNorthing + this.size); if (this.isTruncated) { this.computeTruncatedSegment(p1, p2, positions); } else { positions.add(p1); positions.add(p2); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); Sector lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_EASTING); ge.setValue(easting); this.gridElements.add(ge); } } // West-East lines for (int i = 1; i <= 9; i++) { double northing = this.SWNorthing + gridStep * i; positions.clear(); p1 = this.computePositionFromUTM(this.gridZone.UTMZone, this.gridZone.hemisphere, SWEasting, northing); p2 = this.computePositionFromUTM(this.gridZone.UTMZone, this.gridZone.hemisphere, SWEasting + this.size, northing); if (this.isTruncated) { this.computeTruncatedSegment(p1, p2, positions); } else { positions.add(p1); positions.add(p2); } if (positions.size() > 0) { p1 = positions.get(0); p2 = positions.get(1); Polyline polyline = new Polyline(positions); polyline.setPathType(Polyline.GREAT_CIRCLE); polyline.setFollowTerrain(true); Sector lineSector = Sector.boundingSector(p1, p2); GridElement ge = new GridElement(lineSector, polyline, GridElement.TYPE_LINE_NORTHING); ge.setValue(northing); this.gridElements.add(ge); } } } } }