/* * Copyright (c) 2016 Fraunhofer IGD * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Fraunhofer IGD <http://www.igd.fraunhofer.de/> */ package de.fhg.igd.mapviewer; import java.awt.Cursor; import java.awt.Point; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdesktop.swingx.mapviewer.DefaultTileCache; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.jdesktop.swingx.mapviewer.GeotoolsConverter; import org.jdesktop.swingx.mapviewer.IllegalGeoPositionException; import org.jdesktop.swingx.mapviewer.JXMapKit; import org.jdesktop.swingx.mapviewer.JXMapViewer; import org.jdesktop.swingx.mapviewer.TileCache; import org.jdesktop.swingx.mapviewer.TileOverlayPainter; import org.jdesktop.swingx.painter.CompoundPainter; import org.jdesktop.swingx.painter.Painter; import org.springframework.util.ClassUtils; import de.fhg.igd.mapviewer.concurrency.Concurrency; import de.fhg.igd.mapviewer.concurrency.IJob; import de.fhg.igd.mapviewer.concurrency.Job; import de.fhg.igd.mapviewer.concurrency.Progress; import de.fhg.igd.mapviewer.concurrency.SwingCallback; import de.fhg.igd.mapviewer.server.MapServer; /** * Basic MapKit * * @author <a href="mailto:simon.templer@igd.fhg.de">Simon Templer</a> */ public class BasicMapKit extends JXMapKit { private static final Log log = LogFactory.getLog(BasicMapKit.class); private static final long serialVersionUID = -708805464636342709L; /** * The painter for the current map tool */ private final MapToolPainter toolPainter; /** * The current map server */ private MapServer server; private TileCache cache; /** * Defines whether this is synced with the 3D camera */ private boolean synced; /** * The painter for all overlays */ private final CompoundPainter<JXMapViewer> painter; /** * The custom painters */ private final CompoundPainter<JXMapViewer> customPainter; /** * The painters associated directly with the map * * @see MapServer#getMapOverlay() */ private final CompoundPainter<JXMapViewer> mapPainter; private List<MapPainter> customPainters = new ArrayList<MapPainter>(); private Lock customPaintersLock = new ReentrantLock(); /** * Creates a basic map kit */ public BasicMapKit() { this(new DefaultTileCache()); } /** * Creates a basic map kit * * @param cache the tile cache to use */ public BasicMapKit(TileCache cache) { super(); this.cache = cache; getMiniMap().setPanEnabled(false); getMiniMap().setCursor(Cursor.getDefaultCursor()); getZoomSlider().setCursor(Cursor.getDefaultCursor()); getZoomInButton().setCursor(Cursor.getDefaultCursor()); getZoomOutButton().setCursor(Cursor.getDefaultCursor()); getMiniMap().addMouseListener(new MouseAdapter() { /** * @see MouseAdapter#mouseClicked(MouseEvent) */ @Override public void mouseClicked(MouseEvent me) { getMainMap() .setCenterPosition(getMiniMap().convertPointToGeoPosition(me.getPoint())); } }); // create painter for map tools toolPainter = new MapToolPainter(getMainMap()); customPainter = new CompoundPainter<JXMapViewer>(); customPainter.setCacheable(false); mapPainter = new CompoundPainter<JXMapViewer>(); mapPainter.setCacheable(false); painter = new CompoundPainter<JXMapViewer>(); painter.setPainters(customPainter, toolPainter, mapPainter); painter.setCacheable(false); updatePainters(); // register as state provider // GuiState.getInstance().registerStateProvider(this); } /** * Update the custom painters */ private void updatePainters() { customPaintersLock.lock(); try { for (MapPainter painter : customPainters) { painter.setMapKit(this); } MapPainter[] painters = new MapPainter[customPainters.size()]; customPainters.toArray(painters); customPainter.setPainters(painters); } finally { customPaintersLock.unlock(); } } /** * Get all custom painters of a given type * * @param type the painter type * @param <T> the painter type * @return the list of custom painters (not backed by the map kit) */ @SuppressWarnings("unchecked") public <T extends MapPainter> List<T> getCustomPainters(Class<T> type) { List<T> results = new ArrayList<T>(); customPaintersLock.lock(); try { for (MapPainter painter : customPainters) { if (ClassUtils.isAssignable(type, painter.getClass())) { results.add((T) painter); } } } finally { customPaintersLock.unlock(); } return results; } /** * @return the list of custom painters (not backed by the map kit) */ public List<MapPainter> getCustomPainters() { List<MapPainter> results; customPaintersLock.lock(); try { results = new ArrayList<MapPainter>(customPainters); } finally { customPaintersLock.unlock(); } return results; } /** * Sets the custom painters * * @param customPainters the custom painters */ public void setCustomPainters(List<MapPainter> customPainters) { customPaintersLock.lock(); try { this.customPainters = new ArrayList<MapPainter>(customPainters); } finally { customPaintersLock.unlock(); } updatePainters(); } /** * Adds a custom map painter * * @param painter the map painter */ public void addCustomPainter(MapPainter painter) { synchronized (customPainters) { customPainters.add(painter); } updatePainters(); } /** * Removes a custom map painter * * @param painter the map painter */ public void removeCustomPainter(MapPainter painter) { synchronized (customPainters) { customPainters.remove(painter); } updatePainters(); } /** * Get all tile overlay painters of a certain type * * @param <T> the painter type * @param type the painter type * @return the list of tile overlay painters */ @SuppressWarnings("unchecked") public <T extends TileOverlayPainter> List<T> getTilePainters(Class<T> type) { List<T> results = new ArrayList<T>(); for (TileOverlayPainter painter : getMainMap().getTileOverlays()) { if (ClassUtils.isAssignable(type, painter.getClass())) { results.add((T) painter); } } return results; } /** * Creates all map painters save the tool painter * * @param mapViewer the main map viewer * * @return a list of painters */ protected List<Painter<JXMapViewer>> createPainters(JXMapViewer mapViewer) { return new ArrayList<Painter<JXMapViewer>>(); } /** * Set the current map tool * * @param tool the {@link MapTool} to use */ public void setMapTool(MapTool tool) { if (toolPainter.getMapTool() != null) toolPainter.getMapTool().setActive(false); toolPainter.setMapTool(tool); tool.setActive(true); getMainMap().setPanEnabled(tool.isPanEnabled()); getMainMap().setCursor(tool.getCursor()); getMainMap().repaint(); } /** * Get the current map tool * * @return the current map tool */ public MapTool getMapTool() { return toolPainter.getMapTool(); } /** * Set the map kit's map server * * @param server the map server * @param skipZoom if zooming to the area visible in the last map shall be * skipped (makes sense when instead loading the state from file) */ public void setServer(final MapServer server, final boolean skipZoom) { this.server = server; // remember map area final Set<GeoPosition> gps = new HashSet<GeoPosition>(); gps.add(getMainMap().convertPointToGeoPosition( new Point((int) Math.round(getMainMap().getWidth() * 0.1), (int) Math.round(getMainMap().getHeight() * 0.1)))); gps.add(getMainMap().convertPointToGeoPosition( new Point((int) Math.round(getMainMap().getWidth() * 0.9), (int) Math.round(getMainMap().getHeight() * 0.9)))); onChangingServer(server); IJob<Void> job = new Job<Void>(Messages.BasicMapKit_0, new SwingCallback<Void>() { @Override protected void finished(Void result) { // dispose old map overlay Painter<?>[] oldOverlays = mapPainter.getPainters(); if (oldOverlays != null) { for (Painter<?> oldOverlay : oldOverlays) { if (oldOverlay instanceof MapPainter) { ((MapPainter) oldOverlay).dispose(); } } } // set the new map overlay MapPainter mapOverlay = server.getMapOverlay(); if (mapOverlay != null) { mapPainter.setPainters(mapOverlay); mapOverlay.setMapKit(BasicMapKit.this); } else { mapPainter.setPainters(); } getMainMap().setOverlayPainter(painter); if (!skipZoom) { // done in the step below - setCenterPosition(pos); zoomToPositions(gps); } revalidate(); } @Override protected void error(Throwable e) { log.error("Error configuring map", e); //$NON-NLS-1$ } }) { @Override public Void work(Progress progress) throws Exception { // configure the map kit BasicMapKit.this.setTileFactory(server.getTileFactory(cache)); return null; } }; Concurrency.startJob(job); } /** * Set the tile cache * * @param cache the tile cache to use */ public void setTileCache(TileCache cache) { this.cache = cache; // re-set current map MapServer server = getServer(); setServer(server, false); } /** * Called just before the current map server is set to the given server * * @param server the new map server */ protected void onChangingServer(MapServer server) { // override me } /** * @see JXMapKit#setZoom(int) */ @Override public void setZoom(int zoom) { zoom = Math.min(zoom, getMainMap().getTileFactory().getTileProvider().getMaximumZoom()); super.setZoom(zoom); if (zoom + 4 > getMainMap().getTileFactory().getTileProvider().getMaximumZoom()) setMiniMapVisible(false); else setMiniMapVisible(true); } /** * Refresh the map */ public void refresh() { getMainMap().repaint(); } /** * Zoom in and center on the given {@link GeoPosition} * * @param pos the {@link GeoPosition} */ public void zoomInToPosition(GeoPosition pos) { setZoom(getMainMap().getTileFactory().getTileProvider().getMinimumZoom()); setCenterPosition(pos); } private static Set<GeoPosition> equalizeEpsg(Collection<GeoPosition> positions) { if (positions.isEmpty()) return new HashSet<GeoPosition>(positions); int epsg = -1; Set<GeoPosition> result = new HashSet<GeoPosition>(); for (GeoPosition pos : positions) { if (epsg == -1) { epsg = pos.getEpsgCode(); result.add(pos); } else if (epsg != pos.getEpsgCode()) { GeoPosition altPos; try { altPos = GeotoolsConverter.getInstance().convert(pos, epsg); result.add(altPos); } catch (IllegalGeoPositionException e) { log.warn("Error converting GeoPosition, ignoring this position"); //$NON-NLS-1$ } } else { result.add(pos); } } return result; } /** * Center on the given {@link GeoPosition}s * * @param positions the {@link GeoPosition}s */ public void centerOnPositions(Set<GeoPosition> positions) { if (positions.size() == 0) return; positions = equalizeEpsg(positions); double minX = 0, maxX = 0, minY = 0, maxY = 0; boolean init = false; int epsg = positions.iterator().next().getEpsgCode(); for (GeoPosition pos : positions) { if (!init) { // first pos minY = maxY = pos.getY(); minX = maxX = pos.getX(); init = true; } else { if (pos.getY() < minY) minY = pos.getY(); else if (pos.getY() > maxY) maxY = pos.getY(); if (pos.getX() < minX) minX = pos.getX(); else if (pos.getX() > maxX) maxX = pos.getX(); } } setCenterPosition(new GeoPosition((minX + maxX) / 2.0, (minY + maxY) / 2.0, epsg)); } private Rectangle2D generateBoundingRect(double minX, double minY, double maxX, double maxY, int epsg, int zoom) throws IllegalGeoPositionException { java.awt.geom.Point2D p1 = getMainMap().getTileFactory().getTileProvider().getConverter() .geoToPixel(new GeoPosition(minX, minY, epsg), zoom); java.awt.geom.Point2D p2 = getMainMap().getTileFactory().getTileProvider().getConverter() .geoToPixel(new GeoPosition(maxX, maxY, epsg), zoom); return new Rectangle2D.Double((p1.getX() < p2.getX()) ? (p1.getX()) : (p2.getX()), (p1.getY() < p2.getY()) ? (p1.getY()) : (p2.getY()), Math.abs(p2.getX() - p1.getX()), Math.abs(p2.getY() - p1.getY())); } /** * Zoom in and center on the given {@link GeoPosition}s * * @param positions the {@link GeoPosition}s */ public void zoomToPositions(Set<GeoPosition> positions) { if (positions.size() == 0) return; positions = equalizeEpsg(positions); int epsg = positions.iterator().next().getEpsgCode(); double minX = 0, maxX = 0, minY = 0, maxY = 0; boolean init = false; for (GeoPosition pos : positions) { if (!init) { // first pos minY = maxY = pos.getY(); minX = maxX = pos.getX(); init = true; } else { if (pos.getY() < minY) minY = pos.getY(); else if (pos.getY() > maxY) maxY = pos.getY(); if (pos.getX() < minX) minX = pos.getX(); else if (pos.getX() > maxX) maxX = pos.getX(); } } // center on positions setCenterPosition(new GeoPosition((minX + maxX) / 2.0, (minY + maxY) / 2.0, epsg)); // initial zoom int zoom = getMainMap().getTileFactory().getTileProvider().getMinimumZoom(); try { if (positions.size() >= 2) { int viewWidth = (int) getMainMap().getViewportBounds().getWidth(); int viewHeight = (int) getMainMap().getViewportBounds().getHeight(); Rectangle2D rect = generateBoundingRect(minX, minY, maxX, maxY, epsg, zoom); while ((viewWidth < rect.getWidth() || viewHeight < rect.getHeight()) && zoom < getMainMap().getTileFactory().getTileProvider() .getMaximumZoom()) { zoom++; rect = generateBoundingRect(minX, minY, maxX, maxY, epsg, zoom); } } setZoom(zoom); } catch (IllegalGeoPositionException e) { log.warn("Error zooming to positions");// , e); //$NON-NLS-1$ } } /** * @return the server */ public MapServer getServer() { return server; } /** * @param synced true if synced with the 3D map camera, false otherwise */ public void setSynced(boolean synced) { this.synced = synced; } /** * @return true if synced with the 3D map camera, false otherwise */ public boolean isSynced() { return this.synced; } /* * protected void loadState(GuiState state) { int oldEpsg = * state.getInteger(BasicMapKit.class, LAST_EPSG, DEF_EPSG); * * GeoPosition p1 = new GeoPosition(state.getDouble(BasicMapKit.class, * SHOW_MIN_X, DEF_MIN_X), state.getDouble(BasicMapKit.class, SHOW_MIN_Y, * DEF_MIN_Y), oldEpsg); GeoPosition p2 = new * GeoPosition(state.getDouble(BasicMapKit.class, SHOW_MAX_X, DEF_MAX_X), * state.getDouble(BasicMapKit.class, SHOW_MAX_Y, DEF_MAX_Y), oldEpsg); * * Set<GeoPosition> positions = new HashSet<GeoPosition>(); * positions.add(p1); positions.add(p2); zoomToPositions(positions); } */ /* * (non-Javadoc) * * @see * de.fhg.igd.mutable.gui.StateProvider#onStateSave(de.fhg.igd.mutable.gui. * GuiState) */ /* * @Override public void onStateSave(GuiState state) { GeoPosition min = * getMainMap().convertPointToGeoPosition(new Point((int) * Math.round(getMainMap().getWidth() * 0.1), (int) * Math.round(getMainMap().getHeight() * 0.1))); GeoPosition max = * getMainMap().convertPointToGeoPosition(new Point((int) * Math.round(getMainMap().getWidth() * 0.9), (int) * Math.round(getMainMap().getHeight() * 0.9))); * * //GeoPosition min = getMainMap().convertPointToGeoPosition(new Point(0, * 0)); //GeoPosition max = getMainMap().convertPointToGeoPosition(new * Point(getMainMap().getWidth(), getMainMap().getHeight())); * * int epsg = min.getEpsgCode(); * * try { if (epsg != max.getEpsgCode()) max = * EpsgGeoConverter.INSTANCE.convert(max, epsg); * * state.setDouble(BasicMapKit.class, SHOW_MIN_Y, min.getY()); * state.setDouble(BasicMapKit.class, SHOW_MIN_X, min.getX()); * state.setDouble(BasicMapKit.class, SHOW_MAX_Y, max.getY()); * state.setDouble(BasicMapKit.class, SHOW_MAX_X, max.getX()); * * state.setInteger(BasicMapKit.class, LAST_EPSG, epsg); } catch * (IllegalGeoPositionException e) { log.error( * "Error saving map state: Could not convert GeoPosition", e); } } */ /* * @Override public void guiClosing() { // do nothing } */ /* * @Override public void guiStarted() { loadState(GuiState.getInstance()); } */ }