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.time.Instant; 007import java.util.Arrays; 008import java.util.HashMap; 009import java.util.Iterator; 010import java.util.Map; 011import java.util.Map.Entry; 012import java.util.Optional; 013import java.util.concurrent.Future; 014import java.util.concurrent.RejectedExecutionException; 015import java.util.regex.Matcher; 016 017import org.openstreetmap.josm.data.Bounds; 018import org.openstreetmap.josm.data.osm.AbstractPrimitive; 019import org.openstreetmap.josm.data.osm.DataSet; 020import org.openstreetmap.josm.data.osm.Node; 021import org.openstreetmap.josm.data.osm.NodeData; 022import org.openstreetmap.josm.data.osm.OsmPrimitive; 023import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 024import org.openstreetmap.josm.data.osm.PrimitiveData; 025import org.openstreetmap.josm.data.osm.PrimitiveId; 026import org.openstreetmap.josm.data.osm.RelationData; 027import org.openstreetmap.josm.data.osm.WayData; 028import org.openstreetmap.josm.data.osm.history.History; 029import org.openstreetmap.josm.data.osm.history.HistoryDataSet; 030import org.openstreetmap.josm.data.osm.history.HistoryDataSetListener; 031import org.openstreetmap.josm.data.osm.history.HistoryNode; 032import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive; 033import org.openstreetmap.josm.data.osm.history.HistoryRelation; 034import org.openstreetmap.josm.data.osm.history.HistoryWay; 035import org.openstreetmap.josm.gui.MainApplication; 036import org.openstreetmap.josm.gui.history.HistoryLoadTask; 037import org.openstreetmap.josm.gui.progress.ProgressMonitor; 038import org.openstreetmap.josm.io.Compression; 039import org.openstreetmap.josm.io.OsmApi; 040import org.openstreetmap.josm.io.OsmServerLocationReader; 041import org.openstreetmap.josm.io.OsmServerReader; 042import org.openstreetmap.josm.io.OsmTransferException; 043import org.openstreetmap.josm.io.UrlPatterns.OsmChangeUrlPattern; 044import org.openstreetmap.josm.tools.Logging; 045 046/** 047 * Task allowing to download OsmChange data (http://wiki.openstreetmap.org/wiki/OsmChange). 048 * @since 4530 049 */ 050public class DownloadOsmChangeTask extends DownloadOsmTask { 051 052 @Override 053 public String[] getPatterns() { 054 return patterns(OsmChangeUrlPattern.class); 055 } 056 057 @Override 058 public String getTitle() { 059 return tr("Download OSM Change"); 060 } 061 062 @Override 063 public Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) { 064 return null; 065 } 066 067 @Override 068 public Future<?> loadUrl(DownloadParams settings, final String url, ProgressMonitor progressMonitor) { 069 Optional<OsmChangeUrlPattern> urlPattern = Arrays.stream(OsmChangeUrlPattern.values()).filter(p -> p.matches(url)).findFirst(); 070 String newUrl = url; 071 final Matcher matcher = OsmChangeUrlPattern.OSM_WEBSITE.matcher(url); 072 if (matcher.matches()) { 073 newUrl = OsmApi.getOsmApi().getBaseUrl() + "changeset/" + Long.parseLong(matcher.group(2)) + "/download"; 074 } 075 downloadTask = new DownloadTask(settings, new OsmServerLocationReader(newUrl), progressMonitor, true, 076 Compression.byExtension(newUrl)); 077 // Extract .osc filename from URL to set the new layer name 078 extractOsmFilename(settings, urlPattern.orElse(OsmChangeUrlPattern.EXTERNAL_OSC_FILE).pattern(), newUrl); 079 return MainApplication.worker.submit(downloadTask); 080 } 081 082 /** 083 * OsmChange download task. 084 */ 085 protected class DownloadTask extends DownloadOsmTask.DownloadTask { 086 087 /** 088 * Constructs a new {@code DownloadTask}. 089 * @param settings download settings 090 * @param reader OSM data reader 091 * @param progressMonitor progress monitor 092 * @param zoomAfterDownload If true, the map view will zoom to download area after download 093 * @param compression compression to use 094 */ 095 public DownloadTask(DownloadParams settings, OsmServerReader reader, ProgressMonitor progressMonitor, 096 boolean zoomAfterDownload, Compression compression) { 097 super(settings, reader, progressMonitor, zoomAfterDownload, compression); 098 } 099 100 @Override 101 protected DataSet parseDataSet() throws OsmTransferException { 102 return reader.parseOsmChange(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false), 103 compression); 104 } 105 106 @Override 107 protected void finish() { 108 super.finish(); 109 if (isFailed() || isCanceled() || downloadedData == null) 110 return; // user canceled download or error occurred 111 try { 112 // A changeset does not contain all referred primitives, this is the map of incomplete ones 113 // For each incomplete primitive, we'll have to get its state at date it was referred 114 Map<OsmPrimitive, Instant> toLoad = new HashMap<>(); 115 for (OsmPrimitive p : downloadedData.allNonDeletedPrimitives()) { 116 if (p.isIncomplete()) { 117 Instant timestamp = p.getReferrers().stream() 118 .filter(ref -> !ref.isTimestampEmpty()) 119 .findFirst() 120 .map(AbstractPrimitive::getInstant) 121 .orElse(null); 122 toLoad.put(p, timestamp); 123 } 124 } 125 if (isCanceled()) return; 126 // Let's load all required history 127 MainApplication.worker.submit(new HistoryLoaderAndListener(toLoad)); 128 } catch (RejectedExecutionException e) { 129 rememberException(e); 130 setFailed(true); 131 } 132 } 133 } 134 135 /** 136 * Loads history and updates incomplete primitives. 137 */ 138 private static final class HistoryLoaderAndListener extends HistoryLoadTask implements HistoryDataSetListener { 139 140 private final Map<OsmPrimitive, Instant> toLoad; 141 142 private HistoryLoaderAndListener(Map<OsmPrimitive, Instant> toLoad) { 143 this.toLoad = toLoad; 144 this.setChangesetDataNeeded(false); 145 addOsmPrimitives(toLoad.keySet()); 146 // Updating process is done after all history requests have been made 147 HistoryDataSet.getInstance().addHistoryDataSetListener(this); 148 } 149 150 @Override 151 public void historyUpdated(HistoryDataSet source, PrimitiveId id) { 152 Map<OsmPrimitive, Instant> toLoadNext = new HashMap<>(); 153 for (Iterator<Entry<OsmPrimitive, Instant>> it = toLoad.entrySet().iterator(); it.hasNext();) { 154 Entry<OsmPrimitive, Instant> entry = it.next(); 155 OsmPrimitive p = entry.getKey(); 156 History history = source.getHistory(p.getPrimitiveId()); 157 Instant date = entry.getValue(); 158 // If the history has been loaded and a timestamp is known 159 if (history != null && date != null) { 160 // Lookup for the primitive version at the specified timestamp 161 HistoryOsmPrimitive hp = history.getByDate(date); 162 if (hp != null) { 163 PrimitiveData data; 164 165 switch (p.getType()) { 166 case NODE: 167 data = ((HistoryNode) hp).fillPrimitiveData(new NodeData()); 168 break; 169 case WAY: 170 data = ((HistoryWay) hp).fillPrimitiveData(new WayData()); 171 // Find incomplete nodes to load at next run 172 for (Long nodeId : ((HistoryWay) hp).getNodes()) { 173 if (p.getDataSet().getPrimitiveById(nodeId, OsmPrimitiveType.NODE) == null) { 174 Node n = new Node(nodeId); 175 p.getDataSet().addPrimitive(n); 176 toLoadNext.put(n, date); 177 } 178 } 179 break; 180 case RELATION: 181 data = ((HistoryRelation) hp).fillPrimitiveData(new RelationData()); 182 break; 183 default: throw new AssertionError("Unknown primitive type"); 184 } 185 186 // Load the history data 187 try { 188 p.load(data); 189 // Forget this primitive 190 it.remove(); 191 } catch (AssertionError e) { 192 Logging.log(Logging.LEVEL_ERROR, "Cannot load "+p+':', e); 193 } 194 } 195 } 196 } 197 source.removeHistoryDataSetListener(this); 198 if (toLoadNext.isEmpty()) { 199 // No more primitive to update. Processing is finished 200 // Be sure all updated primitives are correctly drawn 201 MainApplication.getMap().repaint(); 202 } else { 203 // Some primitives still need to be loaded 204 // Let's load all required history 205 MainApplication.worker.submit(new HistoryLoaderAndListener(toLoadNext)); 206 } 207 } 208 209 @Override 210 public void historyDataSetCleared(HistoryDataSet source) { 211 // Do nothing 212 } 213 } 214}