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

Last change on this file since 18728 was 18728, checked in by taylor.smock, 13 months ago

Fix #21886: Download Dialog incorrectly reports note area rejection (patch by gaben, modified)

The modifications are as follows:

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