Skip to content

Commit

Permalink
Merge pull request #653 from lonvia/simplify-tag-filters
Browse files Browse the repository at this point in the history
Simplify handling of tag filter parameters
  • Loading branch information
lonvia authored Mar 5, 2022
2 parents 2b56ad1 + a10f337 commit 0a600b5
Show file tree
Hide file tree
Showing 15 changed files with 455 additions and 799 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,7 @@ public PhotonQueryBuilder buildQuery(PhotonRequest photonRequest, boolean lenien
lastLenient = lenient;
return PhotonQueryBuilder.
builder(photonRequest.getQuery(), photonRequest.getLanguage(), supportedLanguages, lenient).
withTags(photonRequest.tags()).
withKeys(photonRequest.keys()).
withValues(photonRequest.values()).
withoutTags(photonRequest.notTags()).
withoutKeys(photonRequest.notKeys()).
withoutValues(photonRequest.notValues()).
withTagsNotValues(photonRequest.tagNotValues()).
withOsmTagFilters(photonRequest.getOsmTagFilters()).
withLocationBias(photonRequest.getLocationForBias(), photonRequest.getScaleForBias(), photonRequest.getZoomForBias()).
withBoundingBox(photonRequest.getBbox());
}
Expand Down
157 changes: 32 additions & 125 deletions src/main/java/de/komoot/photon/elasticsearch/PhotonQueryBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Point;
import de.komoot.photon.searcher.TagFilter;
import de.komoot.photon.searcher.TagFilterKind;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery.ScoreMode;
import org.elasticsearch.common.unit.Fuzziness;
Expand Down Expand Up @@ -183,119 +185,41 @@ public PhotonQueryBuilder withBoundingBox(Envelope bbox) {
return this;
}

