// ********************************************************************** // // <copyright> // // BBN Technologies, a Verizon Company // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/GraticuleLayer.java,v $ // $RCSfile: GraticuleLayer.java,v $ // $Revision: 1.17 $ // $Date: 2009/02/25 22:34:04 $ // $Author: dietrick $ // // ********************************************************************** // Modified 28 September 2002 by David N. Allsopp to allow font size // to be changed. See sections commented with 'DNA'. // Modified 20 February 2010 by Steve C. Tang to allow graticule lines equally // bisect from projection center to avoid swapping. Also fixed possible dead-loop when // calculating label locations. // package com.bbn.openmap.layer; import java.awt.Color; import java.awt.Font; import java.awt.GridLayout; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Point2D; import java.util.Properties; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JPanel; import com.bbn.openmap.I18n; import com.bbn.openmap.MoreMath; import com.bbn.openmap.event.ProjectionEvent; import com.bbn.openmap.layer.policy.BufferedImageRenderPolicy; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMPoly; import com.bbn.openmap.omGraphics.OMText; import com.bbn.openmap.proj.Cylindrical; import com.bbn.openmap.proj.GeoProj; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.PaletteHelper; import com.bbn.openmap.util.PropUtils; /** * Layer that draws graticule lines. If the showRuler property is set to true, * then longitude values are displayed on the bottom of the map, and latitude * values are displayed on the left side. If the show1And5Lines property is * true, then 5 degree lines are drawn when there are <= threshold ten degree * latitude or longitude lines, and 1 degree lines are drawn when there are * <= threshold five degree latitude or longitude degree lines. * * <P> * The openmap.properties file can control the layer with the following * settings: * <p><pre> * * # Show lat / lon spacing labels * graticule.showRuler=true * graticule.show1And5Lines=true * # Controls when the five degree lines and one degree lines kick in * #- when there is less than the threshold of ten degree lat or lon * #lines, five degree lines are drawn. The same relationship is there * #for one to five degree lines. * graticule.threshold=2 * # the color of 10 degree spacing lines (Hex ARGB) * graticule.10DegreeColor=FF000000 * # the color of 5 degree spacing lines (Hex ARGB) * graticule.5DegreeColor=C7009900 * # the color of 1 degree spacing lines (Hex ARGB) * graticule.1DegreeColor=C7003300 * # the color of the equator (Hex ARGB) * graticule.equatorColor=FFFF0000 * # the color of the international dateline (Hex ARGB) * graticule.datelineColor=7F000099 * # the color of the special lines (Hex ARGB) * graticule.specialLineColor=FF000000 * # the color of the labels (Hex ARGB) * graticule.textColor=FF000000 * * </pre> * <p> In addition, you can get this layer to work with the OpenMap * viewer by editing your openmap.properties file: * <pre> * * # layers * openmap.layers=graticule ... * # class * graticule.class=com.bbn.openmap.layer.GraticuleLayer * # name * graticule.prettyName=Graticule * * </pre> */ public class GraticuleLayer extends OMGraphicHandlerLayer implements ActionListener { // default to not showing the ruler (mimicking older // GraticuleLayer) protected boolean defaultShowRuler = true; protected boolean defaultShowOneAndFiveLines = true; protected boolean defaultShowBelowOneLines = false; protected int defaultThreshold = 2; /** * Flag for lineType - true is LINETYPE_STRAIGHT, false is * LINETYPE_GREATCIRCLE. */ protected boolean boxy = true; /** * Threshold is the total number of ten lines on the screen before the five * lines appear, and the total number of five lines on the screen before the * one lines appear. */ protected int threshold = defaultThreshold; /** The ten degree latitude and longitude lines, premade. */ protected OMGraphicList tenDegreeLines = null; /** The equator, dateline and meridian lines, premade. */ protected OMGraphicList markerLines = null; private final static int SHOW_TENS = 0; private final static int SHOW_FIVES = 1; private final static int SHOW_ONES = 2; protected boolean showOneAndFiveLines = defaultShowOneAndFiveLines; protected boolean showBelowOneLines = defaultShowBelowOneLines; protected boolean showRuler = defaultShowRuler; // protected Font font = new Font("Helvetica", // java.awt.Font.PLAIN, 10); protected Font font = null; protected int fontSize = 10; // Color variables for different line types protected Color tenDegreeColor = null; protected Color fiveDegreeColor = null; protected Color oneDegreeColor = null; protected Color belowOneDegreeColor = null; protected Color equatorColor = null; protected Color dateLineColor = null; protected Color specialLineColor = null; // Tropic of Cancer, // Capricorn protected Color textColor = null; // Default colors to use, if not specified in the properties. protected String defaultTenDegreeColorString = "000000"; protected String defaultFiveDegreeColorString = "33009900"; protected String defaultOneDegreeColorString = "33003300"; protected String defaultBelowOneDegreeColorString = "9900ff00"; protected String defaultEquatorColorString = "990000"; protected String defaultDateLineColorString = "000099"; protected String defaultSpecialLineColorString = "000000"; protected String defaultTextColorString = "000000"; // property text values public static final String TenDegreeColorProperty = "10DegreeColor"; public static final String FiveDegreeColorProperty = "5DegreeColor"; public static final String OneDegreeColorProperty = "1DegreeColor"; public static final String BelowOneDegreeColorProperty = "Below1DegreeColor"; public static final String EquatorColorProperty = "equatorColor"; public static final String DateLineColorProperty = "datelineColor"; public static final String SpecialLineColorProperty = "specialLineColor"; public static final String TextColorProperty = "textColor"; public static final String ThresholdProperty = "threshold"; public static final String ShowRulerProperty = "showRuler"; public static final String ShowOneAndFiveProperty = "show1And5Lines"; public static final String ShowBelowOneProperty = "showBelow1Lines"; public static final String FontSizeProperty = "fontSize"; // DNA /** * Construct the GraticuleLayer. */ public GraticuleLayer() { // precalculate for boxy boxy = true; setName("Graticule"); setRenderPolicy(new BufferedImageRenderPolicy(this)); } /** * The properties and prefix are managed and decoded here, for the standard * uses of the GraticuleLayer. * * @param prefix string prefix used in the properties file for this layer. * @param properties the properties set in the properties file. */ public void setProperties(String prefix, java.util.Properties properties) { super.setProperties(prefix, properties); prefix = PropUtils.getScopedPropertyPrefix(prefix); tenDegreeColor = PropUtils.parseColorFromProperties(properties, prefix + TenDegreeColorProperty, defaultTenDegreeColorString, true); fiveDegreeColor = PropUtils.parseColorFromProperties(properties, prefix + FiveDegreeColorProperty, defaultFiveDegreeColorString, true); oneDegreeColor = PropUtils.parseColorFromProperties(properties, prefix + OneDegreeColorProperty, defaultOneDegreeColorString, true); belowOneDegreeColor = PropUtils.parseColorFromProperties(properties, prefix + BelowOneDegreeColorProperty, defaultBelowOneDegreeColorString, true); equatorColor = PropUtils.parseColorFromProperties(properties, prefix + EquatorColorProperty, defaultEquatorColorString, true); dateLineColor = PropUtils.parseColorFromProperties(properties, prefix + DateLineColorProperty, defaultDateLineColorString, true); specialLineColor = PropUtils.parseColorFromProperties(properties, prefix + SpecialLineColorProperty, defaultSpecialLineColorString, true); textColor = PropUtils.parseColorFromProperties(properties, prefix + TextColorProperty, defaultTextColorString, true); threshold = PropUtils.intFromProperties(properties, prefix + ThresholdProperty, defaultThreshold); fontSize = PropUtils.intFromProperties(properties, prefix + FontSizeProperty, fontSize); font = new Font("Helvetica", java.awt.Font.PLAIN, fontSize); setShowOneAndFiveLines(PropUtils.booleanFromProperties(properties, prefix + ShowOneAndFiveProperty, defaultShowOneAndFiveLines)); setShowBelowOneLines(PropUtils.booleanFromProperties(properties, prefix + ShowBelowOneProperty, defaultShowBelowOneLines)); setShowRuler(PropUtils.booleanFromProperties(properties, prefix + ShowRulerProperty, defaultShowRuler)); // So they will get re-created. tenDegreeLines = null; markerLines = null; } protected JCheckBox showRulerButton = null; protected JCheckBox show15Button = null; protected JCheckBox showBelow1Button = null; public void setShowOneAndFiveLines(boolean set) { showOneAndFiveLines = set; if (show15Button != null) { show15Button.setSelected(set); } } public void setShowBelowOneLines(boolean set) { showBelowOneLines = set; if (showBelow1Button != null) { showBelow1Button.setSelected(set); } } public boolean getShowOneAndFiveLines() { return showOneAndFiveLines; } public boolean getShowBelowOneLines() { return showBelowOneLines; } public void setShowRuler(boolean set) { showRuler = set; if (showRulerButton != null) { showRulerButton.setSelected(set); } } public boolean getShowRuler() { return showRuler; } /** * The properties and prefix are managed and decoded here, for the standard * uses of the GraticuleLayer. * * @param properties the properties set in the properties file. */ public Properties getProperties(Properties properties) { properties = super.getProperties(properties); String prefix = PropUtils.getScopedPropertyPrefix(this); String colorString; if (tenDegreeColor == null) { colorString = defaultTenDegreeColorString; } else { colorString = Integer.toHexString(tenDegreeColor.getRGB()); } properties.put(prefix + TenDegreeColorProperty, colorString); if (fiveDegreeColor == null) { colorString = defaultFiveDegreeColorString; } else { colorString = Integer.toHexString(fiveDegreeColor.getRGB()); } properties.put(prefix + FiveDegreeColorProperty, colorString); if (oneDegreeColor == null) { colorString = defaultOneDegreeColorString; } else { colorString = Integer.toHexString(oneDegreeColor.getRGB()); } properties.put(prefix + OneDegreeColorProperty, colorString); if (belowOneDegreeColor == null) { colorString = defaultBelowOneDegreeColorString; } else { colorString = Integer.toHexString(belowOneDegreeColor.getRGB()); } properties.put(prefix + BelowOneDegreeColorProperty, colorString); if (equatorColor == null) { colorString = defaultEquatorColorString; } else { colorString = Integer.toHexString(equatorColor.getRGB()); } properties.put(prefix + EquatorColorProperty, colorString); if (dateLineColor == null) { colorString = defaultDateLineColorString; } else { colorString = Integer.toHexString(dateLineColor.getRGB()); } properties.put(prefix + DateLineColorProperty, colorString); if (specialLineColor == null) { colorString = defaultSpecialLineColorString; } else { colorString = Integer.toHexString(specialLineColor.getRGB()); } properties.put(prefix + SpecialLineColorProperty, colorString); if (textColor == null) { colorString = defaultTextColorString; } else { colorString = Integer.toHexString(textColor.getRGB()); } properties.put(prefix + TextColorProperty, colorString); properties.put(prefix + ThresholdProperty, Integer.toString(threshold)); properties.put(prefix + FontSizeProperty, Integer.toString(fontSize)); // DNA properties.put(prefix + ShowOneAndFiveProperty, new Boolean(showOneAndFiveLines).toString()); properties.put(prefix + ShowBelowOneProperty, new Boolean(showBelowOneLines).toString()); properties.put(prefix + ShowRulerProperty, new Boolean(showRuler).toString()); return properties; } /** * The properties and prefix are managed and decoded here, for the standard * uses of the GraticuleLayer. * * @param properties the properties set in the properties file. */ public Properties getPropertyInfo(Properties properties) { properties = super.getPropertyInfo(properties); String interString; properties.put(initPropertiesProperty, TenDegreeColorProperty + " " + FiveDegreeColorProperty + " " + OneDegreeColorProperty + " " + /* BelowOneDegreeColorProperty + " " + */EquatorColorProperty + " " + DateLineColorProperty + " " + SpecialLineColorProperty + " " + ShowOneAndFiveProperty /* * + " " + * ShowBelowOneProperty */ + " " + ShowRulerProperty + " " + ThresholdProperty + " " + FontSizeProperty); interString = i18n.get(GraticuleLayer.class, TenDegreeColorProperty, I18n.TOOLTIP, "Color of the ten degree graticule lines."); properties.put(TenDegreeColorProperty, interString); interString = i18n.get(GraticuleLayer.class, TenDegreeColorProperty, "Ten Degree Color"); properties.put(TenDegreeColorProperty + LabelEditorProperty, interString); properties.put(TenDegreeColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, FiveDegreeColorProperty, I18n.TOOLTIP, "Color of the five degree graticule lines."); properties.put(FiveDegreeColorProperty, interString); interString = i18n.get(GraticuleLayer.class, FiveDegreeColorProperty, "Color of the five degree graticule lines."); interString = i18n.get(GraticuleLayer.class, FiveDegreeColorProperty, "File Degree Color"); properties.put(FiveDegreeColorProperty + LabelEditorProperty, interString); properties.put(FiveDegreeColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, OneDegreeColorProperty, I18n.TOOLTIP, "Color of the one degree graticule lines."); properties.put(OneDegreeColorProperty, interString); interString = i18n.get(GraticuleLayer.class, OneDegreeColorProperty, "1 Degree Color"); properties.put(OneDegreeColorProperty + LabelEditorProperty, interString); properties.put(OneDegreeColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, BelowOneDegreeColorProperty, I18n.TOOLTIP, "Color of the sub-one degree graticule lines."); properties.put(BelowOneDegreeColorProperty, interString); interString = i18n.get(GraticuleLayer.class, BelowOneDegreeColorProperty, "Sub-One Degree Color"); properties.put(BelowOneDegreeColorProperty + LabelEditorProperty, interString); properties.put(BelowOneDegreeColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, EquatorColorProperty, I18n.TOOLTIP, "Color of the Equator."); properties.put(EquatorColorProperty, interString); interString = i18n.get(GraticuleLayer.class, EquatorColorProperty, "Equator Line Color"); properties.put(EquatorColorProperty + LabelEditorProperty, interString); properties.put(EquatorColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, DateLineColorProperty, I18n.TOOLTIP, "Color of the Date line."); properties.put(DateLineColorProperty, interString); interString = i18n.get(GraticuleLayer.class, DateLineColorProperty, "Date Line Color"); properties.put(DateLineColorProperty + LabelEditorProperty, interString); properties.put(DateLineColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, SpecialLineColorProperty, I18n.TOOLTIP, "Color of Tropic of Cancer, Capricorn lines."); properties.put(SpecialLineColorProperty, interString); interString = i18n.get(GraticuleLayer.class, SpecialLineColorProperty, "Special Line Color"); properties.put(SpecialLineColorProperty + LabelEditorProperty, interString); properties.put(SpecialLineColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, TextColorProperty, I18n.TOOLTIP, "Color of the line label text."); properties.put(TextColorProperty, interString); interString = i18n.get(GraticuleLayer.class, TextColorProperty, "Text Color"); properties.put(TextColorProperty + LabelEditorProperty, interString); properties.put(TextColorProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.ColorPropertyEditor"); interString = i18n.get(GraticuleLayer.class, ThresholdProperty, I18n.TOOLTIP, "The number of lines showing before finer grain lines appear."); properties.put(ThresholdProperty, interString); interString = i18n.get(GraticuleLayer.class, ThresholdProperty, "Line Threshold"); properties.put(ThresholdProperty + LabelEditorProperty, interString); interString = i18n.get(GraticuleLayer.class, ShowOneAndFiveProperty, I18n.TOOLTIP, "Show the one and five degree lines."); properties.put(ShowOneAndFiveProperty, interString); interString = i18n.get(GraticuleLayer.class, ShowOneAndFiveProperty, "Show 1 and 5 Lines"); properties.put(ShowOneAndFiveProperty + LabelEditorProperty, interString); properties.put(ShowOneAndFiveProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.TrueFalsePropertyEditor"); interString = i18n.get(GraticuleLayer.class, ShowBelowOneProperty, I18n.TOOLTIP, "Show the one and five degree lines."); properties.put(ShowBelowOneProperty, interString); interString = i18n.get(GraticuleLayer.class, ShowBelowOneProperty, "Show Sub-1 Lines"); properties.put(ShowBelowOneProperty + LabelEditorProperty, interString); properties.put(ShowBelowOneProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.TrueFalsePropertyEditor"); interString = i18n.get(GraticuleLayer.class, ShowRulerProperty, I18n.TOOLTIP, "Show the line label text."); properties.put(ShowRulerProperty, interString); interString = i18n.get(GraticuleLayer.class, ShowRulerProperty, "Show Labels"); properties.put(ShowRulerProperty + LabelEditorProperty, interString); properties.put(ShowRulerProperty + ScopedEditorProperty, "com.bbn.openmap.util.propertyEditor.TrueFalsePropertyEditor"); // DNA interString = i18n.get(GraticuleLayer.class, FontSizeProperty, I18n.TOOLTIP, "The size of the font, in points, of the line labels."); properties.put(FontSizeProperty, interString); interString = i18n.get(GraticuleLayer.class, FontSizeProperty, "Label Font Size"); properties.put(FontSizeProperty + LabelEditorProperty, interString); // DNA return properties; } /** * Implementing the ProjectionPainter interface. */ public synchronized void renderDataForProjection(Projection proj, java.awt.Graphics g) { if (proj == null) { Debug.error("GraticuleLayer.renderDataForProjection: null projection!"); return; } else if (!proj.equals(getProjection())) { setProjection(proj.makeClone()); // Figure out which line type to use if (proj instanceof Cylindrical) boxy = true; else boxy = false; setList(constructGraticuleLines()); } paint(g); } /** * Invoked when the projection has changed or this Layer has been added to * the MapBean. * <p> * Perform some extra checks to see if reprojection of the graphics is really * necessary. * * @param e ProjectionEvent * */ public void projectionChanged(ProjectionEvent e) { // extract the projection and check to see if it's really // different. // if it isn't then we don't need to do all the work again, // just // repaint. Projection proj = setProjection(e); if (proj == null) { repaint(); return; } // Figure out which line type to use if (proj instanceof Cylindrical) boxy = true; else boxy = false; setList(null); doPrepare(); } /** * Creates the OMGraphic list with graticule lines. */ public synchronized OMGraphicList prepare() { return constructGraticuleLines(); } /** * Create the graticule lines. * <p> * NOTES: * <ul> * <li>Currently graticule lines are hardcoded to 10 degree intervals. * <li>No thought has been given to clipping based on the view rectangle. For * non-boxy projections performance may be degraded at very large scales. * (But we make up for this by running the task in its own thread to support * liveness). * </ul> * * @return OMGraphicList new graphic list */ protected OMGraphicList constructGraticuleLines() { OMGraphicList newgraphics = new OMGraphicList(20); // Lets figure out which lines should be painted... Projection projection = getProjection(); if (projection == null) { return newgraphics; } tenDegreeLines = null; // Need the cast to make Windows happy during the ant build for jdk1.5.0_22 double ctrLon = ((Point2D)projection.getCenter()).getX(); if (projection instanceof GeoProj) { ctrLon = ((GeoProj) projection).getReferenceLon(); } if (showOneAndFiveLines || showRuler || showBelowOneLines) { Point2D ul = projection.getUpperLeft(); Point2D lr = projection.getLowerRight(); float left = (float) ul.getX(); float right = (float) lr.getX(); float up = (float) ul.getY(); float down = (float) lr.getY(); if (up > 80.0f) up = 80.0f; if (down > 80.0f) down = 80f; // unlikely if (up < -80.0f) up = -80.0f; // unlikely if (down < -80) down = -80.0f; int showWhichLines = evaluateSpacing(up, down, left, right); // Find out whether we need to do one or two queries, // depending on if we're straddling the dateline. if ((left > 0 && right < 0) || (left > right) || (Math.abs(left - right) < 1)) { // Test to draw the ones and fives, which will also do // the labels. if (showWhichLines != SHOW_TENS) { newgraphics.add(constructGraticuleLines(up, down, left, 180.0f, showWhichLines)); newgraphics.add(constructGraticuleLines(up, down, -180.0f, right, showWhichLines)); } else if (showRuler) { // Just do the labels for the // tens lines newgraphics.add(constructTensLabels(up, down, left, 180.0f, true)); newgraphics.add(constructTensLabels(up, down, -180.0f, right, false)); } } else { // Test to draw the ones and fives, which will also do // the labels. if (showWhichLines != SHOW_TENS) { newgraphics = constructGraticuleLines(up, down, left, right, showWhichLines); } else if (showRuler) { // Just do the labels for the // tens lines newgraphics.add(constructTensLabels(up, down, left, right, true)); } } } OMGraphicList list; if (tenDegreeLines == null) { list = constructTenDegreeLines(ctrLon); tenDegreeLines = list; } else { synchronized (tenDegreeLines) { setLineTypeAndProject(tenDegreeLines, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB); } } if (markerLines == null) { list = constructMarkerLines(ctrLon); markerLines = list; } else { synchronized (markerLines) { setLineTypeAndProject(markerLines, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB); } } newgraphics.add(markerLines); newgraphics.add(tenDegreeLines); if (Debug.debugging("graticule")) { Debug.output("GraticuleLayer.constructGraticuleLines(): " + "constructed " + newgraphics.size() + " graticule lines"); } return newgraphics; } /** * Figure out which graticule lines should be drawn based on the treshold set * in the layer, and the coordinates of the screen. Method checks for * crossing of the dateline, but still assumes that the up and down latitude * coordinates are less than abs(+/-80). This is because the projection * shouldn't give anything above 90 degrees, and we limit the lines to less * than 80.. * * @param up northern latitude coordinate, in decimal degrees, * @param down southern latitude coordinate, in decimal degrees. * @param left western longitude coordinate, in decimal degrees, * @param right eastern longitude coordinate, in decimal degrees. * @return which lines should be shown, either SHOW_TENS, SHOW_FIVES and * SHOW_ONES. */ protected int evaluateSpacing(float up, float down, float left, float right) { int ret = SHOW_TENS; // Set the flag for when labels are wanted, but not the 1 and // 5 lines; if (!showOneAndFiveLines && !showBelowOneLines) { return ret; } // Find the north - south difference float nsdiff = up - down; // And the east - west difference float ewdiff; // Check for straddling the dateline -west is positive while // right is negative, or, in a big picture view, the west is // positive, east is positive, and western hemisphere is // between them. if ((left > 0 && right < 0) || (left > right) || (Math.abs(left - right) < 1)) { ewdiff = (180.0f - left) + (right + 180.0f); } else { ewdiff = right - left; } // And use the lesser of the two. float diff = (nsdiff < ewdiff) ? nsdiff : ewdiff; // number of 10 degree lines if ((diff / 10) <= (float) threshold) ret = SHOW_FIVES; // number of five degree lines if ((diff / 5) <= (float) threshold) ret = SHOW_ONES; return ret; } /** * Construct the five degree and one degree graticule lines, depending on the * showWhichLines setting. Assumes that the coordinates passed in do not * cross the dateline, and that the up is not greater than 80 and that the * south is not less than -80. * * @param up northern latitude coordinate, in decimal degrees, * @param down southern latitude coordinate, in decimal degrees. * @param left western longitude coordinate, in decimal degrees, * @param right eastern longitude coordinate, in decimal degrees. * @param showWhichLines indicator for which level of lines should be * included, either SHOW_FIVES or SHOW_ONES. SHOW_TENS could be there, * too, but then we wouldn't do anything. */ protected OMGraphicList constructGraticuleLines(float up, float down, float left, float right, int showWhichLines) { OMGraphicList lines = new OMGraphicList(); // Set the line limits for the lat/lon lines... int north = (int) Math.ceil(up); if (north > 80) north = 80; int south = (int) Math.floor(down); south -= (south % 10); // Push down to the lowest 10 degree // line. // for neg numbers, Mod raised it, lower it again. Also // handle straddling the equator. if ((south < 0 && south > -80) || south == 0) south -= 10; int west = (int) Math.floor(left); west -= (west % 10); // for neg numbers, Mod raised it, lower it again. Also // handle straddling the prime meridian. if ((west < 0 && west > -180) || west == 0) west -= 10; int east = (int) Math.ceil(right); if (east > 180) east = 180; int stepSize; int stepSum; double point_x, point_y; // Choose how far apart the lines will be. stepSize = ((showWhichLines == SHOW_ONES) ? 1 : 5); double[] llp; OMPoly currentLine; OMText currentText; // For calculating text locations Point point = new Point(); LatLonPoint llpoint; Projection projection = getProjection(); // generate other parallels of latitude be creating series // of polylines for (int i = south; i < north; i += stepSize) { float lat = (float) i; // generate parallel of latitude North/South of the // equator if (west < 0 && east > 0) { llp = new double[6]; llp[2] = lat; llp[3] = 0f; llp[4] = lat; llp[5] = east; } else { llp = new double[4]; llp[2] = lat; llp[3] = east; } llp[0] = lat; llp[1] = west; // Do not duplicate the 10 degree line. if ((lat % 10) != 0) { currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB); if ((lat % 5) == 0) { currentLine.setLinePaint(fiveDegreeColor); } else { currentLine.setLinePaint(oneDegreeColor); } lines.add(currentLine); } if (showRuler && (lat % 2) == 0) { if (boxy) { projection.forward(lat, west, point); point.x = 0; llpoint = projection.inverse(point.x, point.y, new LatLonPoint.Double()); } else { llpoint = new LatLonPoint.Double(lat, west); stepSum = 0; while (stepSum < 360) { point_x = projection.forward(llpoint).getX(); if (point_x > 0 && point_x < projection.getWidth()) break; stepSum += stepSize; llpoint.setLongitude(llpoint.getX() + stepSize); } } currentText = new OMText(llpoint.getY(), llpoint.getX(), // Move them up a little (int) 2, (int) -2, Integer.toString((int) lat), font, OMText.JUSTIFY_LEFT); currentText.setLinePaint(textColor); lines.add(currentText); } } // generate lines of longitude for (int i = west; i < east; i += stepSize) { float lon = (float) i; if (north < 0 && south > 0) { llp = new double[6]; llp[2] = 0f; llp[3] = lon; llp[4] = south; llp[5] = lon; } else { llp = new double[4]; llp[2] = south; llp[3] = lon; } llp[0] = north; llp[1] = lon; if ((lon % 10) != 0) { currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_GREATCIRCLE); if ((lon % 5) == 0) { currentLine.setLinePaint(fiveDegreeColor); } else { currentLine.setLinePaint(oneDegreeColor); } lines.add(currentLine); } if (showRuler && (lon % 2) == 0) { if (boxy) { projection.forward(south, lon, point); point.y = projection.getHeight(); llpoint = projection.inverse(point.x, point.y, new LatLonPoint.Double()); } else { llpoint = new LatLonPoint.Double(south, lon); stepSum = 0; while (stepSum < 360) { point_y = projection.forward(llpoint).getY(); if (point_y > 0 && point_y < projection.getHeight()) break; stepSum += stepSize; llpoint.setLatitude(llpoint.getY() + stepSize); } } currentText = new OMText(llpoint.getY(), llpoint.getX(), // Move them up a little (int) 2, (int) -5, Integer.toString((int) lon), font, OMText.JUSTIFY_CENTER); currentText.setLinePaint(textColor); lines.add(currentText); } } if (Debug.debugging("graticule")) { Debug.output("GraticuleLayer.constructTenDegreeLines(): " + "constructed " + lines.size() + " graticule lines"); } lines.generate(projection); return lines; } /** Create the ten degree lines. */ protected OMGraphicList constructTenDegreeLines(double ctrLon) { OMGraphicList lines = new OMGraphicList(3); OMPoly currentLine; // generate other parallels of latitude by creating series // of polylines for (int i = 1; i <= 8; i++) { for (int j = -1; j < 2; j += 2) { float lat = (float) (10 * i * j); // generate parallel of latitude North/South of the // equator double[] llp = { lat, ctrLon - 180f, lat, ctrLon - 90f, lat, ctrLon, lat, ctrLon + 90f, lat, ctrLon + 180f }; currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_RHUMB); currentLine.setLinePaint(tenDegreeColor); lines.add(currentLine); } } // generate lines of longitude for (int i = 1; i < 18; i++) { for (int j = -1; j < 2; j += 2) { float lon = (float) (10 * i * j); // not quite 90.0 for beautification reasons. double[] llp = { 80f, lon, 0f, lon, -80f, lon }; if (MoreMath.approximately_equal(Math.abs(lon), 90f, 0.001f)) { llp[0] = 89.999f; llp[4] = -89.999f; } currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_GREATCIRCLE); currentLine.setLinePaint(tenDegreeColor); lines.add(currentLine); } } if (Debug.debugging("graticule")) { Debug.output("GraticuleLayer.constructTenDegreeLines(): " + "constructed " + lines.size() + " graticule lines"); } lines.generate(getProjection()); return lines; } /** * Constructs the labels for the tens lines. Called from within the * constructGraticuleLines if the showRuler variable is true. Usually called * only if the ones and fives lines are not being drawn. * * @param up northern latitude coordinate, in decimal degrees, * @param down southern latitude coordinate, in decimal degrees. * @param left western longitude coordinate, in decimal degrees, * @param right eastern longitude coordinate, in decimal degrees. * @param doLats do the latitude labels if true. * @return OMGraphicList of labels. */ protected OMGraphicList constructTensLabels(float up, float down, float left, float right, boolean doLats) { OMGraphicList labels = new OMGraphicList(); // Set the line limits for the lat/lon lines... int north = (int) Math.ceil(up); if (north > 80) north = 80; int south = (int) Math.floor(down); south -= (south % 10); // Push down to the lowest 10 degree // line. // for neg numbers, Mod raised it, lower it again if ((south < 0 && south > -70) || south == 0) { south -= 10; } int west = (int) Math.floor(left); west -= (west % 10); // for neg numbers, Mod raised it, lower it again if ((west < 0 && west > -170) || west == 0) { west -= 10; } int east = (int) Math.ceil(right); if (east > 180) east = 180; int stepSize = 10; int stepSum; double point_x, point_y; OMText currentText; // For calculating text locations Point point = new Point(); LatLonPoint llpoint; Projection projection = getProjection(); if (doLats) { // generate other parallels of latitude be creating series // of labels for (int i = south; i < north; i += stepSize) { float lat = (float) i; if ((lat % 2) == 0) { if (boxy) { projection.forward(lat, west, point); point.x = 0; llpoint = projection.inverse(point.x, point.y, new LatLonPoint.Double()); } else { llpoint = new LatLonPoint.Double(lat, west); stepSum = 0; while (stepSum < 360) { point_x = projection.forward(llpoint).getX(); if (point_x > 0 && point_x < projection.getWidth()) break; stepSum += stepSize; llpoint.setLongitude(llpoint.getX() + stepSize); } } currentText = new OMText(llpoint.getY(), llpoint.getX(), (int) 2, (int) -2, // Move // them // up a // little Integer.toString((int) lat), font, OMText.JUSTIFY_LEFT); currentText.setLinePaint(textColor); labels.add(currentText); } } } // generate labels of longitude for (int i = west; i < east; i += stepSize) { float lon = (float) i; if ((lon % 2) == 0) { if (boxy) { projection.forward(south, lon, point); point.y = projection.getHeight(); llpoint = projection.inverse(point.x, point.y, new LatLonPoint.Double()); } else { llpoint = new LatLonPoint.Double(south, lon); stepSum = 0; while (stepSum < 360) { point_y = projection.forward(llpoint).getY(); if (point_y > 0 && point_y < projection.getHeight()) break; stepSum += stepSize; llpoint.setLatitude(llpoint.getY() + stepSize); } } currentText = new OMText(llpoint.getY(), llpoint.getX(), // Move them up a little (int) 2, (int) -5, Integer.toString((int) lon), font, OMText.JUSTIFY_CENTER); currentText.setLinePaint(textColor); labels.add(currentText); } } if (Debug.debugging("graticule")) { Debug.output("GraticuleLayer.constructTensLabels(): " + "constructed " + labels.size() + " graticule labels"); } labels.generate(projection); return labels; } /** Constructs the Dateline and Prime Meridian lines. */ protected OMGraphicList constructMarkerLines(double ctrLon) { OMGraphicList lines = new OMGraphicList(3); OMPoly currentLine; // generate Prime Meridian and Dateline for (int j = 0; j < 360; j += 180) { float lon = (float) j; double[] llp = { 90f, lon, 0f, lon, -90f, lon }; currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_GREATCIRCLE); currentLine.setLinePaint(dateLineColor); lines.add(currentLine); } // equator double[] llp = { 0f, ctrLon - 180f, 0f, ctrLon - 90f, 0f, ctrLon, 0f, ctrLon + 90f, 0f, ctrLon + 180f }; // polyline currentLine = new OMPoly(llp, OMGraphic.DECIMAL_DEGREES, boxy ? OMGraphic.LINETYPE_STRAIGHT : OMGraphic.LINETYPE_GREATCIRCLE); currentLine.setLinePaint(equatorColor); lines.add(currentLine); if (Debug.debugging("graticule")) { Debug.output("GraticuleLayer.constructMarkerLines(): " + "constructed " + lines.size() + " graticule lines"); } lines.generate(getProjection()); return lines; } /** * Take a graphic list, and set all the items on the list to the line type * specified, and project them into the current projection. * * @param list the list containing the lines to change. * @param lineType the line type to change the lines to. */ protected void setLineTypeAndProject(OMGraphicList list, int lineType) { int size = list.size(); OMGraphic graphic; for (int i = 0; i < size; i++) { graphic = list.getOMGraphicAt(i); graphic.setLineType(lineType); graphic.generate(getProjection()); } } // ---------------------------------------------------------------------- // GUI // ---------------------------------------------------------------------- /** The user interface palette for the Graticule layer. */ protected Box paletteBox = null; /** Creates the interface palette. */ public java.awt.Component getGUI() { if (paletteBox == null) { if (Debug.debugging("graticule")) Debug.output("GraticuleLayer: creating Graticule Palette."); paletteBox = Box.createVerticalBox(); JPanel layerPanel = PaletteHelper.createPaletteJPanel(i18n.get(GraticuleLayer.class, "layerPanel", "Graticule Layer Options")); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { String ac = e.getActionCommand(); if (ac.equalsIgnoreCase(ShowRulerProperty)) { JCheckBox jcb = (JCheckBox) e.getSource(); showRuler = jcb.isSelected(); } else if (ac.equalsIgnoreCase(ShowOneAndFiveProperty)) { JCheckBox jcb = (JCheckBox) e.getSource(); showOneAndFiveLines = jcb.isSelected(); } else { Debug.error("Unknown action command \"" + ac + "\" in GraticuleLayer.actionPerformed()."); } } }; showRulerButton = new JCheckBox(i18n.get(GraticuleLayer.class, "showRulerButton", "Show Lat/Lon Labels"), showRuler); showRulerButton.addActionListener(al); showRulerButton.setActionCommand(ShowRulerProperty); show15Button = new JCheckBox(i18n.get(GraticuleLayer.class, "show15Button", "Show 1, 5 Degree Lines"), showOneAndFiveLines); show15Button.addActionListener(al); show15Button.setActionCommand(ShowOneAndFiveProperty); // showBelow1Button = new JCheckBox(i18n.get(GraticuleLayer.class, // "showSub1Button", // "Show Sub-1 Degree Lines"), showBelowOneLines); // showBelow1Button.addActionListener(al); // showBelow1Button.setActionCommand(ShowBelowOneProperty); layerPanel.add(showRulerButton); layerPanel.add(show15Button); // layerPanel.add(showBelow1Button); paletteBox.add(layerPanel); JPanel subbox3 = new JPanel(new GridLayout(0, 1)); JButton setProperties = new JButton(i18n.get(GraticuleLayer.class, "setProperties", "Preferences")); setProperties.setActionCommand(DisplayPropertiesCmd); setProperties.addActionListener(this); subbox3.add(setProperties); JButton redraw = new JButton(i18n.get(GraticuleLayer.class, "redraw", "Redraw Graticule Layer")); redraw.setActionCommand(RedrawCmd); redraw.addActionListener(this); subbox3.add(redraw); paletteBox.add(subbox3); } return paletteBox; } // ---------------------------------------------------------------------- // ActionListener interface implementation // ---------------------------------------------------------------------- /** * Used just for the redraw button. */ public void actionPerformed(ActionEvent e) { super.actionPerformed(e); String command = e.getActionCommand(); if (command == RedrawCmd) { // redrawbutton if (isVisible()) { doPrepare(); } } } }