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

Last change on this file since 11186 was 10436, checked in by Don-vip, 8 years ago

see #13001 - replace calls to Main.main.[add|remove]Layer by Main.getLayerManager().[add|remove]Layer

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