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

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

see #7910 - resolve NPE when trying to suggest imagery layers for a dataset without download bounds

  • Property svn:eol-style set to native
File size: 14.1 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.downloadtasks;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.HashSet;
10import java.util.Iterator;
11import java.util.List;
12import java.util.Set;
13import java.util.concurrent.Future;
14import java.util.regex.Matcher;
15import java.util.regex.Pattern;
16
17import javax.swing.JOptionPane;
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.data.Bounds;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.imagery.ImageryInfo;
22import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
23import org.openstreetmap.josm.data.imagery.Shape;
24import org.openstreetmap.josm.data.osm.DataSet;
25import org.openstreetmap.josm.data.osm.DataSource;
26import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
27import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
28import org.openstreetmap.josm.gui.PleaseWaitRunnable;
29import org.openstreetmap.josm.gui.layer.Layer;
30import org.openstreetmap.josm.gui.layer.OsmDataLayer;
31import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
32import org.openstreetmap.josm.gui.progress.ProgressMonitor;
33import org.openstreetmap.josm.io.BoundingBoxDownloader;
34import org.openstreetmap.josm.io.OsmServerLocationReader;
35import org.openstreetmap.josm.io.OsmServerReader;
36import org.openstreetmap.josm.io.OsmTransferCanceledException;
37import org.openstreetmap.josm.io.OsmTransferException;
38import org.openstreetmap.josm.tools.Utils;
39import org.xml.sax.SAXException;
40
41/**
42 * Open the download dialog and download the data.
43 * Run in the worker thread.
44 */
45public class DownloadOsmTask extends AbstractDownloadTask {
46 protected Bounds currentBounds;
47 protected DataSet downloadedData;
48 protected DownloadTask downloadTask;
49
50 protected OsmDataLayer targetLayer;
51
52 protected String newLayerName = null;
53
54 protected void rememberDownloadedData(DataSet ds) {
55 this.downloadedData = ds;
56 }
57
58 /**
59 * Replies the {@link DataSet} containing the downloaded OSM data.
60 * @return The {@link DataSet} containing the downloaded OSM data.
61 */
62 public DataSet getDownloadedData() {
63 return downloadedData;
64 }
65
66 @Override
67 public Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
68 return download(new BoundingBoxDownloader(downloadArea), newLayer, downloadArea, progressMonitor);
69 }
70
71 /**
72 * Asynchronously launches the download task for a given bounding box.
73 *
74 * Set <code>progressMonitor</code> to null, if the task should create, open, and close a progress monitor.
75 * Set progressMonitor to {@link NullProgressMonitor#INSTANCE} if progress information is to
76 * be discarded.
77 *
78 * You can wait for the asynchronous download task to finish by synchronizing on the returned
79 * {@link Future}, but make sure not to freeze up JOSM. Example:
80 * <pre>
81 * Future<?> future = task.download(...);
82 * // DON'T run this on the Swing EDT or JOSM will freeze
83 * future.get(); // waits for the dowload task to complete
84 * </pre>
85 *
86 * The following example uses a pattern which is better suited if a task is launched from
87 * the Swing EDT:
88 * <pre>
89 * final Future<?> future = task.download(...);
90 * Runnable runAfterTask = new Runnable() {
91 * public void run() {
92 * // this is not strictly necessary because of the type of executor service
93 * // Main.worker is initialized with, but it doesn't harm either
94 * //
95 * future.get(); // wait for the download task to complete
96 * doSomethingAfterTheTaskCompleted();
97 * }
98 * }
99 * Main.worker.submit(runAfterTask);
100 * </pre>
101 * @param reader the reader used to parse OSM data (see {@link OsmServerReader#parseOsm})
102 * @param newLayer true, if the data is to be downloaded into a new layer. If false, the task
103 * selects one of the existing layers as download layer, preferably the active layer.
104 * @param downloadArea the area to download
105 * @param progressMonitor the progressMonitor
106 * @return the future representing the asynchronous task
107 */
108 public Future<?> download(OsmServerReader reader, boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
109 return download(new DownloadTask(newLayer, reader, progressMonitor), downloadArea);
110 }
111
112 protected Future<?> download(DownloadTask downloadTask, Bounds downloadArea) {
113 this.downloadTask = downloadTask;
114 this.currentBounds = new Bounds(downloadArea);
115 // We need submit instead of execute so we can wait for it to finish and get the error
116 // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
117 return Main.worker.submit(downloadTask);
118 }
119
120 /**
121 * Loads a given URL from the OSM Server
122 * @param new_layer True if the data should be saved to a new layer
123 * @param url The URL as String
124 */
125 public Future<?> loadUrl(boolean new_layer, String url, ProgressMonitor progressMonitor) {
126 downloadTask = new DownloadTask(new_layer,
127 new OsmServerLocationReader(url),
128 progressMonitor);
129 currentBounds = null;
130 // Extract .osm filename from URL to set the new layer name
131 extractOsmFilename("http://.*/(.*\\.osm)", url);
132 return Main.worker.submit(downloadTask);
133 }
134
135 protected final void extractOsmFilename(String pattern, String url) {
136 Matcher matcher = Pattern.compile(pattern).matcher(url);
137 newLayerName = matcher.matches() ? matcher.group(1) : null;
138 }
139
140 /* (non-Javadoc)
141 * @see org.openstreetmap.josm.actions.downloadtasks.DownloadTask#acceptsUrl(java.lang.String)
142 */
143 @Override
144 public boolean acceptsUrl(String url) {
145 return url != null && (
146 url.matches("http://.*/api/0.6/(map|nodes?|ways?|relations?|\\*).*")// OSM API 0.6 and XAPI
147 || url.matches("http://.*/interpreter\\?data=.*") // Overpass API
148 || url.matches("http://.*/xapi\\?.*\\[@meta\\].*") // Overpass API XAPI compatibility layer
149 || url.matches("http://.*/.*\\.osm") // Remote .osm files
150 );
151 }
152
153 public void cancel() {
154 if (downloadTask != null) {
155 downloadTask.cancel();
156 }
157 }
158
159 protected class DownloadTask extends PleaseWaitRunnable {
160 protected OsmServerReader reader;
161 protected DataSet dataSet;
162 protected boolean newLayer;
163
164 public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor) {
165 super(tr("Downloading data"), progressMonitor, false);
166 this.reader = reader;
167 this.newLayer = newLayer;
168 }
169
170 protected DataSet parseDataSet() throws OsmTransferException {
171 return reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
172 }
173
174 @Override public void realRun() throws IOException, SAXException, OsmTransferException {
175 try {
176 if (isCanceled())
177 return;
178 dataSet = parseDataSet();
179 } catch(Exception e) {
180 if (isCanceled()) {
181 System.out.println(tr("Ignoring exception because download has been canceled. Exception was: {0}", e.toString()));
182 return;
183 }
184 if (e instanceof OsmTransferCanceledException) {
185 setCanceled(true);
186 return;
187 } else if (e instanceof OsmTransferException) {
188 rememberException(e);
189 } else {
190 rememberException(new OsmTransferException(e));
191 }
192 DownloadOsmTask.this.setFailed(true);
193 }
194 }
195
196 protected OsmDataLayer getEditLayer() {
197 if (!Main.isDisplayingMapView()) return null;
198 return Main.map.mapView.getEditLayer();
199 }
200
201 protected int getNumDataLayers() {
202 int count = 0;
203 if (!Main.isDisplayingMapView()) return 0;
204 Collection<Layer> layers = Main.map.mapView.getAllLayers();
205 for (Layer layer : layers) {
206 if (layer instanceof OsmDataLayer) {
207 count++;
208 }
209 }
210 return count;
211 }
212
213 protected OsmDataLayer getFirstDataLayer() {
214 if (!Main.isDisplayingMapView()) return null;
215 Collection<Layer> layers = Main.map.mapView.getAllLayersAsList();
216 for (Layer layer : layers) {
217 if (layer instanceof OsmDataLayer)
218 return (OsmDataLayer) layer;
219 }
220 return null;
221 }
222
223 protected OsmDataLayer createNewLayer(String layerName) {
224 if (layerName == null || layerName.isEmpty()) {
225 layerName = OsmDataLayer.createNewName();
226 }
227 return new OsmDataLayer(dataSet, layerName, null);
228 }
229
230 protected OsmDataLayer createNewLayer() {
231 return createNewLayer(null);
232 }
233
234 @Override protected void finish() {
235 if (isFailed() || isCanceled())
236 return;
237 if (dataSet == null)
238 return; // user canceled download or error occurred
239 if (dataSet.allPrimitives().isEmpty()) {
240 rememberErrorMessage(tr("No data found in this area."));
241 // need to synthesize a download bounds lest the visual indication of downloaded
242 // area doesn't work
243 dataSet.dataSources.add(new DataSource(currentBounds != null ? currentBounds : new Bounds(new LatLon(0, 0)), "OpenStreetMap server"));
244 }
245
246 rememberDownloadedData(dataSet);
247 int numDataLayers = getNumDataLayers();
248 if (newLayer || numDataLayers == 0 || (numDataLayers > 1 && getEditLayer() == null)) {
249 // the user explicitly wants a new layer, we don't have any layer at all
250 // or it is not clear which layer to merge to
251 //
252 targetLayer = createNewLayer(newLayerName);
253 final boolean isDisplayingMapView = Main.isDisplayingMapView();
254
255 Main.main.addLayer(targetLayer);
256
257 // If the mapView is not there yet, we cannot calculate the bounds (see constructor of MapView).
258 // Otherwise jump to the current download.
259 if (isDisplayingMapView) {
260 computeBboxAndCenterScale();
261 }
262 } else {
263 targetLayer = getEditLayer();
264 if (targetLayer == null) {
265 targetLayer = getFirstDataLayer();
266 }
267 targetLayer.mergeFrom(dataSet);
268 computeBboxAndCenterScale();
269 targetLayer.onPostDownloadFromServer();
270 }
271
272 suggestImageryLayers();
273 }
274
275 protected void computeBboxAndCenterScale() {
276 BoundingXYVisitor v = new BoundingXYVisitor();
277 if (currentBounds != null) {
278 v.visit(currentBounds);
279 } else {
280 v.computeBoundingBox(dataSet.getNodes());
281 }
282 Main.map.mapView.recalculateCenterScale(v);
283 }
284
285 @Override protected void cancel() {
286 setCanceled(true);
287 if (reader != null) {
288 reader.cancel();
289 }
290 }
291
292 protected void suggestImageryLayers() {
293 if (currentBounds != null) {
294 final LatLon center = currentBounds.getCenter();
295 final Set<ImageryInfo> layers = new HashSet<ImageryInfo>();
296
297 for (ImageryInfo i : ImageryLayerInfo.instance.getDefaultLayers()) {
298 if (i.getBounds() != null && i.getBounds().contains(center)) {
299 layers.add(i);
300 }
301 }
302 // Do not suggest layers already in use
303 layers.removeAll(ImageryLayerInfo.instance.getLayers());
304 // For layers containing complex shapes, check that center is in one of its shapes (fix #7910)
305 for (Iterator<ImageryInfo> iti = layers.iterator(); iti.hasNext(); ) {
306 List<Shape> shapes = iti.next().getBounds().getShapes();
307 if (shapes != null && !shapes.isEmpty()) {
308 boolean found = false;
309 for (Iterator<Shape> its = shapes.iterator(); its.hasNext() && !found; ) {
310 found = its.next().contains(center);
311 }
312 if (!found) {
313 iti.remove();
314 }
315 }
316 }
317
318 if (layers.isEmpty()) {
319 return;
320 }
321
322 final List<String> layerNames = new ArrayList<String>();
323 for (ImageryInfo i : layers) {
324 layerNames.add(i.getName());
325 }
326
327 if (!ConditionalOptionPaneUtil.showConfirmationDialog(
328 "download.suggest-imagery-layer",
329 Main.parent,
330 tr("<html>For the downloaded area, the following additional imagery layers are available: {0}" +
331 "Do you want to add those layers to the <em>Imagery</em> menu?" +
332 "<br>(If needed, you can remove those entries in the <em>Preferences</em>.)",
333 Utils.joinAsHtmlUnorderedList(layerNames)),
334 tr("Add imagery layers?"),
335 JOptionPane.YES_NO_OPTION,
336 JOptionPane.QUESTION_MESSAGE,
337 JOptionPane.YES_OPTION)) {
338 return;
339 }
340
341 ImageryLayerInfo.addLayers(layers);
342 }
343 }
344 }
345}
Note: See TracBrowser for help on using the repository browser.