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("https?://.*/.*\\.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("https?://.*/(.*\\.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 }