/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.gui.mapview; import java.awt.BasicStroke; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.geom.Point2D; import javax.swing.JComponent; import mobac.program.interfaces.MapSpace; import mobac.program.model.Coordinate; import mobac.program.model.SettingsWgsGrid; import mobac.utilities.I18nUtils; public class WgsGrid { public static enum WgsDensity { DEGREES_90(0), DEGREES_45(1), DEGREES_30(2), DEGREES_15(3), DEGREES_10(4), DEGREES_5(5), DEGREES_2(6), DEGREE_1( 7), MINUTES_30(8), MINUTES_20(9), MINUTES_10(10), MINUTES_5(11), MINUTES_2(12), MINUTE_1(13), SECONDS_30( 15), SECONDS_20(15), SECONDS_10(16), SECONDS_5(17), SECONDS_2(18), SECOND_1(19); public final int iStep, minZoom; public final boolean compressDegree, compressMinute, displayMinute, displaySecond; //private final String string; private WgsDensity(final int minZoom) { this.minZoom = minZoom; String[] split = name().split("_"); int value = Integer.parseInt(split[1]); if (split[0].startsWith("D")) { iStep = value * Coordinate.DEGREE; displayMinute = displaySecond = false; compressDegree = compressMinute = false; } else if (split[0].startsWith("M")) { iStep = value * Coordinate.MINUTE; compressDegree = true/* value <= 15 */; displayMinute = true; displaySecond = compressMinute = false; } else { iStep = value * Coordinate.SECOND; compressDegree = displayMinute = displaySecond = true; compressMinute = true/* value <= 15 */; } } public String toString() { String[] split = name().split("_"); String unitKey = "map_ctrl_wgs_grid_density_"+ split[0].toLowerCase(); return I18nUtils.localizedStringForKey("map_ctrl_wgs_grid_density_prefix") + " " + split[1] + " " + I18nUtils.localizedStringForKey(unitKey); } } public static enum Placement { BOTTOM_RIGHT, BOTTOM_LEFT, TOP_RIGHT, TOP_LEFT } private static final WgsDensity DENSITIES[] = WgsDensity.values(); private static final Stroke BASIC_STROKE = new BasicStroke(1f); private static final int LABEL_OFFSET = 2; private final StringBuilder stringBuilder = new StringBuilder(16); private final Rectangle viewport = new Rectangle(); public final SettingsWgsGrid s; private final JComponent c; private Placement placement = Placement.BOTTOM_RIGHT; private BasicStroke stroke; private int lastDegree, lastMinute; public WgsGrid(SettingsWgsGrid s, JComponent c) { this.s = s; this.c = c; } public void paintWgsGrid(Graphics2D g, MapSpace ms, Point tlc, int zoom) { if (!s.enabled) { return; } // Check density WgsDensity density = s.density; while (zoom < density.minZoom) { int index = density.ordinal(); if (--index <= 0) { return; } density = DENSITIES[index]; } final int maxPixels = ms.getMaxPixels(zoom); // Check viewport viewport.width = c.getWidth(); viewport.height = c.getHeight(); if (tlc.x > maxPixels || tlc.y > maxPixels || tlc.x + viewport.width < 0 || tlc.y + viewport.width < 0) { return; } viewport.x = c.getX(); viewport.y = c.getY(); // Translate according to mapSpace viewport.translate(tlc.x, tlc.y); g = (Graphics2D) g.create(); g.translate(-tlc.x, -tlc.y); // Calculate viewport coordinates final int x1 = Math.max(tlc.x, 0); final int y1 = Math.max(tlc.y, 0); final int x2 = Math.min(tlc.x + viewport.width, maxPixels); final int y2 = Math.min(tlc.y + viewport.height, maxPixels); // Calculate line coordinates final int hLineX1 = x1 + 1; final int hLineX2 = x2 - 1; final int vLineY1 = y1 + 1; final int vLineY2 = y2 - 1; // Calculate line indexes //final int vMin = Coordinate.doubleToInt(ms.cXToLon(x1, zoom)) / density.iStep; //final int vMax = Coordinate.doubleToInt(ms.cXToLon(x2, zoom)) / density.iStep; //final int hMin = Coordinate.doubleToInt(ms.cYToLat(y2, zoom)) / density.iStep; //final int hMax = Coordinate.doubleToInt(ms.cYToLat(y1, zoom)) / density.iStep; Point2D.Double p1 = ms.cXYToLonLat(x1, y1, zoom); Point2D.Double p2 = ms.cXYToLonLat(x2, y2, zoom); final int vMin = Coordinate.doubleToInt(p1.x) / density.iStep; final int vMax = Coordinate.doubleToInt(p2.x) / density.iStep; final int hMin = Coordinate.doubleToInt(p2.y) / density.iStep; final int hMax = Coordinate.doubleToInt(p1.y) / density.iStep; g.setBackground(Color.WHITE); g.setColor(s.color); g.setStroke(checkAndGetStroke()); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Paint vertical lines double cLat = p2.y; for (int i = vMin; i <= vMax; i++) { int iLon = i * density.iStep; //int x = ms.cLonToX(Coordinate.intToDouble(iLon), zoom); Point p = ms.cLonLatToXY(Coordinate.intToDouble(iLon), cLat, zoom); int x = p.x; if (x > x1 && x < x2) { g.drawLine(x, vLineY1, x, vLineY2); } } // Paint horizontal lines double cLon = p2.x; for (int i = hMin; i <= hMax; i++) { int iLat = i * density.iStep; //int y = ms.cLatToY(Coordinate.intToDouble(iLat), zoom); Point p = ms.cLonLatToXY(cLon, Coordinate.intToDouble(iLat), zoom); int y = p.y; if (y > y1 && y < y2) { g.drawLine(hLineX1, y, hLineX2, y); } } // Set up font metrics g.setStroke(BASIC_STROKE); g.setFont(s.font); final FontMetrics fontMetrics = g.getFontMetrics(); final int fontDescent = fontMetrics.getDescent(); final int fontHeight = fontMetrics.getHeight(); resetLabelCompression(); // Shared Y coordinates for vertical labels int labelRectX; int labelRectY = y2 - LABEL_OFFSET - fontHeight; int labelY = y2 - LABEL_OFFSET - fontDescent; int labelX; // Paint vertical labels for (int i = vMin; i <= vMax; i++) { // Calculate coordinates int iLon = i * density.iStep; //int x = ms.cLonToX(Coordinate.intToDouble(iLon), zoom); Point p = ms.cLonLatToXY(Coordinate.intToDouble(iLon), cLat, zoom); int x = p.x; // Prepare label String label = getLabel(iLon, density); int stringWidth = fontMetrics.stringWidth(label); labelRectX = labelX = x - stringWidth / 2; // Paint label if (viewport.contains(labelRectX, labelRectY, stringWidth, fontHeight)) { g.clearRect(labelRectX, labelRectY, stringWidth, fontHeight); g.drawRect(labelRectX, labelRectY, stringWidth, fontHeight); g.drawString(label, labelX, labelY); } else { resetLabelCompression(); } } resetLabelCompression(); viewport.height -= fontHeight + LABEL_OFFSET; labelX = labelRectX = x1 + LABEL_OFFSET; // Paint horizontal labels for (int i = hMin; i <= hMax; i++) { int iLat = i * density.iStep; //int y = ms.cLatToY(Coordinate.intToDouble(iLat), zoom); Point p = ms.cLonLatToXY(cLon, Coordinate.intToDouble(iLat), zoom); int y = p.y; // Prepare label String label = getLabel(iLat, density); final int stringWidth = fontMetrics.stringWidth(label); if (placement == Placement.BOTTOM_RIGHT || placement == Placement.TOP_RIGHT) { labelX = labelRectX = x2 - LABEL_OFFSET - stringWidth; } labelRectY = y - fontHeight / 2; labelY = labelRectY + fontHeight - fontDescent; // Paint label if (viewport.contains(labelRectX, labelRectY, stringWidth, fontHeight)) { g.clearRect(labelRectX, labelRectY, stringWidth, fontHeight); g.drawRect(labelRectX, labelRectY, stringWidth, fontHeight); g.drawString(label, labelX, labelY); } else { resetLabelCompression(); } } g.dispose(); } public void setPosition(Placement placement) { this.placement = placement != null ? placement : Placement.BOTTOM_RIGHT; } private Stroke checkAndGetStroke() { if (stroke == null || stroke.getLineWidth() != s.width) { stroke = new BasicStroke(s.width); } return stroke; } private void resetLabelCompression() { lastDegree = Integer.MAX_VALUE; lastMinute = Integer.MAX_VALUE; } private String getLabel(int coord, WgsDensity density) { coord = Math.abs(coord); int degree = Coordinate.getDegree(coord); int minute = Coordinate.getMinute(coord); int second = Coordinate.getSecond(coord); stringBuilder.setLength(0); stringBuilder.append(" "); if (!s.compressLabels || lastDegree != degree || !density.compressDegree) { stringBuilder.append(degree); stringBuilder.append('\u00B0'); } if (density.displayMinute && (!s.compressLabels || lastMinute != minute || !density.compressMinute)) { if (minute < 10) { stringBuilder.append('0'); } stringBuilder.append(minute); stringBuilder.append('\''); } if (density.displaySecond) { if (second < 10) { stringBuilder.append('0'); } stringBuilder.append(second); stringBuilder.append('\"'); } stringBuilder.append(" "); lastDegree = degree; lastMinute = minute; return stringBuilder.toString(); } }