/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2011, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.swing.control; import java.awt.Dimension; import java.awt.FontMetrics; import java.util.Arrays; import javax.swing.JLabel; import org.geotools.geometry.DirectPosition2D; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.swing.MapPane; import org.geotools.swing.event.MapMouseAdapter; import org.geotools.swing.event.MapMouseEvent; import org.geotools.swing.event.MapPaneAdapter; import org.geotools.swing.event.MapPaneEvent; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * A status bar item that displays the world coordinates of the mouse cursor * position. * * @author Michael Bedward * @since 8.0 * @source $URL$ * @version $Id$ */ public class CoordsStatusBarItem extends StatusBarItem { private static final int DEFAULT_NUM_INTEGER_DIGITS = 8; private static final String NO_COORDS = "No cursor"; private final JLabel label; private int intLen; private int decLen; private String numFormat; /** * Creates a new item to display cursor position for the given map pane. * * @param mapPane the map pane */ CoordsStatusBarItem(MapPane mapPane) { super("Coordinates"); if (mapPane == null) { throw new IllegalArgumentException("mapPane must not be null"); } label = new JLabel(); label.setFont(JMapStatusBar.DEFAULT_FONT); add(label); decLen = JMapStatusBar.DEFAULT_NUM_DECIMAL_DIGITS; intLen = DEFAULT_NUM_INTEGER_DIGITS; ReferencedEnvelope env = mapPane.getDisplayArea(); if (env != null && !env.isEmpty()) { setFormatFromEnvelope(env); } mapPane.addMouseListener(new MapMouseAdapter() { @Override public void onMouseEntered(MapMouseEvent ev) { displayCoords(ev.getWorldPos()); } @Override public void onMouseExited(MapMouseEvent ev) { displayNoCursor(); } @Override public void onMouseMoved(MapMouseEvent ev) { displayCoords(ev.getWorldPos()); } }); mapPane.addMapPaneListener(new MapPaneAdapter() { @Override public void onDisplayAreaChanged(MapPaneEvent ev) { setFormatFromEnvelope(((Envelope) ev.getData())); } }); displayNoCursor(); } /** * Sets te number of digits to display after the decimal place. * * @param numDecimals number of digits after decimal place */ @Override public void setNumDecimals(int numDecimals) { decLen = numDecimals; setLabelSizeAndFormat(); } /** * Displays coordinates of the given position. * * @param p world position */ private void displayCoords(DirectPosition2D p) { label.setText(String.format(numFormat, p.getX(), p.getY())); ensureMinLabelWidth(); } /** * Sets a message to indicate that the cursor is out of the map pane. */ private void displayNoCursor() { label.setText(NO_COORDS); } /** * Sets the minimum width of the coordinate display label and the * format string used to print values. The map extent is used to * estimate the number of digits required. * * @param env map extent */ private void setFormatFromEnvelope(Envelope env) { setIntegerLen(env); setLabelSizeAndFormat(); } /** * Sets the minimum width of the coordinate display label and the * format string used to print values. */ private void setLabelSizeAndFormat() { int minLabelWidth = getStringWidth(); Dimension labelSize = label.getSize(); if (labelSize.width < minLabelWidth) { label.setMinimumSize(new Dimension(minLabelWidth, labelSize.height)); revalidate(); } StringBuilder sb = new StringBuilder(); sb.append("%").append(intLen).append(".").append(decLen).append("f, "); sb.append("%").append(intLen).append(".").append(decLen).append("f"); numFormat = sb.toString(); } /** * Estimates the maximum display width of the coordinate string based * on current integer and fractional part lengths. * * @return maximum display width */ private int getStringWidth() { FontMetrics fm = label.getFontMetrics(label.getFont()); char[] c = new char[intLen + decLen + 1]; Arrays.fill(c, '0'); String s = String.valueOf(c); s = s + ", " + s; return fm.stringWidth(s); } /** * Examines the map extent and tries to determine the number of digits * that will be needed in the display. If a coordinate reference system * with valid extent defined, it is used to determine coordinate limits; * otherwise the extent of the envelope is used directly. If all else * fails, a default number of digits is set. * * @param env the map extent (may be {@code null}) */ private void setIntegerLen(Envelope env) { int len = -1; if (env != null) { // Try to get a valid extent for the CRS and use this to // determine num coordinate digits CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); if (crs != null) { Envelope validExtent = CRS.getEnvelope(crs); if (validExtent != null) { len = getMaxIntegerLen(validExtent); } } if (len < 0) { // Use map extent directly and add a bit for luck len = getMaxIntegerLen(env) + 4; } } else { // Nothing to go on: use an arbitrary length len = DEFAULT_NUM_INTEGER_DIGITS; } intLen = len; } /** * Gets the maximum number of digits in the integer part of * envelope corner coordinates. * * @param env the envelope * @return maximum number of digits */ private int getMaxIntegerLen(Envelope env) { int len = integerPartLen(env.getMinimum(0)); len = Math.max(len, integerPartLen(env.getMinimum(1))); len = Math.max(len, integerPartLen(env.getMaximum(0))); len = Math.max(len, integerPartLen(env.getMaximum(1))); // Add 1 to allow for negative sign return len + 1; } /** * Gets the length of the integer part of a double value. * * @param x the value * * @return number of digits in the integer part */ private int integerPartLen(double x) { return 1 + (int) Math.log10( Math.abs(x) ); } /** * Checks the current label width against its minimum width and, * if the current width is larger, adjusts the minimum to prevent * the label growing and shrinking as the cursor is moved. */ private void ensureMinLabelWidth() { Dimension minDim = label.getMinimumSize(); Dimension curDim = label.getSize(); if (curDim.width > minDim.width) { label.setMinimumSize(new Dimension(curDim.width, minDim.height)); } } }