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