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

Last change on this file since 17333 was 17333, checked in by Don-vip, 3 years ago

see #20129 - Fix typos and misspellings in the code (patch by gaben)

  • Property svn:eol-style set to native
File size: 15.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.bbox;
3
4import java.awt.Color;
5import java.awt.Dimension;
6import java.awt.Graphics;
7import java.awt.Graphics2D;
8import java.awt.Point;
9import java.awt.Rectangle;
10import java.awt.geom.Area;
11import java.awt.geom.Path2D;
12import java.util.ArrayList;
13import java.util.LinkedHashMap;
14import java.util.List;
15import java.util.concurrent.CopyOnWriteArrayList;
16import java.util.stream.Collectors;
17
18import javax.swing.ButtonModel;
19import javax.swing.JToggleButton;
20import javax.swing.SpringLayout;
21import javax.swing.event.ChangeEvent;
22import javax.swing.event.ChangeListener;
23
24import org.openstreetmap.gui.jmapviewer.Coordinate;
25import org.openstreetmap.gui.jmapviewer.MapMarkerDot;
26import org.openstreetmap.gui.jmapviewer.MemoryTileCache;
27import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
28import org.openstreetmap.gui.jmapviewer.interfaces.MapMarker;
29import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.coor.LatLon;
32import org.openstreetmap.josm.data.osm.BBox;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.preferences.BooleanProperty;
35import org.openstreetmap.josm.data.preferences.StringProperty;
36import org.openstreetmap.josm.gui.MainApplication;
37import org.openstreetmap.josm.gui.MapScaler;
38import org.openstreetmap.josm.gui.NavigatableComponent;
39import org.openstreetmap.josm.gui.layer.ImageryLayer;
40import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
41import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
42import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
43import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
44import org.openstreetmap.josm.gui.layer.MainLayerManager;
45import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
46import org.openstreetmap.josm.spi.preferences.Config;
47import org.openstreetmap.josm.tools.Logging;
48
49/**
50 * This panel displays a map and lets the user chose a {@link BBox}.
51 */
52public class SlippyMapBBoxChooser extends JosmMapViewer implements BBoxChooser, ChangeListener, ActiveLayerChangeListener, LayerChangeListener {
53
54 /**
55 * Plugins that wish to add custom tile sources to slippy map choose should call this method
56 * @param tileSourceProvider new tile source provider
57 */
58 public static void addTileSourceProvider(TileSourceProvider tileSourceProvider) {
59 providers.addIfAbsent(tileSourceProvider);
60 }
61
62 private static final CopyOnWriteArrayList<TileSourceProvider> providers = new CopyOnWriteArrayList<>();
63 static {
64 addTileSourceProvider(new DefaultOsmTileSourceProvider());
65 addTileSourceProvider(new TMSTileSourceProvider());
66 addTileSourceProvider(new CurrentLayersTileSourceProvider());
67 }
68
69 private static final StringProperty PROP_MAPSTYLE = new StringProperty("slippy_map_chooser.mapstyle", "Mapnik");
70 private static final BooleanProperty PROP_SHOWDLAREA = new BooleanProperty("slippy_map_chooser.show_downloaded_area", true);
71
72 /**
73 * The property name used for the resize button.
74 * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
75 */
76 public static final String RESIZE_PROP = SlippyMapBBoxChooser.class.getName() + ".resize";
77
78 /**
79 * The property name used for the {@link org.openstreetmap.josm.data.coor.ILatLon} of the mouse cursor on the map.
80 * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
81 */
82 public static final String CURSOR_COORDINATE_PROP = SlippyMapBBoxChooser.class.getName() + ".coordinate";
83
84 private final SizeButton iSizeButton;
85 private final ButtonModel showDownloadAreaButtonModel;
86 private final SourceButton iSourceButton;
87 private transient Bounds bbox;
88
89 // upper left and lower right corners of the selection rectangle (x/y on ZOOM_MAX)
90 private transient ICoordinate iSelectionRectStart;
91 private transient ICoordinate iSelectionRectEnd;
92
93 static {
94 debug = Logging.isDebugEnabled();
95 }
96
97 /**
98 * Constructs a new {@code SlippyMapBBoxChooser}.
99 */
100 public SlippyMapBBoxChooser() {
101 SpringLayout springLayout = new SpringLayout();
102 setLayout(springLayout);
103
104 setZoomControlsVisible(Config.getPref().getBoolean("slippy_map_chooser.zoomcontrols", false));
105 setMapMarkerVisible(false);
106 setMinimumSize(new Dimension(350, 350 / 2));
107 // We need to set an initial size - this prevents a wrong zoom selection
108 // for the area before the component has been displayed the first time
109 setBounds(new Rectangle(getMinimumSize()));
110 if (cachedLoader == null) {
111 setFileCacheEnabled(false);
112 } else {
113 setFileCacheEnabled(Config.getPref().getBoolean("slippy_map_chooser.file_cache", true));
114 }
115 setMaxTilesInMemory(Config.getPref().getInt("slippy_map_chooser.max_tiles", 1000));
116
117 List<TileSource> tileSources = new ArrayList<>(getAllTileSources().values());
118
119 this.showDownloadAreaButtonModel = new JToggleButton.ToggleButtonModel();
120 this.showDownloadAreaButtonModel.setSelected(PROP_SHOWDLAREA.get());
121 this.showDownloadAreaButtonModel.addChangeListener(this);
122 iSourceButton = new SourceButton(this, tileSources, this.showDownloadAreaButtonModel);
123 add(iSourceButton);
124 springLayout.putConstraint(SpringLayout.EAST, iSourceButton, -2, SpringLayout.EAST, this);
125 springLayout.putConstraint(SpringLayout.NORTH, iSourceButton, 2, SpringLayout.NORTH, this);
126
127 iSizeButton = new SizeButton(this);
128 add(iSizeButton);
129
130 MapScaler scaler = new MapScaler(this::getDist100Pixel, () -> Color.BLACK);
131 add(scaler);
132 springLayout.putConstraint(SpringLayout.NORTH, scaler, 2, SpringLayout.NORTH, this);
133 springLayout.putConstraint(SpringLayout.WEST, scaler, 2, SpringLayout.EAST, iSizeButton);
134
135 String mapStyle = PROP_MAPSTYLE.get();
136 final TileSource tileSource = tileSources.stream()
137 .filter(source -> source.getName().equals(mapStyle))
138 .findFirst()
139 .orElse(tileSources.get(0));
140 setTileSource(tileSource);
141 iSourceButton.setCurrentMap(tileSource);
142
143 MainApplication.getLayerManager().addActiveLayerChangeListener(this);
144
145 new SlippyMapController(this, this);
146 }
147
148 private static LinkedHashMap<String, TileSource> getAllTileSources() {
149 // using a LinkedHashMap of <id, TileSource> to retain ordering but provide deduplication
150 return providers.stream().flatMap(
151 provider -> provider.getTileSources().stream()
152 ).collect(Collectors.toMap(
153 TileSource::getId,
154 ts -> ts,
155 (oldTs, newTs) -> oldTs,
156 LinkedHashMap::new
157 ));
158 }
159
160 /**
161 * Get the distance in meter that correspond to 100 px on screen.
162 * @return the distance in meter that correspond to 100 px on screen
163 * @see NavigatableComponent#getDist100Pixel
164 */
165 private double getDist100Pixel() {
166 int w = getWidth() / 2;
167 int h = getHeight() / 2;
168 ICoordinate c1 = getPosition(w - 50, h);
169 ICoordinate c2 = getPosition(w + 50, h);
170 final LatLon ll1 = new LatLon(c1.getLat(), c1.getLon());
171 final LatLon ll2 = new LatLon(c2.getLat(), c2.getLon());
172 double gcd = ll1.greatCircleDistance(ll2);
173 return gcd <= 0 ? 0.1 : gcd;
174 }
175
176 /**
177 * Handles a click/move on the attribution
178 * @param p The point in the view
179 * @param click true if it was a click, false for hover
180 * @return if the attribution handled the event
181 */
182 public boolean handleAttribution(Point p, boolean click) {
183 return attribution.handleAttribution(p, click);
184 }
185
186 /**
187 * Draw the map.
188 */
189 @Override
190 public void paintComponent(Graphics g) {
191 super.paintComponent(g);
192 Graphics2D g2d = (Graphics2D) g;
193
194 // draw shaded area for non-downloaded region of current data set, but only if there *is* a current data set,
195 // and it has defined bounds. Routine is analogous to that in OsmDataLayer's paint routine (but just different
196 // enough to make sharing code impractical)
197 final DataSet ds = MainApplication.getLayerManager().getActiveDataSet();
198 if (ds != null && this.showDownloadAreaButtonModel.isSelected() && !ds.getDataSources().isEmpty()) {
199 // initialize area with current viewport
200 Rectangle b = this.getBounds();
201 // ensure we comfortably cover full area
202 b.grow(100, 100);
203 Path2D p = new Path2D.Float();
204
205 // combine successively downloaded areas after converting to screen-space
206 for (Bounds bounds : ds.getDataSourceBounds()) {
207 if (bounds.isCollapsed()) {
208 continue;
209 }
210 Rectangle r = new Rectangle(this.getMapPosition(bounds.getMinLat(), bounds.getMinLon(), false));
211 r.add(this.getMapPosition(bounds.getMaxLat(), bounds.getMaxLon(), false));
212 p.append(r, false);
213 }
214 // subtract combined areas
215 Area a = new Area(b);
216 a.subtract(new Area(p));
217
218 // paint remainder
219 g2d.setPaint(new Color(0, 0, 0, 32));
220 g2d.fill(a);
221 }
222
223 // draw selection rectangle
224 if (iSelectionRectStart != null && iSelectionRectEnd != null) {
225 Rectangle box = new Rectangle(getMapPosition(iSelectionRectStart, false));
226 box.add(getMapPosition(iSelectionRectEnd, false));
227
228 g.setColor(new Color(0.9f, 0.7f, 0.7f, 0.6f));
229 g.fillRect(box.x, box.y, box.width, box.height);
230
231 g.setColor(Color.BLACK);
232 g.drawRect(box.x, box.y, box.width, box.height);
233 }
234 }
235
236 @Override
237 public void activeOrEditLayerChanged(MainLayerManager.ActiveLayerChangeEvent e) {
238 this.repaint();
239 }
240
241 @Override
242 public void stateChanged(ChangeEvent e) {
243 // fired for the stateChanged event of this.showDownloadAreaButtonModel
244 PROP_SHOWDLAREA.put(this.showDownloadAreaButtonModel.isSelected());
245 this.repaint();
246 }
247
248 /**
249 * Handles a {@link SlippyMapController#mouseMoved} event
250 * @param point The point in the view
251 */
252 public void handleMouseMoved(Point point) {
253 final ICoordinate coordinate = getPosition(point);
254 final LatLon latLon = new LatLon(coordinate.getLat(), coordinate.getLon());
255 firePropertyChange(CURSOR_COORDINATE_PROP, null, latLon);
256 }
257
258 /**
259 * Callback for the OsmMapControl. (Re-)Sets the start and end point of the selection rectangle.
260 *
261 * @param aStart selection start
262 * @param aEnd selection end
263 */
264 public void setSelection(Point aStart, Point aEnd) {
265 if (aStart == null || aEnd == null || aStart.x == aEnd.x || aStart.y == aEnd.y)
266 return;
267
268 Point pMax = new Point(Math.max(aEnd.x, aStart.x), Math.max(aEnd.y, aStart.y));
269 Point pMin = new Point(Math.min(aEnd.x, aStart.x), Math.min(aEnd.y, aStart.y));
270
271 iSelectionRectStart = getPosition(pMin);
272 iSelectionRectEnd = getPosition(pMax);
273
274 Bounds b = new Bounds(
275 new LatLon(
276 Math.min(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
277 LatLon.toIntervalLon(Math.min(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon()))
278 ),
279 new LatLon(
280 Math.max(iSelectionRectStart.getLat(), iSelectionRectEnd.getLat()),
281 LatLon.toIntervalLon(Math.max(iSelectionRectStart.getLon(), iSelectionRectEnd.getLon())))
282 );
283 Bounds oldValue = this.bbox;
284 this.bbox = b;
285 repaint();
286 firePropertyChange(BBOX_PROP, oldValue, this.bbox);
287 }
288
289 /**
290 * Performs resizing of the DownloadDialog in order to enlarge or shrink the
291 * map.
292 */
293 public void resizeSlippyMap() {
294 boolean large = iSizeButton.isEnlarged();
295 firePropertyChange(RESIZE_PROP, !large, large);
296 }
297
298 /**
299 * Sets the active tile source
300 * @param tileSource The active tile source
301 */
302 public void toggleMapSource(TileSource tileSource) {
303 this.tileController.setTileCache(new MemoryTileCache());
304 this.setTileSource(tileSource);
305 PROP_MAPSTYLE.put(tileSource.getName()); // TODO Is name really unique?
306
307 // we need to refresh the tile sources in case the deselected source should no longer be present
308 // (and only remained there because its removal was deferred while the source was still the
309 // selected one). this should also have the effect of propagating the new selection to the
310 // iSourceButton & menu: it attempts to re-select the current source when rebuilding its menu.
311 this.refreshTileSources();
312 }
313
314 @Override
315 public Bounds getBoundingBox() {
316 return bbox;
317 }
318
319 /**
320 * Sets the current bounding box in this bbox chooser without
321 * emitting a property change event.
322 *
323 * @param bbox the bounding box. null to reset the bounding box
324 */
325 @Override
326 public void setBoundingBox(Bounds bbox) {
327 if (bbox == null || (bbox.getMinLat() == 0 && bbox.getMinLon() == 0
328 && bbox.getMaxLat() == 0 && bbox.getMaxLon() == 0)) {
329 this.bbox = null;
330 iSelectionRectStart = null;
331 iSelectionRectEnd = null;
332 repaint();
333 return;
334 }
335
336 this.bbox = bbox;
337 iSelectionRectStart = new Coordinate(bbox.getMinLat(), bbox.getMinLon());
338 iSelectionRectEnd = new Coordinate(bbox.getMaxLat(), bbox.getMaxLon());
339
340 // calc the screen coordinates for the new selection rectangle
341 MapMarkerDot min = new MapMarkerDot(bbox.getMinLat(), bbox.getMinLon());
342 MapMarkerDot max = new MapMarkerDot(bbox.getMaxLat(), bbox.getMaxLon());
343
344 List<MapMarker> marker = new ArrayList<>(2);
345 marker.add(min);
346 marker.add(max);
347 setMapMarkerList(marker);
348 setDisplayToFitMapMarkers();
349 zoomOut();
350 repaint();
351 }
352
353 /**
354 * Enables or disables painting of the shrink/enlarge button
355 *
356 * @param visible {@code true} to enable painting of the shrink/enlarge button
357 */
358 public void setSizeButtonVisible(boolean visible) {
359 iSizeButton.setVisible(visible);
360 }
361
362 /**
363 * Refreshes the tile sources
364 * @since 6364
365 */
366 public final void refreshTileSources() {
367 final LinkedHashMap<String, TileSource> newTileSources = getAllTileSources();
368 final TileSource currentTileSource = this.getTileController().getTileSource();
369
370 // re-add the currently active TileSource to prevent inconsistent display of menu
371 newTileSources.putIfAbsent(currentTileSource.getId(), currentTileSource);
372
373 this.iSourceButton.setSources(new ArrayList<>(newTileSources.values()));
374 }
375
376 @Override
377 public void layerAdded(LayerAddEvent e) {
378 if (e.getAddedLayer() instanceof ImageryLayer) {
379 this.refreshTileSources();
380 }
381 }
382
383 @Override
384 public void layerRemoving(LayerRemoveEvent e) {
385 if (e.getRemovedLayer() instanceof ImageryLayer) {
386 this.refreshTileSources();
387 }
388 }
389
390 @Override
391 public void layerOrderChanged(LayerOrderChangeEvent e) {
392 // Do nothing
393 }
394
395 /**
396 * Returns the currently visible map area
397 * @return the currently visible map area
398 */
399 public Bounds getVisibleMapArea() {
400 final ICoordinate topLeft = getPosition(0, 0);
401 final ICoordinate bottomRight = getPosition(getWidth(), getHeight());
402 final Bounds bounds = new Bounds(topLeft.getLat(), topLeft.getLon(), false);
403 bounds.extend(bottomRight.getLat(), bottomRight.getLon());
404 return bounds;
405 }
406}
Note: See TracBrowser for help on using the repository browser.