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

Last change on this file since 16914 was 16914, checked in by simon04, 4 years ago

see #19699 - OSMDownloadSource: left align, improve spacing

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