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<GpxData> { 036 037 private DownloadTask downloadTask; 038 039 private static final String PATTERN_TRACE_ID = "https?://.*(osm|openstreetmap).org/trace/\\p{Digit}+/data"; 040 041 private static final String PATTERN_TRACKPOINTS_BBOX = "https?://.*/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; 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); 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) 071 || url.matches(PATTERN_EXTERNAL_GPX_SCRIPT) 072 || url.matches(PATTERN_EXTERNAL_GPX_FILE)) { 073 downloadTask = new DownloadTask(newLayer, 074 new OsmServerLocationReader(url), progressMonitor); 075 // Extract .gpx filename from URL to set the new layer name 076 Matcher matcher = Pattern.compile(PATTERN_EXTERNAL_GPX_FILE).matcher(url); 077 newLayerName = matcher.matches() ? matcher.group(1) : null; 078 // We need submit instead of execute so we can wait for it to finish and get the error 079 // message if necessary. If no one calls getErrorMessage() it just behaves like execute. 080 return Main.worker.submit(downloadTask); 081 082 } else if (url.matches(PATTERN_TRACKPOINTS_BBOX)) { 083 String[] table = url.split("\\?|=|&"); 084 for (int i = 0; i < table.length; i++) { 085 if ("bbox".equals(table[i]) && i < table.length-1) 086 return download(newLayer, new Bounds(table[i+1], ",", ParseMethod.LEFT_BOTTOM_RIGHT_TOP), progressMonitor); 087 } 088 } 089 return null; 090 } 091 092 @Override 093 public void cancel() { 094 if (downloadTask != null) { 095 downloadTask.cancel(); 096 } 097 } 098 099 class DownloadTask extends PleaseWaitRunnable { 100 private OsmServerReader reader; 101 private GpxData rawData; 102 private final boolean newLayer; 103 104 DownloadTask(boolean newLayer, OsmServerReader reader, ProgressMonitor progressMonitor) { 105 super(tr("Downloading GPS data"), progressMonitor, false); 106 this.reader = reader; 107 this.newLayer = newLayer; 108 } 109 110 @Override 111 public void realRun() throws IOException, SAXException, OsmTransferException { 112 try { 113 if (isCanceled()) 114 return; 115 ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false); 116 rawData = reader.parseRawGps(subMonitor); 117 } catch (Exception e) { 118 if (isCanceled()) 119 return; 120 if (e instanceof OsmTransferException) { 121 rememberException(e); 122 } else { 123 rememberException(new OsmTransferException(e)); 124 } 125 } 126 } 127 128 @Override 129 protected void finish() { 130 rememberDownloadedData(rawData); 131 if (isCanceled() || isFailed()) 132 return; 133 if (rawData == null) 134 return; 135 String name = newLayerName != null ? newLayerName : tr("Downloaded GPX Data"); 136 137 GpxImporterData layers = GpxImporter.loadLayers(rawData, reader.isGpxParsedProperly(), name, 138 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 if (Main.main != null) { 150 Main.main.addLayer(layer); 151 } 152 return layer; 153 } else { 154 mergeLayer.mergeFrom(layer); 155 if (Main.map != null) { 156 Main.map.repaint(); 157 } 158 return mergeLayer; 159 } 160 } 161 162 private GpxLayer findGpxMergeLayer() { 163 if (!Main.isDisplayingMapView()) 164 return null; 165 boolean merge = Main.pref.getBoolean("download.gps.mergeWithLocal", false); 166 Layer active = Main.map.mapView.getActiveLayer(); 167 if (active instanceof GpxLayer && (merge || ((GpxLayer) active).data.fromServer)) 168 return (GpxLayer) active; 169 for (GpxLayer l : Main.map.mapView.getLayersOfType(GpxLayer.class)) { 170 if (merge || l.data.fromServer) 171 return l; 172 } 173 return null; 174 } 175 176 private MarkerLayer findMarkerMergeLayer(GpxLayer fromLayer) { 177 if (!Main.isDisplayingMapView()) 178 return null; 179 for (MarkerLayer l : Main.map.mapView.getLayersOfType(MarkerLayer.class)) { 180 if (fromLayer != null && l.fromLayer == fromLayer) 181 return l; 182 } 183 return null; 184 } 185 186 @Override 187 protected void cancel() { 188 setCanceled(true); 189 if (reader != null) { 190 reader.cancel(); 191 } 192 } 193 194 @Override 195 public ProgressTaskId canRunInBackground() { 196 return ProgressTaskIds.DOWNLOAD_GPS; 197 } 198 } 199 200 @Override 201 public String getConfirmationMessage(URL url) { 202 // TODO 203 return null; 204 } 205 206 @Override 207 public boolean isSafeForRemotecontrolRequests() { 208 return true; 209 } 210 211 /** 212 * Determines if the given URL denotes an OSM gpx-related API call. 213 * @param url The url to check 214 * @return true if the url matches "Trace ID" API call or "Trackpoints bbox" API call, false otherwise 215 * @see GpxData#fromServer 216 * @since 5745 217 */ 218 public static final boolean isFromServer(String url) { 219 return url != null && (url.matches(PATTERN_TRACE_ID) || url.matches(PATTERN_TRACKPOINTS_BBOX)); 220 } 221}