Index: trunk/src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java	(revision 16008)
+++ trunk/src/org/openstreetmap/josm/io/MultiFetchOverpassObjectReader.java	(revision 16611)
@@ -2,9 +2,15 @@
 package org.openstreetmap.josm.io;
 
-import java.util.Map.Entry;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.stream.Collectors;
 
 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
 import org.openstreetmap.josm.tools.Logging;
 
@@ -15,47 +21,99 @@
  */
 public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
+    private static final List<OsmPrimitiveType> wantedOrder = Arrays.asList(OsmPrimitiveType.RELATION,
+            OsmPrimitiveType.WAY, OsmPrimitiveType.NODE);
 
     private static String getPackageString(final OsmPrimitiveType type, Set<Long> idPackage) {
         return idPackage.stream().map(String::valueOf)
-                .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ");"));
+                .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ")"));
     }
 
     /**
-     * Create a single query for all elements
-     * @return the request string
+     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
+     * children, the objects, or any combination of them.
+     * @param ids the collection of ids
+     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
+     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
+     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
+     * @return the overpass query
+     * @since xxx
      */
-    protected String buildComplexRequestString() {
-        StringBuilder sb = new StringBuilder();
-        int countTypes = 0;
-        for (Entry<OsmPrimitiveType, Set<Long>> e : primitivesMap.entrySet()) {
-            if (!e.getValue().isEmpty()) {
-                countTypes++;
-                String list = getPackageString(e.getKey(), e.getValue());
-                switch (e.getKey()) {
-                case MULTIPOLYGON:
-                case RELATION:
+    public static String genOverpassQuery(Collection<? extends PrimitiveId> ids, boolean includeObjects, boolean recurseUp,
+            boolean recurseDownRelations) {
+        Map<OsmPrimitiveType, Set<Long>> primitivesMap = new LinkedHashMap<>();
+        Arrays.asList(OsmPrimitiveType.RELATION, OsmPrimitiveType.WAY, OsmPrimitiveType.NODE)
+                .forEach(type -> primitivesMap.put(type, new TreeSet<>()));
+        for (PrimitiveId p : ids) {
+            primitivesMap.get(p.getType()).add(p.getUniqueId());
+        }
+        return genOverpassQuery(primitivesMap, includeObjects, recurseUp, recurseDownRelations);
+    }
+
+    /**
+     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
+     * children, the objects, or any combination of them.
+     * @param primitivesMap map containing the primitives
+     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
+     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
+     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
+     * @return the overpass query
+     */
+    protected static String genOverpassQuery(Map<OsmPrimitiveType, Set<Long>> primitivesMap, boolean includeObjects,
+            boolean recurseUp, boolean recurseDownRelations) {
+        if (!(includeObjects || recurseUp || recurseDownRelations))
+            throw new IllegalArgumentException("At least one options must be true");
+        StringBuilder sb = new StringBuilder(128);
+        StringBuilder setsToInclude = new StringBuilder();
+        StringBuilder up = new StringBuilder();
+        String down = null;
+        for (OsmPrimitiveType type : wantedOrder) {
+            Set<Long> set = primitivesMap.get(type);
+            if (!set.isEmpty()) {
+                sb.append(getPackageString(type, set));
+                if (type == OsmPrimitiveType.NODE) {
+                    sb.append("->.n;");
+                    if (includeObjects) {
+                        setsToInclude.append(".n;");
+                    }
+                    if (recurseUp) {
+                        up.append(".n;way(bn)->.wn;.n;rel(bn)->.rn;");
+                        setsToInclude.append(".wn;node(w);.rn;");
+                    }
+                } else if (type == OsmPrimitiveType.WAY) {
+                    sb.append("->.w;");
+                    if (includeObjects) {
+                        setsToInclude.append(".w;>;");
+                    }
+                    if (recurseUp) {
+                        up.append(".w;rel(bw)->.pw;");
+                        setsToInclude.append(".pw;");
+                    }
+                } else {
+                    sb.append("->.r;");
+                    if (includeObjects) {
+                        setsToInclude.append(".r;");
+                    }
+                    if (recurseUp) {
+                        up.append(".r;rel(br)->.pr;");
+                        setsToInclude.append(".pr;");
+                    }
                     if (recurseDownRelations) {
-                        sb.append('(').append(list);
-                        sb.setLength(sb.length()-1); // remove semicolon
-                        //recurse down only one level, see #18835
-                        sb.append("->.r;.r;rel(r);.r;way(r);>;.r;node(r););");
-                    } else {
-                        sb.append(list);
+                        // get complete ways and nodes of the relation and next level of sub relations
+                        down = ".r;rel(r)->.rm;";
+                        setsToInclude.append(".r;>;.rm;");
                     }
-                    break;
-                case CLOSEDWAY:
-                case WAY:
-                    sb.append('(').append(list).append(">;);");
-                    break;
-                case NODE:
-                    sb.append(list);
                 }
             }
         }
+        if (up.length() > 0) {
+            sb.append(up);
+        }
+        if (down != null) {
+            sb.append(down);
+        }
+        sb.append('(').append(setsToInclude).append(");");
+
+        sb.append("out meta;");
         String query = sb.toString();
-        if (countTypes > 1) {
-            query = "(" + query + ");";
-        }
-        query += "out meta;";
         Logging.debug("{0} {1}", "Generated Overpass query:", query);
         return query;
