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