/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.geosolutions.jaiext.rlookup;
import it.geosolutions.jaiext.range.Range;
import it.geosolutions.jaiext.range.RangeFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A lookup table for the RangeLookup operation. It holds a collection of source value ranges, each mapped to a destination value. Instances of this
* class are immutable.
* <p>
* Use the associated Builder class to construct a new table:
*
* <pre>
* <code>
* // type parameters indicate lookup (source) and result
* // (destination) types
* RangeLookupTable.Builder<Double, Integer> builder = RangeLookupTable.builder();
*
* // map all values <= 0 to -1 and all values > 0 to 1
* builder.add(Range.create(Double.NEGATIVE_INFINITY, false, 0.0, true), -1)
* .add(Range.create(0.0, false, Double.POSITIVE_INFINITY, false), 1);
*
* RangeLookupTable<Double, Integer> table = builder.build();
* </code>
* </pre>
*
* @param <T> type of the lookup (source) value range
* @param <U> type of the result (destination) value
*
* @author Michael Bedward
* @author Simone Giannecchini, GeoSolutions
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class RangeLookupTable<T extends Number & Comparable<? super T>, U extends Number & Comparable<? super U>> {
private final List<LookupItem<T, U>> items;
/**
* Private constructor called from the Builder's build method.
*/
private RangeLookupTable(Builder builder) {
this.items = new ArrayList<LookupItem<T, U>>(builder.items);
// Sort the lookup items on the basis of their source ranges
Collections.sort(this.items, new LookupItemComparator<T, U>());
}
/**
* Finds the LookupItem containing the given source value.
*
* @param srcValue source image value
*
* @return the LookupItem containing the source value or null if no matching item exists
*/
public LookupItem<T, U> getLookupItem(T srcValue) {
if (items.isEmpty()) {
return null;
} else {
/*
* Binary search for source value in items sorted by source range
*/
int lo = 0;
int hi = items.size() - 1;
while (hi >= lo) {
// update mid position, avoiding int overflow
int mid = lo + (hi - lo) / 2;
LookupItem<T, U> item = items.get(mid);
Range r = item.getRange();
if (r.containsN(srcValue)) {
return item;
} else if (!Double.isInfinite(r.getMin().doubleValue())
&& Double.compare(srcValue.doubleValue(), r.getMin().doubleValue()) <= 0) {
hi = mid - 1;
} else {
lo = mid + 1;
}
}
return null; // no match
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (LookupItem item : items) {
sb.append(item).append("; ");
}
return sb.toString();
}
/**
* Package private method called by {@link RangeLookupRIF}.
*
* @return an unmodifiable view of the lookup table items
*/
List<LookupItem<T, U>> getItems() {
return Collections.unmodifiableList(items);
}
/**
* Builder to create an immutable lookup table.
*
* @param <T> lookup (source) value type
* @param <U> result (destination) valuetype
*/
public static class Builder<T extends Number & Comparable<? super T>, U extends Number & Comparable<? super U>> {
private final List<LookupItem<T, U>> items;
/**
* Creates a new builder.
*/
public Builder() {
this.items = new ArrayList<LookupItem<T, U>>();
}
/**
* Creates a new table that will hold the lookup items added to this builder.
*
* @return a new table instance
*/
public RangeLookupTable<T, U> build() {
return new RangeLookupTable<T, U>(this);
}
/**
* Adds a new lookup defined by a range of source values mapping to a result value.
*
* A new lookup range that overlaps one or more previously set ranges will be truncated or split into non-overlapping intervals. For example,
* if the lookup [5, 10] => 1 has previously been set, and a new lookup [0, 20] => 2 is added, then the following lookups will result:
*
* <pre>
* [0, 5) => 2
* [5, 10] => 1
* (10, 20] => 2
* </pre>
*
* Where a new range is completely overlapped by existing ranges it will be ignored.
* <p>
*
* Note that it is possible to end up with unintended gaps in lookup coverage. If the first range in the above example had been the half-open
* interval (5, 10] rather than the closed interval [5, 10] then the following would have resulted:
*
* <pre>
* [0, 5) => 2
* (5, 10] => 1
* (10, 20] => 2
* </pre>
*
* In this case the value 5 would not be matched.
*
* @param srcRange the source value range
* @param resultValue the destination value
*/
public Builder add(Range srcRange, U resultValue) {
if (srcRange == null || resultValue == null) {
throw new IllegalArgumentException("arguments must not be null");
}
// Check for overlap with existing ranges
for (LookupItem item : items) {
if (srcRange.intersects(item.getRange())) {
List<Range> diffs = RangeFactory.subtract(item.getRange(), srcRange);
for (Range diff : diffs) {
add(diff, resultValue);
}
return this;
}
}
items.add(new LookupItem<T, U>(srcRange, resultValue));
return this;
}
}
}