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