package de.westnordost.streetcomplete.data.osm.tql; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import de.westnordost.osmapi.map.data.BoundingBox; import de.westnordost.osmapi.map.data.Element; /** Represents a parse result of a string in filter syntax, i.e. * <tt>"ways with (highway = residential or highway = tertiary) and !name"</tt> */ public class TagFilterExpression { private List<ElementsTypeFilter> elementsTypeFilters; private BooleanExpression<OQLExpressionValue> tagExprRoot; public TagFilterExpression(List<ElementsTypeFilter> elementsTypeFilters, BooleanExpression<OQLExpressionValue> tagExprRoot) { this.elementsTypeFilters = elementsTypeFilters; this.tagExprRoot = tagExprRoot; } /** @return whether the given element is found through (=matches) this expression */ public boolean matches(Element element) { Element.Type eleType = element.getType(); boolean elementTypeMatches = false; switch(eleType) { case NODE: elementTypeMatches = elementsTypeFilters.contains(ElementsTypeFilter.NODES); break; case WAY: elementTypeMatches = elementsTypeFilters.contains(ElementsTypeFilter.WAYS); break; case RELATION: elementTypeMatches = elementsTypeFilters.contains(ElementsTypeFilter.RELATIONS); break; } return elementTypeMatches && tagExprRoot.matches(element); } /** @return this expression as a Overpass query string (in a short one-liner form) */ public String toOverpassQLString(BoundingBox bbox) { StringBuilder oql = new StringBuilder(); if(bbox != null) { oql.append( "[bbox:" + bbox.getMinLatitude() + "," + bbox.getMinLongitude() + "," + bbox.getMaxLatitude() + "," + bbox.getMaxLongitude() + "];"); } BooleanExpression<OQLExpressionValue> expandedExpression = createExpandedExpression(); List<String> elements = getTagFiltersOverpassList(expandedExpression); final boolean useUnion = elementsTypeFilters.size() > 1 || elements.size() > 1; if(useUnion) oql.append("("); for(ElementsTypeFilter filter : elementsTypeFilters) { oql.append(getTagFiltersOverpassString(filter, elements)); } if(useUnion) oql.append(");"); /* "body" print mode (default) does not include version, but "meta" does. "geom" prints out * geometry for every way and relation */ oql.append("out meta geom;"); return oql.toString(); } private BooleanExpression<OQLExpressionValue> createExpandedExpression() { BooleanExpression<OQLExpressionValue> result = tagExprRoot.copy(); result.flatten(); result.expand(); return result; } private static String getTagFiltersOverpassString(ElementsTypeFilter elementType, List<String> elements) { StringBuilder oql = new StringBuilder(); for(String element : elements) { oql.append(elementType.oqlName); oql.append(element); } return oql.toString(); } private static List<String> getTagFiltersOverpassList( BooleanExpression<OQLExpressionValue> expandedExpression) { BooleanExpression<OQLExpressionValue> child = expandedExpression.getFirstChild(); if(child == null) return Collections.singletonList(";"); if(child.isOr()) { return getUnionOverpassQueryString(child); } else if(child.isAnd() || child.isValue()) { return Collections.singletonList(getSingleOverpassQueryString(child)); } throw new RuntimeException("The boolean expression is not in the expected format"); } private static List<String> getUnionOverpassQueryString(BooleanExpression<OQLExpressionValue> child) { List<String> result = new ArrayList<>(); for(BooleanExpression<OQLExpressionValue> orChild : child.getChildren()) { result.add(getSingleOverpassQueryString(orChild)); } return result; } private static String getSingleOverpassQueryString(BooleanExpression<OQLExpressionValue> child) { if(child.isValue()) return child.getValue().toOverpassQLString() + ";"; if(!child.isAnd()) throw new RuntimeException("The boolean expression is not in the expected format"); StringBuilder result = new StringBuilder(); for(BooleanExpression<OQLExpressionValue> valueChild : child.getChildren()) { result.append(valueChild.getValue().toOverpassQLString()); } result.append(";"); return result.toString(); } }