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