package org.geotools.filter.visitor; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.TreeSet; import org.geotools.util.Range; import org.opengis.filter.Filter; import org.opengis.filter.FilterFactory; import org.opengis.filter.PropertyIsNotEqualTo; import org.opengis.filter.expression.Expression; /** * Represents the domain of a variable as a set of ranges * * @author Andrea Aime - GeoSolutions * * @param <T> */ public class MultiRange<T extends Comparable<? super T>> { static final class RangeComparator<T extends Comparable<? super T>> implements Comparator<Range<T>> { @Override public int compare(Range<T> o1, Range<T> o2) { if(o1 == null) { return o2 != null ? -1 : 0; } else if(o2 == null) { return 1; } else if(o1.getMinValue() == null) { return o2.getMinValue() == null ? 0 : -1; } else if(o2.getMinValue() == null) { return 1; } else { return o1.getMinValue().compareTo(o2.getMinValue()); } } } TreeSet<Range<T>> ranges = new TreeSet<>(new RangeComparator<T>()); public MultiRange(Range<T> range) { this.ranges.add(range); } public MultiRange(MultiRange<T> other) { this.ranges.addAll(other.ranges); } public MultiRange(List<Range<T>> ranges) { this.ranges.addAll(ranges); } public MultiRange(Class<T> binding, T exclusion) { this.ranges.add(new Range(binding, null, false, exclusion, false)); this.ranges.add(new Range(binding, exclusion, false, null, false)); } public MultiRange merge(MultiRange<T> other) { MultiRange<T> result = new MultiRange<>(this); for (Range<T> r : other.ranges) { result.addRange(r); } return result; } public void addRange(Range<T> range) { if(range.isEmpty()) { return; } List<Range<T>> overlapping = getOverlappingRanges(range); if(overlapping != null && !overlapping.isEmpty()) { ranges.removeAll(overlapping); Range combined = range; for (Range r : overlapping) { combined = combined.union(r); } ranges.add(combined); } else { ranges.add(range); } } public MultiRange<T> intersect(MultiRange<T> other) { List<Range<T>> intersections = new ArrayList<>(); for (Range<T> r1 : ranges) { for (Range<T> r2 : other.ranges) { if(r1.intersects(r2)) { intersections.add((Range<T>) r1.intersect(r2)); } } } return new MultiRange<>(intersections); } public void removeRange(Range<T> range) { List<Range<T>> overlapping = getOverlappingRanges(range); if(overlapping != null) { ranges.remove(overlapping); List<Range<?>> removed = new ArrayList<>(); for (Range<T> r : overlapping) { Range<?>[] difference = r.subtract(range); for (Range<?> d : difference) { if(!d.isEmpty()) { removed.add(d); } } } for (Range<?> r : removed) { ranges.add((Range<T>) r); } } } private List<Range<T>> getOverlappingRanges(Range<T> range) { List<Range<T>> overlapping = new ArrayList<>(); for (Range r : ranges) { if(r.intersects(range) || contiguous(r, range)) { overlapping.add(r); } } return overlapping; } private boolean contiguous(Range r1, Range<T> r2) { if(r1.getMinValue() != null && r2.getMaxValue() != null && (r1.isMinIncluded() || r2.isMaxIncluded())) { return r1.getMinValue().equals(r2.getMaxValue()); } else if(r1.getMaxValue() != null && r2.getMinValue() != null && (r1.isMaxIncluded() || r2.isMinIncluded())) { return r1.getMaxValue().equals(r2.getMinValue()); } else { return false; } } public Filter toFilter(FilterFactory ff, Expression variable) { if(ranges.size() == 0) { return Filter.EXCLUDE; } else if(ranges.size() == 1 && ranges.first().getMinValue() == null && ranges.first().getMaxValue() == null) { return Filter.INCLUDE; } List<Range<T>> rangeList = new ArrayList<>(ranges); List<Filter> filters = new ArrayList<>(); int rangeCount = rangeList.size(); for (int i = 0; i < rangeCount; ) { Range range = rangeList.get(i); i++; List<T> exclusions = new ArrayList<>(); Range curr = range; while(i < rangeCount) { Range next = rangeList.get(i); if(next.getMinValue().equals(curr.getMaxValue())) { // do we have a hole? if(!next.isMinIncluded() && !curr.isMaxIncluded()) { exclusions.add((T) curr.getMaxValue()); } i++; curr = next; } else { break; } } if(curr == range) { // no exclusions, this range is isolated filters.add(toFilter(ff, variable, range)); } else { Range<T> union = new Range<T>(range.getElementClass(), (T) range.getMinValue(), range.isMinIncluded(), (T) curr.getMaxValue(), curr.isMaxIncluded()); Filter filter = toFilter(ff, variable, union); if(exclusions.size() == 0) { filters.add(filter); } else { List<Filter> exclusionFilters = new ArrayList<>(); if(!filter.INCLUDE.equals(filter)) { exclusionFilters.add(filter); } for (T exclusion : exclusions) { PropertyIsNotEqualTo ne = ff.notEqual(variable, ff.literal(exclusion)); exclusionFilters.add(ne); } if(exclusionFilters.size() == 1) { filter = exclusionFilters.get(0); } else { filter = ff.and(exclusionFilters); } filters.add(filter); } } } if(filters.size() == 0) { return Filter.EXCLUDE; } else if(filters.size() == 1) { return filters.get(0); } else { return ff.or(filters); } } private Filter toFilter(FilterFactory ff, Expression variable, Range<T> range) { if(range.getMinValue() == null && range.getMaxValue() == null) { return Filter.INCLUDE; } else if(range.isMinIncluded() && range.isMaxIncluded()) { if(range.getMinValue().equals(range.getMaxValue())) { return ff.equals(variable, ff.literal(range.getMinValue())); } return ff.between(variable, ff.literal(range.getMinValue()), ff.literal(range.getMaxValue())); } else if(range.getMinValue() == null) { return toLessFilter(ff, variable, range); } else if(range.getMaxValue() == null) { return toGreaterFilter(ff, variable, range); } else { Filter less = toLessFilter(ff, variable, range); Filter greater = toGreaterFilter(ff, variable, range); return ff.and(greater, less); } } private Filter toGreaterFilter(FilterFactory ff, Expression variable, Range<T> range) { if(range.isMinIncluded()) { return ff.greaterOrEqual(variable, ff.literal(range.getMinValue())); } else { return ff.greater(variable, ff.literal(range.getMinValue())); } } private Filter toLessFilter(FilterFactory ff, Expression variable, Range<T> range) { if(range.isMaxIncluded()) { return ff.lessOrEqual(variable, ff.literal(range.getMaxValue())); } else { return ff.less(variable, ff.literal(range.getMaxValue())); } } }