Ticket #18566: 18566.2.patch
File 18566.2.patch, 37.1 KB (added by , 4 years ago) |
---|
-
src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java
12 12 import java.awt.event.ActionEvent; 13 13 import java.awt.event.MouseEvent; 14 14 import java.io.IOException; 15 import java.net.HttpURLConnection;16 15 import java.util.Arrays; 17 16 import java.util.HashSet; 18 import java.util.Iterator;19 17 import java.util.List; 20 18 import java.util.Set; 21 import java.util.Stack;22 19 import java.util.stream.Collectors; 23 20 24 21 import javax.swing.AbstractAction; … … 37 34 import org.openstreetmap.josm.data.osm.DataSetMerger; 38 35 import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 39 36 import org.openstreetmap.josm.data.osm.OsmPrimitive; 40 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;41 37 import org.openstreetmap.josm.data.osm.Relation; 42 38 import org.openstreetmap.josm.data.osm.RelationMember; 43 39 import org.openstreetmap.josm.gui.ExceptionDialogUtil; … … 47 43 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 48 44 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 49 45 import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor; 46 import org.openstreetmap.josm.gui.util.GuiHelper; 50 47 import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; 48 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 51 49 import org.openstreetmap.josm.io.OsmApi; 52 import org.openstreetmap.josm.io.OsmApiException;53 import org.openstreetmap.josm.io.OsmServerObjectReader;54 50 import org.openstreetmap.josm.io.OsmTransferException; 55 51 import org.openstreetmap.josm.tools.CheckParameterUtil; 56 52 import org.openstreetmap.josm.tools.ImageProvider; … … 339 335 OsmApi.getOsmApi().cancel(); 340 336 } 341 337 342 protected void refreshView(Relation relation) { 343 for (int i = 0; i < childTree.getRowCount(); i++) { 344 Relation reference = (Relation) childTree.getPathForRow(i).getLastPathComponent(); 345 if (reference == relation) { 346 model.refreshNode(childTree.getPathForRow(i)); 338 protected MultiFetchServerObjectReader createReader() { 339 return MultiFetchServerObjectReader.create().setRecurseDownAppended(false).setRecurseDownRelations(true); 340 } 341 342 /** 343 * Merges the primitives in <code>ds</code> to the dataset of the edit layer 344 * 345 * @param ds the data set 346 */ 347 protected void mergeDataSet(DataSet ds) { 348 if (ds != null) { 349 final DataSetMerger visitor = new DataSetMerger(getLayer().getDataSet(), ds); 350 visitor.merge(); 351 if (!visitor.getConflicts().isEmpty()) { 352 getLayer().getConflicts().add(visitor.getConflicts()); 353 conflictsCount += visitor.getConflicts().size(); 347 354 } 348 355 } 349 356 } 350 357 358 protected void refreshView(Relation relation) { 359 GuiHelper.runInEDT(() -> { 360 for (int i = 0; i < childTree.getRowCount(); i++) { 361 Relation reference = (Relation) childTree.getPathForRow(i).getLastPathComponent(); 362 if (reference == relation) { 363 model.refreshNode(childTree.getPathForRow(i)); 364 } 365 } 366 }); 367 } 368 351 369 @Override 352 370 protected void finish() { 353 371 if (canceled) … … 374 392 * The asynchronous task for downloading relation members. 375 393 */ 376 394 class DownloadAllChildrenTask extends DownloadTask { 377 private final Stack<Relation> relationsToDownload; 378 private final Set<Long> downloadedRelationIds; 395 private final Relation relation; 379 396 380 397 DownloadAllChildrenTask(Dialog parent, Relation r) { 381 398 super(tr("Download relation members"), parent); 382 relationsToDownload = new Stack<>(); 383 downloadedRelationIds = new HashSet<>(); 384 relationsToDownload.push(r); 399 relation = r; 385 400 } 386 401 387 402 /** … … 405 420 ); 406 421 } 407 422 408 /**409 * Remembers the child relations to download410 *411 * @param parent the parent relation412 */413 protected void rememberChildRelationsToDownload(Relation parent) {414 downloadedRelationIds.add(parent.getId());415 for (RelationMember member: parent.getMembers()) {416 if (member.isRelation()) {417 Relation child = member.getRelation();418 if (!downloadedRelationIds.contains(child.getId())) {419 relationsToDownload.push(child);420 }421 }422 }423 }424 425 /**426 * Merges the primitives in <code>ds</code> to the dataset of the edit layer427 *428 * @param ds the data set429 */430 protected void mergeDataSet(DataSet ds) {431 if (ds != null) {432 final DataSetMerger visitor = new DataSetMerger(getLayer().getDataSet(), ds);433 visitor.merge();434 if (!visitor.getConflicts().isEmpty()) {435 getLayer().getConflicts().add(visitor.getConflicts());436 conflictsCount += visitor.getConflicts().size();437 }438 }439 }440 441 423 @Override 442 424 protected void realRun() throws SAXException, IOException, OsmTransferException { 443 425 try { 444 while (!relationsToDownload.isEmpty() && !canceled) { 445 Relation r = relationsToDownload.pop(); 446 if (r.isNew()) { 447 continue; 448 } 449 rememberChildRelationsToDownload(r); 450 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance()))); 451 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, 452 true); 453 DataSet dataSet = null; 454 try { 455 dataSet = reader.parseOsm(progressMonitor 456 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 457 } catch (OsmApiException e) { 458 if (e.getResponseCode() == HttpURLConnection.HTTP_GONE) { 459 warnBecauseOfDeletedRelation(r); 460 continue; 461 } 462 throw e; 463 } 464 mergeDataSet(dataSet); 465 refreshView(r); 426 MultiFetchServerObjectReader reader = createReader(); 427 reader.append(relation.getMemberPrimitives()); 428 DataSet dataSet = reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 429 mergeDataSet(dataSet); 430 Utils.filteredCollection(reader.getMissingPrimitives(), Relation.class).forEach(this::warnBecauseOfDeletedRelation); 431 for (Relation rel : dataSet.getRelations()) { 432 refreshView((Relation) getLayer().getDataSet().getPrimitiveById(rel)); 466 433 } 467 434 SwingUtilities.invokeLater(MainApplication.getMap()::repaint); 468 435 } catch (OsmTransferException e) { … … 486 453 this.relations = relations; 487 454 } 488 455 489 protected void mergeDataSet(DataSet dataSet) {490 if (dataSet != null) {491 final DataSetMerger visitor = new DataSetMerger(getLayer().getDataSet(), dataSet);492 visitor.merge();493 if (!visitor.getConflicts().isEmpty()) {494 getLayer().getConflicts().add(visitor.getConflicts());495 conflictsCount += visitor.getConflicts().size();496 }497 }498 }499 500 456 @Override 501 457 protected void realRun() throws SAXException, IOException, OsmTransferException { 502 458 try { 503 Iterator<Relation> it = relations.iterator(); 504 while (it.hasNext() && !canceled) { 505 Relation r = it.next(); 506 if (r.isNew()) { 507 continue; 508 } 509 progressMonitor.setCustomText(tr("Downloading relation {0}", r.getDisplayName(DefaultNameFormatter.getInstance()))); 510 OsmServerObjectReader reader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, 511 true); 512 DataSet dataSet = reader.parseOsm(progressMonitor 513 .createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 514 mergeDataSet(dataSet); 515 refreshView(r); 459 MultiFetchServerObjectReader reader = createReader(); 460 reader.append(relations); 461 DataSet dataSet = reader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 462 mergeDataSet(dataSet); 463 464 for (Relation rel : dataSet.getRelations()) { 465 refreshView((Relation) getLayer().getDataSet().getPrimitiveById(rel)); 516 466 } 467 517 468 } catch (OsmTransferException e) { 518 469 if (canceled) { 519 470 Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString())); -
src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java
129 129 if (canceled) return; 130 130 objectReader = MultiFetchServerObjectReader.create(); 131 131 } 132 objectReader.setRecurseDownAppended(false).setRecurseDownRelations(true); 132 133 objectReader.append(children); 133 134 progressMonitor.indeterminateSubTask( 134 135 buildDownloadFeedbackMessage() -
src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java
11 11 12 12 import org.openstreetmap.josm.data.osm.DataSet; 13 13 import org.openstreetmap.josm.data.osm.DataSetMerger; 14 import org.openstreetmap.josm.data.osm.DefaultNameFormatter;15 14 import org.openstreetmap.josm.data.osm.Relation; 16 15 import org.openstreetmap.josm.gui.ExceptionDialogUtil; 17 16 import org.openstreetmap.josm.gui.MainApplication; 18 17 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 19 18 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 20 import org.openstreetmap.josm.io.OsmServerObjectReader; 19 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 20 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 21 import org.openstreetmap.josm.io.OsmServerReader; 21 22 import org.openstreetmap.josm.io.OsmTransferException; 22 23 import org.openstreetmap.josm.tools.CheckParameterUtil; 23 24 import org.openstreetmap.josm.tools.Logging; … … 33 34 private Exception lastException; 34 35 private final Collection<Relation> relations; 35 36 private final OsmDataLayer layer; 36 private OsmServer ObjectReader objectReader;37 private OsmServerReader objectReader; 37 38 38 39 /** 39 40 * Creates the download task … … 77 78 protected void realRun() throws SAXException, IOException, OsmTransferException { 78 79 try { 79 80 final DataSet allDownloads = new DataSet(); 80 int i = 0;81 81 getProgressMonitor().setTicksCount(relations.size()); 82 for (Relation relation: relations) { 83 i++; 84 getProgressMonitor().setCustomText(tr("({0}/{1}): Downloading relation ''{2}''...", i, relations.size(), 85 relation.getDisplayName(DefaultNameFormatter.getInstance()))); 86 synchronized (this) { 87 if (canceled) return; 88 objectReader = new OsmServerObjectReader(relation.getPrimitiveId(), true /* full download */); 89 } 90 DataSet dataSet = objectReader.parseOsm( 91 getProgressMonitor().createSubTaskMonitor(0, false) 92 ); 93 if (dataSet == null) 82 MultiFetchServerObjectReader multiObjectReader; 83 synchronized (this) { 84 if (canceled) 94 85 return; 95 synchronized (this) { 96 if (canceled) return; 97 objectReader = null; 98 } 99 DataSetMerger merger = new DataSetMerger(allDownloads, dataSet); 100 merger.merge(); 101 getProgressMonitor().worked(1); 86 multiObjectReader = MultiFetchServerObjectReader.create(); 102 87 } 103 88 multiObjectReader.setRecurseDownRelations(true).setRecurseDownAppended(false); 89 multiObjectReader.append(relations); 90 DataSet dataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); 91 if (dataSet == null) 92 return; 93 synchronized (this) { 94 if (canceled) 95 return; 96 } 97 new DataSetMerger(allDownloads, dataSet).merge(); 104 98 SwingUtilities.invokeAndWait(() -> { 105 99 layer.mergeFrom(allDownloads); 106 100 layer.onPostDownloadFromServer(); -
src/org/openstreetmap/josm/gui/io/AbstractPrimitiveTask.java
11 11 import org.openstreetmap.josm.data.osm.DataSetMerger; 12 12 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 13 13 import org.openstreetmap.josm.data.osm.PrimitiveId; 14 import org.openstreetmap.josm.data.osm.Relation;15 14 import org.openstreetmap.josm.data.osm.Way; 16 15 import org.openstreetmap.josm.gui.ExceptionDialogUtil; 17 16 import org.openstreetmap.josm.gui.MainApplication; … … 41 40 protected OsmServerObjectReader objectReader; 42 41 43 42 private boolean zoom; 44 pr ivate boolean downloadRelations;45 private boolean fullRelation;43 protected boolean fullRelation; 44 private boolean allowOverpas = true; 46 45 47 46 protected AbstractPrimitiveTask(String title, OsmDataLayer layer) { 48 47 this(title, new PleaseWaitProgressMonitor(title), layer); … … 70 69 } 71 70 72 71 /** 73 * Sets whether . 74 * @param downloadRelations {@code true} if 72 * Sets whether all members of the relation should be downloaded completely. 75 73 * @param fullRelation {@code true} if a full download is required, 76 74 * i.e., a download including the immediate children of a relation. 77 75 * @return {@code this} 76 * since xxx (changed parameter list) 78 77 */ 79 public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) { 80 this.downloadRelations = downloadRelations; 78 public final AbstractPrimitiveTask setDownloadRelations(boolean fullRelation) { 81 79 this.fullRelation = fullRelation; 82 80 return this; 83 81 } 84 82 85 83 /** 84 * Allow Overpass API? By default it is allowed. As of 2020-01-27 Overpass doesn't return invisible objects. 85 * @param b if {@code false} don't allow Overpass api. 86 */ 87 protected void setAllowOverpass(boolean b) { 88 allowOverpas = b; 89 } 90 91 /** 86 92 * Replies the set of ids of all primitives for which a fetch request to the 87 93 * server was submitted but which are not available from the server (the server 88 94 * replied a return code of 404) … … 100 106 synchronized (this) { 101 107 if (canceled) 102 108 return; 103 multiObjectReader = MultiFetchServerObjectReader.create(); 109 if (allowOverpas) 110 multiObjectReader = MultiFetchServerObjectReader.create(); 111 else 112 multiObjectReader = MultiFetchServerObjectReader.create(false); 113 multiObjectReader.setRecurseDownRelations(fullRelation); 104 114 } 105 115 initMultiFetchReader(multiObjectReader); 106 116 theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); … … 110 120 } 111 121 new DataSetMerger(ds, theirDataSet).merge(); 112 122 113 if (downloadRelations) {114 loadIncompleteRelationMembers();115 }116 117 123 loadIncompleteNodes(); 118 124 } catch (OsmTransferException e) { 119 125 if (canceled) … … 122 128 } 123 129 } 124 130 125 protected void loadIncompleteRelationMembers() throws OsmTransferException {126 // if incomplete relation members exist, download them too127 for (Relation r : ds.getRelations()) {128 if (canceled)129 return;130 // Relations may be incomplete in case of nested relations if child relations are accessed before their parent131 // (it may happen because "relations" has no deterministic sort order, see #10388)132 if (r.isIncomplete() || r.hasIncompleteMembers()) {133 synchronized (this) {134 if (canceled)135 return;136 objectReader = new OsmServerObjectReader(r.getId(), OsmPrimitiveType.RELATION, fullRelation);137 }138 DataSet theirDataSet = objectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));139 synchronized (this) {140 objectReader = null;141 }142 new DataSetMerger(ds, theirDataSet).merge();143 }144 }145 }146 147 131 protected void loadIncompleteNodes() throws OsmTransferException { 148 132 // a way loaded with MultiFetch may have incomplete nodes because at least one of its 149 133 // nodes isn't present in the local data set. We therefore fully load all ways with incomplete nodes. -
src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java
5 5 6 6 import java.util.List; 7 7 8 import org.openstreetmap.josm.data.osm.Node;9 import org.openstreetmap.josm.data.osm.OsmPrimitive;10 8 import org.openstreetmap.josm.data.osm.PrimitiveId; 11 import org.openstreetmap.josm.data.osm.Relation;12 import org.openstreetmap.josm.data.osm.Way;13 9 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 14 10 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 15 11 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; … … 52 48 super(tr("Download objects"), progressMonitor, layer); 53 49 this.ids = ids; 54 50 setZoom(true); 55 setDownloadRelations( true,fullRelation);51 setDownloadRelations(fullRelation); 56 52 } 57 53 58 54 @Override 59 55 protected void initMultiFetchReader(MultiFetchServerObjectReader reader) { 60 56 getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to download ...")); 61 for (PrimitiveId id : ids) { 62 OsmPrimitive osm = layer.data.getPrimitiveById(id); 63 if (osm == null) { 64 switch (id.getType()) { 65 case NODE: 66 osm = new Node(id.getUniqueId()); 67 break; 68 case WAY: 69 osm = new Way(id.getUniqueId()); 70 break; 71 case RELATION: 72 osm = new Relation(id.getUniqueId()); 73 break; 74 default: throw new AssertionError(); 75 } 76 } 77 reader.append(osm); 78 } 57 reader.setRecurseDownRelations(fullRelation); 58 reader.appendIds(ids); 79 59 } 80 60 } -
src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java
6 6 import java.util.Collection; 7 7 import java.util.Collections; 8 8 9 import org.openstreetmap.josm.data.osm.Node;10 9 import org.openstreetmap.josm.data.osm.OsmPrimitive; 11 import org.openstreetmap.josm.data.osm.Relation;12 import org.openstreetmap.josm.data.osm.Way;13 10 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 14 11 import org.openstreetmap.josm.io.MultiFetchServerObjectReader; 12 import org.openstreetmap.josm.spi.preferences.Config; 15 13 16 14 /** 17 15 * The asynchronous task for updating a collection of objects using multi fetch. … … 32 30 public UpdatePrimitivesTask(OsmDataLayer layer, Collection<? extends OsmPrimitive> toUpdate) { 33 31 super(tr("Update objects"), layer); 34 32 this.toUpdate = toUpdate != null ? toUpdate : Collections.<OsmPrimitive>emptyList(); 33 setDownloadRelations(Config.getPref().getBoolean("update.selected.complete-relation", true)); 34 setAllowOverpass(false); // Overpass doesn't return invisible objects 35 35 } 36 36 37 protected void initMultiFetchReaderWithNodes(MultiFetchServerObjectReader reader) {38 getProgressMonitor().indeterminateSubTask(tr("Initializing nodes to update ..."));39 for (OsmPrimitive primitive : toUpdate) {40 if (primitive instanceof Node && !primitive.isNew()) {41 reader.append(primitive);42 }43 }44 }45 46 protected void initMultiFetchReaderWithWays(MultiFetchServerObjectReader reader) {47 getProgressMonitor().indeterminateSubTask(tr("Initializing ways to update ..."));48 for (OsmPrimitive primitive : toUpdate) {49 if (primitive instanceof Way && !primitive.isNew()) {50 // this also adds way nodes51 reader.append(primitive);52 }53 }54 }55 56 protected void initMultiFetchReaderWithRelations(MultiFetchServerObjectReader reader) {57 getProgressMonitor().indeterminateSubTask(tr("Initializing relations to update ..."));58 for (OsmPrimitive primitive : toUpdate) {59 if (primitive instanceof Relation && !primitive.isNew()) {60 // this also adds relation members61 reader.append(primitive);62 }63 }64 }65 66 37 @Override 67 38 protected void initMultiFetchReader(MultiFetchServerObjectReader reader) { 68 initMultiFetchReaderWithNodes(reader); 69 initMultiFetchReaderWithWays(reader); 70 initMultiFetchReaderWithRelations(reader); 39 // don't update new primitives 40 toUpdate.stream().filter(p -> !p.isNew()).forEach(reader::append); 71 41 } 72 42 } -
src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java
5 5 import java.util.stream.Collectors; 6 6 7 7 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 8 import org.openstreetmap.josm.tools.Logging; 8 9 import org.openstreetmap.josm.tools.Utils; 9 10 10 11 /** … … 12 13 * 13 14 * @since 9241 14 15 */ 15 class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {16 public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader { 16 17 17 18 @Override 18 19 protected String buildRequestString(final OsmPrimitiveType type, Set<Long> idPackage) { 19 final String query = idPackage.stream() 20 .map(x -> type.getAPIName() + '(' + x + ");>;") 21 .collect(Collectors.joining("", "(", ");out meta;")); 20 final String query = "(" + type.getAPIName() + "(id:" 21 + idPackage.stream().map(String::valueOf).collect(Collectors.joining(",")) + ");" 22 + getRecurseOption(type) + ");out meta;"; 23 Logging.debug("{0} {1}", "Overpass query:", query); 22 24 return "interpreter?data=" + Utils.encodeUrl(query); 23 25 } 24 26 27 private String getRecurseOption(final OsmPrimitiveType type) { 28 if (type == OsmPrimitiveType.WAY) 29 return ">;"; 30 if (type == OsmPrimitiveType.RELATION && recurseDownRelations) 31 return ">>;"; // >>; needed for sub relations 32 return ""; 33 } 34 25 35 @Override 26 36 protected String getBaseUrl() { 27 37 return OverpassDownloadReader.OVERPASS_SERVER.get(); … … 33 43 // accomplished using >; in the query string above 34 44 return true; 35 45 } 46 36 47 } -
src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java
73 73 private Set<PrimitiveId> missingPrimitives; 74 74 private final DataSet outputDataSet; 75 75 76 protected boolean recurseDownRelations; 77 private boolean recurseDownAppended = true; 78 76 79 /** 77 80 * Constructs a {@code MultiFetchServerObjectReader}. 78 81 */ … … 159 162 * @return this 160 163 */ 161 164 public MultiFetchServerObjectReader appendNode(Node node) { 162 if (node == null ) return this;165 if (node == null || node.isNew()) return this; 163 166 remember(node.getPrimitiveId()); 164 167 return this; 165 168 } … … 171 174 * @return this 172 175 */ 173 176 public MultiFetchServerObjectReader appendWay(Way way) { 174 if (way == null) return this; 175 if (way.isNew()) return this; 176 for (Node node: !recursesDown() ? way.getNodes() : Collections.<Node>emptyList()) { 177 if (!node.isNew()) { 178 remember(node.getPrimitiveId()); 177 if (way == null || way.isNew()) return this; 178 if (recurseDownAppended && !recursesDown()) { 179 for (Node node : way.getNodes()) { 180 if (!node.isNew()) { 181 remember(node.getPrimitiveId()); 182 } 179 183 } 180 184 } 181 185 remember(way.getPrimitiveId()); … … 189 193 * @return this 190 194 */ 191 195 protected MultiFetchServerObjectReader appendRelation(Relation relation) { 192 if (relation == null) return this; 193 if (relation.isNew()) return this; 196 if (relation == null || relation.isNew()) return this; 194 197 remember(relation.getPrimitiveId()); 195 for (RelationMember member : !recursesDown() ? relation.getMembers() : Collections.<RelationMember>emptyList()) { 196 // avoid infinite recursion in case of cyclic dependencies in relations 197 if (OsmPrimitiveType.from(member.getMember()) == OsmPrimitiveType.RELATION 198 && relations.contains(member.getMember().getId())) { 199 continue; 198 if (recurseDownAppended && !recursesDown()) { 199 for (RelationMember member : relation.getMembers()) { 200 // avoid infinite recursion in case of cyclic dependencies in relations 201 if (OsmPrimitiveType.from(member.getMember()) == OsmPrimitiveType.RELATION 202 && relations.contains(member.getMember().getId())) { 203 continue; 204 } 205 if (!member.getMember().isIncomplete()) { 206 append(member.getMember()); 207 } 200 208 } 201 if (!member.getMember().isIncomplete()) {202 append(member.getMember());203 }204 209 } 205 210 return this; 206 211 } … … 238 243 } 239 244 240 245 /** 246 * appends a list of {@link PrimitiveId} to the list of ids which will be fetched from the server. 247 * 248 * @param ids the list of primitive Ids (ignored, if null) 249 * @return this 250 * @since xxx 251 * 252 */ 253 public MultiFetchServerObjectReader appendIds(Collection<PrimitiveId> ids) { 254 if (ids == null) 255 return this; 256 for (PrimitiveId id : ids) { 257 if (id.isNew()) continue; 258 switch (id.getType()) { 259 case NODE: 260 nodes.add(id.getUniqueId()); 261 break; 262 case WAY: 263 case CLOSEDWAY: 264 ways.add(id.getUniqueId()); 265 break; 266 case MULTIPOLYGON: 267 case RELATION: 268 relations.add(id.getUniqueId()); 269 break; 270 default: 271 throw new AssertionError(); 272 } 273 } 274 return this; 275 } 276 277 /** 241 278 * extracts a subset of max {@link #MAX_IDS_PER_REQUEST} ids from <code>ids</code> and 242 279 * replies the subset. The extracted subset is removed from <code>ids</code>. 243 280 * … … 275 312 276 313 protected void rememberNodesOfIncompleteWaysToLoad(DataSet from) { 277 314 for (Way w: from.getWays()) { 278 if (w.hasIncompleteNodes()) { 279 for (Node n: w.getNodes()) { 280 if (n.isIncomplete()) { 281 nodes.add(n.getId()); 282 } 315 for (Node n: w.getNodes()) { 316 if (n.isIncomplete()) { 317 nodes.add(n.getId()); 283 318 } 284 319 } 285 320 } … … 372 407 * found on the server (the server response code was 404) 373 408 * 374 409 * @return the parsed data 410 * @param progressMonitor progress monitor 375 411 * @throws OsmTransferException if an error occurs while communicating with the API server 376 412 * @see #getMissingPrimitives() 377 413 * … … 382 418 progressMonitor.beginTask(trn("Downloading {0} object from ''{1}''", 383 419 "Downloading {0} objects from ''{1}''", n, n, OsmApi.getOsmApi().getBaseUrl())); 384 420 try { 421 boolean ignoreAlreadyDownloaded = outputDataSet != null && outputDataSet.isEmpty(); 385 422 missingPrimitives = new HashSet<>(); 423 downloadRelations(progressMonitor); 386 424 if (isCanceled()) return null; 425 if (outputDataSet != null && ignoreAlreadyDownloaded && !ways.isEmpty()) { 426 // avoid to download data that was already downloaded 427 ways.removeAll(outputDataSet.getWays().stream().filter(way -> !way.isIncomplete()) 428 .map(OsmPrimitive::getUniqueId).collect(Collectors.toList())); 429 } 387 430 fetchPrimitives(ways, OsmPrimitiveType.WAY, progressMonitor); 431 if (outputDataSet != null && ignoreAlreadyDownloaded && !nodes.isEmpty()) { 432 // avoid to download data that was already downloaded 433 nodes.removeAll(outputDataSet.getNodes().stream().filter(node -> !node.isIncomplete()) 434 .map(OsmPrimitive::getUniqueId).collect(Collectors.toList())); 435 436 } 388 437 if (isCanceled()) return null; 389 438 fetchPrimitives(nodes, OsmPrimitiveType.NODE, progressMonitor); 390 if (isCanceled()) return null;391 fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor);392 439 if (outputDataSet != null) { 393 440 outputDataSet.deleteInvisible(); 394 441 } 442 395 443 return outputDataSet; 396 444 } finally { 397 445 progressMonitor.finishTask(); … … 399 447 } 400 448 401 449 /** 450 * Finds best way to download a set of relations. 451 * @param progressMonitor progress monitor 452 * @throws OsmTransferException if an error occurs while communicating with the API server 453 * @see #getMissingPrimitives() 454 */ 455 private void downloadRelations(ProgressMonitor progressMonitor) throws OsmTransferException { 456 Set<Long> toDownload = new LinkedHashSet<>(relations); 457 fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor); 458 if (!recurseDownRelations || recursesDown()) { 459 return; 460 } 461 // OSM multi-fetch api may return invisible objects 462 for (Relation r : outputDataSet.getRelations()) { 463 if (!r.isVisible()) 464 toDownload.remove(r.getUniqueId()); 465 } 466 while (!toDownload.isEmpty()) { 467 if (isCanceled()) 468 return; 469 final Set<Long> addedRelations = new LinkedHashSet<>(); 470 471 for (long id : toDownload) { 472 OsmServerObjectReader reader = new OsmServerObjectReader(id, OsmPrimitiveType.RELATION, true /* full*/); 473 DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false)); 474 if (ds != null) { 475 ds.getRelations().stream().map(OsmPrimitive::getUniqueId).filter(uid -> uid != id) 476 .forEach(addedRelations::add); 477 } 478 merge(ds); 479 } 480 if (addedRelations.isEmpty()) 481 break; 482 toDownload = addedRelations; 483 } 484 } 485 486 /** 402 487 * replies the set of ids of all primitives for which a fetch request to the 403 488 * server was submitted but which are not available from the server (the server 404 489 * replied a return code of 404) … … 419 504 } 420 505 421 506 /** 507 * Should downloaded relations be complete? 508 * @param recurseDownRelations true: yes, recurse down to retrieve the complete relation 509 * @return this 510 * @since xxx 511 */ 512 public MultiFetchServerObjectReader setRecurseDownRelations(boolean recurseDownRelations) { 513 this.recurseDownRelations = recurseDownRelations; 514 return this; 515 } 516 517 /** 518 * Determine how appended objects are treated. By default, all children of an appended object are also appended. 519 * @param recurseAppended false: do not append known children of appended objects, i.e. all nodes of way and all members of a relation 520 * @return this 521 * @since xxx 522 */ 523 public MultiFetchServerObjectReader setRecurseDownAppended(boolean recurseAppended) { 524 this.recurseDownAppended = recurseAppended; 525 return this; 526 } 527 528 /** 422 529 * The class holding the results given by {@link Fetcher}. 423 530 * It is only a wrapper of the resulting {@link DataSet} and the collection of {@link PrimitiveId} that could not have been loaded. 424 531 */ … … 538 645 } 539 646 } catch (IOException ex) { 540 647 Logging.warn(ex); 648 throw new OsmTransferException(ex); 541 649 } 542 650 return result; 543 651 } -
test/unit/org/openstreetmap/josm/io/MultiFetchOverpassObjectReaderTest.java
6 6 import java.util.Arrays; 7 7 import java.util.TreeSet; 8 8 9 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;10 9 import org.junit.Rule; 11 10 import org.junit.Test; 12 11 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; … … 13 12 import org.openstreetmap.josm.testutils.JOSMTestRules; 14 13 import org.openstreetmap.josm.tools.Utils; 15 14 15 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 16 16 17 /** 17 18 * Unit tests of {@link MultiFetchOverpassObjectReader}. 18 19 */ … … 29 30 * Test {@link MultiFetchOverpassObjectReader#buildRequestString} 30 31 */ 31 32 @Test 32 public void testBuildRequest String() {33 public void testBuildRequestWaysString() { 33 34 String requestString = new MultiFetchOverpassObjectReader() 34 35 .buildRequestString(OsmPrimitiveType.WAY, new TreeSet<>(Arrays.asList(130L, 123L, 126L))); 35 assertEquals("interpreter?data=" + Utils.encodeUrl(" (way(123);>;way(126);>;way(130);>;);out meta;"), requestString);36 assertEquals("interpreter?data=" + Utils.encodeUrl("way(id:123,126,130);>;out meta;"), requestString); 36 37 } 37 38 39 /** 40 * Test {@link MultiFetchOverpassObjectReader#buildRequestString} 41 */ 42 @Test 43 public void testBuildRequestRelationsString() { 44 MultiFetchOverpassObjectReader reader = new MultiFetchOverpassObjectReader(); 45 reader.setRecurseDownRelations(true); 46 String requestString = reader.buildRequestString(OsmPrimitiveType.RELATION, 47 new TreeSet<>(Arrays.asList(130L, 123L, 126L))); 48 assertEquals("interpreter?data=" + Utils.encodeUrl("relation(id:123,126,130);>>;out meta;"), requestString); 49 reader.setRecurseDownRelations(false); 50 requestString = reader.buildRequestString(OsmPrimitiveType.RELATION, 51 new TreeSet<>(Arrays.asList(130L, 123L, 126L))); 52 assertEquals("interpreter?data=" + Utils.encodeUrl("relation(id:123,126,130);out meta;"), requestString); 53 } 54 38 55 }