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