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