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

Last change on this file was 19269, checked in by stoecker, 7 months ago

silence PMD - some of these rules are bullshit - explicit initialization is always better than default ones even if it costs a little space

File size: 18.4 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 ex) {
113 Thread.currentThread().interrupt();
114 Logging.warn(ex);
115 } catch (ExecutionException ex) {
116 Logging.warn(ex);
117 }
118 }
119 MapFrame map = MainApplication.getMap();
120 // Zoom to the larger download bounds
121 if (map != null && bounds != null) {
122 final ProjectionBounds pb = bounds;
123 GuiHelper.runInEDTAndWait(() -> map.mapView.zoomTo(new ViewportData(pb)));
124 }
125 });
126 }
127 }
128
129 @Override
130 public String getLabel() {
131 return tr("Download from OSM");
132 }
133
134 @Override
135 public boolean onlyExpert() {
136 return false;
137 }
138
139 /**
140 * Returns the possible downloads that JOSM can make in the default Download screen.
141 * @return The possible downloads that JOSM can make in the default Download screen
142 * @since 16503
143 */
144 public static List<IDownloadSourceType> getDownloadTypes() {
145 return Collections.unmodifiableList(DOWNLOAD_SOURCES);
146 }
147
148 /**
149 * Get the instance of a data download type
150 *
151 * @param <T> The type to get
152 * @param typeClazz The class of the type
153 * @return The type instance
154 * @since 16503
155 */
156 public static <T extends IDownloadSourceType> T getDownloadType(Class<T> typeClazz) {
157 return DOWNLOAD_SOURCES.stream().filter(typeClazz::isInstance).map(typeClazz::cast).findFirst().orElse(null);
158 }
159
160 /**
161 * Removes a download source type.
162 * @param type The IDownloadSourceType object to remove
163 * @return {@code true} if this download types contained the specified object
164 * @since 16503
165 */
166 public static boolean removeDownloadType(IDownloadSourceType type) {
167 if (type instanceof OsmDataDownloadType || type instanceof GpsDataDownloadType || type instanceof NotesDataDownloadType) {
168 throw new IllegalArgumentException(type.getClass().getName());
169 }
170 return DOWNLOAD_SOURCES.remove(type);
171 }
172
173 /**
174 * Add a download type to the default JOSM download window
175 *
176 * @param type The initialized type to download
177 * @return {@code true} (as specified by {@link Collection#add}), but it also returns false if the class already has an instance in the list
178 * @since 16503
179 */
180 public static boolean addDownloadType(IDownloadSourceType type) {
181 if (type instanceof OsmDataDownloadType || type instanceof GpsDataDownloadType || type instanceof NotesDataDownloadType) {
182 throw new IllegalArgumentException(type.getClass().getName());
183 } else if (getDownloadType(type.getClass()) != null) {
184 return false;
185 }
186 return DOWNLOAD_SOURCES.add(type);
187 }
188
189 /**
190 * The GUI representation of the OSM download source.
191 * @since 12652
192 */
193 public static class OSMDownloadSourcePanel extends AbstractDownloadSourcePanel<List<IDownloadSourceType>> {
194 private final JLabel sizeCheck = new JLabel();
195
196 /** This is used to keep track of the components for download sources, and to dynamically update/remove them */
197 private final JPanel downloadSourcesPanel;
198
199 private boolean inRestore /* = false */;
200
201 private final ChangeListener checkboxChangeListener;
202
203 /**
204 * Label used in front of data types available for download. Made public for reuse in other download dialogs.
205 * @since 16155
206 */
207 public static final String DATA_SOURCES_AND_TYPES = marktr("Data Sources and Types:");
208
209 /**
210 * Creates a new {@link OSMDownloadSourcePanel}.
211 * @param dialog the parent download dialog, as {@code DownloadDialog.getInstance()} might not be initialized yet
212 * @param ds The osm download source the panel is for.
213 * @since 12900
214 */
215 public OSMDownloadSourcePanel(OSMDownloadSource ds, DownloadDialog dialog) {
216 super(ds);
217 setLayout(new GridBagLayout());
218
219 // size check depends on selected data source
220 checkboxChangeListener = e -> {
221 rememberSettings();
222 dialog.getSelectedDownloadArea().ifPresent(this::boundingBoxChanged);
223 };
224
225 downloadSourcesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
226 add(downloadSourcesPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
227 updateSources();
228
229 sizeCheck.setFont(sizeCheck.getFont().deriveFont(Font.PLAIN));
230 JPanel sizeCheckPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
231 sizeCheckPanel.add(sizeCheck);
232 add(sizeCheckPanel, GBC.eol().fill(GridBagConstraints.HORIZONTAL));
233
234 setMinimumSize(new Dimension(450, 115));
235 }
236
237 /**
238 * Update the source list for downloading data
239 */
240 protected void updateSources() {
241 downloadSourcesPanel.removeAll();
242 downloadSourcesPanel.add(new JLabel(tr(DATA_SOURCES_AND_TYPES)));
243 DOWNLOAD_SOURCES.forEach(obj -> {
244 final Icon icon = obj.getIcon();
245 if (icon != null) {
246 downloadSourcesPanel.add(Box.createHorizontalStrut(6));
247 downloadSourcesPanel.add(new JLabel(icon));
248 }
249 downloadSourcesPanel.add(obj.getCheckBox(checkboxChangeListener));
250 });
251 }
252
253 @Override
254 public List<IDownloadSourceType> getData() {
255 return DOWNLOAD_SOURCES;
256 }
257
258 @Override
259 public void rememberSettings() {
260 if (!inRestore)
261 DOWNLOAD_SOURCES.forEach(type -> type.getBooleanProperty().put(type.getCheckBox().isSelected()));
262 }
263
264 @Override
265 public void restoreSettings() {
266 inRestore = true;
267 updateSources();
268 DOWNLOAD_SOURCES.forEach(type -> type.getCheckBox().setSelected(type.isEnabled()));
269 inRestore = false;
270 }
271
272 @Override
273 public void setVisible(boolean aFlag) {
274 super.setVisible(aFlag);
275 updateSources();
276 }
277
278 @Override
279 public boolean checkDownload(DownloadSettings settings) {
280 /*
281 * It is mandatory to specify the area to download from OSM.
282 */
283 if (settings.getDownloadBounds().isEmpty()) {
284 JOptionPane.showMessageDialog(
285 this.getParent(),
286 tr("Please select a download area first."),
287 tr("Error"),
288 JOptionPane.ERROR_MESSAGE
289 );
290
291 return false;
292 }
293
294 final boolean slippyMapShowsDownloadBounds = settings.getSlippyMapBounds()
295 .map(b -> b.intersects(settings.getDownloadBounds().get()))
296 .orElse(true);
297 if (!slippyMapShowsDownloadBounds) {
298 final int confirmation = JOptionPane.showConfirmDialog(
299 this.getParent(),
300 tr("The slippy map no longer shows the selected download bounds. Continue?"),
301 tr("Confirmation"),
302 JOptionPane.OK_CANCEL_OPTION,
303 JOptionPane.QUESTION_MESSAGE
304 );
305 if (confirmation != JOptionPane.OK_OPTION) {
306 return false;
307 }
308 }
309
310 /*
311 * Checks if the user selected the type of data to download. At least one the following
312 * must be chosen : raw osm data, gpx data, notes.
313 * If none of those are selected, then the corresponding dialog is shown to inform the user.
314 */
315 if (DOWNLOAD_SOURCES.stream().noneMatch(IDownloadSourceType::isEnabled)) {
316 JOptionPane.showMessageDialog(
317 this.getParent(),
318 tr("Please select at least one download source."),
319 tr("Error"),
320 JOptionPane.ERROR_MESSAGE
321 );
322
323 return false;
324 }
325
326 this.rememberSettings();
327
328 return true;
329 }
330
331 @Override
332 public Icon getIcon() {
333 return ImageProvider.get("download");
334 }
335
336 @Override
337 public void boundingBoxChanged(Bounds bbox) {
338 if (bbox == null) {
339 sizeCheck.setText(tr("No area selected yet"));
340 sizeCheck.setForeground(Color.darkGray);
341 return;
342 }
343
344 displaySizeCheckResult(DOWNLOAD_SOURCES.stream()
345 .filter(IDownloadSourceType::isEnabled)
346 .anyMatch(type -> type.isDownloadAreaTooLarge(bbox)));
347 }
348
349 @Override
350 public String getSimpleName() {
351 return SIMPLE_NAME;
352 }
353
354 private void displaySizeCheckResult(boolean isAreaTooLarge) {
355 if (isAreaTooLarge) {
356 sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
357 sizeCheck.setForeground(Color.red);
358 } else {
359 sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
360 sizeCheck.setForeground(Color.darkGray);
361 }
362 }
363 }
364
365 private static final class OsmDataDownloadType implements IDownloadSourceType {
366 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.data", true);
367 JCheckBox cbDownloadOsmData;
368
369 @Override
370 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
371 if (cbDownloadOsmData == null) {
372 cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
373 cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
374 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
375 }
376 if (checkboxChangeListener != null) {
377 cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
378 }
379 return cbDownloadOsmData;
380 }
381
382 @Override
383 public Icon getIcon() {
384 return ImageProvider.get("layer/osmdata_small", ImageProvider.ImageSizes.SMALLICON);
385 }
386
387 @Override
388 public Class<? extends AbstractDownloadTask<DataSet>> getDownloadClass() {
389 return DownloadOsmTask.class;
390 }
391
392 @Override
393 public BooleanProperty getBooleanProperty() {
394 return IS_ENABLED;
395 }
396
397 @Override
398 public boolean isDownloadAreaTooLarge(Bounds bound) {
399 // see max_request_area in
400 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/settings.yml
401 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area", 0.25);
402 }
403 }
404
405 private static final class GpsDataDownloadType implements IDownloadSourceType {
406 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.gps", false);
407 private JCheckBox cbDownloadGpxData;
408
409 @Override
410 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
411 if (cbDownloadGpxData == null) {
412 cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
413 cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
414 }
415 if (checkboxChangeListener != null) {
416 cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
417 }
418
419 return cbDownloadGpxData;
420 }
421
422 @Override
423 public Icon getIcon() {
424 return ImageProvider.get("layer/gpx_small", ImageProvider.ImageSizes.SMALLICON);
425 }
426
427 @Override
428 public Class<? extends AbstractDownloadTask<GpxData>> getDownloadClass() {
429 return DownloadGpsTask.class;
430 }
431
432 @Override
433 public BooleanProperty getBooleanProperty() {
434 return IS_ENABLED;
435 }
436
437 @Override
438 public boolean isDownloadAreaTooLarge(Bounds bound) {
439 return false;
440 }
441 }
442
443 private static final class NotesDataDownloadType implements IDownloadSourceType {
444 static final BooleanProperty IS_ENABLED = new BooleanProperty("download.osm.notes", false);
445 private JCheckBox cbDownloadNotes;
446
447 @Override
448 public JCheckBox getCheckBox(ChangeListener checkboxChangeListener) {
449 if (cbDownloadNotes == null) {
450 cbDownloadNotes = new JCheckBox(tr("Notes"));
451 cbDownloadNotes.setToolTipText(tr("Select to download notes in the selected download area."));
452 }
453 if (checkboxChangeListener != null) {
454 cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
455 }
456
457 return cbDownloadNotes;
458 }
459
460 @Override
461 public Icon getIcon() {
462 return ImageProvider.get("dialogs/notes/note_open", ImageProvider.ImageSizes.SMALLICON);
463 }
464
465 @Override
466 public Class<? extends AbstractDownloadTask<NoteData>> getDownloadClass() {
467 return DownloadNotesTask.class;
468 }
469
470 @Override
471 public BooleanProperty getBooleanProperty() {
472 return IS_ENABLED;
473 }
474
475 @Override
476 public boolean isDownloadAreaTooLarge(Bounds bound) {
477 // see max_note_request_area in
478 // https://github.com/openstreetmap/openstreetmap-website/blob/master/config/settings.yml
479 return bound.getArea() > Config.getPref().getDouble("osm-server.max-request-area-notes", 25);
480 }
481 }
482}
Note: See TracBrowser for help on using the repository browser.