/*******************************************************************************
* 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();
}
}