| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.actions.downloadtasks;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.io.IOException;
|
|---|
| 7 | import java.net.URL;
|
|---|
| 8 | import java.util.Objects;
|
|---|
| 9 | import java.util.Optional;
|
|---|
| 10 | import java.util.concurrent.Future;
|
|---|
| 11 | import java.util.regex.Matcher;
|
|---|
| 12 | import java.util.stream.Stream;
|
|---|
| 13 |
|
|---|
| 14 | import org.openstreetmap.josm.data.Bounds;
|
|---|
| 15 | import org.openstreetmap.josm.data.Bounds.ParseMethod;
|
|---|
| 16 | import org.openstreetmap.josm.data.ProjectionBounds;
|
|---|
| 17 | import org.openstreetmap.josm.data.gpx.GpxConstants;
|
|---|
| 18 | import org.openstreetmap.josm.data.gpx.GpxData;
|
|---|
| 19 | import org.openstreetmap.josm.gui.MainApplication;
|
|---|
| 20 | import org.openstreetmap.josm.gui.PleaseWaitRunnable;
|
|---|
| 21 | import org.openstreetmap.josm.gui.io.importexport.GpxImporter;
|
|---|
| 22 | import org.openstreetmap.josm.gui.io.importexport.GpxImporter.GpxImporterData;
|
|---|
| 23 | import org.openstreetmap.josm.gui.layer.GpxLayer;
|
|---|
| 24 | import org.openstreetmap.josm.gui.layer.GpxRouteLayer;
|
|---|
| 25 | import org.openstreetmap.josm.gui.layer.Layer;
|
|---|
| 26 | import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
|
|---|
| 27 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
|---|
| 28 | import org.openstreetmap.josm.gui.progress.ProgressTaskId;
|
|---|
| 29 | import org.openstreetmap.josm.gui.progress.ProgressTaskIds;
|
|---|
| 30 | import org.openstreetmap.josm.io.BoundingBoxDownloader;
|
|---|
| 31 | import org.openstreetmap.josm.io.OsmServerLocationReader;
|
|---|
| 32 | import org.openstreetmap.josm.io.OsmServerReader;
|
|---|
| 33 | import org.openstreetmap.josm.io.OsmTransferException;
|
|---|
| 34 | import org.openstreetmap.josm.io.UrlPatterns.GpxUrlPattern;
|
|---|
| 35 | import org.openstreetmap.josm.spi.preferences.Config;
|
|---|
| 36 | import org.openstreetmap.josm.tools.Utils;
|
|---|
| 37 | import org.xml.sax.SAXException;
|
|---|
| 38 |
|
|---|
| 39 | /**
|
|---|
| 40 | * Task allowing to download GPS data.
|
|---|
| 41 | */
|
|---|
| 42 | public class DownloadGpsTask extends AbstractDownloadTask<GpxData> {
|
|---|
| 43 |
|
|---|
| 44 | private DownloadTask downloadTask;
|
|---|
| 45 | private GpxLayer gpxLayer;
|
|---|
| 46 |
|
|---|
| 47 | protected String url;
|
|---|
| 48 |
|
|---|
| 49 | @Override
|
|---|
| 50 | public String[] getPatterns() {
|
|---|
| 51 | return patterns(GpxUrlPattern.class);
|
|---|
| 52 | }
|
|---|
| 53 |
|
|---|
| 54 | @Override
|
|---|
| 55 | public String getTitle() {
|
|---|
| 56 | return tr("Download GPS");
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | @Override
|
|---|
| 60 | public Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) {
|
|---|
| 61 | downloadTask = new DownloadTask(settings,
|
|---|
| 62 | new BoundingBoxDownloader(downloadArea), progressMonitor);
|
|---|
| 63 | // We need submit instead of execute so we can wait for it to finish and get the error
|
|---|
| 64 | // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
|
|---|
| 65 | return MainApplication.worker.submit(downloadTask);
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | @Override
|
|---|
| 69 | public Future<?> loadUrl(DownloadParams settings, String url, ProgressMonitor progressMonitor) {
|
|---|
| 70 | this.url = Objects.requireNonNull(url);
|
|---|
| 71 | final Optional<String> mappedUrl = Stream.of(GpxUrlPattern.USER_TRACE_ID, GpxUrlPattern.EDIT_TRACE_ID)
|
|---|
| 72 | .map(p -> p.matcher(url))
|
|---|
| 73 | .filter(Matcher::matches)
|
|---|
| 74 | .map(m -> "https://www.openstreetmap.org/trace/" + m.group(2) + "/data")
|
|---|
| 75 | .findFirst();
|
|---|
| 76 | if (mappedUrl.isPresent()) {
|
|---|
| 77 | return loadUrl(settings, mappedUrl.get(), progressMonitor);
|
|---|
| 78 | }
|
|---|
| 79 | if (Stream.of(GpxUrlPattern.TRACE_ID, GpxUrlPattern.EXTERNAL_GPX_SCRIPT,
|
|---|
| 80 | GpxUrlPattern.EXTERNAL_GPX_FILE, GpxUrlPattern.TASKING_MANAGER)
|
|---|
| 81 | .anyMatch(p -> p.matches(url))) {
|
|---|
| 82 | downloadTask = new DownloadTask(settings,
|
|---|
| 83 | new OsmServerLocationReader(url), progressMonitor);
|
|---|
| 84 | // We need submit instead of execute so we can wait for it to finish and get the error
|
|---|
| 85 | // message if necessary. If no one calls getErrorMessage() it just behaves like execute.
|
|---|
| 86 | return MainApplication.worker.submit(downloadTask);
|
|---|
| 87 |
|
|---|
| 88 | } else if (GpxUrlPattern.TRACKPOINTS_BBOX.matches(url)) {
|
|---|
| 89 | String[] table = url.split("[?=&]", -1);
|
|---|
| 90 | for (int i = 0; i < table.length; i++) {
|
|---|
| 91 | if ("bbox".equals(table[i]) && i < table.length-1)
|
|---|
| 92 | return download(settings, new Bounds(table[i+1], ",", ParseMethod.LEFT_BOTTOM_RIGHT_TOP), progressMonitor);
|
|---|
| 93 | }
|
|---|
| 94 | }
|
|---|
| 95 | return null;
|
|---|
| 96 | }
|
|---|
| 97 |
|
|---|
| 98 | @Override
|
|---|
| 99 | 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 | }
|
|---|