001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.io.IOException; 008import java.util.List; 009import java.util.Set; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.actions.AutoScaleAction; 013import org.openstreetmap.josm.data.osm.DataSet; 014import org.openstreetmap.josm.data.osm.DataSetMerger; 015import org.openstreetmap.josm.data.osm.Node; 016import org.openstreetmap.josm.data.osm.OsmPrimitive; 017import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 018import org.openstreetmap.josm.data.osm.PrimitiveId; 019import org.openstreetmap.josm.data.osm.Relation; 020import org.openstreetmap.josm.data.osm.Way; 021import org.openstreetmap.josm.gui.ExceptionDialogUtil; 022import org.openstreetmap.josm.gui.PleaseWaitRunnable; 023import org.openstreetmap.josm.gui.layer.OsmDataLayer; 024import org.openstreetmap.josm.gui.progress.ProgressMonitor; 025import org.openstreetmap.josm.gui.util.GuiHelper; 026import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 027import org.openstreetmap.josm.io.OsmServerObjectReader; 028import org.openstreetmap.josm.io.OsmTransferException; 029import org.xml.sax.SAXException; 030 031/** 032 * Task downloading a set of OSM primitives. 033 * @since 4081 034 */ 035public class DownloadPrimitivesTask extends PleaseWaitRunnable { 036 private DataSet ds; 037 private boolean canceled; 038 private Exception lastException; 039 private final List<PrimitiveId> ids; 040 041 private Set<PrimitiveId> missingPrimitives; 042 043 private final OsmDataLayer layer; 044 private final boolean fullRelation; 045 private MultiFetchServerObjectReader multiObjectReader; 046 private OsmServerObjectReader objectReader; 047 048 /** 049 * Constructs a new {@code DownloadPrimitivesTask}. 050 * 051 * @param layer the layer in which primitives are updated. Must not be null. 052 * @param ids a collection of primitives to update from the server. Set to 053 * the empty collection if null. 054 * @param fullRelation true if a full download is required, i.e., 055 * a download including the immediate children of a relation. 056 * @throws IllegalArgumentException thrown if layer is null. 057 */ 058 public DownloadPrimitivesTask(OsmDataLayer layer, List<PrimitiveId> ids, boolean fullRelation) throws IllegalArgumentException { 059 this(layer, ids, fullRelation, null); 060 } 061 062 /** 063 * Constructs a new {@code DownloadPrimitivesTask}. 064 * 065 * @param layer the layer in which primitives are updated. Must not be null. 066 * @param ids a collection of primitives to update from the server. Set to 067 * the empty collection if null. 068 * @param fullRelation true if a full download is required, i.e., 069 * a download including the immediate children of a relation. 070 * @param progressMonitor ProgressMonitor to use or null to create a new one. 071 * @throws IllegalArgumentException thrown if layer is null. 072 */ 073 public DownloadPrimitivesTask(OsmDataLayer layer, List<PrimitiveId> ids, boolean fullRelation, 074 ProgressMonitor progressMonitor) throws IllegalArgumentException { 075 super(tr("Download objects"), progressMonitor, false /* don't ignore exception */); 076 ensureParameterNotNull(layer, "layer"); 077 this.ids = ids; 078 this.layer = layer; 079 this.fullRelation = fullRelation; 080 } 081 082 @Override 083 protected void cancel() { 084 canceled = true; 085 synchronized(this) { 086 if (multiObjectReader != null) { 087 multiObjectReader.cancel(); 088 } 089 if (objectReader != null) { 090 objectReader.cancel(); 091 } 092 } 093 } 094 095 @Override 096 protected void finish() { 097 if (canceled) 098 return; 099 if (lastException != null) { 100 ExceptionDialogUtil.explainException(lastException); 101 return; 102 } 103 GuiHelper.runInEDTAndWait(new Runnable() { 104 @Override 105 public void run() { 106 layer.mergeFrom(ds); 107 if(Main.map != null) 108 AutoScaleAction.zoomTo(ds.allPrimitives()); 109 layer.onPostDownloadFromServer(); 110 } 111 }); 112 } 113 114 protected void initMultiFetchReader(MultiFetchServerObjectReader reader) { 115 getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to download ...")); 116 for (PrimitiveId id : ids) { 117 OsmPrimitive osm = layer.data.getPrimitiveById(id); 118 if (osm == null) { 119 switch (id.getType()) { 120 case NODE: 121 osm = new Node(id.getUniqueId()); 122 break; 123 case WAY: 124 osm = new Way(id.getUniqueId()); 125 break; 126 case RELATION: 127 osm = new Relation(id.getUniqueId()); 128 break; 129 default: throw new AssertionError(); 130 } 131 } 132 reader.append(osm); 133 } 134 } 135 136 @Override 137 protected void realRun() throws SAXException, IOException, OsmTransferException { 138 this.ds = new DataSet(); 139 DataSet theirDataSet; 140 try { 141 synchronized(this) { 142 if (canceled) return; 143 multiObjectReader = new MultiFetchServerObjectReader(); 144 } 145 initMultiFetchReader(multiObjectReader); 146 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 147 missingPrimitives = multiObjectReader.getMissingPrimitives(); 148 synchronized(this) { 149 multiObjectReader = null; 150 } 151 DataSetMerger merger = new DataSetMerger(ds, theirDataSet); 152 merger.merge(); 153 154 // if incomplete relation members exist, download them too 155 for (Relation r : ds.getRelations()) { 156 if (canceled) return; 157 // Relations may be incomplete in case of nested relations if child relations are accessed before their parent 158 // (it may happen because "relations" has no deterministic sort order, see #10388) 159 if (r.isIncomplete() || r.hasIncompleteMembers()) { 160 synchronized(this) { 161 if (canceled) return; 162 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation); 163 } 164 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 165 synchronized (this) { 166 objectReader = null; 167 } 168 merger = new DataSetMerger(ds, theirDataSet); 169 merger.merge(); 170 } 171 } 172 173 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 174 // nodes isn't present in the local data set. We therefore fully load all 175 // ways with incomplete nodes. 176 // 177 for (Way w : ds.getWays()) { 178 if (canceled) return; 179 if (w.hasIncompleteNodes()) { 180 synchronized(this) { 181 if (canceled) return; 182 objectReader = new OsmServerObjectReader(w.getId(), OsmPrimitiveType.WAY, true /* full */); 183 } 184 theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 185 synchronized (this) { 186 objectReader = null; 187 } 188 merger = new DataSetMerger(ds, theirDataSet); 189 merger.merge(); 190 } 191 } 192 193 } catch(Exception e) { 194 if (canceled) return; 195 lastException = e; 196 } 197 } 198 199 /** 200 * replies the set of ids of all primitives for which a fetch request to the 201 * server was submitted but which are not available from the server (the server 202 * replied a return code of 404) 203 * 204 * @return the set of ids of missing primitives 205 */ 206 public Set<PrimitiveId> getMissingPrimitives() { 207 return missingPrimitives; 208 } 209 210}