001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.download;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Color;
007import java.awt.Dimension;
008import java.awt.Font;
009import java.awt.GridBagLayout;
010import java.util.ArrayList;
011import java.util.List;
012import java.util.concurrent.ExecutionException;
013import java.util.concurrent.Future;
014
015import javax.swing.Icon;
016import javax.swing.JCheckBox;
017import javax.swing.JLabel;
018import javax.swing.JOptionPane;
019import javax.swing.event.ChangeListener;
020
021import org.openstreetmap.josm.actions.downloadtasks.AbstractDownloadTask;
022import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
023import org.openstreetmap.josm.actions.downloadtasks.DownloadNotesTask;
024import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
025import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
026import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
027import org.openstreetmap.josm.data.Bounds;
028import org.openstreetmap.josm.data.ProjectionBounds;
029import org.openstreetmap.josm.data.ViewportData;
030import org.openstreetmap.josm.data.preferences.BooleanProperty;
031import org.openstreetmap.josm.gui.MainApplication;
032import org.openstreetmap.josm.gui.MapFrame;
033import org.openstreetmap.josm.gui.util.GuiHelper;
034import org.openstreetmap.josm.spi.preferences.Config;
035import org.openstreetmap.josm.tools.GBC;
036import org.openstreetmap.josm.tools.ImageProvider;
037import org.openstreetmap.josm.tools.Logging;
038import org.openstreetmap.josm.tools.Pair;
039
040/**
041 * Class defines the way data is fetched from the OSM server.
042 * @since 12652
043 */
044public class OSMDownloadSource implements DownloadSource<OSMDownloadSource.OSMDownloadData> {
045    /**
046     * The simple name for the {@link OSMDownloadSourcePanel}
047     * @since 12706
048     */
049    public static final String SIMPLE_NAME = "osmdownloadpanel";
050
051    @Override
052    public AbstractDownloadSourcePanel<OSMDownloadData> createPanel(DownloadDialog dialog) {
053        return new OSMDownloadSourcePanel(this, dialog);
054    }
055
056    @Override
057    public void doDownload(OSMDownloadData data, DownloadSettings settings) {
058        Bounds bbox = settings.getDownloadBounds()
059                .orElseThrow(() -> new IllegalArgumentException("OSM downloads requires bounds"));
060        boolean zoom = settings.zoomToData();
061        boolean newLayer = settings.asNewLayer();
062        List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>();
063
064        if (data.isDownloadOSMData()) {
065            DownloadOsmTask task = new DownloadOsmTask();
066            task.setZoomAfterDownload(zoom && !data.isDownloadGPX() && !data.isDownloadNotes());
067            Future<?> future = task.download(new DownloadParams().withNewLayer(newLayer), bbox, null);
068            MainApplication.worker.submit(new PostDownloadHandler(task, future));
069            if (zoom) {
070                tasks.add(new Pair<>(task, future));
071            }
072        }
073
074        if (data.isDownloadGPX()) {
075            DownloadGpsTask task = new DownloadGpsTask();
076            task.setZoomAfterDownload(zoom && !data.isDownloadOSMData() && !data.isDownloadNotes());
077            Future<?> future = task.download(new DownloadParams().withNewLayer(newLayer), bbox, null);
078            MainApplication.worker.submit(new PostDownloadHandler(task, future));
079            if (zoom) {
080                tasks.add(new Pair<>(task, future));
081            }
082        }
083
084        if (data.isDownloadNotes()) {
085            DownloadNotesTask task = new DownloadNotesTask();
086            task.setZoomAfterDownload(zoom && !data.isDownloadOSMData() && !data.isDownloadGPX());
087            Future<?> future = task.download(new DownloadParams(), bbox, null);
088            MainApplication.worker.submit(new PostDownloadHandler(task, future));
089            if (zoom) {
090                tasks.add(new Pair<>(task, future));
091            }
092        }
093
094        if (zoom && tasks.size() > 1) {
095            MainApplication.worker.submit(() -> {
096                ProjectionBounds bounds = null;
097                // Wait for completion of download jobs
098                for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) {
099                    try {
100                        p.b.get();
101                        ProjectionBounds b = p.a.getDownloadProjectionBounds();
102                        if (bounds == null) {
103                            bounds = b;
104                        } else if (b != null) {
105                            bounds.extend(b);
106                        }
107                    } catch (InterruptedException | ExecutionException ex) {
108                        Logging.warn(ex);
109                    }
110                }
111                MapFrame map = MainApplication.getMap();
112                // Zoom to the larger download bounds
113                if (map != null && bounds != null) {
114                    final ProjectionBounds pb = bounds;
115                    GuiHelper.runInEDTAndWait(() -> map.mapView.zoomTo(new ViewportData(pb)));
116                }
117            });
118        }
119    }
120
121    @Override
122    public String getLabel() {
123        return tr("Download from OSM");
124    }
125
126    @Override
127    public boolean onlyExpert() {
128        return false;
129    }
130
131    /**
132     * The GUI representation of the OSM download source.
133     * @since 12652
134     */
135    public static class OSMDownloadSourcePanel extends AbstractDownloadSourcePanel<OSMDownloadData> {
136
137        private final JCheckBox cbDownloadOsmData;
138        private final JCheckBox cbDownloadGpxData;
139        private final JCheckBox cbDownloadNotes;
140        private final JLabel sizeCheck = new JLabel();
141
142        private static final BooleanProperty DOWNLOAD_OSM = new BooleanProperty("download.osm.data", true);
143        private static final BooleanProperty DOWNLOAD_GPS = new BooleanProperty("download.osm.gps", false);
144        private static final BooleanProperty DOWNLOAD_NOTES = new BooleanProperty("download.osm.notes", false);
145
146        /**
147         * Creates a new {@link OSMDownloadSourcePanel}.
148         * @param dialog the parent download dialog, as {@code DownloadDialog.getInstance()} might not be initialized yet
149         * @param ds The osm download source the panel is for.
150         * @since 12900
151         */
152        public OSMDownloadSourcePanel(OSMDownloadSource ds, DownloadDialog dialog) {
153            super(ds);
154            setLayout(new GridBagLayout());
155
156            // size check depends on selected data source
157            final ChangeListener checkboxChangeListener = e ->
158                    dialog.getSelectedDownloadArea().ifPresent(this::updateSizeCheck);
159
160            // adding the download tasks
161            add(new JLabel(tr("Data Sources and Types:")), GBC.std().insets(5, 5, 1, 5).anchor(GBC.CENTER));
162            cbDownloadOsmData = new JCheckBox(tr("OpenStreetMap data"), true);
163            cbDownloadOsmData.setToolTipText(tr("Select to download OSM data in the selected download area."));
164            cbDownloadOsmData.getModel().addChangeListener(checkboxChangeListener);
165
166            cbDownloadGpxData = new JCheckBox(tr("Raw GPS data"));
167            cbDownloadGpxData.setToolTipText(tr("Select to download GPS traces in the selected download area."));
168            cbDownloadGpxData.getModel().addChangeListener(checkboxChangeListener);
169
170            cbDownloadNotes = new JCheckBox(tr("Notes"));
171            cbDownloadNotes.setToolTipText(tr("Select to download notes in the selected download area."));
172            cbDownloadNotes.getModel().addChangeListener(checkboxChangeListener);
173
174            Font labelFont = sizeCheck.getFont();
175            sizeCheck.setFont(labelFont.deriveFont(Font.PLAIN, labelFont.getSize()));
176
177            add(cbDownloadOsmData, GBC.std().insets(1, 5, 1, 5));
178            add(cbDownloadGpxData, GBC.std().insets(1, 5, 1, 5));
179            add(cbDownloadNotes, GBC.eol().insets(1, 5, 1, 5));
180            add(sizeCheck, GBC.eol().anchor(GBC.EAST).insets(5, 5, 5, 2));
181
182            setMinimumSize(new Dimension(450, 115));
183        }
184
185        @Override
186        public OSMDownloadData getData() {
187            return new OSMDownloadData(
188                    isDownloadOsmData(),
189                    isDownloadNotes(),
190                    isDownloadGpxData());
191        }
192
193        @Override
194        public void rememberSettings() {
195            DOWNLOAD_OSM.put(isDownloadOsmData());
196            DOWNLOAD_GPS.put(isDownloadGpxData());
197            DOWNLOAD_NOTES.put(isDownloadNotes());
198        }
199
200        @Override
201        public void restoreSettings() {
202            cbDownloadOsmData.setSelected(DOWNLOAD_OSM.get());
203            cbDownloadGpxData.setSelected(DOWNLOAD_GPS.get());
204            cbDownloadNotes.setSelected(DOWNLOAD_NOTES.get());
205        }
206
207        @Override
208        public boolean checkDownload(DownloadSettings settings) {
209            /*
210             * It is mandatory to specify the area to download from OSM.
211             */
212            if (!settings.getDownloadBounds().isPresent()) {
213                JOptionPane.showMessageDialog(
214                        this.getParent(),
215                        tr("Please select a download area first."),
216                        tr("Error"),
217                        JOptionPane.ERROR_MESSAGE
218                );
219
220                return false;
221            }
222
223            /*
224             * Checks if the user selected the type of data to download. At least one the following
225             * must be chosen : raw osm data, gpx data, notes.
226             * If none of those are selected, then the corresponding dialog is shown to inform the user.
227             */
228            if (!isDownloadOsmData() && !isDownloadGpxData() && !isDownloadNotes()) {
229                JOptionPane.showMessageDialog(
230                        this.getParent(),
231                        tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> nor <strong>{2}</strong> is enabled.<br>"
232                                        + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",
233                                cbDownloadOsmData.getText(),
234                                cbDownloadGpxData.getText(),
235                                cbDownloadNotes.getText()
236                        ),
237                        tr("Error"),
238                        JOptionPane.ERROR_MESSAGE
239                );
240
241                return false;
242            }
243
244            this.rememberSettings();
245
246            return true;
247        }
248
249        /**
250         * Replies true if the user selected to download OSM data
251         *
252         * @return true if the user selected to download OSM data
253         */
254        public boolean isDownloadOsmData() {
255            return cbDownloadOsmData.isSelected();
256        }
257
258        /**
259         * Replies true if the user selected to download GPX data
260         *
261         * @return true if the user selected to download GPX data
262         */
263        public boolean isDownloadGpxData() {
264            return cbDownloadGpxData.isSelected();
265        }
266
267        /**
268         * Replies true if user selected to download notes
269         *
270         * @return true if user selected to download notes
271         */
272        public boolean isDownloadNotes() {
273            return cbDownloadNotes.isSelected();
274        }
275
276        @Override
277        public Icon getIcon() {
278            return ImageProvider.get("download");
279        }
280
281        @Override
282        public void boundingBoxChanged(Bounds bbox) {
283            updateSizeCheck(bbox);
284        }
285
286        @Override
287        public String getSimpleName() {
288            return SIMPLE_NAME;
289        }
290
291        private void updateSizeCheck(Bounds bbox) {
292            if (bbox == null) {
293                sizeCheck.setText(tr("No area selected yet"));
294                sizeCheck.setForeground(Color.darkGray);
295                return;
296            }
297
298            boolean isAreaTooLarge = false;
299            if (!isDownloadNotes() && !isDownloadOsmData() && !isDownloadGpxData()) {
300                isAreaTooLarge = false;
301            } else if (isDownloadNotes() && !isDownloadOsmData() && !isDownloadGpxData()) {
302                // see max_note_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
303                isAreaTooLarge = bbox.getArea() > Config.getPref().getDouble("osm-server.max-request-area-notes", 25);
304            } else {
305                // see max_request_area in https://github.com/openstreetmap/openstreetmap-website/blob/master/config/example.application.yml
306                isAreaTooLarge = bbox.getArea() > Config.getPref().getDouble("osm-server.max-request-area", 0.25);
307            }
308
309            displaySizeCheckResult(isAreaTooLarge);
310        }
311
312        private void displaySizeCheckResult(boolean isAreaTooLarge) {
313            if (isAreaTooLarge) {
314                sizeCheck.setText(tr("Download area too large; will probably be rejected by server"));
315                sizeCheck.setForeground(Color.red);
316            } else {
317                sizeCheck.setText(tr("Download area ok, size probably acceptable to server"));
318                sizeCheck.setForeground(Color.darkGray);
319            }
320        }
321
322    }
323
324    /**
325     * Encapsulates data that is required to download from the OSM server.
326     */
327    static class OSMDownloadData {
328        private final boolean downloadOSMData;
329        private final boolean downloadNotes;
330        private final boolean downloadGPX;
331
332        OSMDownloadData(boolean downloadOSMData, boolean downloadNotes, boolean downloadGPX) {
333            this.downloadOSMData = downloadOSMData;
334            this.downloadNotes = downloadNotes;
335            this.downloadGPX = downloadGPX;
336        }
337
338        boolean isDownloadOSMData() {
339            return downloadOSMData;
340        }
341
342        boolean isDownloadNotes() {
343            return downloadNotes;
344        }
345
346        boolean isDownloadGPX() {
347            return downloadGPX;
348        }
349    }
350}