/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.gce.imagemosaic; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.factory.CommonFactoryFinder; import org.geotools.feature.visitor.AbstractCalcResult; import org.geotools.feature.visitor.CalcResult; import org.geotools.feature.visitor.FeatureCalc; import org.geotools.util.Converters; import org.geotools.util.DateRange; import org.geotools.util.Range; import org.geotools.util.Utilities; import org.opengis.feature.Feature; import org.opengis.feature.simple.SimpleFeature; import org.opengis.filter.FilterFactory; import org.opengis.filter.expression.Expression; /** * Generates a list of NumberRanges from a collection * * @author Daniele Romagnoli, GeoSolutions SAS * */ class RangeVisitor implements FeatureCalc { enum RangeType { NUMBER, DATE; } /** * A DateRange comparator */ class DateRangeComparator implements Comparator<DateRange> { @Override public int compare(DateRange firstDateRange, DateRange secondDateRange) { Utilities.ensureNonNull("firstDateRange", firstDateRange); Utilities.ensureNonNull("secondDateRange", secondDateRange); final long beginFirst = firstDateRange.getMinValue().getTime(); final long endFirst = firstDateRange.getMaxValue().getTime(); final long beginSecond = secondDateRange.getMinValue().getTime(); final long endSecond = secondDateRange.getMaxValue().getTime(); return NumberRangeComparator.doubleCompare(beginFirst, endFirst, beginSecond, endSecond); } } /** * A NumberRange comparator */ static class NumberRangeComparator implements Comparator<Range<? extends Number>> { @Override public int compare(Range<? extends Number> firstRange, Range<? extends Number> secondRange) { Utilities.ensureNonNull("firstRange", firstRange); Utilities.ensureNonNull("secondRange", secondRange); final Number firstRangeMin = firstRange.getMinValue(); final Number firstRangeMax = firstRange.getMaxValue(); final Number secondRangeMin = secondRange.getMinValue(); final Number secondRangeMax = secondRange.getMaxValue(); return doubleCompare(firstRangeMin.doubleValue(), firstRangeMax.doubleValue(), secondRangeMin.doubleValue(), secondRangeMax.doubleValue()); } /** * Given a set of 4 double representing the extrema of 2 ranges, compare the 2 ranges. * * @param firstRangeMin the min value of the first range * @param firstRangeMax the max value of the first range * @param secondRangeMin the min value of the second range * @param secondRangeMax the max value of the second range * @return * * TODO: Improve that logic to deal with special cases on intervals management */ public static int doubleCompare(final double firstRangeMin, final double firstRangeMax, final double secondRangeMin, final double secondRangeMax) { if (firstRangeMin == secondRangeMin && firstRangeMax == secondRangeMax) { return 0; } if (firstRangeMin > secondRangeMin) { if (firstRangeMax > secondRangeMax) { return +2; } else { return +1; } } else { if (firstRangeMax <= secondRangeMax) { return -2; } else { return -1; } } } } /** expression related to the first attribute to be evaluated */ protected Expression expr1; /** expression related to the second attribute to be evaluated */ protected Expression expr2; /** The comparator instance to sort items inside the Tree set */ private Comparator comparator; /** The set containing the added ranges */ Set<Range> set = null; /** * A set of string representations of the returned ranges. Time ranges will be returned into a compact form so that intersecting ranges are merged * together into a bigger time range. */ Set<String> minimalRanges = null; public RangeVisitor(String attributeTypeName1, String attributeTypeName2) { this(attributeTypeName1, attributeTypeName2, RangeType.NUMBER); } /** * Range visitor constructor. * * @param attributeTypeName1 the name of the attribute to be related to the left side of the range * @param attributeTypeName2 the name of the attribute to be related to the right side of the range * @param rangeType the type of range, one of {@link RangeType#NUMBER},{@link RangeType#DATE} */ public RangeVisitor(String attributeTypeName1, String attributeTypeName2, RangeType rangeType) { FilterFactory factory = CommonFactoryFinder.getFilterFactory(null); expr1 = factory.property(attributeTypeName1); expr2 = factory.property(attributeTypeName2); this.comparator = rangeType == RangeType.NUMBER ? new NumberRangeComparator() : new DateRangeComparator(); set = new TreeSet(comparator); } public void init(SimpleFeatureCollection collection) { // do nothing } public void visit(SimpleFeature feature) { visit((Feature) feature); } public void visit(Feature feature) { // we ignore null attributes final Object firstValue = expr1.evaluate(feature); final Object secondValue = expr2.evaluate(feature); if (firstValue != null && secondValue != null) { set.add(Utils.createRange(firstValue, secondValue)); } } public void setValue(Object newSet) { if (newSet instanceof Collection) { // convert to set this.set = new HashSet((Collection) newSet); } else { Collection collection = Converters.convert(newSet, List.class); if (collection != null) { this.set = new HashSet(collection); } else { this.set = new HashSet(Collections.singleton(newSet)); } } } /** * Reset the collected ranges */ public void reset() { this.set = new TreeSet(comparator); this.minimalRanges = null; } /** * Return the minimal set of Ranges * * @return */ public Set<String> getRange() { if (minimalRanges == null) { minimalRanges = new LinkedHashSet<String>(); populateRange(); } return minimalRanges; } /** * Populate the set of minimal ranges as a set of Strings */ protected void populateRange() { Iterator<Range> iterator = set.iterator(); while (iterator.hasNext()) { Range range = iterator.next(); minimalRanges.add((range.getMinValue() + "/" + range.getMaxValue())); } } public CalcResult getResult() { if (set.size() < 1) { return CalcResult.NULL_RESULT; } return new RangeResult(set); } static class RangeResult extends AbstractCalcResult { private Set ranges; public RangeResult(Set newSet) { ranges = newSet; } public Object getValue() { return new HashSet(ranges); } public boolean isCompatible(CalcResult targetResults) { // list each calculation result which can merge with this type of result if (targetResults instanceof RangeResult || targetResults == CalcResult.NULL_RESULT) return true; return false; } public CalcResult merge(CalcResult resultsToAdd) { if (!isCompatible(resultsToAdd)) { throw new IllegalArgumentException("Parameter is not a compatible type"); } if (resultsToAdd == CalcResult.NULL_RESULT) { return this; } if (resultsToAdd instanceof RangeResult) { // add one set to the other (to create one big unique list) Set newSet = new HashSet(ranges); newSet.addAll((Set) resultsToAdd.getValue()); return new RangeResult(newSet); } else { throw new IllegalArgumentException( "The CalcResults claim to be compatible, but the appropriate merge method has not been implemented."); } } } }