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.Objects; 009import java.util.Optional; 010import java.util.concurrent.Future; 011import java.util.regex.Matcher; 012import java.util.stream.Stream; 013 014import org.openstreetmap.josm.data.Bounds; 015import org.openstreetmap.josm.data.Bounds.ParseMethod; 016import org.openstreetmap.josm.data.ProjectionBounds; 017import org.openstreetmap.josm.data.gpx.GpxConstants; 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.OsmServerReader; 032import org.openstreetmap.josm.io.OsmTransferException; 033import org.openstreetmap.josm.io.UrlPatterns.GpxUrlPattern; 034import org.openstreetmap.josm.spi.preferences.Config; 035import org.openstreetmap.josm.tools.Utils; 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 url; 047 048 @Override 049 public String[] getPatterns() { 050 return patterns(GpxUrlPattern.class); 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 this.url = Objects.requireNonNull(url); 070 final Optional<String> mappedUrl = Stream.of(GpxUrlPattern.USER_TRACE_ID, GpxUrlPattern.EDIT_TRACE_ID) 071 .map(p -> p.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 -> p.matches(url))) { 081 downloadTask = new DownloadTask(settings, 082 new OsmServerLocationReader(url), progressMonitor); 083 // We need submit instead of execute so we can wait for it to finish and get the error 084 // message if necessary. If no one calls getErrorMessage() it just behaves like execute. 085 return MainApplication.worker.submit(downloadTask); 086 087 } else if (GpxUrlPattern.TRACKPOINTS_BBOX.matches(url)) { 088 String[] table = url.split("\\?|=|&"); 089 for (int i = 0; i < table.length; i++) { 090 if ("bbox".equals(table[i]) && i < table.length-1) 091 return download(settings, new Bounds(table[i+1], ",", ParseMethod.LEFT_BOTTOM_RIGHT_TOP), progressMonitor); 092 } 093 } 094 return null; 095 } 096 097 @Override 098 public void cancel() { 099 if (downloadTask != null) { 100 downloadTask.cancel(); 101 } 102 } 103 104 @Override 105 public ProjectionBounds getDownloadProjectionBounds() { 106 return gpxLayer != null ? gpxLayer.getViewProjectionBounds() : null; 107 } 108 109 class DownloadTask extends PleaseWaitRunnable { 110 private final OsmServerReader reader; 111 private GpxData rawData; 112 private final boolean newLayer; 113 114 DownloadTask(DownloadParams settings, OsmServerReader reader, ProgressMonitor progressMonitor) { 115 super(tr("Downloading GPS data"), progressMonitor, false); 116 this.reader = reader; 117 this.newLayer = settings.isNewLayer(); 118 } 119 120 @Override 121 public void realRun() throws IOException, SAXException, OsmTransferException { 122 try { 123 if (isCanceled()) 124 return; 125 rawData = reader.parseRawGps(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 126 } catch (OsmTransferException e) { 127 if (isCanceled()) 128 return; 129 rememberException(e); 130 } 131 } 132 133 @Override 134 protected void finish() { 135 rememberDownloadedData(rawData); 136 if (rawData == null) 137 return; 138 String name = getLayerName(); 139 140 GpxImporterData layers = GpxImporter.loadLayers(rawData, reader.isGpxParsedProperly(), name, 141 tr("Markers from {0}", name)); 142 143 gpxLayer = layers.getGpxLayer(); 144 addOrMergeLayer(gpxLayer, findGpxMergeLayer()); 145 addOrMergeLayer(layers.getMarkerLayer(), findMarkerMergeLayer(gpxLayer)); 146 147 layers.getPostLayerTask().run(); 148 } 149 150 private String getLayerName() { 151 // Extract .gpx filename from URL to set the new layer name 152 final Matcher matcher = url != null ? GpxUrlPattern.EXTERNAL_GPX_FILE.matcher(url) : null; 153 final String newLayerName = matcher != null && matcher.matches() ? matcher.group(1) : null; 154 final String metadataName = rawData != null ? rawData.getString(GpxConstants.META_NAME) : null; 155 final String defaultName = tr("Downloaded GPX Data"); 156 157 if (Config.getPref().getBoolean("gpx.prefermetadataname", false)) { 158 return Utils.firstNotEmptyString(defaultName, metadataName, newLayerName); 159 } else { 160 return Utils.firstNotEmptyString(defaultName, newLayerName, metadataName); 161 } 162 } 163 164 private <L extends Layer> L addOrMergeLayer(L layer, L mergeLayer) { 165 if (layer == null) return null; 166 if (newLayer || mergeLayer == null) { 167 MainApplication.getLayerManager().addLayer(layer, zoomAfterDownload); 168 return layer; 169 } else { 170 mergeLayer.mergeFrom(layer); 171 mergeLayer.invalidate(); 172 return mergeLayer; 173 } 174 } 175 176 private GpxLayer findGpxMergeLayer() { 177 boolean merge = Config.getPref().getBoolean("download.gps.mergeWithLocal", false); 178 Layer active = MainApplication.getLayerManager().getActiveLayer(); 179 if (active instanceof GpxLayer && (merge || ((GpxLayer) active).data.fromServer)) 180 return (GpxLayer) active; 181 for (GpxLayer l : MainApplication.getLayerManager().getLayersOfType(GpxLayer.class)) { 182 if (merge || l.data.fromServer) 183 return l; 184 } 185 return null; 186 } 187 188 private MarkerLayer findMarkerMergeLayer(GpxLayer fromLayer) { 189 for (MarkerLayer l : MainApplication.getLayerManager().getLayersOfType(MarkerLayer.class)) { 190 if (fromLayer != null && l.fromLayer == fromLayer) 191 return l; 192 } 193 return null; 194 } 195 196 @Override 197 protected void cancel() { 198 setCanceled(true); 199 if (reader != null) { 200 reader.cancel(); 201 } 202 } 203 204 @Override 205 public ProgressTaskId canRunInBackground() { 206 return ProgressTaskIds.DOWNLOAD_GPS; 207 } 208 } 209 210 @Override 211 public String getConfirmationMessage(URL url) { 212 // TODO 213 return null; 214 } 215 216 @Override 217 public boolean isSafeForRemotecontrolRequests() { 218 return true; 219 } 220}