/*******************************************************************************
* 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.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import mobac.gui.mapview.controller.DefaultMapController;
import mobac.gui.mapview.controller.JMapController;
import mobac.gui.mapview.controller.MapKeyboardController;
import mobac.gui.mapview.controller.RectangleSelectionMapController;
import mobac.gui.mapview.interfaces.MapEventListener;
import mobac.mapsources.MapSourcesManager;
import mobac.program.interfaces.MapSource;
import mobac.program.interfaces.MapSourceTextAttribution;
import mobac.program.interfaces.MapSpace;
import mobac.program.model.Bookmark;
import mobac.program.model.EastNorthCoordinate;
import mobac.program.model.MapSelection;
import mobac.program.model.MercatorPixelCoordinate;
import mobac.program.model.Settings;
import mobac.utilities.I18nUtils;
import mobac.utilities.MyMath;
import org.apache.log4j.Logger;
public class PreviewMap extends JMapViewer {
private static final long serialVersionUID = 1L;
public static final Color GRID_COLOR = new Color(200, 20, 20, 130);
public static final Color SEL_COLOR = new Color(0.9f, 0.7f, 0.7f, 0.6f);
public static final Color MAP_COLOR = new Color(1.0f, 0.84f, 0.0f, 0.4f);
public static final int MAP_CONTROLLER_RECTANGLE_SELECT = 0;
public static final int MAP_CONTROLLER_GPX = 1;
protected static final Font LOADING_FONT = new Font("Sans Serif", Font.BOLD, 30);
private static Logger log = Logger.getLogger(PreviewMap.class);
/**
* Interactive map selection max/min pixel coordinates regarding zoom level <code>MAX_ZOOM</code>
*/
private Point iSelectionMin;
private Point iSelectionMax;
/**
* Map selection max/min pixel coordinates regarding zoom level <code>MAX_ZOOM</code> with respect to the grid zoom.
*/
private Point gridSelectionStart;
private Point gridSelectionEnd;
/**
* Pre-painted transparent tile with grid lines on it. This makes painting the grid a lot faster in difference to
* painting each line or rectangle if the grid zoom is much higher that the current zoom level.
*/
private BufferedImage gridTile = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
private int gridZoom = -1;
private int gridSize;
protected LinkedList<MapEventListener> mapEventListeners = new LinkedList<MapEventListener>();
protected JMapController mapKeyboardController;
protected JMapController mapSelectionController;
protected DefaultMapController defaultMapController;
private final WgsGrid wgsGrid = new WgsGrid(Settings.getInstance().wgsGrid, this);
public PreviewMap() {
super(MapSourcesManager.getInstance().getDefaultMapSource(), 5);
setEnabled(false);
defaultMapController = new DefaultMapController(this);
mapMarkersVisible = false;
setZoomContolsVisible(false);
mapKeyboardController = new MapKeyboardController(this, true);
setMapSelectionController(new RectangleSelectionMapController(this));
}
public void setDisplayPositionByLatLon(EastNorthCoordinate c, int zoom) {
setDisplayPositionByLatLon(new Point(getWidth() / 2, getHeight() / 2), c.lat, c.lon, zoom);
}
/**
* Updates the current position in {@link Settings} to the current view
*/
public void settingsSave() {
Settings settings = Settings.getInstance();
settings.mapviewZoom = getZoom();
settings.mapviewCenterCoordinate = getCenterCoordinate();
settings.mapviewGridZoom = gridZoom;
settings.mapviewMapSource = mapSource.getName();
settings.mapviewSelectionMin = iSelectionMin;
settings.mapviewSelectionMax = iSelectionMax;
}
/**
* Sets the current view by the current values from {@link Settings}
*/
public void settingsLoad() {
Settings settings = Settings.getInstance();
MapSource mapSource = MapSourcesManager.getInstance().getSourceByName(settings.mapviewMapSource);
if (mapSource != null)
setMapSource(mapSource);
EastNorthCoordinate c = settings.mapviewCenterCoordinate;
gridZoom = settings.mapviewGridZoom;
setDisplayPositionByLatLon(c, settings.mapviewZoom);
setSelectionByTileCoordinate(MAX_ZOOM, settings.mapviewSelectionMin, settings.mapviewSelectionMax, true);
}
@Override
public void setMapSource(MapSource newMapSource) {
if (newMapSource.equals(mapSource))
return;
log.trace("Preview map source changed from " + mapSource + " to " + newMapSource);
MapSource oldMapSource = mapSource;
super.setMapSource(newMapSource);
if (oldMapSource != null
&& oldMapSource.getMapSpace().getMapSpaceType() != newMapSource.getMapSpace().getMapSpaceType()) {
MapSpace oldMapSpace = oldMapSource.getMapSpace();
MapSpace newMapSpace = newMapSource.getMapSpace();
Point2D.Double c = oldMapSpace.cXYToLonLat(center.x, center.y, zoom);
setDisplayPositionByLatLon(new EastNorthCoordinate(c.y, c.x), zoom);
if (iSelectionMin != null && iSelectionMax != null) {
Point2D.Double llMin = oldMapSpace.cXYToLonLat(iSelectionMin.x, iSelectionMin.y, MAX_ZOOM);
Point2D.Double llMax = oldMapSpace.cXYToLonLat(iSelectionMax.x, iSelectionMax.y, MAX_ZOOM);
Point sMin = newMapSpace.cLonLatToXY(llMin.x, llMin.y, MAX_ZOOM);
Point sMax = newMapSpace.cLonLatToXY(llMax.x, llMax.y, MAX_ZOOM);
setSelectionByTileCoordinate(MAX_ZOOM, sMin, sMax, false);
}
}
if (mapEventListeners == null)
return;
for (MapEventListener listener : mapEventListeners)
listener.mapSourceChanged(mapSource);
}
protected void zoomChanged(int oldZoom) {
log.trace("Preview map zoom changed from " + oldZoom + " to " + zoom);
if (mapEventListeners != null)
for (MapEventListener listener : mapEventListeners)
listener.zoomChanged(zoom);
updateGridValues();
}
public void setGridZoom(int gridZoom) {
if (gridZoom == this.gridZoom)
return;
this.gridZoom = gridZoom;
updateGridValues();
applyGridOnSelection();
updateMapSelection();
repaint();
}
public int getGridZoom() {
return gridZoom;
}
/**
* Updates the <code>gridSize</code> and the <code>gridTile</code>. This method has to called if
* <code>mapSource</code> or <code>zoom</code> as been changed.
*/
protected void updateGridValues() {
if (gridZoom < 0)
return;
int zoomToGridZoom = zoom - gridZoom;
int tileSize = mapSource.getMapSpace().getTileSize();
if (zoomToGridZoom > 0) {
gridSize = tileSize << zoomToGridZoom;
gridTile = null;
} else {
gridSize = tileSize >> (-zoomToGridZoom);
BufferedImage newGridTile = null;
if (gridSize > 2) {
newGridTile = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = newGridTile.createGraphics();
int alpha = 5 + (6 + zoomToGridZoom) * 16;
alpha = Math.max(0, alpha);
alpha = Math.min(130, alpha);
g.setColor(new Color(200, 20, 20, alpha));
for (int x = 0; x < tileSize; x += gridSize)
g.drawLine(x, 0, x, 255);
for (int y = 0; y < tileSize; y += gridSize)
g.drawLine(0, y, 255, y);
}
gridTile = newGridTile;
}
}
@Override
protected void paintComponent(Graphics graphics) {
if (!isEnabled()) {
graphics.setFont(LOADING_FONT);
graphics.drawString(I18nUtils.localizedStringForKey("map_loading_wait"), 100, 100);
return;
}
if (mapSource == null)
return;
Graphics2D g = (Graphics2D) graphics;
super.paintComponent(g);
Point tlc = getTopLeftCoordinate();
if (gridZoom >= 0) {
// Only paint grid if it is enabled (gridZoom not -1)
int max = (256 << zoom);
int w = Math.min(getWidth(), max - tlc.x);
int h = Math.min(getHeight(), max - tlc.y);
g.setColor(GRID_COLOR);
// g.setStroke(new BasicStroke(4.0f));
if (gridSize > 1) {
int tilesize = mapSource.getMapSpace().getTileSize();
if (gridSize >= tilesize) {
int off_x = tlc.x < 0 ? -tlc.x : -(tlc.x % gridSize);
int off_y = tlc.y < 0 ? -tlc.y : -(tlc.y % gridSize);
for (int x = off_x; x <= w; x += gridSize) {
g.drawLine(x, off_y, x, h);
}
for (int y = off_y; y <= h; y += gridSize) {
g.drawLine(off_x, y, w, y);
}
} else {
int off_x = (tlc.x < 0) ? tlc.x : tlc.x % tilesize;
int off_y = (tlc.y < 0) ? tlc.y : tlc.y % tilesize;
for (int x = -off_x; x < w; x += 256) {
for (int y = -off_y; y < h; y += 256) {
g.drawImage(gridTile, x, y, null);
}
}
}
}
}
if (gridSelectionStart != null && gridSelectionEnd != null) {
// Draw the selection rectangle widened by the current grid
int zoomDiff = MAX_ZOOM - zoom;
int x_min = (gridSelectionStart.x >> zoomDiff) - tlc.x;
int y_min = (gridSelectionStart.y >> zoomDiff) - tlc.y;
int x_max = (gridSelectionEnd.x >> zoomDiff) - tlc.x;
int y_max = (gridSelectionEnd.y >> zoomDiff) - tlc.y;
int w = x_max - x_min + 1;
int h = y_max - y_min + 1;
g.setColor(SEL_COLOR);
g.fillRect(x_min, y_min, w, h);
}
if (iSelectionMin != null && iSelectionMax != null) {
// Draw the selection rectangle exactly as it has been specified by the user
int zoomDiff = MAX_ZOOM - zoom;
int x_min = (iSelectionMin.x >> zoomDiff) - tlc.x;
int y_min = (iSelectionMin.y >> zoomDiff) - tlc.y;
int x_max = (iSelectionMax.x >> zoomDiff) - tlc.x;
int y_max = (iSelectionMax.y >> zoomDiff) - tlc.y;
int w = x_max - x_min + 1;
int h = y_max - y_min + 1;
g.setColor(GRID_COLOR);
g.drawRect(x_min, y_min, w, h);
}
if (mapSource instanceof MapSourceTextAttribution) {
MapSourceTextAttribution ta = (MapSourceTextAttribution) mapSource;
String attributionText = ta.getAttributionText();
if (attributionText != null) {
Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(attributionText, g);
int text_x = getWidth() - 10 - (int) stringBounds.getWidth();
int text_y = getHeight() - 1 - (int) stringBounds.getHeight();
g.setColor(Color.black);
g.drawString(attributionText, text_x + 1, text_y + 1);
g.setColor(Color.white);
g.drawString(attributionText, text_x, text_y);
}
}
if (Settings.getInstance().wgsGrid.enabled) {
wgsGrid.paintWgsGrid(g, mapSource.getMapSpace(), tlc, zoom);
}
ScaleBar.paintScaleBar(this, g, mapSource.getMapSpace(), tlc, zoom);
}
public Bookmark getPositionBookmark() {
return new Bookmark(mapSource, zoom, center.x, center.y);
}
public void gotoPositionBookmark(Bookmark bookmark) {
setMapSource(bookmark.getMapSource());
setDisplayPositionByLatLon(bookmark, bookmark.getZoom());
setZoom(bookmark.getZoom());
}
/**
* @return Coordinate of the point in the center of the currently displayed map region
*/
public EastNorthCoordinate getCenterCoordinate() {
MapSpace mapSpace = mapSource.getMapSpace();
//double lon = mapSpace.cXToLon(center.x, zoom);
//double lat = mapSpace.cYToLat(center.y, zoom);
Point2D.Double p = mapSpace.cXYToLonLat(center.x, center.y, zoom);
double lon = p.x;
double lat = p.y;
return new EastNorthCoordinate(lat, lon);
}
/**
* @return Coordinate of the top left corner visible regarding the current map source (pixel)
*/
public Point getTopLeftCoordinate() {
return new Point(center.x - (getWidth() / 2), center.y - (getHeight() / 2));
}
public void zoomTo(MapSelection ms) {
if (!ms.isAreaSelected())
return;
log.trace("Setting selection to: " + ms);
Point max = ms.getBottomRightPixelCoordinate(MAX_ZOOM);
Point min = ms.getTopLeftPixelCoordinate(MAX_ZOOM);
setDisplayToFitPixelCoordinates(max.x, max.y, min.x, min.y);
}
/**
* Zooms to the specified {@link MapSelection} and sets the selection to it;
*
* @param ms
* @param notifyListeners
*/
public void setSelectionAndZoomTo(MapSelection ms, boolean notifyListeners) {
log.trace("Setting selection to: " + ms);
Point max = ms.getBottomRightPixelCoordinate(MAX_ZOOM);
Point min = ms.getTopLeftPixelCoordinate(MAX_ZOOM);
setDisplayToFitPixelCoordinates(max.x, max.y, min.x, min.y);
Point pStart = ms.getTopLeftPixelCoordinate(zoom);
Point pEnd = ms.getBottomRightPixelCoordinate(zoom);
setSelectionByTileCoordinate(pStart, pEnd, notifyListeners);
}
/**
*
* @param pStart
* x/y tile coordinate of the top left tile regarding the current zoom level
* @param pEnd
* x/y tile coordinate of the bottom right tile regarding the current zoom level
* @param notifyListeners
*/
public void setSelectionByTileCoordinate(Point pStart, Point pEnd, boolean notifyListeners) {
setSelectionByTileCoordinate(zoom, pStart, pEnd, notifyListeners);
}
/**
* Sets the rectangular selection to the absolute tile coordinates <code>pStart</code> and <code>pEnd</code>
* regarding the zoom-level <code>cZoom</code>.
*
* @param cZoom
* @param pStart
* @param pEnd
* @param notifyListeners
*/
public void setSelectionByTileCoordinate(int cZoom, Point pStart, Point pEnd, boolean notifyListeners) {
if (pStart == null || pEnd == null) {
iSelectionMin = null;
iSelectionMax = null;
gridSelectionStart = null;
gridSelectionEnd = null;
return;
}
Point pNewStart = new Point();
Point pNewEnd = new Point();
int mapMaxCoordinate = mapSource.getMapSpace().getMaxPixels(cZoom) - 1;
// Sort x/y coordinate of points so that pNewStart < pnewEnd and limit selection to map size
pNewStart.x = Math.max(0, Math.min(mapMaxCoordinate, Math.min(pStart.x, pEnd.x)));
pNewStart.y = Math.max(0, Math.min(mapMaxCoordinate, Math.min(pStart.y, pEnd.y)));
pNewEnd.x = Math.max(0, Math.min(mapMaxCoordinate, Math.max(pStart.x, pEnd.x)));
pNewEnd.y = Math.max(0, Math.min(mapMaxCoordinate, Math.max(pStart.y, pEnd.y)));
int zoomDiff = MAX_ZOOM - cZoom;
pNewEnd.x <<= zoomDiff;
pNewEnd.y <<= zoomDiff;
pNewStart.x <<= zoomDiff;
pNewStart.y <<= zoomDiff;
iSelectionMin = pNewStart;
iSelectionMax = pNewEnd;
gridSelectionStart = null;
gridSelectionEnd = null;
updateGridValues();
applyGridOnSelection();
if (notifyListeners)
updateMapSelection();
repaint();
}
protected void applyGridOnSelection() {
if (gridZoom < 0) {
gridSelectionStart = iSelectionMin;
gridSelectionEnd = iSelectionMax;
return;
}
if (iSelectionMin == null || iSelectionMax == null)
return;
int gridZoomDiff = MAX_ZOOM - gridZoom;
int gridFactor = mapSource.getMapSpace().getTileSize() << gridZoomDiff;
Point pNewStart = new Point(iSelectionMin);
Point pNewEnd = new Point(iSelectionMax);
// Snap to the current grid
pNewStart.x = MyMath.roundDownToNearest(pNewStart.x, gridFactor);
pNewStart.y = MyMath.roundDownToNearest(pNewStart.y, gridFactor);
pNewEnd.x = MyMath.roundUpToNearest(pNewEnd.x, gridFactor) - 1;
pNewEnd.y = MyMath.roundUpToNearest(pNewEnd.y, gridFactor) - 1;
gridSelectionStart = pNewStart;
gridSelectionEnd = pNewEnd;
}
/**
* Notifies all registered {@link MapEventListener} of a
* {@link MapEventListener#selectionChanged(MercatorPixelCoordinate, MercatorPixelCoordinate)} event.
*/
public void updateMapSelection() {
int x_min, y_min, x_max, y_max;
if (gridZoom >= 0) {
if (gridSelectionStart == null || gridSelectionEnd == null)
return;
x_min = gridSelectionStart.x;
y_min = gridSelectionStart.y;
x_max = gridSelectionEnd.x;
y_max = gridSelectionEnd.y;
} else {
if (iSelectionMin == null || iSelectionMax == null)
return;
x_min = iSelectionMin.x;
y_min = iSelectionMin.y;
x_max = iSelectionMax.x;
y_max = iSelectionMax.y;
}
MercatorPixelCoordinate min = new MercatorPixelCoordinate(mapSource.getMapSpace(), x_min, y_min, MAX_ZOOM);
MercatorPixelCoordinate max = new MercatorPixelCoordinate(mapSource.getMapSpace(), x_max, y_max, MAX_ZOOM);
// log.debug("sel min: [" + min + "]");
// log.debug("sel max: [" + max + "]");
for (MapEventListener listener : mapEventListeners)
listener.selectionChanged(max, min);
}
public void addMapEventListener(MapEventListener l) {
mapEventListeners.add(l);
}
public void selectPreviousMap() {
for (MapEventListener listener : mapEventListeners) {
listener.selectPreviousMapSource();
}
}
public void selectNextMap() {
for (MapEventListener listener : mapEventListeners) {
listener.selectNextMapSource();
}
}
/**
* Clears the in-memory tile cache and performs a repaint which causes a reload of all displayed tiles (from disk or
* if not present from the map source via network).
*/
public void refreshMap() {
tileCache.clear();
repaint();
}
public JMapController getMapKeyboardController() {
return mapKeyboardController;
}
/**
* @return Currently active <code>mapSelectionController</code>
*/
public JMapController getMapSelectionController() {
return mapSelectionController;
}
/**
* Sets a new mapSelectionController. Previous controller are disabled and removed.
*
* @param mapSelectionController
*/
public void setMapSelectionController(JMapController mapSelectionController) {
if (this.mapSelectionController != null)
this.mapSelectionController.disable();
this.mapSelectionController = mapSelectionController;
mapSelectionController.enable();
for (MapEventListener listener : mapEventListeners) {
listener.mapSelectionControllerChanged(mapSelectionController);
}
repaint();
}
}