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}