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