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

Last change on this file since 16918 was 16918, checked in by Klumbumbus, 4 years ago

see #19699 - use correct icons for Notes and Zoom to download

File size: 20.3 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<AbstractDownloadTask<?>, Future<?>>(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 dialog.getSelectedDownloadArea().ifPresent(this::updateSizeCheck);
216
217 downloadSourcesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
218 add(downloadSourcesPanel, GBC.eol().fill(GBC.HORIZONTAL));
219 updateSources();
220
221 sizeCheck.setFont(sizeCheck.getFont().deriveFont(Font.PLAIN));
222 JPanel sizeCheckPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
223 sizeCheckPanel.add(sizeCheck);
224 add(sizeCheckPanel, GBC.eol().fill(GBC.HORIZONTAL));
225
226 setMinimumSize(new Dimension(450, 115));
227 }
228
229 /**
230 * Update the source list for downloading data
231 */
232 protected void updateSources() {
233 downloadSourcesPanel.removeAll();
234 downloadSourcesPanel.add(new JLabel(tr(DATA_SOURCES_AND_TYPES)));
235 DOWNLOAD_SOURCES.forEach(obj -> {
236 final Icon icon = obj.getIcon();
237 if (icon != null) {
238 downloadSourcesPanel.add(Box.createHorizontalStrut(6));
239 downloadSourcesPanel.add(new JLabel(icon));
240 }
241 downloadSourcesPanel.add(obj.getCheckBox(checkboxChangeListener));
242 });
243 }
244
245 @Override
246 public List<IDownloadSourceType> getData() {
247 return DOWNLOAD_SOURCES;
248 }
249
250 @Override
251 public void rememberSettings() {
252 DOWNLOAD_SOURCES.forEach(type -> type.getBooleanProperty().put(type.getCheckBox().isSelected()));
253 }
254
255 @Override
256 public void restoreSettings() {
257 updateSources();
258 DOWNLOAD_SOURCES.forEach(type -> type.getCheckBox().setSelected(type.isEnabled()));
259 }
260
261 @Override
262 public void setVisible(boolean aFlag) {
263 super.setVisible(aFlag);
264 updateSources();
265 }
266
267 @Override
268 public boolean checkDownload(DownloadSettings settings) {
269 /*
270 * It is mandatory to specify the area to download from OSM.
271 */
272 if (!settings.getDownloadBounds().isPresent()) {
273 JOptionPane.showMessageDialog(
274 this.getParent(),
275 tr("Please select a download area first."),
276 tr("Error"),
277 JOptionPane.ERROR_MESSAGE
278 );
279
280 return false;
281 }
282
283 final Boolean slippyMapShowsDownloadBounds = settings.getSlippyMapBounds()
284 .map(b -> b.intersects(settings.getDownloadBounds().get()))
285 .orElse(true);
286 if (!slippyMapShowsDownloadBounds) {
287 final int confirmation = JOptionPane.showConfirmDialog(
288 this.getParent(),
289 tr("The slippy map no longer shows the selected download bounds. Continue?"),
290 tr("Confirmation"),
291 JOptionPane.OK_CANCEL_OPTION,
292 JOptionPane.QUESTION_MESSAGE
293 );
294 if (confirmation != JOptionPane.OK_OPTION) {
295 return false;
296 }
297 }
298
299 /*
300 * Checks if the user selected the type of data to download. At least one the following
301 * must be chosen : raw osm data, gpx data, notes.
302 * If none of those are selected, then the corresponding dialog is shown to inform the user.
303 */
304 if (DOWNLOAD_SOURCES.stream().noneMatch(IDownloadSourceType::isEnabled)) {
305 JOptionPane.showMessageDialog(
306 this.getParent(),
307 tr("Please select at least one download source."),
308 tr("Error"),
309 JOptionPane.ERROR_MESSAGE
310 );
311
312 return false;
313 }
314
315 this.rememberSettings();
316
317 return true;
318 }
319
320 /**
321 * Replies true if the user selected to download OSM data
322 *
323 * @return true if the user selected to download OSM data
324 * @deprecated since 16503 -- use {@code getDownloadType(OsmDataDownloadType.class).getCheckBox().isSelected()}
325 */
326 @Deprecated
327 public boolean isDownloadOsmData() {
328 return getDownloadType(OsmDataDownloadType.class).getCheckBox().isSelected();
329 }
330
331 /**
332 * Replies true if the user selected to download GPX data
333 *
334 * @return true if the user selected to download GPX data
335 * @deprecated since 16503 -- use {@code getDownloadType(GpsDataDownloadType.class).getCheckBox().isSelected()}
336 */
337 @Deprecated
338 public boolean isDownloadGpxData() {
339 return getDownloadType(GpsDataDownloadType.class).getCheckBox().isSelected();
340 }
341
342 /**
343 * Replies true if user selected to download notes
344 *
345 * @return true if user selected to download notes
346 * @deprecated since 16503 -- use {@code getDownloadType(NotesDataDownloadType.class).getCheckBox().isSelected()}
347 */
348 @Deprecated
349 public boolean isDownloadNotes() {
350 return getDownloadType(NotesDataDownloadType.class).getCheckBox().isSelected();
351 }
352
353 @Override
354 public Icon getIcon() {
355 return ImageProvider.get("download");
356 }
357
358 @Override
359 public void boundingBoxChanged(Bounds bbox) {
360 updateSizeCheck(bbox);
361 }
362
363 @Override
364 public String getSimpleName() {
365 return SIMPLE_NAME;
366 }
367
368 private void updateSizeCheck(Bounds bbox) {
369 if (bbox == null) {
370 sizeCheck.setText(tr("No area selected yet"));
371 sizeCheck.setForeground(Color.darkGray);
372 return;
373 }
374
375 displaySizeCheckResult(DOWNLOAD_SOURCES.stream()
376 .anyMatch(type -> type.isDownloadAreaTooLarge(bbox)));
377 }
378
379 private void displaySizeCheckResult(boolean isAreaTooLarge) {
380 if (isAreaTooLarge) {
381 sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
382 sizeCheck.setForeground(Color.red);
383 } else {
384 sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
385 sizeCheck.setForeground(Color.darkGray);
386 }
387 }
388 }
389
390 /**
391 * Encapsulates data that is required to download from the OSM server.
392 */
393 static class OSMDownloadData {
394
395 private final List<IDownloadSourceType> downloadPossibilities;
396
397 /**
398 * Constructs a new {@code OSMDownloadData}.
399 * @param downloadPossibilities A list of DataDownloadTypes (instantiated, with
400 * options set)
401 */
402 OSMDownloadData(List<IDownloadSourceType> downloadPossibilities) {
403 this.downloadPossibilities = downloadPossibilities;
404 }
405
406 /**
407 * Returns the download possibilities.
408 * @return A list of DataDownloadTypes (instantiated, with options set)
409 */
410 public List<IDownloadSourceType> getDownloadPossibilities() {
411 return downloadPossibilities;
412 }
413 }
414
415 private static class OsmDataDownloadType implements IDownloadSourceType {
416 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.data", true);
417 JCheckBox cbDownloadOsmData;
418
419 @Override
420 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
421 if (cbDownloadOsmData == null) {
422 cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
423 cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
424 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
425 }
426 if (checkboxChangeListener != null) {
427 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
428 }
429 return cbDownloadOsmData;
430 }
431
432 @Override
433 public Icon getIcon() {
434 return ImageProvider.get("layer/osmdata_small", ImageProvider.ImageSizes.SMALLICON);
435 }
436
437 @Override
438 public Class<? extends AbstractDownloadTask<DataSet>> getDownloadClass() {
439 return DownloadOsmTask.class;
440 }
441
442 @Override
443 public BooleanProperty getBooleanProperty() {
444 return IS_ENABLED;
445 }
446
447 @Override
448 public boolean isDownloadAreaTooLarge(Bounds bound) {
449 // see max_request_area in
450 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
451 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area", 0.25);
452 }
453 }
454
455 private static class GpsDataDownloadType implements IDownloadSourceType {
456 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.gps", false);
457 private JCheckBox cbDownloadGpxData;
458
459 @Override
460 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
461 if (cbDownloadGpxData == null) {
462 cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
463 cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
464 }
465 if (checkboxChangeListener != null) {
466 cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
467 }
468
469 return cbDownloadGpxData;
470 }
471
472 @Override
473 public Icon getIcon() {
474 return ImageProvider.get("layer/gpx_small", ImageProvider.ImageSizes.SMALLICON);
475 }
476
477 @Override
478 public Class<? extends AbstractDownloadTask<GpxData>> getDownloadClass() {
479 return DownloadGpsTask.class;
480 }
481
482 @Override
483 public BooleanProperty getBooleanProperty() {
484 return IS_ENABLED;
485 }
486
487 @Override
488 public boolean isDownloadAreaTooLarge(Bounds bound) {
489 return false;
490 }
491 }
492
493 private static class NotesDataDownloadType implements IDownloadSourceType {
494 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.notes", false);
495 private JCheckBox cbDownloadNotes;
496
497 @Override
498 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
499 if (cbDownloadNotes == null) {
500 cbDownloadNotes = new JCheckBox(tr("Notes"));
501 cbDownloadNotes.setToolTipText(tr("Select to download notes in the selected download area."));
502 }
503 if (checkboxChangeListener != null) {
504 cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
505 }
506
507 return cbDownloadNotes;
508 }
509
510 @Override
511 public Icon getIcon() {
512 return ImageProvider.get("dialogs/notes/note_open", ImageProvider.ImageSizes.SMALLICON);
513 }
514
515 @Override
516 public Class<? extends AbstractDownloadTask<NoteData>> getDownloadClass() {
517 return DownloadNotesTask.class;
518 }
519
520 @Override
521 public BooleanProperty getBooleanProperty() {
522 return IS_ENABLED;
523 }
524
525 @Override
526 public boolean isDownloadAreaTooLarge(Bounds bound) {
527 // see max_note_request_area in
528 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
529 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area-notes", 25);
530 }
531 }
532}
Note: See TracBrowser for help on using the repository browser.