Ticket #18566: 18566.2.patch

File 18566.2.patch, 37.1 KB (added by GerdP, 4 years ago)

overpass api behaves a bit strange. Brakets are needed for ways but not for relations.

  • src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java

     
    1212import java.awt.event.ActionEvent;
    1313import java.awt.event.MouseEvent;
    1414import java.io.IOException;
    15 import java.net.HttpURLConnection;
    1615import java.util.Arrays;
    1716import java.util.HashSet;
    18 import java.util.Iterator;
    1917import java.util.List;
    2018import java.util.Set;
    21 import java.util.Stack;
    2219import java.util.stream.Collectors;
    2320
    2421import javax.swing.AbstractAction;
     
    3734import org.openstreetmap.josm.data.osm.DataSetMerger;
    3835import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
    3936import org.openstreetmap.josm.data.osm.OsmPrimitive;
    40 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    4137import org.openstreetmap.josm.data.osm.Relation;
    4238import org.openstreetmap.josm.data.osm.RelationMember;
    4339import org.openstreetmap.josm.gui.ExceptionDialogUtil;
     
    4743import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    4844import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    4945import org.openstreetmap.josm.gui.progress.swing.PleaseWaitProgressMonitor;
     46import org.openstreetmap.josm.gui.util.GuiHelper;
    5047import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
     48import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
    5149import org.openstreetmap.josm.io.OsmApi;
    52 import org.openstreetmap.josm.io.OsmApiException;
    53 import org.openstreetmap.josm.io.OsmServerObjectReader;
    5450import org.openstreetmap.josm.io.OsmTransferException;
    5551import org.openstreetmap.josm.tools.CheckParameterUtil;
    5652import org.openstreetmap.josm.tools.ImageProvider;
     
    339335            OsmApi.getOsmApi().cancel();
    340336        }
    341337
    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();
    347354                }
    348355            }
    349356        }
    350357
     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
    351369        @Override
    352370        protected void finish() {
    353371            if (canceled)
     
    374392     * The asynchronous task for downloading relation members.
    375393     */
    376394    class DownloadAllChildrenTask extends DownloadTask {
    377         private final Stack<Relation> relationsToDownload;
    378         private final Set<Long> downloadedRelationIds;
     395        private final Relation relation;
    379396
    380397        DownloadAllChildrenTask(Dialog parent, Relation r) {
    381398            super(tr("Download relation members"), parent);
    382             relationsToDownload = new Stack<>();
    383             downloadedRelationIds = new HashSet<>();
    384             relationsToDownload.push(r);
     399            relation = r;
    385400        }
    386401
    387402        /**
     
    405420            );
    406421        }
    407422
    408         /**
    409          * Remembers the child relations to download
    410          *
    411          * @param parent the parent relation
    412          */
    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 layer
    427          *
    428          * @param ds the data set
    429          */
    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 
    441423        @Override
    442424        protected void realRun() throws SAXException, IOException, OsmTransferException {
    443425            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));
    466433                }
    467434                SwingUtilities.invokeLater(MainApplication.getMap()::repaint);
    468435            } catch (OsmTransferException e) {
     
    486453            this.relations = relations;
    487454        }
    488455
    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 
    500456        @Override
    501457        protected void realRun() throws SAXException, IOException, OsmTransferException {
    502458            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));
    516466                }
     467
    517468            } catch (OsmTransferException e) {
    518469                if (canceled) {
    519470                    Logging.warn(tr("Ignoring exception because task was canceled. Exception: {0}", e.toString()));
  • src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java

     
    129129                if (canceled) return;
    130130                objectReader = MultiFetchServerObjectReader.create();
    131131            }
     132            objectReader.setRecurseDownAppended(false).setRecurseDownRelations(true);
    132133            objectReader.append(children);
    133134            progressMonitor.indeterminateSubTask(
    134135                    buildDownloadFeedbackMessage()
  • src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java

     
    1111
    1212import org.openstreetmap.josm.data.osm.DataSet;
    1313import org.openstreetmap.josm.data.osm.DataSetMerger;
    14 import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
    1514import org.openstreetmap.josm.data.osm.Relation;
    1615import org.openstreetmap.josm.gui.ExceptionDialogUtil;
    1716import org.openstreetmap.josm.gui.MainApplication;
    1817import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    1918import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    20 import org.openstreetmap.josm.io.OsmServerObjectReader;
     19import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     20import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
     21import org.openstreetmap.josm.io.OsmServerReader;
    2122import org.openstreetmap.josm.io.OsmTransferException;
    2223import org.openstreetmap.josm.tools.CheckParameterUtil;
    2324import org.openstreetmap.josm.tools.Logging;
     
    3334    private Exception lastException;
    3435    private final Collection<Relation> relations;
    3536    private final OsmDataLayer layer;
    36     private OsmServerObjectReader objectReader;
     37    private OsmServerReader objectReader;
    3738
    3839    /**
    3940     * Creates the download task
     
    7778    protected void realRun() throws SAXException, IOException, OsmTransferException {
    7879        try {
    7980            final DataSet allDownloads = new DataSet();
    80             int i = 0;
    8181            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)
    9485                    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();
    10287            }
    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();
    10498            SwingUtilities.invokeAndWait(() -> {
    10599                layer.mergeFrom(allDownloads);
    106100                layer.onPostDownloadFromServer();
  • src/org/openstreetmap/josm/gui/io/AbstractPrimitiveTask.java

     
    1111import org.openstreetmap.josm.data.osm.DataSetMerger;
    1212import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    1313import org.openstreetmap.josm.data.osm.PrimitiveId;
    14 import org.openstreetmap.josm.data.osm.Relation;
    1514import org.openstreetmap.josm.data.osm.Way;
    1615import org.openstreetmap.josm.gui.ExceptionDialogUtil;
    1716import org.openstreetmap.josm.gui.MainApplication;
     
    4140    protected OsmServerObjectReader objectReader;
    4241
    4342    private boolean zoom;
    44     private boolean downloadRelations;
    45     private boolean fullRelation;
     43    protected boolean fullRelation;
     44    private boolean allowOverpas = true;
    4645
    4746    protected AbstractPrimitiveTask(String title, OsmDataLayer layer) {
    4847        this(title, new PleaseWaitProgressMonitor(title), layer);
     
    7069    }
    7170
    7271    /**
    73      * Sets whether .
    74      * @param downloadRelations {@code true} if
     72     * Sets whether all members of the relation should be downloaded completely.
    7573     * @param fullRelation {@code true} if a full download is required,
    7674     *                     i.e., a download including the immediate children of a relation.
    7775     * @return {@code this}
     76     * since xxx (changed parameter list)
    7877     */
    79     public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
    80         this.downloadRelations = downloadRelations;
     78    public final AbstractPrimitiveTask setDownloadRelations(boolean fullRelation) {
    8179        this.fullRelation = fullRelation;
    8280        return this;
    8381    }
    8482
    8583    /**
     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    /**
    8692     * Replies the set of ids of all primitives for which a fetch request to the
    8793     * server was submitted but which are not available from the server (the server
    8894     * replied a return code of 404)
     
    100106            synchronized (this) {
    101107                if (canceled)
    102108                    return;
    103                 multiObjectReader = MultiFetchServerObjectReader.create();
     109                if (allowOverpas)
     110                    multiObjectReader = MultiFetchServerObjectReader.create();
     111                else
     112                    multiObjectReader = MultiFetchServerObjectReader.create(false);
     113                multiObjectReader.setRecurseDownRelations(fullRelation);
    104114            }
    105115            initMultiFetchReader(multiObjectReader);
    106116            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
     
    110120            }
    111121            new DataSetMerger(ds, theirDataSet).merge();
    112122
    113             if (downloadRelations) {
    114                 loadIncompleteRelationMembers();
    115             }
    116 
    117123            loadIncompleteNodes();
    118124        } catch (OsmTransferException e) {
    119125            if (canceled)
     
    122128        }
    123129    }
    124130
    125     protected void loadIncompleteRelationMembers() throws OsmTransferException {
    126         // if incomplete relation members exist, download them too
    127         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 parent
    131             // (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 
    147131    protected void loadIncompleteNodes() throws OsmTransferException {
    148132        // a way loaded with MultiFetch may have incomplete nodes because at least one of its
    149133        // 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

     
    55
    66import java.util.List;
    77
    8 import org.openstreetmap.josm.data.osm.Node;
    9 import org.openstreetmap.josm.data.osm.OsmPrimitive;
    108import org.openstreetmap.josm.data.osm.PrimitiveId;
    11 import org.openstreetmap.josm.data.osm.Relation;
    12 import org.openstreetmap.josm.data.osm.Way;
    139import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1410import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    1511import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
     
    5248        super(tr("Download objects"), progressMonitor, layer);
    5349        this.ids = ids;
    5450        setZoom(true);
    55         setDownloadRelations(true, fullRelation);
     51        setDownloadRelations(fullRelation);
    5652    }
    5753
    5854    @Override
    5955    protected void initMultiFetchReader(MultiFetchServerObjectReader reader) {
    6056        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);
    7959    }
    8060}
  • src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java

     
    66import java.util.Collection;
    77import java.util.Collections;
    88
    9 import org.openstreetmap.josm.data.osm.Node;
    109import org.openstreetmap.josm.data.osm.OsmPrimitive;
    11 import org.openstreetmap.josm.data.osm.Relation;
    12 import org.openstreetmap.josm.data.osm.Way;
    1310import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1411import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
     12import org.openstreetmap.josm.spi.preferences.Config;
    1513
    1614/**
    1715 * The asynchronous task for updating a collection of objects using multi fetch.
     
    3230    public UpdatePrimitivesTask(OsmDataLayer layer, Collection<? extends OsmPrimitive> toUpdate) {
    3331        super(tr("Update objects"), layer);
    3432        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
    3535    }
    3636
    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 nodes
    51                 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 members
    61                 reader.append(primitive);
    62             }
    63         }
    64     }
    65 
    6637    @Override
    6738    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);
    7141    }
    7242}
  • src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java

     
    55import java.util.stream.Collectors;
    66
    77import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     8import org.openstreetmap.josm.tools.Logging;
    89import org.openstreetmap.josm.tools.Utils;
    910
    1011/**
     
    1213 *
    1314 * @since 9241
    1415 */
    15 class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
     16public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
    1617
    1718    @Override
    1819    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);
    2224        return "interpreter?data=" + Utils.encodeUrl(query);
    2325    }
    2426
     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
    2535    @Override
    2636    protected String getBaseUrl() {
    2737        return OverpassDownloadReader.OVERPASS_SERVER.get();
     
    3343        // accomplished using >; in the query string above
    3444        return true;
    3545    }
     46
    3647}
  • src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java

     
    7373    private Set<PrimitiveId> missingPrimitives;
    7474    private final DataSet outputDataSet;
    7575
     76    protected boolean recurseDownRelations;
     77    private boolean recurseDownAppended = true;
     78
    7679    /**
    7780     * Constructs a {@code MultiFetchServerObjectReader}.
    7881     */
     
    159162     * @return this
    160163     */
    161164    public MultiFetchServerObjectReader appendNode(Node node) {
    162         if (node == null) return this;
     165        if (node == null || node.isNew()) return this;
    163166        remember(node.getPrimitiveId());
    164167        return this;
    165168    }
     
    171174     * @return this
    172175     */
    173176    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                }
    179183            }
    180184        }
    181185        remember(way.getPrimitiveId());
     
    189193     * @return this
    190194     */
    191195    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;
    194197        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                }
    200208            }
    201             if (!member.getMember().isIncomplete()) {
    202                 append(member.getMember());
    203             }
    204209        }
    205210        return this;
    206211    }
     
    238243    }
    239244
    240245    /**
     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    /**
    241278     * extracts a subset of max {@link #MAX_IDS_PER_REQUEST} ids from <code>ids</code> and
    242279     * replies the subset. The extracted subset is removed from <code>ids</code>.
    243280     *
     
    275312
    276313    protected void rememberNodesOfIncompleteWaysToLoad(DataSet from) {
    277314        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());
    283318                }
    284319            }
    285320        }
     
    372407     * found on  the server (the server response code was 404)
    373408     *
    374409     * @return the parsed data
     410     * @param progressMonitor progress monitor
    375411     * @throws OsmTransferException if an error occurs while communicating with the API server
    376412     * @see #getMissingPrimitives()
    377413     *
     
    382418        progressMonitor.beginTask(trn("Downloading {0} object from ''{1}''",
    383419                "Downloading {0} objects from ''{1}''", n, n, OsmApi.getOsmApi().getBaseUrl()));
    384420        try {
     421            boolean ignoreAlreadyDownloaded = outputDataSet != null && outputDataSet.isEmpty();
    385422            missingPrimitives = new HashSet<>();
     423            downloadRelations(progressMonitor);
    386424            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            }
    387430            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            }
    388437            if (isCanceled()) return null;
    389438            fetchPrimitives(nodes, OsmPrimitiveType.NODE, progressMonitor);
    390             if (isCanceled()) return null;
    391             fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor);
    392439            if (outputDataSet != null) {
    393440                outputDataSet.deleteInvisible();
    394441            }
     442
    395443            return outputDataSet;
    396444        } finally {
    397445            progressMonitor.finishTask();
     
    399447    }
    400448
    401449    /**
     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    /**
    402487     * replies the set of ids of all primitives for which a fetch request to the
    403488     * server was submitted but which are not available from the server (the server
    404489     * replied a return code of 404)
     
    419504    }
    420505
    421506    /**
     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    /**
    422529     * The class holding the results given by {@link Fetcher}.
    423530     * It is only a wrapper of the resulting {@link DataSet} and the collection of {@link PrimitiveId} that could not have been loaded.
    424531     */
     
    538645                }
    539646            } catch (IOException ex) {
    540647                Logging.warn(ex);
     648                throw new OsmTransferException(ex);
    541649            }
    542650            return result;
    543651        }
  • test/unit/org/openstreetmap/josm/io/MultiFetchOverpassObjectReaderTest.java

     
    66import java.util.Arrays;
    77import java.util.TreeSet;
    88
    9 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    109import org.junit.Rule;
    1110import org.junit.Test;
    1211import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     
    1312import org.openstreetmap.josm.testutils.JOSMTestRules;
    1413import org.openstreetmap.josm.tools.Utils;
    1514
     15import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     16
    1617/**
    1718 * Unit tests of {@link MultiFetchOverpassObjectReader}.
    1819 */
     
    2930     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
    3031     */
    3132    @Test
    32     public void testBuildRequestString() {
     33    public void testBuildRequestWaysString() {
    3334        String requestString = new MultiFetchOverpassObjectReader()
    3435                .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);
    3637    }
    3738
     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
    3855}