001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.LinkedHashMap;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010import java.util.TreeSet;
011import java.util.stream.Collectors;
012
013import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
014import org.openstreetmap.josm.data.osm.PrimitiveId;
015import org.openstreetmap.josm.tools.Logging;
016
017/**
018 * Retrieves a set of {@link org.openstreetmap.josm.data.osm.OsmPrimitive}s from an Overpass API server.
019 *
020 * @since 9241
021 */
022public class MultiFetchOverpassObjectReader extends MultiFetchServerObjectReader {
023    private static final List<OsmPrimitiveType> wantedOrder = Arrays.asList(OsmPrimitiveType.RELATION,
024            OsmPrimitiveType.WAY, OsmPrimitiveType.NODE);
025
026    private static String getPackageString(final OsmPrimitiveType type, Set<Long> idPackage) {
027        return idPackage.stream().map(String::valueOf)
028                .collect(Collectors.joining(",", type.getAPIName() + (idPackage.size() == 1 ? "(" : "(id:"), ")"));
029    }
030
031    /**
032     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
033     * children, the objects, or any combination of them.
034     * @param ids the collection of ids
035     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
036     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
037     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
038     * @return the overpass query
039     * @since 16611
040     */
041    public static String genOverpassQuery(Collection<? extends PrimitiveId> ids, boolean includeObjects, boolean recurseUp,
042            boolean recurseDownRelations) {
043        Map<OsmPrimitiveType, Set<Long>> primitivesMap = new LinkedHashMap<>();
044        Arrays.asList(OsmPrimitiveType.RELATION, OsmPrimitiveType.WAY, OsmPrimitiveType.NODE)
045                .forEach(type -> primitivesMap.put(type, new TreeSet<>()));
046        for (PrimitiveId p : ids) {
047            primitivesMap.get(p.getType()).add(p.getUniqueId());
048        }
049        return genOverpassQuery(primitivesMap, includeObjects, recurseUp, recurseDownRelations);
050    }
051
052    /**
053     * Generate single overpass query to retrieve multiple primitives. Can be used to download parents,
054     * children, the objects, or any combination of them.
055     * @param primitivesMap map containing the primitives
056     * @param includeObjects if false, don't retrieve the primitives (e.g. only the referrers)
057     * @param recurseUp if true, referrers (parents) of the objects are downloaded and all nodes of parent ways
058     * @param recurseDownRelations true: yes, recurse down to retrieve complete relations
059     * @return the overpass query
060     */
061    protected static String genOverpassQuery(Map<OsmPrimitiveType, Set<Long>> primitivesMap, boolean includeObjects,
062            boolean recurseUp, boolean recurseDownRelations) {
063        if (!(includeObjects || recurseUp || recurseDownRelations))
064            throw new IllegalArgumentException("At least one options must be true");
065        StringBuilder sb = new StringBuilder(128);
066        StringBuilder setsToInclude = new StringBuilder();
067        StringBuilder up = new StringBuilder();
068        String down = null;
069        for (OsmPrimitiveType type : wantedOrder) {
070            Set<Long> set = primitivesMap.get(type);
071            if (!set.isEmpty()) {
072                sb.append(getPackageString(type, set));
073                if (type == OsmPrimitiveType.NODE) {
074                    sb.append("->.n;");
075                    if (includeObjects) {
076                        setsToInclude.append(".n;");
077                    }
078                    if (recurseUp) {
079                        up.append(".n;way(bn)->.wn;.n;rel(bn)->.rn;");
080                        setsToInclude.append(".wn;node(w);.rn;");
081                    }
082                } else if (type == OsmPrimitiveType.WAY) {
083                    sb.append("->.w;");
084                    if (includeObjects) {
085                        setsToInclude.append(".w;>;");
086                    }
087                    if (recurseUp) {
088                        up.append(".w;rel(bw)->.pw;");
089                        setsToInclude.append(".pw;");
090                    }
091                } else {
092                    sb.append("->.r;");
093                    if (includeObjects) {
094                        setsToInclude.append(".r;");
095                    }
096                    if (recurseUp) {
097                        up.append(".r;rel(br)->.pr;");
098                        setsToInclude.append(".pr;");
099                    }
100                    if (recurseDownRelations) {
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;");
104                    }
105                }
106            }
107        }
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(");out meta;");
115
116        String query = sb.toString();
117        Logging.debug("{0} {1}", "Generated Overpass query:", query);
118        return query;
119    }
120
121    @Override
122    protected String getBaseUrl() {
123        return OverpassDownloadReader.OVERPASS_SERVER.get();
124    }
125}