public PhotonQueryBuilder withTags(Map<String, Set<String>> tags) {
if (!checkTags(tags)) return this;

ensureFiltered();

List<BoolQueryBuilder> termQueries = new ArrayList<BoolQueryBuilder>(tags.size());
for (String tagKey : tags.keySet()) {
Set<String> valuesToInclude = tags.get(tagKey);
TermQueryBuilder keyQuery = QueryBuilders.termQuery("osm_key", tagKey);
TermsQueryBuilder valueQuery = QueryBuilders.termsQuery("osm_value", valuesToInclude.toArray(new String[valuesToInclude.size()]));
BoolQueryBuilder includeAndQuery = QueryBuilders.boolQuery().must(keyQuery).must(valueQuery);
termQueries.add(includeAndQuery);
public PhotonQueryBuilder withOsmTagFilters(List<TagFilter> filters) {
for (TagFilter filter : filters) {
addOsmTagFilter(filter);
}
this.appendIncludeTermQueries(termQueries);
return this;
}


public PhotonQueryBuilder withKeys(Set<String> keys) {
if (!checkTags(keys)) return this;

ensureFiltered();

List<TermsQueryBuilder> termQueries = new ArrayList<TermsQueryBuilder>(keys.size());
termQueries.add(QueryBuilders.termsQuery("osm_key", keys.toArray()));
this.appendIncludeTermQueries(termQueries);
return this;
}

public PhotonQueryBuilder addOsmTagFilter(TagFilter filter) {
state = State.FILTERED;

public PhotonQueryBuilder withValues(Set<String> values) {
if (!checkTags(values)) return this;

ensureFiltered();

List<TermsQueryBuilder> termQueries = new ArrayList<TermsQueryBuilder>(values.size());
termQueries.add(QueryBuilders.termsQuery("osm_value", values.toArray(new String[values.size()])));
this.appendIncludeTermQueries(termQueries);
return this;
}


public PhotonQueryBuilder withTagsNotValues(Map<String, Set<String>> tags) {
if (!checkTags(tags)) return this;

ensureFiltered();

List<BoolQueryBuilder> termQueries = new ArrayList<BoolQueryBuilder>(tags.size());
for (String tagKey : tags.keySet()) {
Set<String> valuesToInclude = tags.get(tagKey);
TermQueryBuilder keyQuery = QueryBuilders.termQuery("osm_key", tagKey);
TermsQueryBuilder valueQuery = QueryBuilders.termsQuery("osm_value", valuesToInclude.toArray(new String[valuesToInclude.size()]));

BoolQueryBuilder includeAndQuery = QueryBuilders.boolQuery().must(keyQuery).mustNot(valueQuery);

termQueries.add(includeAndQuery);
}
this.appendIncludeTermQueries(termQueries);
return this;
}


public PhotonQueryBuilder withoutTags(Map<String, Set<String>> tagsToExclude) {
if (!checkTags(tagsToExclude)) return this;

ensureFiltered();

List<QueryBuilder> termQueries = new ArrayList<>(tagsToExclude.size());
for (String tagKey : tagsToExclude.keySet()) {
Set<String> valuesToExclude = tagsToExclude.get(tagKey);
TermQueryBuilder keyQuery = QueryBuilders.termQuery("osm_key", tagKey);
TermsQueryBuilder valueQuery = QueryBuilders.termsQuery("osm_value", valuesToExclude.toArray(new String[valuesToExclude.size()]));

BoolQueryBuilder withoutTagsQuery = QueryBuilders.boolQuery().mustNot(QueryBuilders.boolQuery().must(keyQuery).must(valueQuery));

termQueries.add(withoutTagsQuery);
if (filter.getKind() == TagFilterKind.EXCLUDE_VALUE) {
appendIncludeTermQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("osm_key", filter.getKey()))
.mustNot(QueryBuilders.termQuery("osm_value", filter.getValue())));
} else {
QueryBuilder builder;
if (filter.isKeyOnly()) {
builder = QueryBuilders.termQuery("osm_key", filter.getKey());
} else if (filter.isValueOnly()) {
builder = QueryBuilders.termQuery("osm_value", filter.getValue());
} else {
builder = QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("osm_key", filter.getKey()))
.must(QueryBuilders.termQuery("osm_value", filter.getValue()));
}
if (filter.getKind() == TagFilterKind.INCLUDE) {
appendIncludeTermQuery(builder);
} else {
appendExcludeTermQuery(builder);
}
}

this.appendExcludeTermQueries(termQueries);

return this;
}


public PhotonQueryBuilder withoutKeys(Set<String> keysToExclude) {
if (!checkTags(keysToExclude)) return this;

ensureFiltered();

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery("osm_key", keysToExclude.toArray()));

LinkedList<QueryBuilder> lList = new LinkedList<>();
lList.add(boolQuery);
this.appendExcludeTermQueries(lList);

return this;
}


public PhotonQueryBuilder withoutValues(Set<String> valuesToExclude) {
if (!checkTags(valuesToExclude)) return this;

ensureFiltered();

BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().mustNot(QueryBuilders.termsQuery("osm_value", valuesToExclude.toArray()));

LinkedList<QueryBuilder> lList = new LinkedList<>();
lList.add(boolQuery);
this.appendExcludeTermQueries(lList);

return this;
}


