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}