/*
* 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.referencing.piecewise;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Locale;
import java.util.MissingResourceException;
import org.geotools.renderer.i18n.Vocabulary;
import org.geotools.renderer.i18n.VocabularyKeys;
import org.geotools.util.NumberRange;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.Utilities;
import org.opengis.util.InternationalString;
/**
* Convenience implementation of the {@link Domain1D} interface.
* @author Simone Giannecchini
*
* @source $URL$
*/
public class DefaultDomain1D<E extends DefaultDomainElement1D> extends AbstractList<E> implements Domain1D<E>{
/*
* (non-Javadoc)
*
* @see org.geotools.referencing.piecewise.Domain1D#getName()
*/
public synchronized InternationalString getName() {
if (name == null) {
final StringBuffer buffer = new StringBuffer(30);
final Locale locale = Locale.getDefault();
if (main != null) {
buffer.append(main.getName().toString(locale));
} else {
buffer.append('(');
buffer.append(Vocabulary.getResources(locale).getString(
VocabularyKeys.UNTITLED));
buffer.append(')');
}
name = SimpleInternationalString.wrap(buffer.toString());
}
return name;
}
/*
* (non-Javadoc)
*
* @see org.geotools.referencing.piecewise.Domain1D#getRange()
*/
public NumberRange<? extends Number> getApproximateDomainRange() {
synchronized (elements) {
// @todo TODO should I include the NaN value?
if (range == null) {
NumberRange<?> range = null;
for (E element:elements) {
final NumberRange<? extends Number> extent = element.getRange();
if (!Double.isNaN(extent.getMinimum())&& !Double.isNaN(extent.getMaximum())) {
if (range != null) {
range = new NumberRange(range.union(extent));
} else {
range = extent;
}
}
}
this.range = range;
}
return range;
}
}
/**
* The list of elements. This list most be sorted in increasing order of
* left range element.
*/
private E[] elements;
/**
* {@code true} if there is gaps between elements, or {@code false}
* otherwise. A gap is found if for example the range of value is [-9999 ..
* -9999] for the first domain element and [0 .. 1000] for the second one.
*/
private boolean hasGaps;
/**
* The "main" domain element, or {@code null} if there is none. The main
* domain element is the quantitative domain element with the widest range of sample
* values.
*/
private E main;
/**
* List of {@link #inputMinimum} values for each domain element in
* {@link #elements} . This array <strong>must</strong> be in
* increasing order. Actually, this is the need to sort this array that
* determines the element order in {@link #elements} .
*/
private double[] minimums;
/**
* The name for this domain element list. Will be constructed only when
* first needed.
*
* @see #getName
*/
private InternationalString name;
/**
* The range of values in this domain element list. This is the union of
* the range of values of every elements, excluding {@code NaN}
* values. This field will be computed only when first requested.
*/
private NumberRange<?> range;
/**
* Lazily initialized hashcode for this class
*/
private int hashCode=-1;
/**
* Constructor for {@link DefaultDomain1D}.
*
* @param inDomainElements
* {@link DomainElement1D} objects that make up this list.
*/
public DefaultDomain1D(E[] inDomainElements) {
init(inDomainElements);
}
/**
* @param inDomainElements
* @throws IllegalArgumentException
* @throws MissingResourceException
*/
@SuppressWarnings("unchecked")
private void init(E[] inDomainElements)
throws IllegalArgumentException, MissingResourceException {
// /////////////////////////////////////////////////////////////////////
//
// input checks
//
// /////////////////////////////////////////////////////////////////////
PiecewiseUtilities.ensureNonNull("DomainElement1D[]", inDomainElements);
// @todo TODOCHECK ME
if (inDomainElements == null)
this.elements = (E[]) new DefaultDomainElement1D[]{ new DefaultPassthroughPiecewiseTransform1DElement("p0")};
else
this.elements = inDomainElements.clone();
// /////////////////////////////////////////////////////////////////////
//
// Sort the input elements.
//
// /////////////////////////////////////////////////////////////////////
Arrays.sort(this.elements);
// /////////////////////////////////////////////////////////////////////
//
// Construct the array of minimum values. During the loop, we make sure
// there is no overlapping in input and output.
//
// /////////////////////////////////////////////////////////////////////
hasGaps = false;
minimums = new double[elements.length];
for (int i = 0; i < elements.length; i++) {
final DefaultDomainElement1D c = elements[i];
final double inMinimum = minimums[i] = c.getInputMinimum();
if (i != 0) {
assert !(inMinimum < minimums[i - 1]) : inMinimum;
// Use '!' to accept NaN.
final DefaultDomainElement1D previous = elements[i - 1];
if (PiecewiseUtilities.compare(inMinimum, previous
.getInputMaximum()) <= 0) {
PiecewiseUtilities.domainElementsOverlap(elements, i);
}
// Check if there is a gap between this domain element and the
// previous one.
if (!Double.isNaN(inMinimum)
&& inMinimum != ((NumberRange<?>) previous.getRange()).getMaximum(false)) {
hasGaps = true;
}
}
}
/*
* Search for what seems to be the "main" domain element. This loop looks for
* the quantitative domain element (if there is one) with the widest range of
* sample values.
*/
double range = 0;
E main = null;
for (int i = elements.length; --i >= 0;) {
final E candidate = elements[i];
if (Double.isInfinite(candidate.getInputMinimum())
&& Double.isInfinite(candidate.getInputMaximum())) {
range = Double.POSITIVE_INFINITY;
main = candidate;
continue;
}
final double candidateRange = candidate.getInputMaximum()
- candidate.getInputMinimum();
if (candidateRange >= range) {
range = candidateRange;
main = candidate;
}
}
this.main = main;
// postcondition
assert PiecewiseUtilities.isSorted(elements);
}
/**
* Returns the domain element of the specified sample value. If no domain element fits,
* then this method returns {@code null}.
*
* @param value
* The value.
* @return The domain element of the supplied value, or {@code null}.
*/
public E findDomainElement(final double value) {
int i = getDomainElementIndex(value);
// //
//
// Checks
//
// //
if (i < 0)
return null;
E domainElement1D;
if (i > elements.length)
return null;
// //
//
// First of all let's check if we spotted a break point in out domains
// element. If so the index we got is not an insertion point but it is
// an actual domain element index. This happens when we catch precisely
// a minimum element for a domain.
//
// //
if (i < elements.length) {
domainElement1D = elements[i];
if (domainElement1D.contains(value))
return domainElement1D;
// if the index was 0, unless we caught the smallest minimum we have
// got something smaller than the leftmost domain
if (i == 0)
return null;
}
// //
//
// Ok, now we know that we did not precisely caught a minimum for a
// domain, we therefore got an insertion point. This means that, unless
// we have fallen into a gap we need to subtract 1 to check for
// inclusion in the right domain.
//
// //
domainElement1D = elements[i - 1];
if (domainElement1D.contains(value))
return domainElement1D;
// //
//
// Well, if we get here, we have definitely fallen into a gap or the
// value is beyond the limits of the last domain, too bad....
//
// //
assert i >= elements.length || hasGaps : value;
return null;
}
/**
* @param sample
* @return
*/
private int getDomainElementIndex(final double sample) {
int i = -1;
// Special 'binarySearch' for NaN
i = PiecewiseUtilities.binarySearch(minimums, sample);
if (i >= 0) {
// The value is exactly equals to one of minimum,
// or is one of NaN values. There is nothing else to do.
assert Double.doubleToRawLongBits(sample) == Double
.doubleToRawLongBits(minimums[i]);
return i;
}
assert i == Arrays.binarySearch(minimums, sample) : i;
// 'binarySearch' found the index of "insertion point" (-(insertion
// point) - 1). The
// insertion point is defined as the point at which the key would be
// inserted into the list: the index of the first element greater than
// the key, or list.size(), if all elements in the list are less than
// the specified key. Note that this guarantees that the return value
// will be >= 0 if and only if the key is found.
i = -i - 1;
return i;
}
// ////////////////////////////////////////////////////////////////////////////////////////
// ////// ////////
// ////// I M P L E M E N T A T I O N O F List I N T E R F A C E ////////
// ////// ////////
// ////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the number of elements in this list.
*/
@Override
public int size() {
return elements.length;
}
/**
* Returns the element at the specified position in this list.
*/
@Override
public E get(final int i) {
return elements[i];
}
/**
* Returns all elements in this {@code }.
*/
@Override
public Object[] toArray() {
return (DomainElement1D[]) elements.clone();
}
/**
* Compares the specified object with this domain element list for equality. If
* the two objects are instances of the {@link DefaultDomain1D} class, then
* the test check for the equality of the single elements.
*/
@SuppressWarnings("unchecked")
@Override
public boolean equals(final Object object) {
if(this== object)
return true;
if(!(object instanceof DefaultDomain1D))
return false;
final DefaultDomain1D that = (DefaultDomain1D) object;
if(getEquivalenceClass()!=that.getEquivalenceClass())
return false;
if(!this.getName().equals(that.getName()))
return false;
if(!this.getApproximateDomainRange().equals(that.getApproximateDomainRange()))
return false;
if (Arrays.equals(this.elements, that.elements)) {
assert Arrays.equals(this.minimums, that.minimums);
return true;
}
return false;
}
protected Class<?> getEquivalenceClass(){
return DefaultDomain1D.class;
}
/*
* (non-Javadoc)
*
* @see org.geotools.referencing.piecewise.Domain1D#hasGaps()
*/
public boolean hasGaps() {
return hasGaps;
}
/**
* Return what seems to be the main {@link DomainElement1D} for this
* list.
*
* @return what seems to be the main {@link DomainElement1D} for this
* list.
*/
public E getMain() {
return main;
}
/**
* @return
*/
public double[] getMinimums() {
return (double[]) minimums.clone();
}
@Override
public int hashCode() {
if(hashCode<0)
{
int result =Utilities.deepHashCode( elements );
result=Utilities.deepHashCode( minimums );
result=Utilities.hash( getName(),result );
hashCode=Utilities.hash(getApproximateDomainRange(),hashCode );
}
return hashCode;
}
}