001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.downloadtasks;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.IOException;
007import java.net.URL;
008import java.util.concurrent.Future;
009import java.util.regex.Matcher;
010import java.util.regex.Pattern;
011
012import org.openstreetmap.josm.Main;
013import org.openstreetmap.josm.data.Bounds;
014import org.openstreetmap.josm.data.Bounds.ParseMethod;
015import org.openstreetmap.josm.data.gpx.GpxData;
016import org.openstreetmap.josm.gui.PleaseWaitRunnable;
017import org.openstreetmap.josm.gui.layer.GpxLayer;
018import org.openstreetmap.josm.gui.layer.Layer;
019import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
020import org.openstreetmap.josm.gui.progress.ProgressMonitor;
021import org.openstreetmap.josm.gui.progress.ProgressTaskId;
022import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
023import org.openstreetmap.josm.io.BoundingBoxDownloader;
024import org.openstreetmap.josm.io.GpxImporter;
025import org.openstreetmap.josm.io.GpxImporter.GpxImporterData;
026import org.openstreetmap.josm.io.OsmServerLocationReader;
027import org.openstreetmap.josm.io.OsmServerReader;
028import org.openstreetmap.josm.io.OsmTransferException;
029import org.openstreetmap.josm.tools.CheckParameterUtil;
030import org.xml.sax.SAXException;
031
032/**
033 * Task allowing to download GPS data.
034 */
035public class DownloadGpsTask extends AbstractDownloadTask {
036
037    private DownloadTask downloadTask;
038
039    private static final String PATTERN_TRACE_ID = "http://.*(osm|openstreetmap).org/trace/\\p{Digit}+/data";
040
041    private static final String PATTERN_TRACKPOINTS_BBOX = "http://.*/api/0.6/trackpoints\\?bbox=.*,.*,.*,.*";
042
043    private static final String PATTERN_EXTERNAL_GPX_SCRIPT = "https?://.*exportgpx.*";
044    private static final String PATTERN_EXTERNAL_GPX_FILE = "https?://.*/(.*\\.gpx)";
045
046    protected String newLayerName = null;
047
048    @Override
049    public String[] getPatterns() {
050        return new String[] {PATTERN_EXTERNAL_GPX_FILE, PATTERN_EXTERNAL_GPX_SCRIPT, PATTERN_TRACE_ID, PATTERN_TRACKPOINTS_BBOX};
051    }
052
053    @Override
054    public String getTitle() {
055        return tr("Download GPS");
056    }
057
058    @Override
059    public Future<?> download(boolean newLayer, Bounds downloadArea, ProgressMonitor progressMonitor) {
060        downloadTask = new DownloadTask(newLayer,
061                new BoundingBoxDownloader(downloadArea), progressMonitor, false);
062        // We need submit instead of execute so we can wait for it to finish and get the error
063        // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
064        return Main.worker.submit(downloadTask);
065    }
066
067    @Override
068    public Future<?> loadUrl(boolean newLayer, String url, ProgressMonitor progressMonitor) {
069        CheckParameterUtil.ensureParameterNotNull(url, "url");
070        if (url.matches(PATTERN_TRACE_ID) || url.matches(PATTERN_EXTERNAL_GPX_SCRIPT) || url.matches(PATTERN_EXTERNAL_GPX_FILE)) {
071            downloadTask = new DownloadTask(newLayer,
072                    new OsmServerLocationReader(url), progressMonitor, url.matches(PATTERN_TRACE_ID));
073            // Extract .gpx filename from URL to set the new layer name
074            Matcher matcher = Pattern.compile(PATTERN_EXTERNAL_GPX_FILE).matcher(url);
075            newLayerName = matcher.matches() ? matcher.group(1) : null;
076            // We need submit instead of execute so we can wait for it to finish and get the error
077            // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
078            return Main.worker.submit(downloadTask);
079
080        } else if (url.matches(PATTERN_TRACKPOINTS_BBOX)) {
081            String[] table = url.split("\\?|=|&");
082            for (int i = 0; i<table.length; i++) {
083                if (table[i].equals("bbox") && i<table.length-1 )
084                    return download(newLayer, new Bounds(table[i+1], ",", ParseMethod.LEFT_BOTTOM_RIGHT_TOP), progressMonitor);
085            }
086        }
087        return null;
088    }
089
090    @Override
091    public void cancel() {
092        if (downloadTask != null) {
093            downloadTask.cancel();
094        }
095    }
096
097    class DownloadTask extends PleaseWaitRunnable {
098        private OsmServerReader reader;
099        private GpxData rawData;
100        private final boolean newLayer;
101        private final boolean compressed;
102
103        public DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor, boolean compressed) {
104            super(tr("Downloading GPS data"));
105            this.reader = reader;
106            this.newLayer = newLayer;
107            this.compressed = compressed;
108        }
109
110        @Override public void realRun() throws IOException, SAXException, OsmTransferException {
111            try {
112                if (isCanceled())
113                    return;
114                ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
115                if (compressed) {
116                    rawData = reader.parseRawGpsBzip2(subMonitor);
117                } else {
118                    rawData = reader.parseRawGps(subMonitor);
119                }
120            } catch(Exception e) {
121                if (isCanceled())
122                    return;
123                if (e instanceof OsmTransferException) {
124                    rememberException(e);
125                } else {
126                    rememberException(new OsmTransferException(e));
127                }
128            }
129        }
130
131        @Override protected void finish() {
132            if (isCanceled() || isFailed())
133                return;
134            if (rawData == null)
135                return;
136            String name = newLayerName != null ? newLayerName : tr("Downloaded GPX Data");
137
138            GpxImporterData layers = GpxImporter.loadLayers(rawData, reader.isGpxParsedProperly(), name, tr("Markers from {0}", name));
139
140            GpxLayer gpxLayer = addOrMergeLayer(layers.getGpxLayer(), findGpxMergeLayer());
141            addOrMergeLayer(layers.getMarkerLayer(), findMarkerMergeLayer(gpxLayer));
142
143            layers.getPostLayerTask().run();
144        }
145
146        private <L extends Layer> L addOrMergeLayer(L layer, L mergeLayer) {
147            if (layer == null) return null;
148            if (newLayer || mergeLayer == null) {
149                Main.main.addLayer(layer);
150                return layer;
151            } else {
152                mergeLayer.mergeFrom(layer);
153                Main.map.repaint();
154                return mergeLayer;
155            }
156        }
157
158        private GpxLayer findGpxMergeLayer() {
159            if (!Main.isDisplayingMapView())
160                return null;
161            boolean merge = Main.pref.getBoolean("download.gps.mergeWithLocal", false);
162            Layer active = Main.map.mapView.getActiveLayer();
163            if (active instanceof GpxLayer && (merge || ((GpxLayer)active).data.fromServer))
164                return (GpxLayer) active;
165            for (GpxLayer l : Main.map.mapView.getLayersOfType(GpxLayer.class)) {
166                if (merge || l.data.fromServer)
167                    return l;
168            }
169            return null;
170        }
171
172        private MarkerLayer findMarkerMergeLayer(GpxLayer fromLayer) {
173            if (!Main.isDisplayingMapView())
174                return null;
175            for (MarkerLayer l : Main.map.mapView.getLayersOfType(MarkerLayer.class)) {
176                if (fromLayer != null && l.fromLayer == fromLayer)
177                    return l;
178            }
179            return null;
180        }
181
182        @Override protected void cancel() {
183            setCanceled(true);
184            if (reader != null) {
185                reader.cancel();
186            }
187        }
188
189        @Override
190        public ProgressTaskId canRunInBackground() {
191            return ProgressTaskIds.DOWNLOAD_GPS;
192        }
193    }
194
195    @Override
196    public String getConfirmationMessage(URL url) {
197        // TODO
198        return null;
199    }
200
201    /**
202     * Determines if the given URL denotes an OSM gpx-related API call.
203     * @param url The url to check
204     * @return true if the url matches "Trace ID" API call or "Trackpoints bbox" API call, false otherwise
205     * @see GpxData#fromServer
206     * @since 5745
207     */
208    public static final boolean isFromServer(String url) {
209        return url != null && (url.matches(PATTERN_TRACE_ID) || url.matches(PATTERN_TRACKPOINTS_BBOX));
210    }
211}