/**
* When this method is called, all filters are placed inside their containers and the top level filter
Expand All @@ -312,7 +236,7 @@ public QueryBuilder buildQuery() {
if (orQueryBuilderForIncludeTagFiltering != null)
tagFilters.must(orQueryBuilderForIncludeTagFiltering);
if (andQueryBuilderForExcludeTagFiltering != null)
tagFilters.must(andQueryBuilderForExcludeTagFiltering);
tagFilters.mustNot(andQueryBuilderForExcludeTagFiltering);
finalQueryBuilder.filter(tagFilters);
}

Expand All @@ -325,38 +249,21 @@ public QueryBuilder buildQuery() {
}


private Boolean checkTags(Set<String> keys) {
return !(keys == null || keys.isEmpty());
}


private Boolean checkTags(Map<String, Set<String>> tags) {
return !(tags == null || tags.isEmpty());
}


private void appendIncludeTermQueries(List<? extends QueryBuilder> termQueries) {
private void appendIncludeTermQuery(QueryBuilder termQuery) {

if (orQueryBuilderForIncludeTagFiltering == null)
orQueryBuilderForIncludeTagFiltering = QueryBuilders.boolQuery();

for (QueryBuilder eachTagFilter : termQueries)
orQueryBuilderForIncludeTagFiltering.should(eachTagFilter);
orQueryBuilderForIncludeTagFiltering.should(termQuery);
}


private void appendExcludeTermQueries(List<QueryBuilder> termQueries) {
private void appendExcludeTermQuery(QueryBuilder termQuery) {

if (andQueryBuilderForExcludeTagFiltering == null)
andQueryBuilderForExcludeTagFiltering = QueryBuilders.boolQuery();

for (QueryBuilder eachTagFilter : termQueries)
andQueryBuilderForExcludeTagFiltering.must(eachTagFilter);
}


private void ensureFiltered() {
state = State.FILTERED;
andQueryBuilderForExcludeTagFiltering.should(termQuery);
}


Expand Down
39 changes: 21 additions & 18 deletions src/main/java/de/komoot/photon/query/BoundingBoxParamConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,37 @@
public class BoundingBoxParamConverter {

public static final String INVALID_BBOX_ERROR_MESSAGE = "invalid number of supplied coordinates for parameter 'bbox', expected format is: minLon,minLat,maxLon,maxLat";
public static final String INVALID_BBOX_BOUNDS_MESSAGE = "Invalid bounds for parameter bbox, expected values minLat, maxLat element [-90,90], minLon, maxLon element [-180,180]";
public static final String INVALID_BBOX_BOUNDS_MESSAGE = "Invalid bounds for parameter 'bbox', expected values minLat, maxLat element [-90,90], minLon, maxLon element [-180,180]";

public Envelope apply(Request webRequest) throws BadRequestException {
String bboxParam = webRequest.queryParams("bbox");
if (bboxParam == null) {
return null;
}
Envelope bbox = null;

String[] bboxCoords = bboxParam.split(",");
if (bboxCoords.length != 4) {
throw new BadRequestException(400, INVALID_BBOX_ERROR_MESSAGE);
}

return new Envelope(parseDouble(bboxCoords[0], 180),
parseDouble(bboxCoords[2], 180),
parseDouble(bboxCoords[1], 90),
parseDouble(bboxCoords[3], 90));
}

private double parseDouble(String coord, double limit) throws BadRequestException {
double result;
try {
String[] bboxCoords = bboxParam.split(",");
if (bboxCoords.length != 4) {
throw new BadRequestException(400, INVALID_BBOX_ERROR_MESSAGE);
}
Double minLon = Double.valueOf(bboxCoords[0]);
Double minLat = Double.valueOf(bboxCoords[1]);
Double maxLon = Double.valueOf(bboxCoords[2]);
Double maxLat = Double.valueOf(bboxCoords[3]);
if (minLon > 180.0 || minLon < -180.00 || maxLon > 180.0 || maxLon < -180.00) {
throw new BadRequestException(400, INVALID_BBOX_BOUNDS_MESSAGE);
}
if (minLat > 90.0 || minLat < -90.00 || maxLat > 90.0 || maxLat < -90.00) {
throw new BadRequestException(400, INVALID_BBOX_BOUNDS_MESSAGE);
}
bbox = new Envelope(minLon, maxLon, minLat, maxLat);
result = Double.parseDouble(coord);
} catch (NumberFormatException nfe) {
throw new BadRequestException(400, INVALID_BBOX_ERROR_MESSAGE);
}

return bbox;
if (Double.isNaN(result) || result < -limit || result > limit) {
throw new BadRequestException(400, INVALID_BBOX_BOUNDS_MESSAGE);
}

return result;
}
}
21 changes: 0 additions & 21 deletions src/main/java/de/komoot/photon/query/CheckIfFilteredRequest.java

This file was deleted.

Loading

0 comments on commit 0a600b5

Please sign in to comment.