Changeset 15811 in josm


Ignore:
Timestamp:
2020-02-03T06:35:22+01:00 (3 weeks ago)
Author:
GerdP
Message:

fix #18566: Download object: member of relations always downloaded (18566.3.patch)

  • return same result when downloading objects, no matter if overpass api is enabled or not.
  • fix an EDT violation in class ChildRelationBrowser
  • let MultiFetchOverpassObjectReader create a single overpass query, no matter how many objects are requested

(gives much better reaction times).

  • use multi-fetch in ChildRelationBrowser to reduce duplicated code. Small disadvantage: When downloading multiple members of a relation the dialog doesn't update after each member. Should not matter much as the overall time for multiple members is shorter

I tried to keep the code backward compatible. Plugins reverter and undelete should continue to work.

Location:
trunk
Files:
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/ChildRelationBrowser.java

    r14713 r15811  
    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
     
    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;
     
    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;
     
    340336        }
    341337
     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();
     354                }
     355            }
     356        }
     357
    342358        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));
    347                 }
    348             }
     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            });
    349367        }
    350368
     
    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
     
    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);
     
    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);
    516                 }
     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));
     466                }
     467
    517468            } catch (OsmTransferException e) {
    518469                if (canceled) {
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationMemberTask.java

    r13486 r15811  
    130130                objectReader = MultiFetchServerObjectReader.create();
    131131            }
     132            objectReader.setRecurseDownAppended(false).setRecurseDownRelations(true);
    132133            objectReader.append(children);
    133134            progressMonitor.indeterminateSubTask(
  • trunk/src/org/openstreetmap/josm/gui/dialogs/relation/DownloadRelationTask.java

    r13486 r15811  
    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;
     
    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;
     
    3435    private final Collection<Relation> relations;
    3536    private final OsmDataLayer layer;
    36     private OsmServerObjectReader objectReader;
     37    private OsmServerReader objectReader;
    3738
    3839    /**
     
    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);
  • trunk/src/org/openstreetmap/josm/gui/io/AbstractPrimitiveTask.java

    r13486 r15811  
    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;
     
    4241
    4342    private boolean zoom;
    44     private boolean downloadRelations;
    45     private boolean fullRelation;
     43    protected boolean fullRelation;
    4644
    4745    protected AbstractPrimitiveTask(String title, OsmDataLayer layer) {
     
    7169
    7270    /**
    73      * Sets whether .
    74      * @param downloadRelations {@code true} if
     71     * Sets whether all members of the relation should be downloaded completely.
    7572     * @param fullRelation {@code true} if a full download is required,
    7673     *                     i.e., a download including the immediate children of a relation.
    7774     * @return {@code this}
     75     * since 15811 (changed parameter list)
    7876     */
    79     public final AbstractPrimitiveTask setDownloadRelations(boolean downloadRelations, boolean fullRelation) {
    80         this.downloadRelations = downloadRelations;
     77    public final AbstractPrimitiveTask setDownloadRelations(boolean fullRelation) {
    8178        this.fullRelation = fullRelation;
    8279        return this;
     
    10198                if (canceled)
    10299                    return;
    103                 multiObjectReader = MultiFetchServerObjectReader.create();
     100                multiObjectReader = MultiFetchServerObjectReader.create().setRecurseDownRelations(fullRelation);
    104101            }
    105102            initMultiFetchReader(multiObjectReader);
     
    111108            new DataSetMerger(ds, theirDataSet).merge();
    112109
    113             if (downloadRelations) {
    114                 loadIncompleteRelationMembers();
    115             }
    116 
    117110            loadIncompleteNodes();
    118111        } catch (OsmTransferException e) {
     
    120113                return;
    121114            lastException = e;
    122         }
    123     }
    124 
    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             }
    144115        }
    145116    }
  • trunk/src/org/openstreetmap/josm/gui/io/DownloadPrimitivesTask.java

    r10129 r15811  
    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;
     
    5349        this.ids = ids;
    5450        setZoom(true);
    55         setDownloadRelations(true, fullRelation);
     51        setDownloadRelations(fullRelation);
    5652    }
    5753
     
    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}
  • trunk/src/org/openstreetmap/josm/gui/io/UpdatePrimitivesTask.java

    r10129 r15811  
    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/**
     
    3331        super(tr("Update objects"), layer);
    3432        this.toUpdate = toUpdate != null ? toUpdate : Collections.<OsmPrimitive>emptyList();
    35     }
    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 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         }
     33        setDownloadRelations(Config.getPref().getBoolean("update.selected.complete-relation", true));
    6434    }
    6535
    6636    @Override
    6737    protected void initMultiFetchReader(MultiFetchServerObjectReader reader) {
    68         initMultiFetchReaderWithNodes(reader);
    69         initMultiFetchReaderWithWays(reader);
    70         initMultiFetchReaderWithRelations(reader);
     38        // don't update new primitives
     39        toUpdate.stream().filter(p -> !p.isNew()).forEach(reader::append);
    7140    }
    7241}
  • trunk/src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java

    r15717 r15811  
    22package org.openstreetmap.josm.io;
    33
     4import java.util.Map.Entry;
    45import java.util.Set;
    56import java.util.stream.Collectors;
    67
    78import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    8 import org.openstreetmap.josm.tools.Utils;
     9import org.openstreetmap.josm.tools.Logging;
    910
    1011/**
     
    1314 * @since 9241
    1415 */
    15 class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
     16public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
    1617
    17     @Override
    18     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;"));
    22         return "interpreter?data=" + Utils.encodeUrl(query);
     18    private static String getPackageString(final OsmPrimitiveType type, Set<Long> idPackage) {
     19        return idPackage.stream().map(String::valueOf)
     20                .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ");"));
     21    }
     22
     23    /**
     24     * Create a single query for all elements
     25     * @return the request string
     26     */
     27    protected String buildComplexRequestString() {
     28        StringBuilder sb = new StringBuilder();
     29        int countTypes = 0;
     30        for (Entry<OsmPrimitiveType, Set<Long>> e : primitivesMap.entrySet()) {
     31            if (!e.getValue().isEmpty()) {
     32                countTypes++;
     33                String list = getPackageString(e.getKey(), e.getValue());
     34                switch (e.getKey()) {
     35                case MULTIPOLYGON:
     36                case RELATION:
     37                    sb.append(list);
     38                    if (recurseDownRelations)
     39                        sb.append(">>;");
     40                    break;
     41                case CLOSEDWAY:
     42                case WAY:
     43                    sb.append('(').append(list).append(">;);");
     44                    break;
     45                case NODE:
     46                    sb.append(list);
     47                }
     48            }
     49        }
     50        String query = sb.toString();
     51        if (countTypes > 1) {
     52            query = "(" + query + ");";
     53        }
     54        query += "out meta;";
     55        Logging.debug("{0} {1}", "Generated Overpass query:", query);
     56        return query;
    2357    }
    2458
     
    2761        return OverpassDownloadReader.OVERPASS_SERVER.get();
    2862    }
    29 
    30     @Override
    31     protected boolean recursesDown() {
    32         // see https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#Recurse_down_.28.3E.29 for documentation
    33         // accomplished using >; in the query string above
    34         return true;
    35     }
    3663}
  • trunk/src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java

    r15717 r15811  
    1313import java.util.HashSet;
    1414import java.util.Iterator;
     15import java.util.LinkedHashMap;
    1516import java.util.LinkedHashSet;
    1617import java.util.List;
     18import java.util.Map;
     19import java.util.Map.Entry;
    1720import java.util.Set;
    1821import java.util.concurrent.Callable;
     
    2528import java.util.stream.Collectors;
    2629
     30import org.openstreetmap.josm.data.Bounds;
    2731import org.openstreetmap.josm.data.osm.DataSet;
    2832import org.openstreetmap.josm.data.osm.DataSetMerger;
     
    6468     * this leads to a max. request URL of ~ 1900 Bytes ((10 digits +  1 Separator) * 170),
    6569     * which should be safe according to the
    66      * <a href="http://www.boutell.com/newfaq/misc/urllength.html">WWW FAQ</a>.
     70     * <a href="https://web.archive.org/web/20190902193246/https://boutell.com/newfaq/misc/urllength.html">WWW FAQ</a>.
    6771     */
    6872    private static final int MAX_IDS_PER_REQUEST = 170;
     
    7175    private final Set<Long> ways;
    7276    private final Set<Long> relations;
    73     private Set<PrimitiveId> missingPrimitives;
     77    private final Set<PrimitiveId> missingPrimitives;
    7478    private final DataSet outputDataSet;
     79    protected final Map<OsmPrimitiveType, Set<Long>> primitivesMap;
     80
     81    protected boolean recurseDownRelations;
     82    private boolean recurseDownAppended = true;
    7583
    7684    /**
     
    8391        this.outputDataSet = new DataSet();
    8492        this.missingPrimitives = new LinkedHashSet<>();
     93        primitivesMap = new LinkedHashMap<>();
     94        primitivesMap.put(OsmPrimitiveType.RELATION, relations);
     95        primitivesMap.put(OsmPrimitiveType.WAY, ways);
     96        primitivesMap.put(OsmPrimitiveType.NODE, nodes);
    8597    }
    8698
     
    160172     */
    161173    public MultiFetchServerObjectReader appendNode(Node node) {
    162         if (node == null) return this;
     174        if (node == null || node.isNew()) return this;
    163175        remember(node.getPrimitiveId());
    164176        return this;
     
    172184     */
    173185    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());
     186        if (way == null || way.isNew()) return this;
     187        if (recurseDownAppended) {
     188            for (Node node : way.getNodes()) {
     189                if (!node.isNew()) {
     190                    remember(node.getPrimitiveId());
     191                }
    179192            }
    180193        }
     
    190203     */
    191204    protected MultiFetchServerObjectReader appendRelation(Relation relation) {
    192         if (relation == null) return this;
    193         if (relation.isNew()) return this;
     205        if (relation == null || relation.isNew()) return this;
    194206        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;
    200             }
    201             if (!member.getMember().isIncomplete()) {
    202                 append(member.getMember());
     207        if (recurseDownAppended) {
     208            for (RelationMember member : relation.getMembers()) {
     209                // avoid infinite recursion in case of cyclic dependencies in relations
     210                if (OsmPrimitiveType.from(member.getMember()) == OsmPrimitiveType.RELATION
     211                        && relations.contains(member.getMember().getId())) {
     212                    continue;
     213                }
     214                if (!member.getMember().isIncomplete()) {
     215                    append(member.getMember());
     216                }
    203217            }
    204218        }
     
    234248        for (OsmPrimitive primitive : primitives) {
    235249            append(primitive);
     250        }
     251        return this;
     252    }
     253
     254    /**
     255     * appends a list of {@link PrimitiveId} to the list of ids which will be fetched from the server.
     256     *
     257     * @param ids the list of primitive Ids (ignored, if null)
     258     * @return this
     259     * @since 15811
     260     *
     261     */
     262    public MultiFetchServerObjectReader appendIds(Collection<PrimitiveId> ids) {
     263        if (ids == null)
     264            return this;
     265        for (PrimitiveId id : ids) {
     266            if (id.isNew()) continue;
     267            switch (id.getType()) {
     268            case NODE:
     269                nodes.add(id.getUniqueId());
     270                break;
     271            case WAY:
     272            case CLOSEDWAY:
     273                ways.add(id.getUniqueId());
     274                break;
     275            case MULTIPOLYGON:
     276            case RELATION:
     277                relations.add(id.getUniqueId());
     278                break;
     279            default:
     280                throw new AssertionError();
     281            }
    236282        }
    237283        return this;
     
    276322    protected void rememberNodesOfIncompleteWaysToLoad(DataSet from) {
    277323        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                     }
     324            for (Node n: w.getNodes()) {
     325                if (n.isIncomplete()) {
     326                    nodes.add(n.getId());
    283327                }
    284328            }
     
    373417     *
    374418     * @return the parsed data
     419     * @param progressMonitor progress monitor
    375420     * @throws OsmTransferException if an error occurs while communicating with the API server
    376421     * @see #getMissingPrimitives()
     
    379424    @Override
    380425    public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
     426        missingPrimitives.clear();
    381427        int n = nodes.size() + ways.size() + relations.size();
    382428        progressMonitor.beginTask(trn("Downloading {0} object from ''{1}''",
    383                 "Downloading {0} objects from ''{1}''", n, n, OsmApi.getOsmApi().getBaseUrl()));
     429                "Downloading {0} objects from ''{1}''", n, n, getBaseUrl()));
    384430        try {
    385             missingPrimitives = new HashSet<>();
    386             if (isCanceled()) return null;
    387             fetchPrimitives(ways, OsmPrimitiveType.WAY, progressMonitor);
    388             if (isCanceled()) return null;
    389             fetchPrimitives(nodes, OsmPrimitiveType.NODE, progressMonitor);
    390             if (isCanceled()) return null;
    391             fetchPrimitives(relations, OsmPrimitiveType.RELATION, progressMonitor);
    392             if (outputDataSet != null) {
    393                 outputDataSet.deleteInvisible();
    394             }
     431            if (this instanceof MultiFetchOverpassObjectReader) {
     432                // calculate a single request for all the objects
     433                String request = ((MultiFetchOverpassObjectReader) this).buildComplexRequestString();
     434                if (isCanceled())
     435                    return null;
     436                OverpassDownloadReader reader = new OverpassDownloadReader(new Bounds(0, 0, 0, 0), getBaseUrl(), request);
     437                DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(n, false));
     438                new DataSetMerger(outputDataSet, ds).merge();
     439                checkMissing(outputDataSet, progressMonitor);
     440            } else {
     441                downloadRelations(progressMonitor);
     442                if (isCanceled())
     443                    return null;
     444                fetchPrimitives(ways, OsmPrimitiveType.WAY, progressMonitor);
     445                if (isCanceled())
     446                    return null;
     447                fetchPrimitives(nodes, OsmPrimitiveType.NODE, progressMonitor);
     448            }
     449            outputDataSet.deleteInvisible();
    395450            return outputDataSet;
    396451        } finally {
     
    400455
    401456    /**
     457     * Workaround for difference in Oerpass API.
     458     * As of now (version 7.55) Overpass api doesn't return invisible objects.
     459     * Check if we have objects which do not appear in the dataset and fetch them from OSM instead.
     460     * @param ds the dataset
     461     * @param progressMonitor progress monitor
     462     * @throws OsmTransferException if an error occurs while communicating with the API server
     463     */
     464    private void checkMissing(DataSet ds, ProgressMonitor progressMonitor) throws OsmTransferException {
     465        Set<OsmPrimitive> missing = new LinkedHashSet<>();
     466        for (Entry<OsmPrimitiveType, Set<Long>> e : primitivesMap.entrySet()) {
     467            for (long id : e.getValue()) {
     468                if (ds.getPrimitiveById(id, e.getKey()) == null)
     469                    missing.add(e.getKey().newInstance(id, true));
     470            }
     471        }
     472        if (isCanceled() || missing.isEmpty())
     473            return;
     474
     475        MultiFetchServerObjectReader missingReader = MultiFetchServerObjectReader.create(false);
     476        missingReader.setRecurseDownAppended(false);
     477        missingReader.setRecurseDownRelations(false);
     478        missingReader.append(missing);
     479        DataSet mds = missingReader.parseOsm(progressMonitor.createSubTaskMonitor(missing.size(), false));
     480        new DataSetMerger(ds, mds).merge();
     481        missingPrimitives.addAll(missingReader.getMissingPrimitives());
     482    }
     483
     484    /**
     485     * Finds best way to download a set of relations.
     486     * @param progressMonitor progress monitor
     487     * @throws OsmTransferException if an error occurs while communicating with the API server
     488     * @see #getMissingPrimitives()
     489     */
     490    private void downloadRelations(ProgressMonitor progressMonitor) throws OsmTransferException {
     491        Set<Long> toDownload = new LinkedHashSet<>(relations);
     492        fetchPrimitives(toDownload, OsmPrimitiveType.RELATION, progressMonitor);
     493        if (!recurseDownRelations) {
     494            return;
     495        }
     496        // OSM multi-fetch api may return invisible objects
     497        for (Relation r : outputDataSet.getRelations()) {
     498            if (!r.isVisible())
     499                toDownload.remove(r.getUniqueId());
     500        }
     501        while (!toDownload.isEmpty()) {
     502            if (isCanceled())
     503                return;
     504            final Set<Long> addedRelations = new LinkedHashSet<>();
     505
     506            for (long id : toDownload) {
     507                OsmServerObjectReader reader = new OsmServerObjectReader(id, OsmPrimitiveType.RELATION, true /* full*/);
     508                DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
     509                if (ds != null) {
     510                    ds.getRelations().stream().map(OsmPrimitive::getUniqueId).filter(uid -> uid != id)
     511                            .forEach(addedRelations::add);
     512                }
     513                merge(ds);
     514            }
     515            if (addedRelations.isEmpty())
     516                break;
     517            toDownload = addedRelations;
     518        }
     519    }
     520
     521    /**
    402522     * replies the set of ids of all primitives for which a fetch request to the
    403523     * server was submitted but which are not available from the server (the server
     
    411531
    412532    /**
    413      * Whether this reader fetches nodes when loading ways, or members when loading relations.
    414      *
    415      * @return {@code true} if the reader recurses down
    416      */
    417     protected boolean recursesDown() {
    418         return false;
     533     * Should downloaded relations be complete?
     534     * @param recurseDownRelations true: yes, recurse down to retrieve the complete relation
     535     * @return this
     536     * @since 15811
     537     */
     538    public MultiFetchServerObjectReader setRecurseDownRelations(boolean recurseDownRelations) {
     539        this.recurseDownRelations = recurseDownRelations;
     540        return this;
     541    }
     542
     543    /**
     544     * Determine how appended objects are treated. By default, all children of an appended object are also appended.
     545     * @param recurseAppended false: do not append known children of appended objects, i.e. all nodes of way and all members of a relation
     546     * @return this
     547     * @since 15811
     548     */
     549    public MultiFetchServerObjectReader setRecurseDownAppended(boolean recurseAppended) {
     550        this.recurseDownAppended = recurseAppended;
     551        return this;
    419552    }
    420553
     
    539672            } catch (IOException ex) {
    540673                Logging.warn(ex);
     674                throw new OsmTransferException(ex);
    541675            }
    542676            return result;
  • trunk/test/unit/org/openstreetmap/josm/io/MultiFetchOverpassObjectReaderTest.java

    r15717 r15811  
    55
    66import java.util.Arrays;
    7 import java.util.TreeSet;
     7
     8import org.junit.Rule;
     9import org.junit.Test;
     10import org.openstreetmap.josm.data.osm.Node;
     11import org.openstreetmap.josm.data.osm.Relation;
     12import org.openstreetmap.josm.data.osm.Way;
     13import org.openstreetmap.josm.testutils.JOSMTestRules;
    814
    915import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
    10 import org.junit.Rule;
    11 import org.junit.Test;
    12 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
    13 import org.openstreetmap.josm.testutils.JOSMTestRules;
    14 import org.openstreetmap.josm.tools.Utils;
    1516
    1617/**
     
    3031     */
    3132    @Test
    32     public void testBuildRequestString() {
    33         String requestString = new MultiFetchOverpassObjectReader()
    34                 .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);
     33    public void testBuildRequestWaysString() {
     34        MultiFetchOverpassObjectReader reader = new MultiFetchOverpassObjectReader();
     35        reader.append(Arrays.asList(new Way(123), new Way(126), new Way(130)));
     36        String requestString = reader.buildComplexRequestString();
     37        assertEquals("(way(id:123,126,130);>;);out meta;", requestString);
     38    }
     39
     40    /**
     41     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
     42     */
     43    @Test
     44    public void testBuildRequestRelationsString() {
     45        MultiFetchOverpassObjectReader reader = new MultiFetchOverpassObjectReader();
     46        reader.append(Arrays.asList(new Relation(123), new Relation(126), new Relation(130)));
     47        reader.setRecurseDownRelations(true);
     48        String requestString = reader.buildComplexRequestString();
     49        assertEquals("relation(id:123,126,130);>>;out meta;", requestString);
     50        reader.setRecurseDownRelations(false);
     51        requestString = reader.buildComplexRequestString();
     52        assertEquals("relation(id:123,126,130);out meta;", requestString);
     53    }
     54
     55    /**
     56     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
     57     */
     58    @Test
     59    public void testBuildComplexString() {
     60        MultiFetchOverpassObjectReader reader = new MultiFetchOverpassObjectReader();
     61        reader.setRecurseDownRelations(true);
     62        reader.append(Arrays.asList(new Relation(123), new Relation(126), new Relation(130), new Way(88), new Way(99),
     63                new Node(1)));
     64        String requestString = reader.buildComplexRequestString();
     65        assertEquals("(relation(id:123,126,130);>>;(way(id:88,99);>;);node(1););out meta;", requestString);
     66        reader.setRecurseDownRelations(false);
     67        requestString = reader.buildComplexRequestString();
     68        assertEquals("(relation(id:123,126,130);(way(id:88,99);>;);node(1););out meta;", requestString);
    3669    }
    3770
Note: See TracChangeset for help on using the changeset viewer.