source: josm/trunk/src/org/openstreetmap/josm/gui/bbox/SlippyMapBBoxChooser.java@ 12927

Last change on this file since 12927 was 12927, checked in by bastiK, 7 years ago

applied #15369 - download area display in SlippyMapBBoxChooser (patch by ris)

  • Property svn:eol-style set to native
File size: 15.0 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[3687]2package org.openstreetmap.josm.gui.bbox;
3
[4531]4import static org.openstreetmap.josm.tools.I18n.tr;
5
[3687]6import java.awt.Color;
7import java.awt.Dimension;
8import java.awt.Graphics;
[12927]9import java.awt.Graphics2D;
[3687]10import java.awt.Point;
11import java.awt.Rectangle;
[12927]12import java.awt.geom.Area;
13import java.awt.geom.Path2D;
[3687]14import java.util.ArrayList;
15import java.util.Arrays;
[3715]16import java.util.Collections;
[8168]17import java.util.HashMap;
[3715]18import java.util.HashSet;
[3687]19import java.util.List;
[8168]20import java.util.Map;
[6316]21import java.util.Set;
[3687]22import java.util.concurrent.CopyOnWriteArrayList;
23
[4531]24import javax.swing.JOptionPane;
[6539]25import javax.swing.SpringLayout;
[4531]26
[3687]27import org.openstreetmap.gui.jmapviewer.Coordinate;
28import org.openstreetmap.gui.jmapviewer.JMapViewer;
29import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
30import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
31import org.openstreetmap.gui.jmapviewer.OsmTileLoader;
[8526]32import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
[3687]33import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
[8168]34import org.openstreetmap.gui.jmapviewer.interfaces.TileLoader;
[3687]35import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
[3915]36import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource;
[3687]37import org.openstreetmap.josm.Main;
38import org.openstreetmap.josm.data.Bounds;
[5898]39import org.openstreetmap.josm.data.Version;
[3687]40import org.openstreetmap.josm.data.coor.LatLon;
[3715]41import org.openstreetmap.josm.data.imagery.ImageryInfo;
42import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
[8598]43import org.openstreetmap.josm.data.imagery.TMSCachedTileLoader;
[11188]44import org.openstreetmap.josm.data.imagery.TileLoaderFactory;
[12339]45import org.openstreetmap.josm.data.osm.BBox;
[3687]46import org.openstreetmap.josm.data.preferences.StringProperty;
[12927]47import org.openstreetmap.josm.gui.MainApplication;
[8598]48import org.openstreetmap.josm.gui.layer.AbstractCachedTileSourceLayer;
[12927]49import org.openstreetmap.josm.gui.layer.MainLayerManager;
50import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[3715]51import org.openstreetmap.josm.gui.layer.TMSLayer;
[12846]52import org.openstreetmap.josm.spi.preferences.Config;
[12620]53import org.openstreetmap.josm.tools.Logging;
[3687]54
[12339]55/**
56 * This panel displays a map and lets the user chose a {@link BBox}.
57 */
[12927]58public class SlippyMapBBoxChooser extends JMapViewer implements BBoxChooser, MainLayerManager.ActiveLayerChangeListener {
[3687]59
[12339]60 /**
61 * A list of tile sources that can be used for displaying the map.
62 */
[10600]63 @FunctionalInterface
[3687]64 public interface TileSourceProvider {
[12339]65 /**
66 * Gets the tile sources that can be displayed
67 * @return The tile sources
68 */
[3687]69 List<TileSource> getTileSources();
70 }
71
72 /**
[3715]73 * TMS TileSource provider for the slippymap chooser
74 */
75 public static class TMSTileSourceProvider implements TileSourceProvider {
[8285]76 private static final Set<String> existingSlippyMapUrls = new HashSet<>();
[3715]77 static {
78 // Urls that already exist in the slippymap chooser and shouldn't be copied from TMS layer list
[6917]79 existingSlippyMapUrls.add("https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png"); // Mapnik
[3715]80 }
81
82 @Override
83 public List<TileSource> getTileSources() {
84 if (!TMSLayer.PROP_ADD_TO_SLIPPYMAP_CHOOSER.get()) return Collections.<TileSource>emptyList();
[7005]85 List<TileSource> sources = new ArrayList<>();
[3715]86 for (ImageryInfo info : ImageryLayerInfo.instance.getLayers()) {
[3826]87 if (existingSlippyMapUrls.contains(info.getUrl())) {
[3715]88 continue;
89 }
[4531]90 try {
[8526]91 TileSource source = TMSLayer.getTileSourceStatic(info);
[4531]92 if (source != null) {
93 sources.add(source);
94 }
95 } catch (IllegalArgumentException ex) {
[12620]96 Logging.warn(ex);
[4531]97 if (ex.getMessage() != null && !ex.getMessage().isEmpty()) {
98 JOptionPane.showMessageDialog(Main.parent,
99 ex.getMessage(), tr("Warning"),
100 JOptionPane.WARNING_MESSAGE);
101 }
[3715]102 }
103 }
104 return sources;
105 }
106 }
107
108 /**
[3687]109 * Plugins that wish to add custom tile sources to slippy map choose should call this method
[8470]110 * @param tileSourceProvider new tile source provider
[3687]111 */
112 public static void addTileSourceProvider(TileSourceProvider tileSourceProvider) {
113 providers.addIfAbsent(tileSourceProvider);
114 }
115
[7005]116 private static CopyOnWriteArrayList<TileSourceProvider> providers = new CopyOnWriteArrayList<>();
[3687]117 static {
[12203]118 addTileSourceProvider(() -> Arrays.<TileSource>asList(new OsmTileSource.Mapnik()));
[3715]119 addTileSourceProvider(new TMSTileSourceProvider());
[3687]120 }
121
122 private static final StringProperty PROP_MAPSTYLE = new StringProperty("slippy_map_chooser.mapstyle", "Mapnik");
[12339]123 /**
124 * The property name used for the resize button.
125 * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
126 */
[4336]127 public static final String RESIZE_PROP = SlippyMapBBoxChooser.class.getName() + ".resize";
[3687]128
[9078]129 private final transient TileLoader cachedLoader;
130 private final transient OsmTileLoader uncachedLoader;
[3687]131
[6539]132 private final SizeButton iSizeButton;
[3687]133 private final SourceButton iSourceButton;
[8308]134 private transient Bounds bbox;
[3687]135
[6364]136 // upper left and lower right corners of the selection rectangle (x/y on ZOOM_MAX)
[9623]137 private transient ICoordinate iSelectionRectStart;
138 private transient ICoordinate iSelectionRectEnd;
[3687]139
[6364]140 /**
141 * Constructs a new {@code SlippyMapBBoxChooser}.
142 */
[3687]143 public SlippyMapBBoxChooser() {
[12620]144 debug = Logging.isDebugEnabled();
[6539]145 SpringLayout springLayout = new SpringLayout();
146 setLayout(springLayout);
[3777]147
[8168]148 Map<String, String> headers = new HashMap<>();
149 headers.put("User-Agent", Version.getInstance().getFullAgentString());
150
[11188]151 TileLoaderFactory cachedLoaderFactory = AbstractCachedTileSourceLayer.getTileLoaderFactory("TMS", TMSCachedTileLoader.class);
152 if (cachedLoaderFactory != null) {
153 cachedLoader = cachedLoaderFactory.makeTileLoader(this, headers);
154 } else {
155 cachedLoader = null;
156 }
[8168]157
[3687]158 uncachedLoader = new OsmTileLoader(this);
[8168]159 uncachedLoader.headers.putAll(headers);
[12846]160 setZoomContolsVisible(Config.getPref().getBoolean("slippy_map_chooser.zoomcontrols", false));
[3687]161 setMapMarkerVisible(false);
162 setMinimumSize(new Dimension(350, 350 / 2));
163 // We need to set an initial size - this prevents a wrong zoom selection
[6364]164 // for the area before the component has been displayed the first time
[3687]165 setBounds(new Rectangle(getMinimumSize()));
166 if (cachedLoader == null) {
167 setFileCacheEnabled(false);
168 } else {
[12846]169 setFileCacheEnabled(Config.getPref().getBoolean("slippy_map_chooser.file_cache", true));
[3687]170 }
[12846]171 setMaxTilesInMemory(Config.getPref().getInt("slippy_map_chooser.max_tiles", 1000));
[3687]172
[6378]173 List<TileSource> tileSources = getAllTileSources();
[3687]174
[6539]175 iSourceButton = new SourceButton(this, tileSources);
176 add(iSourceButton);
177 springLayout.putConstraint(SpringLayout.EAST, iSourceButton, 0, SpringLayout.EAST, this);
178 springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 30, SpringLayout.NORTH, this);
[3687]179
[6539]180 iSizeButton = new SizeButton(this);
181 add(iSizeButton);
182
[3687]183 String mapStyle = PROP_MAPSTYLE.get();
184 boolean foundSource = false;
185 for (TileSource source: tileSources) {
186 if (source.getName().equals(mapStyle)) {
187 this.setTileSource(source);
188 iSourceButton.setCurrentMap(source);
189 foundSource = true;
190 break;
191 }
192 }
193 if (!foundSource) {
194 setTileSource(tileSources.get(0));
195 iSourceButton.setCurrentMap(tileSources.get(0));
196 }
197
[12927]198 MainApplication.getLayerManager().addActiveLayerChangeListener(this);
199
[6539]200 new SlippyMapControler(this, this);
[3687]201 }
[6539]202
[10755]203 private static List<TileSource> getAllTileSources() {
[7005]204 List<TileSource> tileSources = new ArrayList<>();
[6364]205 for (TileSourceProvider provider: providers) {
206 tileSources.addAll(provider.getTileSources());
207 }
208 return tileSources;
209 }
[3687]210
[12339]211 /**
212 * Handles a click/move on the attribution
213 * @param p The point in the view
214 * @param click true if it was a click, false for hover
215 * @return if the attribution handled the event
216 */
[4195]217 public boolean handleAttribution(Point p, boolean click) {
[4489]218 return attribution.handleAttribution(p, click);
[4195]219 }
220
[3687]221 /**
222 * Draw the map.
223 */
224 @Override
[12927]225 public void paintComponent(Graphics g) {
226 super.paintComponent(g);
227 Graphics2D g2d = (Graphics2D)g;
[3687]228
[12927]229 // draw shaded area for non-downloaded region of current "edit layer", but only if there *is* a current "edit layer",
230 // and it has defined bounds. Routine is analogous to that in OsmDataLayer's paint routine (but just different
231 // enough to make sharing code impractical)
232 final OsmDataLayer editLayer = MainApplication.getLayerManager().getEditLayer();
233 if (editLayer != null && Config.getPref().getBoolean("draw.data.downloaded_area", true) && !editLayer.data.getDataSources().isEmpty()) {
234 // initialize area with current viewport
235 Rectangle b = this.getBounds();
236 // ensure we comfortably cover full area
237 b.grow(100, 100);
238 Path2D p = new Path2D.Float();
239
240 // combine successively downloaded areas after converting to screen-space
241 for (Bounds bounds : editLayer.data.getDataSourceBounds()) {
242 if (bounds.isCollapsed()) {
243 continue;
244 }
245 Rectangle r = new Rectangle(this.getMapPosition(bounds.getMinLat(), bounds.getMinLon(), false));
246 r.add(this.getMapPosition(bounds.getMaxLat(), bounds.getMaxLon(), false));
247 p.append(r, false);
248 }
249 // subtract combined areas
250 Area a = new Area(b);
251 a.subtract(new Area(p));
252
253 // paint remainder
254 g2d.setPaint(new Color(0, 0, 0, 32));
255 g2d.fill(a);
256 }
257
[10212]258 // draw selection rectangle
259 if (iSelectionRectStart != null && iSelectionRectEnd != null) {
260 Rectangle box = new Rectangle(getMapPosition(iSelectionRectStart, false));
261 box.add(getMapPosition(iSelectionRectEnd, false));
[3687]262
[10212]263 g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
264 g.fillRect(box.x, box.y, box.width, box.height);
[3687]265
[10212]266 g.setColor(Color.BLACK);
267 g.drawRect(box.x, box.y, box.width, box.height);
[3687]268 }
269 }
270
[12927]271 @Override
272 public void activeOrEditLayerChanged(MainLayerManager.ActiveLayerChangeEvent e) {
273 this.repaint();
274 }
275
[12339]276 /**
277 * Enables the disk tile cache.
278 * @param enabled true to enable, false to disable
279 */
[6890]280 public final void setFileCacheEnabled(boolean enabled) {
[11188]281 if (enabled && cachedLoader != null) {
[3687]282 setTileLoader(cachedLoader);
283 } else {
284 setTileLoader(uncachedLoader);
285 }
286 }
287
[12339]288 /**
289 * Sets the maximum number of tiles that may be held in memory
290 * @param tiles The maximum number of tiles.
291 */
[6890]292 public final void setMaxTilesInMemory(int tiles) {
[3687]293 ((MemoryTileCache) getTileCache()).setCacheSize(tiles);
294 }
295
296 /**
[8470]297 * Callback for the OsmMapControl. (Re-)Sets the start and end point of the selection rectangle.
[3687]298 *
[8470]299 * @param aStart selection start
300 * @param aEnd selection end
[3687]301 */
302 public void setSelection(Point aStart, Point aEnd) {
303 if (aStart == null || aEnd == null || aStart.x == aEnd.x || aStart.y == aEnd.y)
304 return;
305
[10001]306 Point pMax = new Point(Math.max(aEnd.x, aStart.x), Math.max(aEnd.y, aStart.y));
307 Point pMin = new Point(Math.min(aEnd.x, aStart.x), Math.min(aEnd.y, aStart.y));
[3687]308
[10001]309 iSelectionRectStart = getPosition(pMin);
[10378]310 iSelectionRectEnd = getPosition(pMax);
[3687]311
312 Bounds b = new Bounds(
313 new LatLon(
[8349]314 Math.min(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
315 LatLon.toIntervalLon(Math.min(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon()))
[4868]316 ),
317 new LatLon(
[8349]318 Math.max(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
319 LatLon.toIntervalLon(Math.max(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon())))
[4868]320 );
[3687]321 Bounds oldValue = this.bbox;
322 this.bbox = b;
[4336]323 repaint();
[3687]324 firePropertyChange(BBOX_PROP, oldValue, this.bbox);
325 }
326
327 /**
328 * Performs resizing of the DownloadDialog in order to enlarge or shrink the
329 * map.
330 */
331 public void resizeSlippyMap() {
[4336]332 boolean large = iSizeButton.isEnlarged();
333 firePropertyChange(RESIZE_PROP, !large, large);
[3687]334 }
335
[12339]336 /**
337 * Sets the active tile source
338 * @param tileSource The active tile source
339 */
[3687]340 public void toggleMapSource(TileSource tileSource) {
341 this.tileController.setTileCache(new MemoryTileCache());
342 this.setTileSource(tileSource);
343 PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
344 }
345
[6084]346 @Override
[3687]347 public Bounds getBoundingBox() {
348 return bbox;
349 }
350
351 /**
352 * Sets the current bounding box in this bbox chooser without
353 * emiting a property change event.
354 *
355 * @param bbox the bounding box. null to reset the bounding box
356 */
[6084]357 @Override
[3687]358 public void setBoundingBox(Bounds bbox) {
[8393]359 if (bbox == null || (bbox.getMinLat() == 0 && bbox.getMinLon() == 0
360 && bbox.getMaxLat() == 0 && bbox.getMaxLon() == 0)) {
[3687]361 this.bbox = null;
[4336]362 iSelectionRectStart = null;
363 iSelectionRectEnd = null;
364 repaint();
[3687]365 return;
366 }
367
[4336]368 this.bbox = bbox;
[8349]369 iSelectionRectStart = new Coordinate(bbox.getMinLat(), bbox.getMinLon());
370 iSelectionRectEnd = new Coordinate(bbox.getMaxLat(), bbox.getMaxLon());
[4868]371
[3687]372 // calc the screen coordinates for the new selection rectangle
[8387]373 MapMarkerDot min = new MapMarkerDot(bbox.getMinLat(), bbox.getMinLon());
374 MapMarkerDot max = new MapMarkerDot(bbox.getMaxLat(), bbox.getMaxLon());
[3687]375
[7005]376 List<MapMarker> marker = new ArrayList<>(2);
[8387]377 marker.add(min);
378 marker.add(max);
[3687]379 setMapMarkerList(marker);
380 setDisplayToFitMapMarkers();
381 zoomOut();
[4552]382 repaint();
[3687]383 }
[6539]384
[6364]385 /**
[8732]386 * Enables or disables painting of the shrink/enlarge button
387 *
388 * @param visible {@code true} to enable painting of the shrink/enlarge button
389 */
390 public void setSizeButtonVisible(boolean visible) {
391 iSizeButton.setVisible(visible);
392 }
393
394 /**
[6364]395 * Refreshes the tile sources
396 * @since 6364
397 */
398 public final void refreshTileSources() {
[6378]399 iSourceButton.setSources(getAllTileSources());
[6364]400 }
[3687]401}
Note: See TracBrowser for help on using the repository browser.