Ticket #12303: 12303.6.patch

File 12303.6.patch, 31.9 KB (added by GerdP, 4 years ago)
  • src/org/openstreetmap/josm/actions/downloadtasks/DownloadReferrersTask.java

     
    1414import javax.swing.JOptionPane;
    1515import javax.swing.SwingUtilities;
    1616
     17import org.openstreetmap.josm.data.Bounds;
    1718import org.openstreetmap.josm.data.osm.DataSet;
    1819import org.openstreetmap.josm.data.osm.DataSetMerger;
    1920import org.openstreetmap.josm.data.osm.Node;
     
    2627import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    2728import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    2829import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     30import org.openstreetmap.josm.io.MultiFetchOverpassObjectReader;
    2931import org.openstreetmap.josm.io.MultiFetchServerObjectReader;
    3032import org.openstreetmap.josm.io.OsmServerBackreferenceReader;
    3133import org.openstreetmap.josm.io.OsmServerReader;
    3234import org.openstreetmap.josm.io.OsmTransferException;
     35import org.openstreetmap.josm.io.OverpassDownloadReader;
    3336import org.openstreetmap.josm.tools.CheckParameterUtil;
    3437import org.openstreetmap.josm.tools.ExceptionUtil;
    3538import org.xml.sax.SAXException;
     
    154157    @Override
    155158    protected void realRun() throws SAXException, IOException, OsmTransferException {
    156159        try {
    157             progressMonitor.setTicksCount(children.size());
    158             int i = 1;
    159             for (PrimitiveId p : children) {
    160                 if (canceled)
    161                     return;
    162                 String msg;
    163                 String id = Long.toString(p.getUniqueId());
    164                 switch(p.getType()) {
    165                 case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i, children.size(), id); break;
    166                 case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i, children.size(), id); break;
    167                 case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i, children.size(), id); break;
    168                 default: throw new AssertionError();
     160            if (Boolean.TRUE.equals(OverpassDownloadReader.FOR_MULTI_FETCH.get())) {
     161                String request = MultiFetchOverpassObjectReader.genOverpassQuery(children, false, true, false);
     162                reader = new OverpassDownloadReader(new Bounds(0, 0, 0, 0),
     163                        OverpassDownloadReader.OVERPASS_SERVER.get(), request);
     164                DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
     165                new DataSetMerger(parents, ds).merge();
     166            } else {
     167                progressMonitor.setTicksCount(children.size());
     168                int i = 1;
     169                for (PrimitiveId p : children) {
     170                    if (canceled)
     171                        return;
     172                    String msg;
     173                    String id = Long.toString(p.getUniqueId());
     174                    switch(p.getType()) {
     175                    case NODE: msg = tr("({0}/{1}) Loading parents of node {2}", i, children.size(), id); break;
     176                    case WAY: msg = tr("({0}/{1}) Loading parents of way {2}", i, children.size(), id); break;
     177                    case RELATION: msg = tr("({0}/{1}) Loading parents of relation {2}", i, children.size(), id); break;
     178                    default: throw new AssertionError();
     179                    }
     180                    progressMonitor.subTask(msg);
     181                    downloadParents(p.getUniqueId(), p.getType(), progressMonitor);
     182                    i++;
    169183                }
    170                 progressMonitor.subTask(msg);
    171                 downloadParents(p.getUniqueId(), p.getType(), progressMonitor);
    172                 i++;
    173             }
    174             Collection<Way> ways = parents.getWays();
     184                Collection<Way> ways = parents.getWays();
    175185
    176             if (!ways.isEmpty()) {
    177                 // Collect incomplete nodes of parent ways
    178                 Set<Node> nodes = ways.stream().flatMap(w -> w.getNodes().stream().filter(OsmPrimitive::isIncomplete))
    179                         .collect(Collectors.toSet());
    180                 if (!nodes.isEmpty()) {
    181                     reader = MultiFetchServerObjectReader.create();
    182                     ((MultiFetchServerObjectReader) reader).append(nodes);
    183                     DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
    184                     synchronized (this) { // avoid race condition in cancel()
    185                         reader = null;
     186                if (!ways.isEmpty()) {
     187                    // Collect incomplete nodes of parent ways
     188                    Set<Node> nodes = ways.stream().flatMap(w -> w.getNodes().stream().filter(OsmPrimitive::isIncomplete))
     189                            .collect(Collectors.toSet());
     190                    if (!nodes.isEmpty()) {
     191                        reader = MultiFetchServerObjectReader.create();
     192                        ((MultiFetchServerObjectReader) reader).append(nodes);
     193                        DataSet wayNodes = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
     194                        synchronized (this) { // avoid race condition in cancel()
     195                            reader = null;
     196                        }
     197                        new DataSetMerger(parents, wayNodes).merge();
    186198                    }
    187                     new DataSetMerger(parents, wayNodes).merge();
    188199                }
    189200            }
    190201        } catch (OsmTransferException e) {
     
    193204            lastException = e;
    194205        }
    195206    }
     207
    196208}
  • src/org/openstreetmap/josm/gui/io/DownloadFromOverpassTask.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.io.IOException;
     7
     8import org.openstreetmap.josm.data.Bounds;
     9import org.openstreetmap.josm.data.osm.DataSet;
     10import org.openstreetmap.josm.data.osm.DataSetMerger;
     11import org.openstreetmap.josm.gui.ExceptionDialogUtil;
     12import org.openstreetmap.josm.gui.PleaseWaitRunnable;
     13import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     14import org.openstreetmap.josm.io.OsmTransferException;
     15import org.openstreetmap.josm.io.OverpassDownloadReader;
     16import org.xml.sax.SAXException;
     17
     18/**
     19 * Download OSM data from Overpass API
     20 *
     21 */
     22public class DownloadFromOverpassTask extends PleaseWaitRunnable {
     23    private boolean canceled;
     24    private final String request;
     25    private final DataSet ds;
     26    private Exception lastException;
     27
     28    /**
     29     * Constructor
     30     * @param request the overpass query
     31     * @param ds the {@code DataSet} instance that should contain the downloaded data
     32     * @param monitor ProgressMonitor to use or null to create a new one.
     33     */
     34    public DownloadFromOverpassTask(String request, DataSet ds, ProgressMonitor monitor) {
     35        super(tr("Download objects via Overpass API"), monitor, false);
     36        this.request = request;
     37        this.ds = ds;
     38    }
     39
     40    @Override
     41    protected void cancel() {
     42        canceled = true;
     43    }
     44
     45    @Override
     46    protected void realRun() throws SAXException, IOException, OsmTransferException {
     47        try {
     48            OverpassDownloadReader reader = new OverpassDownloadReader(new Bounds(0, 0, 0, 0),
     49                    OverpassDownloadReader.OVERPASS_SERVER.get(), request);
     50            DataSet tmpDs = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
     51            if (!canceled) {
     52                new DataSetMerger(ds, tmpDs).merge();
     53            }
     54        } catch (OsmTransferException e) {
     55            if (canceled)
     56                return;
     57            lastException = e;
     58        }
     59
     60    }
     61
     62    @Override
     63    protected void finish() {
     64        if (canceled)
     65            return;
     66        if (lastException != null) {
     67            ExceptionDialogUtil.explainException(lastException);
     68        }
     69    }
     70}
  • src/org/openstreetmap/josm/gui/io/DownloadPrimitivesWithReferrersTask.java

     
    99import java.io.IOException;
    1010import java.text.MessageFormat;
    1111import java.util.ArrayList;
     12import java.util.Collections;
    1213import java.util.HashSet;
     14import java.util.LinkedHashSet;
    1315import java.util.List;
    1416import java.util.Set;
    1517import java.util.stream.Collectors;
     
    3133import org.openstreetmap.josm.gui.util.GuiHelper;
    3234import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    3335import org.openstreetmap.josm.gui.widgets.JosmTextArea;
     36import org.openstreetmap.josm.io.MultiFetchOverpassObjectReader;
    3437import org.openstreetmap.josm.io.OsmTransferException;
     38import org.openstreetmap.josm.io.OverpassDownloadReader;
    3539import org.openstreetmap.josm.tools.GBC;
    3640import org.xml.sax.SAXException;
    3741
     
    5054
    5155    /** Temporary layer where downloaded primitives are put */
    5256    private final OsmDataLayer tmpLayer;
    53     /** Reference to the task that download requested primitives */
    54     private DownloadPrimitivesTask mainTask;
    5557    /** Flag indicated that user ask for cancel this task */
    5658    private boolean canceled;
    5759    /** Reference to the task currently running */
    5860    private PleaseWaitRunnable currentTask;
    5961
     62    /** set of missing ids, with overpass API these are also deleted objects */
     63    private Set<PrimitiveId> missingPrimitives;
     64
    6065    /**
    6166     * Constructor
    6267     *
     
    101106
    102107    @Override
    103108    protected void realRun() throws SAXException, IOException, OsmTransferException {
     109        if (Boolean.TRUE.equals(OverpassDownloadReader.FOR_MULTI_FETCH.get())) {
     110            useOverpassApi();
     111        } else {
     112            useOSMApi();
     113        }
     114    }
     115
     116    private void useOverpassApi() {
     117        String request = MultiFetchOverpassObjectReader.genOverpassQuery(ids, true, downloadReferrers, full);
     118        currentTask = new DownloadFromOverpassTask(request, tmpLayer.data, getProgressMonitor().createSubTaskMonitor(1, false));
     119        currentTask.run();
     120        missingPrimitives = ids.stream()
     121                .filter(id -> tmpLayer.data.getPrimitiveById(id) == null)
     122                .collect(Collectors.toSet());
     123    }
     124
     125    private void useOSMApi() {
    104126        getProgressMonitor().setTicksCount(ids.size()+1);
    105127        // First, download primitives
    106         mainTask = new DownloadPrimitivesTask(tmpLayer, ids, full, getProgressMonitor().createSubTaskMonitor(1, false));
     128        DownloadPrimitivesTask mainTask = new DownloadPrimitivesTask(tmpLayer, ids, full,
     129                getProgressMonitor().createSubTaskMonitor(1, false));
    107130        synchronized (this) {
    108131            currentTask = mainTask;
    109132            if (canceled) {
     
    112135            }
    113136        }
    114137        currentTask.run();
     138
     139        missingPrimitives = mainTask.getMissingPrimitives();
     140
    115141        // Then, download referrers for each primitive
    116142        if (downloadReferrers && tmpLayer.data != null) {
    117143            // see #18895: don't try to download parents for invisible objects
     
    143169        else
    144170            layer.mergeFrom(tmpLayer);
    145171
     172        // Collect known deleted primitives
     173        final Set<PrimitiveId> del = new HashSet<>();
     174        DataSet ds = MainApplication.getLayerManager().getEditDataSet();
     175        for (PrimitiveId id : ids) {
     176            OsmPrimitive osm = ds.getPrimitiveById(id);
     177            if (osm != null && osm.isDeleted()) {
     178                del.add(id);
     179            }
     180        }
     181        final Set<PrimitiveId> errs;
     182        if (missingPrimitives != null) {
     183            errs = missingPrimitives.stream().filter(id -> !del.contains(id)).collect(Collectors.toCollection(LinkedHashSet::new));
     184        } else {
     185            errs = Collections.emptySet();
     186        }
     187
    146188        // Warm about missing primitives
    147         final Set<PrimitiveId> errs = mainTask.getMissingPrimitives();
    148         if (errs != null && !errs.isEmpty())
     189        if (!errs.isEmpty()) {
     190            final String assumedApiRC;
     191            if (Boolean.TRUE.equals(OverpassDownloadReader.FOR_MULTI_FETCH.get())) {
     192                assumedApiRC = tr("The server does not know an object with the requested id, it was either deleted or doesn't exist.");
     193
     194            } else {
     195                assumedApiRC = tr("The server replied with response code 404.<br>"
     196                        + "This usually means, the server does not know an object with the requested id.");
     197            }
    149198            GuiHelper.runInEDTAndWait(() -> reportProblemDialog(errs,
    150199                    trn("Object could not be downloaded", "Some objects could not be downloaded", errs.size()),
    151200                    trn("One object could not be downloaded.<br>",
     
    152201                            "{0} objects could not be downloaded.<br>",
    153202                            errs.size(),
    154203                            errs.size())
    155                             + tr("The server replied with response code 404.<br>"
    156                                  + "This usually means, the server does not know an object with the requested id."),
     204                            + assumedApiRC,
    157205                    tr("missing objects:"),
    158206                    JOptionPane.ERROR_MESSAGE
    159207                    ).showDialog());
     208        }
    160209
    161210        // Warm about deleted primitives
    162         final Set<PrimitiveId> del = new HashSet<>();
    163         DataSet ds = MainApplication.getLayerManager().getEditDataSet();
    164         for (PrimitiveId id : ids) {
    165             OsmPrimitive osm = ds.getPrimitiveById(id);
    166             if (osm != null && osm.isDeleted()) {
    167                 del.add(id);
    168             }
    169         }
    170211        if (!del.isEmpty())
    171212            GuiHelper.runInEDTAndWait(() -> reportProblemDialog(del,
    172213                    trn("Object deleted", "Objects deleted", del.size()),
     
    190231                return null;
    191232        }
    192233        List<PrimitiveId> downloaded = new ArrayList<>(ids);
    193         downloaded.removeAll(mainTask.getMissingPrimitives());
     234        downloaded.removeAll(missingPrimitives);
    194235        return downloaded;
    195236    }
    196237
  • src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.io;
    33
    4 import java.util.Map.Entry;
     4import java.util.Arrays;
     5import java.util.Collection;
     6import java.util.LinkedHashMap;
     7import java.util.List;
     8import java.util.Map;
    59import java.util.Set;
     10import java.util.TreeSet;
    611import java.util.stream.Collectors;
    712
    813import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     14import org.openstreetmap.josm.data.osm.PrimitiveId;
    915import org.openstreetmap.josm.tools.Logging;
    1016
    1117/**
     
    1420 * @since 9241
    1521 */
    1622public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
     23    private static final List<OsmPrimitiveType> wantedOrder = Arrays.asList(OsmPrimitiveType.RELATION,
     24            OsmPrimitiveType.WAY, OsmPrimitiveType.NODE);
    1725
    1826    private static String getPackageString(final OsmPrimitiveType type, Set<Long> idPackage) {
    1927        return idPackage.stream().map(String::valueOf)
    20                 .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ");"));
     28                .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ")"));
    2129    }
    2230
    2331    /**
    24      * Create a single query for all elements
    25      * @return the request string
     32     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
     33     * children, the objects, or any combination of them.
     34     * @param ids the collection of ids
     35     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
     36     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
     37     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
     38     * @return the overpass query
     39     * @since xxx
    2640     */
    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:
     41    public static String genOverpassQuery(Collection<? extends PrimitiveId> ids, boolean includeObjects, boolean recurseUp,
     42            boolean recurseDownRelations) {
     43        Map<OsmPrimitiveType, Set<Long>> primitivesMap = new LinkedHashMap<>();
     44        Arrays.asList(OsmPrimitiveType.RELATION, OsmPrimitiveType.WAY, OsmPrimitiveType.NODE)
     45                .forEach(type -> primitivesMap.put(type, new TreeSet<>()));
     46        for (PrimitiveId p : ids) {
     47            primitivesMap.get(p.getType()).add(p.getUniqueId());
     48        }
     49        return genOverpassQuery(primitivesMap, includeObjects, recurseUp, recurseDownRelations);
     50    }
     51
     52    /**
     53     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
     54     * children, the objects, or any combination of them.
     55     * @param primitivesMap map containing the primitives
     56     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
     57     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
     58     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
     59     * @return the overpass query
     60     */
     61    protected static String genOverpassQuery(Map<OsmPrimitiveType, Set<Long>> primitivesMap, boolean includeObjects,
     62            boolean recurseUp, boolean recurseDownRelations) {
     63        if (!(includeObjects || recurseUp || recurseDownRelations))
     64            throw new IllegalArgumentException("At least one options must be true");
     65        StringBuilder sb = new StringBuilder(128);
     66        StringBuilder setsToInclude = new StringBuilder();
     67        StringBuilder up = new StringBuilder();
     68        String down = null;
     69        for (OsmPrimitiveType type : wantedOrder) {
     70            Set<Long> set = primitivesMap.get(type);
     71            if (!set.isEmpty()) {
     72                sb.append(getPackageString(type, set));
     73                if (type == OsmPrimitiveType.NODE) {
     74                    sb.append("->.n;");
     75                    if (includeObjects) {
     76                        setsToInclude.append(".n;");
     77                    }
     78                    if (recurseUp) {
     79                        up.append(".n;way(bn)->.wn;.n;rel(bn)->.rn;");
     80                        setsToInclude.append(".wn;node(w);.rn;");
     81                    }
     82                } else if (type == OsmPrimitiveType.WAY) {
     83                    sb.append("->.w;");
     84                    if (includeObjects) {
     85                        setsToInclude.append(".w;>;");
     86                    }
     87                    if (recurseUp) {
     88                        up.append(".w;rel(bw)->.pw;");
     89                        setsToInclude.append(".pw;");
     90                    }
     91                } else {
     92                    sb.append("->.r;");
     93                    if (includeObjects) {
     94                        setsToInclude.append(".r;");
     95                    }
     96                    if (recurseUp) {
     97                        up.append(".r;rel(br)->.pr;");
     98                        setsToInclude.append(".pr;");
     99                    }
    37100                    if (recurseDownRelations) {
    38                         sb.append('(').append(list);
    39                         sb.setLength(sb.length()-1); // remove semicolon
    40                         //recurse down only one level, see #18835
    41                         sb.append("->.r;.r;rel(r);.r;way(r);>;.r;node(r););");
    42                     } else {
    43                         sb.append(list);
     101                        // get complete ways and nodes of the relation and next level of sub relations
     102                        down = ".r;rel(r)->.rm;";
     103                        setsToInclude.append(".r;>;.rm;");
    44104                    }
    45                     break;
    46                 case CLOSEDWAY:
    47                 case WAY:
    48                     sb.append('(').append(list).append(">;);");
    49                     break;
    50                 case NODE:
    51                     sb.append(list);
    52105                }
    53106            }
    54107        }
     108        if (up.length() > 0) {
     109            sb.append(up);
     110        }
     111        if (down != null) {
     112            sb.append(down);
     113        }
     114        sb.append('(').append(setsToInclude).append(");");
     115
     116        sb.append("out meta;");
    55117        String query = sb.toString();
    56         if (countTypes > 1) {
    57             query = "(" + query + ");";
    58         }
    59         query += "out meta;";
    60118        Logging.debug("{0} {1}", "Generated Overpass query:", query);
    61119        return query;
    62120    }
  • src/org/openstreetmap/josm/io/MultiFetchServerObjectReader.java

     
    382382        try {
    383383            if (this instanceof MultiFetchOverpassObjectReader) {
    384384                // calculate a single request for all the objects
    385                 String request = ((MultiFetchOverpassObjectReader) this).buildComplexRequestString();
     385                String request = MultiFetchOverpassObjectReader.genOverpassQuery(primitivesMap, true, false, recurseDownRelations);
    386386                if (isCanceled())
    387387                    return null;
    388388                OverpassDownloadReader reader = new OverpassDownloadReader(new Bounds(0, 0, 0, 0), getBaseUrl(), request);
    389                 DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(n, false));
     389                DataSet ds = reader.parseOsm(progressMonitor.createSubTaskMonitor(1, false));
    390390                new DataSetMerger(outputDataSet, ds).merge();
    391391                checkMissing(outputDataSet, progressMonitor);
    392392            } else {
  • test/unit/org/openstreetmap/josm/io/MultiFetchOverpassObjectReaderTest.java

     
    44import static org.junit.Assert.assertEquals;
    55
    66import java.util.Arrays;
     7import java.util.List;
    78
    89import org.junit.Rule;
    910import org.junit.Test;
    1011import org.openstreetmap.josm.data.osm.Node;
     12import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1113import org.openstreetmap.josm.data.osm.Relation;
    1214import org.openstreetmap.josm.data.osm.Way;
    1315import org.openstreetmap.josm.testutils.JOSMTestRules;
     
    2729    public JOSMTestRules test = new JOSMTestRules().preferences();
    2830
    2931    /**
    30      * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
    31      */
    32     @Test
    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 
     32     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
     33     */
     34    @Test
     35    public void testBuildRequestNodesString() {
     36        List<OsmPrimitive> objects = Arrays.asList(new Node(123), new Node(126), new Node(130));
     37        String requestString;
     38        // nodes without parents
     39        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, false);
     40        assertEquals("node(id:123,126,130)->.n;(.n;);out meta;", requestString);
     41        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, true);
     42        assertEquals("node(id:123,126,130)->.n;(.n;);out meta;", requestString);
     43
     44        // nodes with parents
     45        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, false);
     46        assertEquals("node(id:123,126,130)->.n;.n;way(bn)->.wn;.n;rel(bn)->.rn;(.n;.wn;node(w);.rn;);out meta;",
     47                requestString);
     48        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, true);
     49        assertEquals("node(id:123,126,130)->.n;.n;way(bn)->.wn;.n;rel(bn)->.rn;(.n;.wn;node(w);.rn;);out meta;",
     50                requestString);
     51
     52        // simulate download referrers
     53        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, false, true, false);
     54        assertEquals("node(id:123,126,130)->.n;.n;way(bn)->.wn;.n;rel(bn)->.rn;(.wn;node(w);.rn;);out meta;",
     55                requestString);
     56
     57    }
     58
     59    /**
     60     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
     61     */
     62    @Test
     63    public void testBuildRequestWaysString() {
     64        List<OsmPrimitive> objects = Arrays.asList(new Way(123), new Way(126), new Way(130));
     65        String requestString;
     66        // ways without parents (always with nodes)
     67        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, false);
     68        assertEquals("way(id:123,126,130)->.w;(.w;>;);out meta;", requestString);
     69        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, true);
     70        assertEquals("way(id:123,126,130)->.w;(.w;>;);out meta;", requestString);
     71
     72        // ways with parents (always with nodes)
     73        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, false);
     74        assertEquals("way(id:123,126,130)->.w;.w;rel(bw)->.pw;(.w;>;.pw;);out meta;", requestString);
     75        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, true);
     76        assertEquals("way(id:123,126,130)->.w;.w;rel(bw)->.pw;(.w;>;.pw;);out meta;", requestString);
     77
     78        // simulate download referrers
     79        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, false, true, false);
     80        assertEquals("way(id:123,126,130)->.w;.w;rel(bw)->.pw;(.pw;);out meta;", requestString);
     81
     82    }
     83
    4084    /**
    4185     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
    4286     */
    4387    @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)->.r;.r;rel(r);.r;way(r);>;.r;node(r););out meta;", requestString);
    50         reader.setRecurseDownRelations(false);
    51         requestString = reader.buildComplexRequestString();
    52         assertEquals("relation(id:123,126,130);out meta;", requestString);
     88    public void testBuildRequestRelationsString() {
     89        List<OsmPrimitive> objects = Arrays.asList(new Relation(123), new Relation(126), new Relation(130));
     90        String requestString;
     91        // objects without parents or children
     92        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, false);
     93        assertEquals("relation(id:123,126,130)->.r;(.r;);out meta;", requestString);
     94        // objects without parents, with children
     95        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, true);
     96        assertEquals("relation(id:123,126,130)->.r;.r;rel(r)->.rm;(.r;.r;>;.rm;);out meta;", requestString);
     97        // objects with parents, without children
     98        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, false);
     99        assertEquals("relation(id:123,126,130)->.r;.r;rel(br)->.pr;(.r;.pr;);out meta;", requestString);
     100        // objects with parents and with children
     101        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, true);
     102        assertEquals("relation(id:123,126,130)->.r;.r;rel(br)->.pr;.r;rel(r)->.rm;(.r;.pr;.r;>;.rm;);out meta;",
     103                requestString);
     104        // simulate download referrers
     105        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, false, true, false);
     106        assertEquals("relation(id:123,126,130)->.r;.r;rel(br)->.pr;(.pr;);out meta;", requestString);
     107
    53108    }
    54109
    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();
     110    /**
     111     * Test {@link MultiFetchOverpassObjectReader#buildRequestString}
     112     */
     113    @Test
     114    public void testBuildComplexString() {
     115        List<OsmPrimitive> objects = Arrays.asList(new Relation(123), new Relation(126), new Relation(130), new Way(88), new Way(99),
     116                new Node(1));
     117        // all request strings should start with the same list of objects
     118        final String ids =  "relation(id:123,126,130)->.r;way(id:88,99)->.w;node(1)->.n;";
     119        String requestString;
     120
     121        // objects without parents (ways always with nodes)
     122        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, false);
     123        assertEquals(ids + "(.r;.w;>;.n;);out meta;", requestString);
     124        // objects without parents (ways always with nodes), recurse down one level for sub relations
     125        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, false, true);
     126        assertEquals(ids + ".r;rel(r)->.rm;(.r;.r;>;.rm;.w;>;.n;);out meta;", requestString);
     127
     128        // objects with parents
     129        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, false);
    65130        assertEquals(
    66                 "((relation(id:123,126,130)->.r;.r;rel(r);.r;way(r);>;.r;node(r););(way(id:88,99);>;);node(1););out meta;",
     131                ids + ".r;rel(br)->.pr;.w;rel(bw)->.pw;.n;way(bn)->.wn;.n;rel(bn)->.rn;(.r;.pr;.w;>;.pw;.n;.wn;node(w);.rn;);out meta;",
    67132                requestString);
    68         reader.setRecurseDownRelations(false);
    69         requestString = reader.buildComplexRequestString();
    70         assertEquals("(relation(id:123,126,130);(way(id:88,99);>;);node(1););out meta;", requestString);
    71     }
    72 
     133
     134        // objects with parents, recurse down one level for sub relations
     135        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, true, true, true);
     136        assertEquals(ids + ".r;rel(br)->.pr;.w;rel(bw)->.pw;.n;way(bn)->.wn;.n;rel(bn)->.rn;.r;rel(r)->.rm;"
     137                + "(.r;.pr;.r;>;.rm;.w;>;.pw;.n;.wn;node(w);.rn;);out meta;", requestString);
     138        // simulate download referrers
     139        requestString = MultiFetchOverpassObjectReader.genOverpassQuery(objects, false, true, false);
     140        assertEquals(
     141                ids + ".r;rel(br)->.pr;.w;rel(bw)->.pw;.n;way(bn)->.wn;.n;rel(bn)->.rn;(.pr;.pw;.wn;node(w);.rn;);out meta;",
     142                requestString);
     143    }
    73144}