source: josm/trunk/src/org/openstreetmap/josm/gui/download/OSMDownloadSource.java

Last change on this file was 19050, checked in by taylor.smock, 3 days ago

Revert most var changes from r19048, fix most new compile warnings and checkstyle issues

Also, document why various ErrorProne checks were originally disabled and fix
generic SonarLint issues.

File size: 18.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.download;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Dimension;
9import java.awt.FlowLayout;
10import java.awt.Font;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.lang.reflect.InvocationTargetException;
14import java.util.ArrayList;
15import java.util.Collection;
16import java.util.Collections;
17import java.util.List;
18import java.util.concurrent.ExecutionException;
19import java.util.concurrent.Future;
20
21import javax.swing.Box;
22import javax.swing.Icon;
23import javax.swing.JCheckBox;
24import javax.swing.JLabel;
25import javax.swing.JOptionPane;
26import javax.swing.JPanel;
27import javax.swing.event.ChangeListener;
28
29import org.openstreetmap.josm.actions.downloadtasks.AbstractDownloadTask;
30import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
31import org.openstreetmap.josm.actions.downloadtasks.DownloadNotesTask;
32import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
33import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
34import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
35import org.openstreetmap.josm.data.Bounds;
36import org.openstreetmap.josm.data.ProjectionBounds;
37import org.openstreetmap.josm.data.ViewportData;
38import org.openstreetmap.josm.data.gpx.GpxData;
39import org.openstreetmap.josm.data.osm.DataSet;
40import org.openstreetmap.josm.data.osm.NoteData;
41import org.openstreetmap.josm.data.preferences.BooleanProperty;
42import org.openstreetmap.josm.gui.MainApplication;
43import org.openstreetmap.josm.gui.MapFrame;
44import org.openstreetmap.josm.gui.util.GuiHelper;
45import org.openstreetmap.josm.spi.preferences.Config;
46import org.openstreetmap.josm.tools.GBC;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.openstreetmap.josm.tools.Logging;
49import org.openstreetmap.josm.tools.Pair;
50
51/**
52 * Class defines the way data is fetched from the OSM server.
53 * @since 12652
54 */
55public class OSMDownloadSource implements DownloadSource<List<IDownloadSourceType>> {
56 /**
57 * The simple name for the {@link OSMDownloadSourcePanel}
58 * @since 12706
59 */
60 public static final String SIMPLE_NAME = "osmdownloadpanel";
61
62 /** The possible methods to get data */
63 static final List<IDownloadSourceType> DOWNLOAD_SOURCES = new ArrayList<>();
64 static {
65 // Order is important (determines button order, and what gets zoomed to)
66 DOWNLOAD_SOURCES.add(new OsmDataDownloadType());
67 DOWNLOAD_SOURCES.add(new GpsDataDownloadType());
68 DOWNLOAD_SOURCES.add(new NotesDataDownloadType());
69 }
70
71 @Override
72 public AbstractDownloadSourcePanel<List<IDownloadSourceType>> createPanel(DownloadDialog dialog) {
73 return new OSMDownloadSourcePanel(this, dialog);
74 }
75
76 @Override
77 public void doDownload(List<IDownloadSourceType> data, DownloadSettings settings) {
78 Bounds bbox = settings.getDownloadBounds()
79 .orElseThrow(() -> new IllegalArgumentException("OSM downloads requires bounds"));
80 boolean zoom = settings.zoomToData();
81 boolean newLayer = settings.asNewLayer();
82 final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
83 IDownloadSourceType zoomTask = zoom ? data.stream().findFirst().orElse(null) : null;
84 data.stream().filter(IDownloadSourceType::isEnabled).forEach(type -> {
85 try {
86 AbstractDownloadTask<?> task = type.getDownloadClass().getDeclaredConstructor().newInstance();
87 task.setZoomAfterDownload(type.equals(zoomTask));
88 Future<?> future = task.download(new DownloadParams().withNewLayer(newLayer), bbox, null);
89 MainApplication.worker.submit(new PostDownloadHandler(task, future));
90 if (zoom) {
91 tasks.add(new Pair<>(task, future));
92 }
93 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
94 | InvocationTargetException | NoSuchMethodException | SecurityException e) {
95 Logging.error(e);
96 }
97 });
98
99 if (zoom && tasks.size() > 1) {
100 MainApplication.worker.submit(() -> {
101 ProjectionBounds bounds = null;
102 // Wait for completion of download jobs
103 for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
104 try {
105 p.b.get();
106 ProjectionBounds b = p.a.getDownloadProjectionBounds();
107 if (bounds == null) {
108 bounds = b;
109 } else if (b != null) {
110 bounds.extend(b);
111 }
112 } catch (InterruptedException | ExecutionException ex) {
113 Logging.warn(ex);
114 }
115 }
116 MapFrame map = MainApplication.getMap();
117 // Zoom to the larger download bounds
118 if (map != null && bounds != null) {
119 final ProjectionBounds pb = bounds;
120 GuiHelper.runInEDTAndWait(() -> map.mapView.zoomTo(new ViewportData(pb)));
121 }
122 });
123 }
124 }
125
126 @Override
127 public String getLabel() {
128 return tr("Download from OSM");
129 }
130
131 @Override
132 public boolean onlyExpert() {
133 return false;
134 }
135
136 /**
137 * Returns the possible downloads that JOSM can make in the default Download screen.
138 * @return The possible downloads that JOSM can make in the default Download screen
139 * @since 16503
140 */
141 public static List<IDownloadSourceType> getDownloadTypes() {
142 return Collections.unmodifiableList(DOWNLOAD_SOURCES);
143 }
144
145 /**
146 * Get the instance of a data download type
147 *
148 * @param <T> The type to get
149 * @param typeClazz The class of the type
150 * @return The type instance
151 * @since 16503
152 */
153 public static <T extends IDownloadSourceType> T getDownloadType(Class<T> typeClazz) {
154 return DOWNLOAD_SOURCES.stream().filter(typeClazz::isInstance).map(typeClazz::cast).findFirst().orElse(null);
155 }
156
157 /**
158 * Removes a download source type.
159 * @param type The IDownloadSourceType object to remove
160 * @return {@code true} if this download types contained the specified object
161 * @since 16503
162 */
163 public static boolean removeDownloadType(IDownloadSourceType type) {
164 if (type instanceof OsmDataDownloadType || type instanceof GpsDataDownloadType || type instanceof NotesDataDownloadType) {
165 throw new IllegalArgumentException(type.getClass().getName());
166 }
167 return DOWNLOAD_SOURCES.remove(type);
168 }
169
170 /**
171 * Add a download type to the default JOSM download window
172 *
173 * @param type The initialized type to download
174 * @return {@code true} (as specified by {@link Collection#add}), but it also returns false if the class already has an instance in the list
175 * @since 16503
176 */
177 public static boolean addDownloadType(IDownloadSourceType type) {
178 if (type instanceof OsmDataDownloadType || type instanceof GpsDataDownloadType || type instanceof NotesDataDownloadType) {
179 throw new IllegalArgumentException(type.getClass().getName());
180 } else if (getDownloadType(type.getClass()) != null) {
181 return false;
182 }
183 return DOWNLOAD_SOURCES.add(type);
184 }
185
186 /**
187 * The GUI representation of the OSM download source.
188 * @since 12652
189 */
190 public static class OSMDownloadSourcePanel extends AbstractDownloadSourcePanel<List<IDownloadSourceType>> {
191 private final JLabel sizeCheck = new JLabel();
192
193 /** This is used to keep track of the components for download sources, and to dynamically update/remove them */
194 private final JPanel downloadSourcesPanel;
195
196 private final ChangeListener checkboxChangeListener;
197
198 /**
199 * Label used in front of data types available for download. Made public for reuse in other download dialogs.
200 * @since 16155
201 */
202 public static final String DATA_SOURCES_AND_TYPES = marktr("Data Sources and Types:");
203
204 /**
205 * Creates a new {@link OSMDownloadSourcePanel}.
206 * @param dialog the parent download dialog, as {@code DownloadDialog.getInstance()} might not be initialized yet
207 * @param ds The osm download source the panel is for.
208 * @since 12900
209 */
210 public OSMDownloadSourcePanel(OSMDownloadSource ds, DownloadDialog dialog) {
211 super(ds);
212 setLayout(new GridBagLayout());
213
214 // size check depends on selected data source
215 checkboxChangeListener = e -> {
216 rememberSettings();
217 dialog.getSelectedDownloadArea().ifPresent(OSMDownloadSourcePanel.this::boundingBoxChanged);
218 };
219
220 downloadSourcesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
221 add(downloadSourcesPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
222 updateSources();
223
224 sizeCheck.setFont(sizeCheck.getFont().deriveFont(Font.PLAIN));
225 JPanel sizeCheckPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
226 sizeCheckPanel.add(sizeCheck);
227 add(sizeCheckPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
228
229 setMinimumSize(new Dimension(450, 115));
230 }
231
232 /**
233 * Update the source list for downloading data
234 */
235 protected void updateSources() {
236 downloadSourcesPanel.removeAll();
237 downloadSourcesPanel.add(new JLabel(tr(DATA_SOURCES_AND_TYPES)));
238 DOWNLOAD_SOURCES.forEach(obj -> {
239 final Icon icon = obj.getIcon();
240 if (icon != null) {
241 downloadSourcesPanel.add(Box.createHorizontalStrut(6));
242 downloadSourcesPanel.add(new JLabel(icon));
243 }
244 downloadSourcesPanel.add(obj.getCheckBox(checkboxChangeListener));
245 });
246 }
247
248 @Override
249 public List<IDownloadSourceType> getData() {
250 return DOWNLOAD_SOURCES;
251 }
252
253 @Override
254 public void rememberSettings() {
255 DOWNLOAD_SOURCES.forEach(type -> type.getBooleanProperty().put(type.getCheckBox().isSelected()));
256 }
257
258 @Override
259 public void restoreSettings() {
260 updateSources();
261 DOWNLOAD_SOURCES.forEach(type -> type.getCheckBox().setSelected(type.isEnabled()));
262 }
263
264 @Override
265 public void setVisible(boolean aFlag) {
266 super.setVisible(aFlag);
267 updateSources();
268 }
269
270 @Override
271 public boolean checkDownload(DownloadSettings settings) {
272 /*
273 * It is mandatory to specify the area to download from OSM.
274 */
275 if (!settings.getDownloadBounds().isPresent()) {
276 JOptionPane.showMessageDialog(
277 this.getParent(),
278 tr("Please select a download area first."),
279 tr("Error"),
280 JOptionPane.ERROR_MESSAGE
281 );
282
283 return false;
284 }
285
286 final boolean slippyMapShowsDownloadBounds = settings.getSlippyMapBounds()
287 .map(b -> b.intersects(settings.getDownloadBounds().get()))
288 .orElse(true);
289 if (!slippyMapShowsDownloadBounds) {
290 final int confirmation = JOptionPane.showConfirmDialog(
291 this.getParent(),
292 tr("The slippy map no longer shows the selected download bounds. Continue?"),
293 tr("Confirmation"),
294 JOptionPane.OK_CANCEL_OPTION,
295 JOptionPane.QUESTION_MESSAGE
296 );
297 if (confirmation != JOptionPane.OK_OPTION) {
298 return false;
299 }
300 }
301
302 /*
303 * Checks if the user selected the type of data to download. At least one the following
304 * must be chosen : raw osm data, gpx data, notes.
305 * If none of those are selected, then the corresponding dialog is shown to inform the user.
306 */
307 if (DOWNLOAD_SOURCES.stream().noneMatch(IDownloadSourceType::isEnabled)) {
308 JOptionPane.showMessageDialog(
309 this.getParent(),
310 tr("Please select at least one download source."),
311 tr("Error"),
312 JOptionPane.ERROR_MESSAGE
313 );
314
315 return false;
316 }
317
318 this.rememberSettings();
319
320 return true;
321 }
322
323 @Override
324 public Icon getIcon() {
325 return ImageProvider.get("download");
326 }
327
328 @Override
329 public void boundingBoxChanged(Bounds bbox) {
330 if (bbox == null) {
331 sizeCheck.setText(tr("No area selected yet"));
332 sizeCheck.setForeground(Color.darkGray);
333 return;
334 }
335
336 displaySizeCheckResult(DOWNLOAD_SOURCES.stream()
337 .filter(IDownloadSourceType::isEnabled)
338 .anyMatch(type -> type.isDownloadAreaTooLarge(bbox)));
339 }
340
341 @Override
342 public String getSimpleName() {
343 return SIMPLE_NAME;
344 }
345
346 private void displaySizeCheckResult(boolean isAreaTooLarge) {
347 if (isAreaTooLarge) {
348 sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
349 sizeCheck.setForeground(Color.red);
350 } else {
351 sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
352 sizeCheck.setForeground(Color.darkGray);
353 }
354 }
355 }
356
357 private static final class OsmDataDownloadType implements IDownloadSourceType {
358 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.data", true);
359 JCheckBox cbDownloadOsmData;
360
361 @Override
362 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
363 if (cbDownloadOsmData == null) {
364 cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
365 cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
366 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
367 }
368 if (checkboxChangeListener != null) {
369 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
370 }
371 return cbDownloadOsmData;
372 }
373
374 @Override
375 public Icon getIcon() {
376 return ImageProvider.get("layer/osmdata_small", ImageProvider.ImageSizes.SMALLICON);
377 }
378
379 @Override
380 public Class<? extends AbstractDownloadTask<DataSet>> getDownloadClass() {
381 return DownloadOsmTask.class;
382 }
383
384 @Override
385 public BooleanProperty getBooleanProperty() {
386 return IS_ENABLED;
387 }
388
389 @Override
390 public boolean isDownloadAreaTooLarge(Bounds bound) {
391 // see max_request_area in
392 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/settings.yml
393 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area", 0.25);
394 }
395 }
396
397 private static final class GpsDataDownloadType implements IDownloadSourceType {
398 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.gps", false);
399 private JCheckBox cbDownloadGpxData;
400
401 @Override
402 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
403 if (cbDownloadGpxData == null) {
404 cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
405 cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
406 }
407 if (checkboxChangeListener != null) {
408 cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
409 }
410
411 return cbDownloadGpxData;
412 }
413
414 @Override
415 public Icon getIcon() {
416 return ImageProvider.get("layer/gpx_small", ImageProvider.ImageSizes.SMALLICON);
417 }
418
419 @Override
420 public Class<? extends AbstractDownloadTask<GpxData>> getDownloadClass() {
421 return DownloadGpsTask.class;
422 }
423
424 @Override
425 public BooleanProperty getBooleanProperty() {
426 return IS_ENABLED;
427 }
428
429 @Override
430 public boolean isDownloadAreaTooLarge(Bounds bound) {
431 return false;
432 }
433 }
434
435 private static final class NotesDataDownloadType implements IDownloadSourceType {
436 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.notes", false);
437 private JCheckBox cbDownloadNotes;
438
439 @Override
440 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
441 if (cbDownloadNotes == null) {
442 cbDownloadNotes = new JCheckBox(tr("Notes"));
443 cbDownloadNotes.setToolTipText(tr("Select to download notes in the selected download area."));
444 }
445 if (checkboxChangeListener != null) {
446 cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
447 }
448
449 return cbDownloadNotes;
450 }
451
452 @Override
453 public Icon getIcon() {
454 return ImageProvider.get("dialogs/notes/note_open", ImageProvider.ImageSizes.SMALLICON);
455 }
456
457 @Override
458 public Class<? extends AbstractDownloadTask<NoteData>> getDownloadClass() {
459 return DownloadNotesTask.class;
460 }
461
462 @Override
463 public BooleanProperty getBooleanProperty() {
464 return IS_ENABLED;
465 }
466
467 @Override
468 public boolean isDownloadAreaTooLarge(Bounds bound) {
469 // see max_note_request_area in
470 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/settings.yml
471 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area-notes", 25);
472 }
473 }
474}
Note: See TracBrowser for help on using the repository browser.