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