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}