source: josm/trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadOsmTask.java@ 11627

Last change on this file since 11627 was 11627, checked in by Don-vip, 7 years ago

fix #3346 - improve drastically the performance of fixing duplicate nodes by:

  • caching data sources area computation
  • moving layer invalidation from UndoRedoHandler.addNoRedraw to UndoRedoHandler.add
  • avoiding any EDT call when building tag conflict dialog if it's not meant to be displayed
  • Property svn:eol-style set to native
File size: 15.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions.downloadtasks;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.net.URL;
8import java.util.ArrayList;
9import java.util.Collection;
10import java.util.Optional;
11import java.util.concurrent.Future;
12import java.util.regex.Matcher;
13import java.util.regex.Pattern;
14
15import org.openstreetmap.josm.Main;
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.DataSource;
18import org.openstreetmap.josm.data.ProjectionBounds;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.osm.DataSet;
21import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
22import org.openstreetmap.josm.gui.PleaseWaitRunnable;
23import org.openstreetmap.josm.gui.layer.OsmDataLayer;
24import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
25import org.openstreetmap.josm.gui.progress.ProgressMonitor;
26import org.openstreetmap.josm.io.BoundingBoxDownloader;
27import org.openstreetmap.josm.io.OsmServerLocationReader;
28import org.openstreetmap.josm.io.OsmServerReader;
29import org.openstreetmap.josm.io.OsmTransferCanceledException;
30import org.openstreetmap.josm.io.OsmTransferException;
31import org.openstreetmap.josm.tools.Utils;
32import org.xml.sax.SAXException;
33
34/**
35 * Open the download dialog and download the data.
36 * Run in the worker thread.
37 */
38public class DownloadOsmTask extends AbstractDownloadTask<DataSet> {
39
40 // CHECKSTYLE.OFF: SingleSpaceSeparator
41 protected static final String PATTERN_OSM_API_URL = "https?://.*/api/0.6/(map|nodes?|ways?|relations?|\\*).*";
42 protected static final String PATTERN_OVERPASS_API_URL = "https?://.*/interpreter\\?data=.*";
43 protected static final String PATTERN_OVERPASS_API_XAPI_URL = "https?://.*/xapi(\\?.*\\[@meta\\]|_meta\\?).*";
44 protected static final String PATTERN_EXTERNAL_OSM_FILE = "https?://.*/.*\\.osm";
45 // CHECKSTYLE.ON: SingleSpaceSeparator
46
47 protected Bounds currentBounds;
48 protected DownloadTask downloadTask;
49
50 protected String newLayerName;
51
52 /** This allows subclasses to ignore this warning */
53 protected boolean warnAboutEmptyArea = true;
54
55 @Override
56 public String[] getPatterns() {
57 if (this.getClass() == DownloadOsmTask.class) {
58 return new String[]{PATTERN_OSM_API_URL, PATTERN_OVERPASS_API_URL,
59 PATTERN_OVERPASS_API_XAPI_URL, PATTERN_EXTERNAL_OSM_FILE};
60 } else {
61 return super.getPatterns();
62 }
63 }
64
65 @Override
66 public String getTitle() {
67 if (this.getClass() == DownloadOsmTask.class) {
68 return tr("Download OSM");
69 } else {
70 return super.getTitle();
71 }
72 }
73
74 @Override
75 public Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
76 return download(new BoundingBoxDownloader(downloadArea), newLayer, downloadArea, progressMonitor);
77 }
78
79 /**
80 * Asynchronously launches the download task for a given bounding box.
81 *
82 * Set <code>progressMonitor</code> to null, if the task should create, open, and close a progress monitor.
83 * Set progressMonitor to {@link NullProgressMonitor#INSTANCE} if progress information is to
84 * be discarded.
85 *
86 * You can wait for the asynchronous download task to finish by synchronizing on the returned
87 * {@link Future}, but make sure not to freeze up JOSM. Example:
88 * <pre>
89 * Future&lt;?&gt; future = task.download(...);
90 * // DON'T run this on the Swing EDT or JOSM will freeze
91 * future.get(); // waits for the dowload task to complete
92 * </pre>
93 *
94 * The following example uses a pattern which is better suited if a task is launched from
95 * the Swing EDT:
96 * <pre>
97 * final Future&lt;?&gt; future = task.download(...);
98 * Runnable runAfterTask = new Runnable() {
99 * public void run() {
100 * // this is not strictly necessary because of the type of executor service
101 * // Main.worker is initialized with, but it doesn't harm either
102 * //
103 * future.get(); // wait for the download task to complete
104 * doSomethingAfterTheTaskCompleted();
105 * }
106 * }
107 * Main.worker.submit(runAfterTask);
108 * </pre>
109 * @param reader the reader used to parse OSM data (see {@link OsmServerReader#parseOsm})
110 * @param newLayer true, if the data is to be downloaded into a new layer. If false, the task
111 * selects one of the existing layers as download layer, preferably the active layer.
112 * @param downloadArea the area to download
113 * @param progressMonitor the progressMonitor
114 * @return the future representing the asynchronous task
115 */
116 public Future<?> download(OsmServerReader reader, boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
117 return download(new DownloadTask(newLayer, reader, progressMonitor), downloadArea);
118 }
119
120 protected Future<?> download(DownloadTask downloadTask, Bounds downloadArea) {
121 this.downloadTask = downloadTask;
122 this.currentBounds = new Bounds(downloadArea);
123 // We need submit instead of execute so we can wait for it to finish and get the error
124 // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
125 return Main.worker.submit(downloadTask);
126 }
127
128 /**
129 * This allows subclasses to perform operations on the URL before {@link #loadUrl} is performed.
130 * @param url the original URL
131 * @return the modified URL
132 */
133 protected String modifyUrlBeforeLoad(String url) {
134 return url;
135 }
136
137 /**
138 * Loads a given URL from the OSM Server
139 * @param newLayer True if the data should be saved to a new layer
140 * @param url The URL as String
141 */
142 @Override
143 public Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
144 String newUrl = modifyUrlBeforeLoad(url);
145 downloadTask = new DownloadTask(newLayer,
146 new OsmServerLocationReader(newUrl),
147 progressMonitor);
148 currentBounds = null;
149 // Extract .osm filename from URL to set the new layer name
150 extractOsmFilename("https?://.*/(.*\\.osm)", newUrl);
151 return Main.worker.submit(downloadTask);
152 }
153
154 protected final void extractOsmFilename(String pattern, String url) {
155 Matcher matcher = Pattern.compile(pattern).matcher(url);
156 newLayerName = matcher.matches() ? matcher.group(1) : null;
157 }
158
159 @Override
160 public void cancel() {
161 if (downloadTask != null) {
162 downloadTask.cancel();
163 }
164 }
165
166 @Override
167 public boolean isSafeForRemotecontrolRequests() {
168 return true;
169 }
170
171 /**
172 * Superclass of internal download task.
173 * @since 7636
174 */
175 public abstract static class AbstractInternalTask extends PleaseWaitRunnable {
176
177 protected final boolean newLayer;
178 protected final boolean zoomAfterDownload;
179 protected DataSet dataSet;
180
181 /**
182 * Constructs a new {@code AbstractInternalTask}.
183 *
184 * @param newLayer if {@code true}, force download to a new layer
185 * @param title message for the user
186 * @param ignoreException If true, exception will be propagated to calling code. If false then
187 * exception will be thrown directly in EDT. When this runnable is executed using executor framework
188 * then use false unless you read result of task (because exception will get lost if you don't)
189 * @param zoomAfterDownload If true, the map view will zoom to download area after download
190 */
191 public AbstractInternalTask(boolean newLayer, String title, boolean ignoreException, boolean zoomAfterDownload) {
192 super(title, ignoreException);
193 this.newLayer = newLayer;
194 this.zoomAfterDownload = zoomAfterDownload;
195 }
196
197 /**
198 * Constructs a new {@code AbstractInternalTask}.
199 *
200 * @param newLayer if {@code true}, force download to a new layer
201 * @param title message for the user
202 * @param progressMonitor progress monitor
203 * @param ignoreException If true, exception will be propagated to calling code. If false then
204 * exception will be thrown directly in EDT. When this runnable is executed using executor framework
205 * then use false unless you read result of task (because exception will get lost if you don't)
206 * @param zoomAfterDownload If true, the map view will zoom to download area after download
207 */
208 public AbstractInternalTask(boolean newLayer, String title, ProgressMonitor progressMonitor, boolean ignoreException,
209 boolean zoomAfterDownload) {
210 super(title, progressMonitor, ignoreException);
211 this.newLayer = newLayer;
212 this.zoomAfterDownload = zoomAfterDownload;
213 }
214
215 protected OsmDataLayer getEditLayer() {
216 if (!Main.isDisplayingMapView()) return null;
217 return Main.getLayerManager().getEditLayer();
218 }
219
220 protected int getNumDataLayers() {
221 return Main.getLayerManager().getLayersOfType(OsmDataLayer.class).size();
222 }
223
224 protected OsmDataLayer getFirstDataLayer() {
225 return Utils.find(Main.getLayerManager().getLayers(), OsmDataLayer.class);
226 }
227
228 protected OsmDataLayer createNewLayer(String layerName) {
229 if (layerName == null || layerName.isEmpty()) {
230 layerName = OsmDataLayer.createNewName();
231 }
232 return new OsmDataLayer(dataSet, layerName, null);
233 }
234
235 protected OsmDataLayer createNewLayer() {
236 return createNewLayer(null);
237 }
238
239 protected ProjectionBounds computeBbox(Bounds bounds) {
240 BoundingXYVisitor v = new BoundingXYVisitor();
241 if (bounds != null) {
242 v.visit(bounds);
243 } else {
244 v.computeBoundingBox(dataSet.getNodes());
245 }
246 return v.getBounds();
247 }
248
249 protected void computeBboxAndCenterScale(Bounds bounds) {
250 ProjectionBounds pb = computeBbox(bounds);
251 BoundingXYVisitor v = new BoundingXYVisitor();
252 v.visit(pb);
253 Main.map.mapView.zoomTo(v);
254 }
255
256 protected OsmDataLayer addNewLayerIfRequired(String newLayerName) {
257 int numDataLayers = getNumDataLayers();
258 if (newLayer || numDataLayers == 0 || (numDataLayers > 1 && getEditLayer() == null)) {
259 // the user explicitly wants a new layer, we don't have any layer at all
260 // or it is not clear which layer to merge to
261 //
262 final OsmDataLayer layer = createNewLayer(newLayerName);
263 if (Main.main != null)
264 Main.getLayerManager().addLayer(layer);
265 return layer;
266 }
267 return null;
268 }
269
270 protected void loadData(String newLayerName, Bounds bounds) {
271 OsmDataLayer layer = addNewLayerIfRequired(newLayerName);
272 if (layer == null) {
273 layer = Optional.ofNullable(getEditLayer()).orElseGet(this::getFirstDataLayer);
274 layer.mergeFrom(dataSet);
275 if (zoomAfterDownload) {
276 computeBboxAndCenterScale(bounds);
277 }
278 layer.onPostDownloadFromServer();
279 }
280 }
281 }
282
283 protected class DownloadTask extends AbstractInternalTask {
284 protected final OsmServerReader reader;
285
286 /**
287 * Constructs a new {@code DownloadTask}.
288 * @param newLayer if {@code true}, force download to a new layer
289 * @param reader OSM data reader
290 * @param progressMonitor progress monitor
291 */
292 public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor) {
293 this(newLayer, reader, progressMonitor, true);
294 }
295
296 /**
297 * Constructs a new {@code DownloadTask}.
298 * @param newLayer if {@code true}, force download to a new layer
299 * @param reader OSM data reader
300 * @param progressMonitor progress monitor
301 * @param zoomAfterDownload If true, the map view will zoom to download area after download
302 * @since 8942
303 */
304 public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor, boolean zoomAfterDownload) {
305 super(newLayer, tr("Downloading data"), progressMonitor, false, zoomAfterDownload);
306 this.reader = reader;
307 }
308
309 protected DataSet parseDataSet() throws OsmTransferException {
310 return reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
311 }
312
313 @Override
314 public void realRun() throws IOException, SAXException, OsmTransferException {
315 try {
316 if (isCanceled())
317 return;
318 dataSet = parseDataSet();
319 } catch (OsmTransferException e) {
320 if (isCanceled()) {
321 Main.info(tr("Ignoring exception because download has been canceled. Exception was: {0}", e.toString()));
322 return;
323 }
324 if (e instanceof OsmTransferCanceledException) {
325 setCanceled(true);
326 return;
327 } else {
328 rememberException(e);
329 }
330 DownloadOsmTask.this.setFailed(true);
331 }
332 }
333
334 @Override
335 protected void finish() {
336 if (isFailed() || isCanceled())
337 return;
338 if (dataSet == null)
339 return; // user canceled download or error occurred
340 if (dataSet.allPrimitives().isEmpty()) {
341 if (warnAboutEmptyArea) {
342 rememberErrorMessage(tr("No data found in this area."));
343 }
344 // need to synthesize a download bounds lest the visual indication of downloaded area doesn't work
345 dataSet.addDataSource(new DataSource(currentBounds != null ? currentBounds :
346 new Bounds(LatLon.ZERO), "OpenStreetMap server"));
347 }
348
349 rememberDownloadedData(dataSet);
350 loadData(newLayerName, currentBounds);
351 }
352
353 @Override
354 protected void cancel() {
355 setCanceled(true);
356 if (reader != null) {
357 reader.cancel();
358 }
359 }
360 }
361
362 @Override
363 public String getConfirmationMessage(URL url) {
364 if (url != null) {
365 String urlString = url.toExternalForm();
366 if (urlString.matches(PATTERN_OSM_API_URL)) {
367 // TODO: proper i18n after stabilization
368 Collection<String> items = new ArrayList<>();
369 items.add(tr("OSM Server URL:") + ' ' + url.getHost());
370 items.add(tr("Command")+": "+url.getPath());
371 if (url.getQuery() != null) {
372 items.add(tr("Request details: {0}", url.getQuery().replaceAll(",\\s*", ", ")));
373 }
374 return Utils.joinAsHtmlUnorderedList(items);
375 }
376 // TODO: other APIs
377 }
378 return null;
379 }
380}
Note: See TracBrowser for help on using the repository browser.