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

Last change on this file was 18871, checked in by taylor.smock, 6 months ago

See #23218: Use newer error_prone versions when compiling on Java 11+

error_prone 2.11 dropped support for compiling with Java 8, although it still
supports compiling for Java 8. The "major" new check for us is NotJavadoc since
we used /** in quite a few places which were not javadoc.

Other "new" checks that are of interest:

  • AlreadyChecked: if (foo) { doFoo(); } else if (!foo) { doBar(); }
  • UnnecessaryStringBuilder: Avoid StringBuilder (Java converts + to StringBuilder behind-the-scenes, but may also do something else if it performs better)
  • NonApiType: Avoid specific interface types in function definitions
  • NamedLikeContextualKeyword: Avoid using restricted names for classes and methods
  • UnusedMethod: Unused private methods should be removed

This fixes most of the new error_prone issues and some SonarLint issues.

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