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

Last change on this file since 7817 was 7817, checked in by bastiK, 9 years ago

fixed #10861 - Zoom to layer when opening new file (see #10860)

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