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