Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
* A filter which selects OSM contributions by matching to a list of changeset ids.
*/
public class ChangesetIdFilterEqualsAnyOf extends NegatableFilter {
private final Collection<Long> changesetIdList;
private final Collection<Long> changesetIds;

ChangesetIdFilterEqualsAnyOf(@Nonnull Collection<Long> changesetIdList) {
this(new HashSet<>(changesetIdList));
}
ChangesetIdFilterEqualsAnyOf(@Nonnull Set<Long> changesetIds) {
super(new FilterInternal() {
private final Set<Long> changesetIds = new HashSet<>(changesetIdList);

@Override
public boolean applyOSH(OSHEntity entity) {
Expand All @@ -37,15 +39,15 @@ public boolean applyOSMEntitySnapshot(OSMEntitySnapshot ignored) {

@Override
public String toString() {
return "changeset:in(" + changesetIdList.stream().map(String::valueOf)
return "changeset:in(" + changesetIds.stream().map(String::valueOf)
.collect(Collectors.joining(",")) + ")";
}
});
this.changesetIdList = changesetIdList;
this.changesetIds = changesetIds;
}

@Contract(pure = true)
public Collection<Long> getChangesetIdList() {
return this.changesetIdList;
return this.changesetIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* A filter which selects OSM contributions by matching to a range of changeset ids.
*/
public class ChangesetIdFilterRange extends NegatableFilter {
private final IdRange changesetIdRange;

ChangesetIdFilterRange(IdRange changesetIdRange) {
super(new FilterInternal() {
@Override
Expand All @@ -30,5 +32,10 @@ public String toString() {
return "changeset:in-range" + changesetIdRange;
}
});
this.changesetIdRange = changesetIdRange;
}

public IdRange getChangesetIdRange() {
return changesetIdRange;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ default boolean applyOSMGeometry(OSMEntity entity, Geometry geometry) {
* Apply a filter to a snapshot ({@link OSMEntitySnapshot}) of an OSM entity.
*
* @param snapshot a snapshot of the OSM entity to check
* @return true if the the OSM entity snapshot fulfills the specified filter, otherwise false.
* @return true if the OSM entity snapshot fulfills the specified filter, otherwise false.
*/
@Contract(pure = true)
default boolean applyOSMEntitySnapshot(OSMEntitySnapshot snapshot) {
Expand All @@ -95,7 +95,7 @@ default boolean applyOSMEntitySnapshot(OSMEntitySnapshot snapshot) {
* modification or the state of it after the modification matches the filter.</p>
*
* @param contribution a modification of the OSM entity to check
* @return true if the the OSM contribution fulfills the specified filter, otherwise false.
* @return true if the OSM contribution fulfills the specified filter, otherwise false.
*/
@Contract(pure = true)
default boolean applyOSMContribution(OSMContribution contribution) {
Expand Down Expand Up @@ -133,11 +133,11 @@ default boolean applyOSMContribution(OSMContribution contribution) {
*/
@Contract(pure = true)
default List<List<Filter>> normalize() {
if (this instanceof Filter) {
return Collections.singletonList(Collections.singletonList((Filter) this));
} else if (this instanceof AndOperator) {
List<List<Filter>> exp1 = ((BinaryOperator) this).getLeftOperand().normalize();
List<List<Filter>> exp2 = ((BinaryOperator) this).getRightOperand().normalize();
if (this instanceof Filter filter) {
return Collections.singletonList(Collections.singletonList(filter));
} else if (this instanceof AndOperator operator) {
List<List<Filter>> exp1 = operator.getLeftOperand().normalize();
List<List<Filter>> exp2 = operator.getRightOperand().normalize();
// return cross product of exp1 and exp2
List<List<Filter>> combined = new LinkedList<>();
for (List<Filter> e1 : exp1) {
Expand All @@ -149,9 +149,9 @@ default List<List<Filter>> normalize() {
}
}
return combined;
} else if (this instanceof OrOperator) {
List<List<Filter>> exp1 = ((BinaryOperator) this).getLeftOperand().normalize();
List<List<Filter>> exp2 = ((BinaryOperator) this).getRightOperand().normalize();
} else if (this instanceof OrOperator operator) {
List<List<Filter>> exp1 = operator.getLeftOperand().normalize();
List<List<Filter>> exp2 = operator.getRightOperand().normalize();
List<List<Filter>> combined = new ArrayList<>(exp1.size() + exp2.size());
combined.addAll(exp1);
combined.addAll(exp2);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.heigit.ohsome.oshdb.filter;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static java.util.Collections.emptySet;

public class FilterUtil {

private FilterUtil() {
// utility class
Comment thread
rtroilo marked this conversation as resolved.
Outdated
}

public static Set<Long> ids(FilterExpression expression) {
return ids(expression.normalize());
}

public static Set<Long> ids(List<List<Filter>> normalized) {
var ids = new HashSet<Long>();
for (var orGroups : normalized) {
var groupIds = new HashSet<Long>();
for (var filter: orGroups) {
if (filter instanceof IdFilterEquals equals){
groupIds.add(equals.getId());
} else if (filter instanceof IdFilterEqualsAnyOf equalsAnyOf) {
groupIds.addAll(equalsAnyOf.getIds());
} else if (filter instanceof IdFilterRange range) {
range.getRange().getIds().forEach(groupIds::add);
}
}
if (groupIds.isEmpty()) {
return emptySet();
}
ids.addAll(groupIds);
}
return ids;
}

@tyrasd tyrasd Jun 28, 2023

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could move the code from optimizeFilters1 to this class?!

    public static Set<OSMType> extractTypes(FilterExpression expression) {
        return extractTypes(expression.normalize());
    }

    public static Set<OSMType> extractTypes(List<List<Filter>> normalized) {
        var allTypes = EnumSet.noneOf(OSMType.class);
        extract(normalized, ignored -> Stream.of(true)).keySet()
                .forEach(allTypes::addAll);
        return allTypes;
    }

and in MapReducer.optimizeFilters1:

    mapRed = mapRed.osmTypeInternal(FilterUtil.extractTypes(filter));

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@
* A tag filter which executes a "id [not] in (id1, id2, …)" check.
*/
public class IdFilterEqualsAnyOf extends NegatableFilter {

private final Set<Long> ids;

IdFilterEqualsAnyOf(@Nonnull Collection<Long> idList) {
this(new HashSet<>(idList));
}

IdFilterEqualsAnyOf(@Nonnull Set<Long> ids) {
super(new FilterInternal() {
private final Set<Long> ids = new HashSet<>(idList);

@Override
public boolean applyOSH(OSHEntity entity) {
return this.ids.contains(entity.getId());
return ids.contains(entity.getId());
}

@Override
Expand All @@ -28,7 +34,7 @@ boolean applyOSHNegated(OSHEntity entity) {

@Override
public boolean applyOSM(OSMEntity entity) {
return this.ids.contains(entity.getId());
return ids.contains(entity.getId());
}

@Override
Expand All @@ -38,12 +44,18 @@ boolean applyOSMNegated(OSMEntity entity) {

@Override
public String toString() {
return "id:in" + this.ids.stream().map(String::valueOf).collect(Collectors.joining(","));
return "id:in" + ids.stream().map(String::valueOf).collect(Collectors.joining(","));
}
});

if (idList.isEmpty()) {
if (ids.isEmpty()) {
throw new IllegalStateException("list of ids must not be empty in a id in (list) filter");
}

this.ids = ids;
}

public Set<Long> getIds() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be included in ParseTest.testIdFilterEqualsAnyOf to check if it returns the expected values

return ids;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* A filter which executes a "id [not] in range" check.
*/
public class IdFilterRange extends NegatableFilter {
private final IdRange range;

IdFilterRange(@Nonnull IdRange range) {
super(new FilterInternal() {
@Override
Expand Down Expand Up @@ -35,5 +37,10 @@ public String toString() {
return "id:in-range" + range;
}
});
this.range = range;
}

public IdRange getRange() {
return range;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.heigit.ohsome.oshdb.filter;

import java.io.Serializable;
import java.util.stream.LongStream;

/**
* Helper class to handle ranges of ids (incl. user ids, changeset ids, etc.).
Expand Down Expand Up @@ -34,4 +35,8 @@ public String toString() {
+ ".."
+ (toId == Long.MAX_VALUE ? "" : toId);
}

public LongStream getIds() {
Comment thread
rtroilo marked this conversation as resolved.
return LongStream.range(fromId, toId+1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,26 @@
*/
abstract class TagFilterAnyOf implements Filter {
final int keyId;
final Set<OSHDBTag> tags;
final HashSet<OSHDBTag> tags;

TagFilterAnyOf(@Nonnull Collection<OSHDBTag> tags) {
Optional<OSHDBTag> firstTag = tags.stream().findFirst();
if (!firstTag.isPresent()) {
throw new IllegalStateException("list of tags must not be empty in a key in (values) filter");
} else {
this.keyId = firstTag.get().getKey();
this.tags = new HashSet<>(tags);
}
this.keyId = firstTag
.orElseThrow(() -> new IllegalStateException("list of tags must not be empty in a key in (values) filter"))
.getKey();
this.tags = new HashSet<>(tags);

if (!tags.stream().allMatch(tag -> tag.getKey() == this.keyId)) {
throw new IllegalStateException(
"list of tags must all share the same tag key in a key in (values) filter");
}
}

public int getKeyId() {
return keyId;
}

public Set<OSHDBTag> getTags() {
return tags;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.heigit.ohsome.oshdb.filter;

import org.junit.jupiter.api.Test;

import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

class FilterUtilTest extends FilterTest{

@Test
void extractIds() {
extractIds("id:123", Set.of(123L));
extractIds("id:123 or id:234", Set.of(123L, 234L));
extractIds("id:123 and not id:234", Set.of(123L));
extractIds("id:123 or name=heigit", Set.of());
extractIds("id:(123, 234)", Set.of(123L, 234L));
extractIds("id:(123 .. 125)", Set.of(123L, 124L, 125L));
}

private void extractIds(String filter, Set<Long> expected) {
var expression = parser.parse(filter);
var actual = FilterUtil.ids(expression);

assertEquals(expected.size(), actual.size());
assertTrue(expected.containsAll(actual));
}

}