Ticket #18566: 18566-wip.patch

File 18566-wip.patch, 33.6 KB (added by GerdP, 6 years ago)

Work in progress

  • 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

     
    4141    protected OsmServerObjectReader objectReader;
    4242
    4343    private boolean zoom;
    44     private boolean downloadRelations;
    45     private boolean fullRelation;
     44    protected boolean fullRelation;
    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.
     73     * @param downloadRelations ignored since xxx
    7574     * @param fullRelation {@code true} if a full download is required,
    7675     *                     i.e., a download including the immediate children of a relation.
    7776     * @return {@code this}
    7877     */
    7978    public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
    80         this.downloadRelations = downloadRelations;
    8179        this.fullRelation = fullRelation;
    8280        return this;
    8381    }
     
    10098            synchronized (this) {
    10199                if (canceled)
    102100                    return;
    103                 multiObjectReader = MultiFetchServerObjectReader.create();
     101                multiObjectReader = MultiFetchServerObjectReader.create().setRecurseDownRelations(fullRelation);
    104102            }
    105103            initMultiFetchReader(multiObjectReader);
    106104            theirDataSet = multiObjectReader.parseOsm(progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false));
     
    110108            }
    111109            new DataSetMerger(ds, theirDataSet).merge();
    112110
    113             if (downloadRelations) {
     111            if (fullRelation) {
    114112                loadIncompleteRelationMembers();
    115113            }
    116114
  • 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;
     
    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

     
    55
    66import java.util.Collection;
    77import java.util.Collections;
     8import java.util.stream.Collectors;
    89
    9 import org.openstreetmap.josm.data.osm.Node;
    1010import org.openstreetmap.josm.data.osm.OsmPrimitive;
    11 import org.openstreetmap.josm.data.osm.Relation;
    12 import org.openstreetmap.josm.data.osm.Way;
    1311import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1412import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
    1513
     
    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        // complete updated objects, e.g. download all members of relations
     34        this.setDownloadRelations(false, true);
    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        reader.append(toUpdate.stream().filter(p -> !p.isNew()).collect(Collectors.toList()));
    7140    }
    7241}
  • 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    protected 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     *
     
    382419        progressMonitor.beginTask(trn("Downloading {0} object from ''{1}''",
    383420                "Downloading {0} objects from ''{1}''", n, n, OsmApi.getOsmApi().getBaseUrl()));
    384421        try {
     422            boolean ignoreAlreadyDownloaded = outputDataSet != null && outputDataSet.isEmpty();
    385423            missingPrimitives = new HashSet<>();
     424            Set<Long> origRelations = new LinkedHashSet<>(relations);
     425            Set<Relation> incompleteRelations = new LinkedHashSet<>();
     426            do {
     427                if (isCanceled())
     428                    return null;
     429                incompleteRelations.clear();
     430                fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor);
     431                if (outputDataSet != null && recurseDownRelations) {
     432                    incompleteRelations = getIncompleteRelations(outputDataSet);
     433                    relations.clear();
     434                    append(incompleteRelations);
     435                }
     436            } while (!incompleteRelations.isEmpty());
     437            if (recurseDownRelations && !recursesDown()) {
     438                Set<PrimitiveId> members = new LinkedHashSet<>();
     439                for (Relation r : outputDataSet.getRelations()) {
     440                    for (OsmPrimitive p : r.getMemberPrimitives()) {
     441                        if (!(p instanceof Relation)) {
     442                            members.add(p.getPrimitiveId());
     443                        }
     444
     445                    }
     446                }
     447                appendIds(members);
     448            }
     449            relations.clear();
     450            relations.addAll(origRelations);
    386451            if (isCanceled()) return null;
    387452            fetchPrimitives(ways, OsmPrimitiveType.WAY, progressMonitor);
     453            if (outputDataSet != null && ignoreAlreadyDownloaded) {
     454                // avoid to download data that was already downloaded
     455                nodes.removeAll(outputDataSet.getNodes().stream().filter(node -> !node.isIncomplete())
     456                        .map(OsmPrimitive::getUniqueId).collect(Collectors.toList()));
     457            }
    388458            if (isCanceled()) return null;
    389459            fetchPrimitives(nodes, OsmPrimitiveType.NODE, progressMonitor);
    390             if (isCanceled()) return null;
    391             fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor);
    392460            if (outputDataSet != null) {
    393461                outputDataSet.deleteInvisible();
    394462            }
     463
    395464            return outputDataSet;
    396465        } finally {
    397466            progressMonitor.finishTask();
     
    398467        }
    399468    }
    400469
     470    /** Collect incomplete relations in the dataset.
     471     *
     472     * @param ds the dataset
     473     * @return set of incomplete relations
     474     */
     475    private Set<Relation> getIncompleteRelations(DataSet ds) {
     476        Set<Relation> incomplete = new LinkedHashSet<>();
     477        for (Relation r : ds.getRelations()) {
     478            if (r.isIncomplete() && !relations.contains(r.getUniqueId())) {
     479                incomplete.add(r);
     480            }
     481        }
     482        return incomplete;
     483    }
     484
    401485    /**
    402486     * replies the set of ids of all primitives for which a fetch request to the
    403487     * server was submitted but which are not available from the server (the server
     
    419503    }
    420504
    421505    /**
     506     * Should downloaded relations be complete?
     507     * @param recurseDownRelations true: yes, recurse down to retrieve the complete relation
     508     * @return this
     509     * @since xxx
     510     */
     511    public MultiFetchServerObjectReader setRecurseDownRelations(boolean recurseDownRelations) {
     512        this.recurseDownRelations = recurseDownRelations;
     513        return this;
     514    }
     515
     516    /**
     517     * Determine how appended objects are treated. By default, all children of an appended object are also appended.
     518     * @param recurseAppended false: do not append known children of appended objects, i.e. all nodes of way and all members of a relation
     519     * @return this
     520     * @since xxx
     521     */
     522    public MultiFetchServerObjectReader setRecurseDownAppended(boolean recurseAppended) {
     523        this.recurseDownAppended = recurseAppended;
     524        return this;
     525    }
     526
     527    /**
    422528     * The class holding the results given by {@link Fetcher}.
    423529     * It is only a wrapper of the resulting {@link DataSet} and the collection of {@link PrimitiveId} that could not have been loaded.
    424530     */
     
    614720            return result;
    615721        }
    616722    }
     723
    617724}
  • test/functional/org/openstreetmap/josm/io/MultiFetchServerObjectReaderTest.java

     
    4242import org.openstreetmap.josm.data.osm.Way;
    4343import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    4444import org.openstreetmap.josm.spi.preferences.Config;
     45import org.openstreetmap.josm.testutils.JOSMTestRules;
    4546
    4647import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    47 import org.openstreetmap.josm.testutils.JOSMTestRules;
    4848
    4949/**
    5050 * Unit tests of {@link MultiFetchServerObjectReader}.
  • 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}