0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, Geotools Project Management Committee (PMC)
0005: * (C) 2003, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: */
0017: package org.geotools.coverage;
0018:
0019: // J2SE and JAI dependencies
0020: import java.util.ArrayList;
0021: import java.util.Arrays;
0022: import java.util.Collection;
0023: import java.util.Collections;
0024: import java.util.Comparator;
0025: import java.util.HashSet;
0026: import java.util.Iterator;
0027: import java.util.List;
0028: import java.util.Locale;
0029: import java.util.logging.Level;
0030: import java.util.logging.Logger;
0031: import java.util.logging.LogRecord;
0032: import java.io.IOException;
0033: import java.io.ObjectInputStream;
0034: import javax.imageio.ImageReader;
0035: import javax.imageio.event.IIOReadWarningListener;
0036: import javax.imageio.event.IIOReadProgressListener;
0037: import javax.media.jai.InterpolationNearest;
0038: import java.lang.reflect.Array;
0039: import java.lang.reflect.UndeclaredThrowableException;
0040:
0041: // OpenGIS dependencies
0042: import org.opengis.coverage.Coverage;
0043: import org.opengis.coverage.SampleDimension;
0044: import org.opengis.coverage.CannotEvaluateException;
0045: import org.opengis.coverage.grid.GridRange;
0046: import org.opengis.coverage.grid.GridGeometry;
0047: import org.opengis.coverage.grid.GridCoverage;
0048: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0049: import org.opengis.referencing.crs.TemporalCRS;
0050: import org.opengis.referencing.operation.MathTransform;
0051: import org.opengis.referencing.operation.TransformException;
0052: import org.opengis.geometry.MismatchedDimensionException;
0053: import org.opengis.geometry.DirectPosition;
0054: import org.opengis.geometry.Envelope;
0055:
0056: // Geotools dependencies
0057: import org.geotools.coverage.grid.GridCoverage2D;
0058: import org.geotools.coverage.grid.Interpolator2D;
0059: import org.geotools.geometry.GeneralDirectPosition;
0060: import org.geotools.geometry.GeneralEnvelope;
0061: import org.geotools.image.io.IIOListeners;
0062: import org.geotools.image.io.IIOReadProgressAdapter;
0063: import org.geotools.util.logging.Logging;
0064: import org.geotools.resources.Utilities;
0065: import org.geotools.resources.CRSUtilities;
0066: import org.geotools.resources.i18n.Errors;
0067: import org.geotools.resources.i18n.ErrorKeys;
0068: import org.geotools.resources.i18n.Vocabulary;
0069: import org.geotools.resources.i18n.VocabularyKeys;
0070: import org.geotools.referencing.CRS;
0071: import org.geotools.referencing.crs.DefaultTemporalCRS;
0072: import org.geotools.util.SimpleInternationalString;
0073: import org.geotools.util.NumberRange;
0074:
0075: /**
0076: * Wraps a stack of {@linkplain Coverage coverages} as an extra dimension. For example this class
0077: * can wraps an array of {@link org.geotools.coverage.grid.GridCoverage2D} on the same geographic
0078: * area, but where each {@code GridCoverage2D} is for a different date. This {@code CoverageStack}
0079: * manages the two-dimensional coverages as if the whole set was a huge three-dimensional coverage.
0080: * <p>
0081: * Each {@linkplain Element coverage element} in the stack usually covers the same
0082: * {@linkplain Coverage#getEnvelope geographic area}, but this is not a requirement. However,
0083: * they must use the same {@linkplain CoordinateReferenceSystem coordinate reference system}.
0084: * For performance reason, the later condition will not be checked except at construction time
0085: * if the CRS is provided in the envelope, and at evaluation time if Java assertion are enabled.
0086: * If the CRS of coverage elements is uncertain, consider wrapping them in a
0087: * {@link TransformedCoverage} object.
0088: * <p>
0089: * Coverage elements are often two-dimensional, but this is not a requirement. This stack will
0090: * simply append one more dimension to the coverage element's CRS dimensions. Coverage elements
0091: * may be other {@code CoverateStack} objects, thus allowing construction of coverages with four
0092: * or more dimensions.
0093: * <p>
0094: * {@code GridCoverage2D} objects tend to be big. In order to keep memory usage raisonable, this
0095: * implementation doesn't requires all {@code GridCoverage} objects at once. Instead, it requires
0096: * an array of {@link Element} objects, which will load the coverage content only when first
0097: * needed. This {@code CoverageStack} implementation remember the last coverage elements used;
0098: * it will not trig new data loading as long as consecutive calls to {@code evaluate(...)}
0099: * methods require the same coverage elements. Apart from this very simple caching mechanism,
0100: * caching is the responsability of {@link Element} implementations. Note that this simple
0101: * caching mechanism is suffisient if {@code evaluate(...)} methods are invoked with increasing
0102: * <var>z</var> values.
0103: * <p>
0104: * Each coverage element is expected to extends over a range of <var>z</var> values (the new
0105: * dimensions appended by this {@code CoverageStack}). If an {@code evaluate(...)} method is
0106: * invoked with a <var>z</var> value not falling in the middle of a coverage element, a linear
0107: * interpolation is applied.
0108: * <p>
0109: * <strong>Note:</strong> This implementation is thread-safe.
0110: *
0111: * @since 2.1
0112: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/CoverageStack.java $
0113: * @version $Id: CoverageStack.java 27862 2007-11-12 19:51:19Z desruisseaux $
0114: * @author Martin Desruisseaux
0115: */
0116: public class CoverageStack extends AbstractCoverage {
0117: /**
0118: * An element in a {@linkplain CoverageStack coverage stack}. Each element is expected to
0119: * extents over a range of <var>z</var> values (the new dimensions appended by the
0120: * {@code CoverageStack} container). Implementations should be capable to returns coverage's
0121: * {@linkplain #getZRange range of z-values} without loading the coverage's data. If an
0122: * expensive loading is required, it should be delayed until the {@link #getCoverage} method
0123: * is invoked. If {@code getCoverage} is invoked more than once, caching (if desirable) is
0124: * implementor's responsability.
0125: * <p>
0126: * All methods declares {@link IOException} in their throws cause in case I/O operations are
0127: * required. Subclasses of {@code IOException} include {@link javax.imageio.IIOException} for
0128: * image I/O operations, or {@link java.rmi.RemoteOperation} for remote method invocation
0129: * (which may be useful for large images database backed by a distant server).
0130: *
0131: * @since 2.1
0132: * @version $Id: CoverageStack.java 27862 2007-11-12 19:51:19Z desruisseaux $
0133: * @author Martin Desruisseaux
0134: */
0135: public static interface Element {
0136: /**
0137: * Returns a name for the coverage. This method should not load a large amount of data,
0138: * since it may be invoked soon. This method is invoked just before {@link #getCoverage}
0139: * in order to log a "Loading data..." message.
0140: */
0141: String getName() throws IOException;
0142:
0143: /**
0144: * Returns the minimum and maximum <var>z</var> value for the coverage.
0145: * This information is mandatory. This method should not load a large
0146: * amount of data, since it may be invoked soon. Note that this method
0147: * may be invoked often, so it should be efficient.
0148: *
0149: * @throws IOException if an I/O operation was required but failed.
0150: */
0151: NumberRange getZRange() throws IOException;
0152:
0153: /**
0154: * Returns the coverage envelope, or {@code null} if this information is too expensive to
0155: * compute. The envelope may or may not contains an extra dimension for the
0156: * {@linkplain #getZRange range of z values}, since the {@link CoverageStack} class is
0157: * tolerant in this regard. This method should not load a large amount of data, since it
0158: * may be invoked soon.
0159: *
0160: * @throws IOException if an I/O operation was required but failed.
0161: */
0162: Envelope getEnvelope() throws IOException;
0163:
0164: /**
0165: * The coverage grid geometry, or {@code null} if this information do not applies or is too
0166: * expensive to compute. This method should not load a large amount of data, since it may be
0167: * invoked soon.
0168: *
0169: * @throws IOException if an I/O operation was required but failed.
0170: */
0171: GridGeometry getGridGeometry() throws IOException;
0172:
0173: /**
0174: * The sample dimension for the coverage, or {@code null} if this information is too
0175: * expensive to compute. This method should not load a large amount of data, since it
0176: * may be invoked soon.
0177: *
0178: * @throws IOException if an I/O operation was required but failed.
0179: */
0180: SampleDimension[] getSampleDimensions() throws IOException;
0181:
0182: /**
0183: * Returns the coverage, loading the data if needed. Implementations should invokes the
0184: * {@link IIOListeners#addListenersTo(ImageReader)} method if they use an image reader
0185: * for loading data. Caching (if desired) is implementor's responsability. The default
0186: * {@link CoverageStack} implementation caches only the last coverage used.
0187: *
0188: * @param listeners Listeners to register to the {@linkplain ImageReader image I/O reader},
0189: * if such a reader is going to be used.
0190: * @throws IOException if a data loading was required but failed.
0191: */
0192: Coverage getCoverage(IIOListeners listeners) throws IOException;
0193: }
0194:
0195: /**
0196: * A convenience adapter class for wrapping a pre-loaded {@link Coverage} into an
0197: * {@link Element} object. This adapter provides basic implementation for all methods,
0198: * but they a require a fully constructed {@link Coverage} object. Subclasses are strongly
0199: * encouraged to provides alternative implementation loading only the minimum amount of data
0200: * required for each method.
0201: *
0202: * @since 2.1
0203: * @version $Id: CoverageStack.java 27862 2007-11-12 19:51:19Z desruisseaux $
0204: * @author Martin Desruisseaux
0205: */
0206: public static class Adapter implements Element {
0207: /**
0208: * The wrapped coverage, or {@code null} if not yet loaded.
0209: * If null, the loading must be performed by the {@link #getCoverage} method.
0210: */
0211: protected Coverage coverage;
0212:
0213: /**
0214: * Minimum and maximum <var>z</var> values for this element, or {@code null} if not yet
0215: * determined. If {@code null}, the range must be computed by the {@link #getZRange} method.
0216: */
0217: protected NumberRange range;
0218:
0219: /**
0220: * Constructs a new adapter for the specified coverage and <var>z</var> values.
0221: *
0222: * @param coverage The coverage to wrap. Can be {@code null} only if this constructor
0223: * is invoked from a sub-class constructor.
0224: * @param range The minimum and maximum <var>z</var> values for this element, or
0225: * {@code null} to infers it from the last dimension in the coverage's
0226: * envelope.
0227: */
0228: public Adapter(final Coverage coverage, final NumberRange range) {
0229: this .coverage = coverage;
0230: this .range = range;
0231: if (getClass() == Adapter.class) {
0232: if (coverage == null) {
0233: // TODO: provides a localized message.
0234: throw new IllegalArgumentException("coverage");
0235: }
0236: }
0237: }
0238:
0239: /**
0240: * Returns the coverage name. The default implementation delegates to the
0241: * {@linkplain #getCoverage underlying coverage} if it is an instance of
0242: * {@link AbstractCoverage}.
0243: */
0244: public String getName() throws IOException {
0245: Object coverage = getCoverage(null);
0246: if (coverage instanceof AbstractCoverage) {
0247: coverage = ((AbstractCoverage) coverage).getName();
0248: }
0249: return coverage.toString();
0250: }
0251:
0252: /**
0253: * Returns the minimum and maximum <var>z</var> values for the coverage. If the range was
0254: * not explicitly specified to the constructor, then the default implementation infers it
0255: * from the last dimension in the coverage's envelope.
0256: */
0257: public NumberRange getZRange() throws IOException {
0258: if (range == null) {
0259: final Envelope envelope = getEnvelope();
0260: final int zDimension = envelope.getDimension() - 1;
0261: range = new NumberRange(
0262: envelope.getMinimum(zDimension), envelope
0263: .getMaximum(zDimension));
0264: }
0265: return range;
0266: }
0267:
0268: /**
0269: * Returns the coverage envelope. The default implementation delegates to the
0270: * {@linkplain #getCoverage underlying coverage}.
0271: */
0272: public Envelope getEnvelope() throws IOException {
0273: return getCoverage(null).getEnvelope();
0274: }
0275:
0276: /**
0277: * Returns the coverage grid geometry. The default implementation delegates to the
0278: * {@linkplain #getCoverage underlying coverage} if it is an instance of
0279: * {@link GridCoverage}.
0280: */
0281: public GridGeometry getGridGeometry() throws IOException {
0282: final Coverage coverage = getCoverage(null);
0283: return (coverage instanceof GridCoverage) ? ((GridCoverage) coverage)
0284: .getGridGeometry()
0285: : null;
0286: }
0287:
0288: /**
0289: * Returns the sample dimension for the coverage. The default implementation delegates to the
0290: * {@linkplain #getCoverage underlying coverage}.
0291: */
0292: public SampleDimension[] getSampleDimensions()
0293: throws IOException {
0294: final Coverage coverage = getCoverage(null);
0295: final SampleDimension[] sd = new SampleDimension[coverage
0296: .getNumSampleDimensions()];
0297: for (int i = 0; i < sd.length; i++) {
0298: sd[i] = coverage.getSampleDimension(i);
0299: }
0300: return sd;
0301: }
0302:
0303: /**
0304: * Returns the coverage. Implementors can overrides this method if they want to load
0305: * {@link #coverage} only when first needed. However, they are strongly encouraged to
0306: * override all other methods as well in order to load the minimum amount of data,
0307: * since all default implementations invoke {@code getCoverage(null)}.
0308: */
0309: public Coverage getCoverage(final IIOListeners listeners)
0310: throws IOException {
0311: return coverage;
0312: }
0313: }
0314:
0315: /**
0316: * Coverage elements in this stack. Elements may be shared by more than one
0317: * instances of {@code CoverageStack}.
0318: */
0319: private final Element[] elements;
0320:
0321: /**
0322: * The sample dimensions for this coverage, or {@code null} if unknown.
0323: */
0324: private final SampleDimension[] sampleDimensions;
0325:
0326: /**
0327: * The number of sample dimensions for this coverage, or 0 is unknow.
0328: * Note: this attribute may be non-null even if {@link #sampleDimensions} is null.
0329: */
0330: private final int numSampleDimensions;
0331:
0332: /**
0333: * The envelope for this coverage. This is the union of all elements envelopes.
0334: *
0335: * @see #getEnvelope
0336: */
0337: private final GeneralEnvelope envelope;
0338:
0339: /**
0340: * A direct position with {@link #zDimension} dimensions. will be created only
0341: * when first needed.
0342: */
0343: private transient GeneralDirectPosition reducedPosition;
0344:
0345: /**
0346: * The dimension of the <var>z</var> ordinate (the last value in coordinate points).
0347: * This is always the {@linkplain #getCoordinateReferenceSystem() coordinate reference
0348: * system} dimension minus 1.
0349: *
0350: * @since 2.3
0351: */
0352: public final int zDimension;
0353:
0354: /**
0355: * The coordinate reference system for the {@linkplain #zDimension z dimension},
0356: * or {@code null} if unknown.
0357: */
0358: private final CoordinateReferenceSystem zCRS;
0359:
0360: /**
0361: * {@code true} if interpolations are allowed.
0362: */
0363: private boolean interpolationEnabled = true;
0364:
0365: /**
0366: * Maximal interval between the upper z-value of a coverage and the lower z-value of the next
0367: * one. If a greater difference is found, we will consider that there is a hole in the data
0368: * and {@code evaluate(...)} methods will returns NaN for <var>z</var> values in this hole.
0369: */
0370: private final double lagTolerance = 0;
0371:
0372: /**
0373: * List of objects to inform when image loading are trigged.
0374: */
0375: private final IIOListeners listeners = new IIOListeners();
0376:
0377: /**
0378: * Internal listener for logging image loading.
0379: */
0380: private transient Listeners readListener;
0381:
0382: /**
0383: * Coverage with a minimum z-value lower than or equals to the requested <var>z</var> value.
0384: * If possible, this class will tries to select a coverage with a middle value (not just the
0385: * minimum value) lower than the requested <var>z</var> value.
0386: */
0387: private transient Coverage lower;
0388:
0389: /**
0390: * Coverage with a maximum z-value higher than or equals to the requested <var>z</var> value.
0391: * If possible, this class will tries to select a coverage with a middle value (not just the
0392: * maximum value) higher than the requested <var>z</var> value.
0393: */
0394: private transient Coverage upper;
0395:
0396: /**
0397: * <var>Z</var> values in the middle of {@link #lower} and {@link #upper} envelope.
0398: */
0399: private transient double lowerZ = Double.POSITIVE_INFINITY,
0400: upperZ = Double.NEGATIVE_INFINITY;
0401:
0402: /**
0403: * Range for {@link #lower} and {@link #upper}.
0404: */
0405: private transient NumberRange lowerRange, upperRange;
0406:
0407: /**
0408: * Sample byte values. Allocated when first needed, in order to avoid allocating
0409: * thel again everytime an {@code evaluate(...)} method is invoked.
0410: */
0411: private transient byte[] byteBuffer;
0412:
0413: /**
0414: * Sample integer values. Allocated when first needed, in order to avoid allocating
0415: * thel again everytime an {@code evaluate(...)} method is invoked.
0416: */
0417: private transient int[] intBuffer;
0418:
0419: /**
0420: * Sample float values. Allocated when first needed, in order to avoid allocating
0421: * thel again everytime an {@code evaluate(...)} method is invoked.
0422: */
0423: private transient float[] floatBuffer;
0424:
0425: /**
0426: * Sample double values. Allocated when first needed, in order to avoid allocating
0427: * thel again everytime an {@code evaluate(...)} method is invoked.
0428: */
0429: private transient double[] doubleBuffer;
0430:
0431: /**
0432: * Initialize fields after deserialization.
0433: */
0434: private void readObject(final ObjectInputStream in)
0435: throws IOException, ClassNotFoundException {
0436: in.defaultReadObject();
0437: lowerZ = Double.POSITIVE_INFINITY;
0438: upperZ = Double.NEGATIVE_INFINITY;
0439: }
0440:
0441: /**
0442: * Constructs a new coverage stack with all the supplied elements. All coverages must uses the
0443: * same coordinate reference system. Additionnaly, all coverages must specify their <var>z</var>
0444: * value in the last dimension of their envelope. The example below constructs two dimensional
0445: * grid coverages (to be given as the {@code coverages} argument) for the same area, but at
0446: * different times:
0447: *
0448: * <blockquote><pre>
0449: * GridCoverageFactory factory = ...;
0450: * CoordinateReferenceSystem crs2D = ...; // Yours horizontal CRS.
0451: * TemporalCRS timeCRS = ...; // Yours CRS for time measurement.
0452: * CoordinateReferenceSystem crs3D = new CompoundCRS(crs3D, timeCRS);
0453: *
0454: * List<Coverage> coverages = new ArrayList<Coverage>();
0455: * GeneralEnvelope envelope = new GeneralEnvelope(3); // A <strong>3-dimensional</strong> envelope.
0456: * envelope.setRange(...); // Set the horizontal part.
0457: * for (int i=0; i<...; i++) {
0458: * envelope.setRange(2, startTime, endTime);
0459: * coverages.add(factory.create(..., crs, envelope, ...);
0460: * }
0461: * </pre></blockquote>
0462: *
0463: * This convenience constructor wraps all coverage intos a {@link Adapter Adapter} object.
0464: * Users with a significant amount of data are encouraged to uses the constructor expecting
0465: * {@link Element Element} objects instead, in order to provides their own implementation
0466: * loading data only when needed.
0467: *
0468: * @param name The name for this coverage.
0469: * @param coverages All {@link Coverage} elements for this stack.
0470: * @throws IOException if an I/O operation was required and failed.
0471: */
0472: public CoverageStack(final CharSequence name,
0473: final Collection/*<Coverage>*/coverages)
0474: throws IOException {
0475: this (name, getCoordinateReferenceSystem(coverages),
0476: toElements(coverages));
0477: }
0478:
0479: /**
0480: * Workaround for RFE #4093999 ("Relax constraint on placement of this()/super()
0481: * call in constructors").
0482: */
0483: private static CoordinateReferenceSystem getCoordinateReferenceSystem(
0484: final Collection coverages) {
0485: CoordinateReferenceSystem crs = null;
0486: for (final Iterator it = coverages.iterator(); it.hasNext();) {
0487: final CoordinateReferenceSystem candidate = ((Coverage) it
0488: .next()).getCoordinateReferenceSystem();
0489: if (crs == null) {
0490: crs = candidate;
0491: } else if (!crs.equals(candidate)) {
0492: // TODO: localize
0493: throw new IllegalArgumentException(
0494: "Inconsistent coordinate reference system");
0495: }
0496: }
0497: return crs;
0498: }
0499:
0500: /**
0501: * Workaround for RFE #4093999 ("Relax constraint on placement of this()/super()
0502: * call in constructors").
0503: */
0504: private static Collection/*<Element>*/toElements(
0505: final Collection/*<Coverage>*/coverages) {
0506: final List elements = new ArrayList(coverages.size());
0507: for (final Iterator it = coverages.iterator(); it.hasNext();) {
0508: elements.add(new Adapter((Coverage) it.next(), null));
0509: }
0510: return elements;
0511: }
0512:
0513: /**
0514: * Constructs a new coverage stack with all the supplied elements.
0515: *
0516: * @param name The name for this coverage.
0517: * @param crs The coordinate reference system for this coverage.
0518: * @param elements All coverage {@link Element Element}s for this stack.
0519: * @throws IOException if an I/O operation was required and failed.
0520: */
0521: public CoverageStack(final CharSequence name,
0522: final CoordinateReferenceSystem crs,
0523: final Collection/*<Element>*/elements) throws IOException {
0524: super (name, crs, null, null);
0525: this .elements = (Element[]) elements
0526: .toArray(new Element[elements.size()]);
0527: try {
0528: Arrays.sort(this .elements, COMPARATOR);
0529: } catch (UndeclaredThrowableException exception) {
0530: throw rethrow(exception);
0531: }
0532: zDimension = crs.getCoordinateSystem().getDimension() - 1;
0533: boolean sampleDimensionMismatch = false;
0534: SampleDimension[] sampleDimensions = null;
0535: GeneralEnvelope envelope = null;
0536: for (int j = 0; j < this .elements.length; j++) {
0537: final Element element = this .elements[j];
0538: if (true) {
0539: /*
0540: * Ensures that all coverages uses the same number of sample dimension.
0541: * To be strict, we should ensure that all sample dimensions are identical.
0542: * However, this is not needed for proper working of this class, so we will
0543: * ensure this condition only in 'getSampleDimension' method.
0544: */
0545: final SampleDimension[] candidate = element
0546: .getSampleDimensions();
0547: if (candidate != null) {
0548: if (sampleDimensions == null) {
0549: sampleDimensions = candidate;
0550: } else {
0551: if (sampleDimensions.length != candidate.length) {
0552: throw new IllegalArgumentException( // TODO: localize
0553: "Inconsistent number of sample dimensions.");
0554: }
0555: if (!Arrays.equals(sampleDimensions, candidate)) {
0556: sampleDimensionMismatch = true;
0557: }
0558: }
0559: }
0560: }
0561: /*
0562: * Computes an envelope for all coverage elements. If a coordinate reference system
0563: * information is bundled with the envelope, it will be used in order to reproject
0564: * the envelope on the fly (if needed). Otherwise, CRS are assumed the same than the
0565: * one specified at construction time.
0566: */
0567: final Envelope candidate = element.getEnvelope();
0568: if (candidate == null) {
0569: continue;
0570: }
0571: final CoordinateReferenceSystem sourceCRS;
0572: sourceCRS = candidate.getCoordinateReferenceSystem();
0573: if (sourceCRS != null) {
0574: final int dim = sourceCRS.getCoordinateSystem()
0575: .getDimension();
0576: if (dim < zDimension || dim > zDimension + 1) {
0577: // TODO: localize
0578: throw new MismatchedDimensionException(
0579: "An element uses an incompatible CRS");
0580: }
0581: final CoordinateReferenceSystem targetCRS = CRSUtilities
0582: .getSubCRS(crs, 0, dim);
0583: if (!CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
0584: // TODO: localize
0585: throw new IllegalArgumentException(
0586: "An element uses an incompatible CRS");
0587: }
0588: }
0589: /*
0590: * Increase the envelope in order to contains 'candidate'.
0591: * The range of z-values will be included in the envelope.
0592: */
0593: final boolean set = (envelope == null);
0594: if (set) {
0595: envelope = new GeneralEnvelope(zDimension + 1);
0596: }
0597: final int dim = candidate.getDimension();
0598: for (int i = 0; i <= zDimension; i++) {
0599: double min = envelope.getMinimum(i);
0600: double max = envelope.getMaximum(i);
0601: final double minimum, maximum;
0602: if (i < dim) {
0603: minimum = candidate.getMinimum(i);
0604: maximum = candidate.getMaximum(i);
0605: } else if (i == zDimension) {
0606: final NumberRange range = element.getZRange();
0607: minimum = range.getMinimum();
0608: maximum = range.getMaximum();
0609: } else {
0610: minimum = Double.NEGATIVE_INFINITY;
0611: maximum = Double.POSITIVE_INFINITY;
0612: }
0613: if (set || minimum < min)
0614: min = minimum;
0615: if (set || maximum > max)
0616: max = maximum;
0617: envelope.setRange(i, min, max);
0618: }
0619: }
0620: this .numSampleDimensions = (sampleDimensions != null) ? sampleDimensions.length
0621: : 0;
0622: this .sampleDimensions = sampleDimensionMismatch ? null
0623: : sampleDimensions;
0624: if (envelope != null) {
0625: this .envelope = envelope;
0626: envelope.setCoordinateReferenceSystem(crs);
0627: } else {
0628: assert this .elements.length == 0;
0629: this .envelope = new GeneralEnvelope(CRS.getEnvelope(crs));
0630: }
0631: zCRS = CRSUtilities.getSubCRS(crs, zDimension, zDimension + 1);
0632: }
0633:
0634: /**
0635: * Constructs a new coverage using the same elements than the specified coverage stack.
0636: */
0637: protected CoverageStack(final CharSequence name,
0638: final CoverageStack source) {
0639: super (name, source);
0640: elements = source.elements;
0641: sampleDimensions = source.sampleDimensions;
0642: numSampleDimensions = source.numSampleDimensions;
0643: envelope = source.envelope;
0644: zDimension = source.zDimension;
0645: zCRS = source.zCRS;
0646: interpolationEnabled = source.interpolationEnabled;
0647: }
0648:
0649: /**
0650: * Rethrows the exception in {@link #COMPARATOR} as a {@link RuntimeException}.
0651: * It gives an opportunity for implementations of {@link Element} to uses some
0652: * checked exception like {@link IOException}.
0653: */
0654: private static IOException rethrow(
0655: final UndeclaredThrowableException exception) {
0656: final Throwable cause = exception.getCause();
0657: if (cause instanceof IOException) {
0658: return (IOException) cause;
0659: }
0660: if (cause instanceof RuntimeException) {
0661: throw (RuntimeException) cause;
0662: }
0663: throw exception;
0664: }
0665:
0666: /**
0667: * A comparator for {@link Element} sorting and binary search. This comparator uses the
0668: * middle <var>z</var> value as criterion. It must accepts {@link Double} objects as well
0669: * as {@link Element}, because binary search will mix those two kinds of object.
0670: */
0671: private static final Comparator COMPARATOR = new Comparator() {
0672: public int compare(final Object entry1, final Object entry2) {
0673: try {
0674: return Double.compare(zFromObject(entry1),
0675: zFromObject(entry2));
0676: } catch (IOException exception) {
0677: throw new UndeclaredThrowableException(exception);
0678: // Will be catch and rethrown as IOException
0679: // by all methods using this comparator.
0680: }
0681: }
0682: };
0683:
0684: /**
0685: * Returns the <var>z</var> value of the specified object. The specified
0686: * object may be a {@link Double} or an {@link Element} instance.
0687: *
0688: * @param object The object to sort.
0689: * @return The z-value of the specified object.
0690: * @throws IOException if an I/O operation was required but failed.
0691: * @throws ClassCastException if {@code object} is not an instance of {@link Double}
0692: * or {@link Element}.
0693: */
0694: private static double zFromObject(final Object object)
0695: throws IOException, ClassCastException {
0696: if (object instanceof Number) {
0697: return ((Number) object).doubleValue();
0698: }
0699: return getZ((Element) object);
0700: }
0701:
0702: /**
0703: * Returns the middle <var>z</var> value. If the element has no <var>z</var> value
0704: * (for example if the <var>z</var> value is the time and the coverage is constant
0705: * over the time), then this method returns {@link Double#NaN}.
0706: */
0707: private static double getZ(final Element entry) throws IOException {
0708: return getZ(entry.getZRange());
0709: }
0710:
0711: /**
0712: * Returns the <var>z</var> value in the middle of the specified range.
0713: * If the range is null, then this method returns {@link Double#NaN}.
0714: */
0715: private static double getZ(final NumberRange range) {
0716: if (range != null) {
0717: final Number lower = (Number) range.getMinValue();
0718: final Number upper = (Number) range.getMaxValue();
0719: if (lower != null) {
0720: if (upper != null) {
0721: return 0.5 * (lower.doubleValue() + upper
0722: .doubleValue());
0723: } else {
0724: return lower.doubleValue();
0725: }
0726: } else if (upper != null) {
0727: return upper.doubleValue();
0728: }
0729: }
0730: return Double.NaN;
0731: }
0732:
0733: /**
0734: * Returns {@code true} if the specified z-value is inside the specified range.
0735: */
0736: private static boolean contains(final NumberRange range,
0737: final double z) {
0738: return z >= range.getMinimum() && z <= range.getMaximum();
0739: }
0740:
0741: /**
0742: * Returns the bounding box for the coverage domain in coordinate system coordinates.
0743: */
0744: public Envelope getEnvelope() {
0745: return (Envelope) envelope.clone();
0746: }
0747:
0748: /**
0749: * Returns the number of sample dimension in this coverage.
0750: */
0751: public int getNumSampleDimensions() {
0752: if (numSampleDimensions != 0) {
0753: return numSampleDimensions;
0754: } else {
0755: // TODO: provides a localized message.
0756: throw new IllegalStateException(
0757: "Sample dimensions are undetermined.");
0758: }
0759: }
0760:
0761: /**
0762: * Retrieve sample dimension information for the coverage.
0763: * For a grid coverage, a sample dimension is a band. The sample dimension information
0764: * include such things as description, data type of the value (bit, byte, integer...),
0765: * the no data values, minimum and maximum values and a color table if one is associated
0766: * with the dimension.
0767: */
0768: public SampleDimension getSampleDimension(final int index) {
0769: if (sampleDimensions != null) {
0770: return sampleDimensions[index];
0771: } else {
0772: // TODO: provides a localized message.
0773: throw new IllegalStateException(
0774: "Sample dimensions are undetermined.");
0775: }
0776: }
0777:
0778: /**
0779: * Snaps the specified coordinate point to the coordinate of the nearest voxel available in
0780: * this coverage. First, this method locate the {@linkplain Element coverage element} at or
0781: * near the last ordinate value (the <var>z</var> value). If no coverage is available at the
0782: * specified <var>z</var> value, then the nearest one is selected. Next, this method locate
0783: * the pixel under the {@code point} coordinate in the coverage element. The {@code point}
0784: * is then set to the pixel center coordinate and to the <var>z</var> value of the selected
0785: * coverage element. Consequently, calling any {@code evaluate(...)} method with snapped
0786: * coordinates will returns non-interpolated values.
0787: *
0788: * @param point The point to snap.
0789: * @throws IOException if an I/O operation was required but failed.
0790: */
0791: public void snap(final DirectPosition point) throws IOException { // No synchronization needed.
0792: double z = point.getOrdinate(zDimension);
0793: int index;
0794: try {
0795: index = Arrays.binarySearch(elements, new Double(z),
0796: COMPARATOR);
0797: } catch (UndeclaredThrowableException exception) {
0798: throw rethrow(exception);
0799: }
0800: if (index < 0) {
0801: /*
0802: * There is no exact match for the z value.
0803: * Snap it to the closest coverage element.
0804: */
0805: index = ~index;
0806: if (index == elements.length) {
0807: if (index == 0) {
0808: return; // No elements in this coverage
0809: }
0810: z = getZ(elements[--index]);
0811: } else if (index == 0) {
0812: z = getZ(elements[index]);
0813: } else {
0814: final double lowerZ = getZ(elements[index - 1]);
0815: final double upperZ = getZ(elements[index]);
0816: assert !(z <= lowerZ || z >= upperZ) : z; // Use !(...) in order to accept NaN values.
0817: if (Double.isNaN(upperZ) || z - lowerZ < upperZ - z) {
0818: index--;
0819: z = lowerZ;
0820: } else {
0821: z = upperZ;
0822: }
0823: }
0824: point.setOrdinate(zDimension, z);
0825: }
0826: /*
0827: * Now that we know the coverage element,
0828: * snap the spatial coordinate point.
0829: */
0830: final Element element = elements[index];
0831: final GridGeometry geometry = element.getGridGeometry();
0832: if (geometry != null) {
0833: final GridRange range = geometry.getGridRange();
0834: final MathTransform transform = geometry.getGridToCRS();
0835: final int dimension = transform.getSourceDimensions();
0836: DirectPosition position = new GeneralDirectPosition(
0837: dimension);
0838: for (int i = dimension; --i >= 0;) {
0839: // Copy only the first dimensions (may not be up to crs.dimension)
0840: position.setOrdinate(i, point.getOrdinate(i));
0841: }
0842: try {
0843: position = transform.inverse().transform(position,
0844: position);
0845: for (int i = dimension; --i >= 0;) {
0846: position.setOrdinate(i, Math.max(range.getLower(i),
0847: Math.min(range.getUpper(i) - 1, (int) Math
0848: .rint(position.getOrdinate(i)))));
0849: }
0850: position = transform.transform(position, position);
0851: for (int i = Math.min(dimension, zDimension); --i >= 0;) {
0852: // Do not touch the z-value, copy the other ordinates.
0853: point.setOrdinate(i, position.getOrdinate(i));
0854: }
0855: } catch (TransformException exception) {
0856: throw new CannotEvaluateException(
0857: cannotEvaluate(point), exception);
0858: }
0859: }
0860: }
0861:
0862: /**
0863: * Returns a message for exception.
0864: *
0865: * @todo provides a better formatting of the point coordinate.
0866: */
0867: private static String cannotEvaluate(final DirectPosition point) {
0868: return Errors.format(ErrorKeys.CANT_EVALUATE_$1, point);
0869: }
0870:
0871: /**
0872: * Loads a single coverage for the specified element. All {@code evaluate(...)} methods
0873: * ultimately loads their coverages through this method. It provides a single place where
0874: * to add post-loading processing, if needed.
0875: *
0876: * @param element The coverage to load.
0877: * @return The loaded coverage.
0878: * @throws IOException if an error occured while loading image.
0879: */
0880: private Coverage load(final Element element) throws IOException {
0881: Coverage coverage = element.getCoverage(listeners);
0882: if (coverage instanceof GridCoverage2D) {
0883: final GridCoverage2D coverage2D = (GridCoverage2D) coverage;
0884: if (interpolationEnabled) {
0885: if (coverage2D.getInterpolation() instanceof InterpolationNearest) {
0886: coverage = Interpolator2D.create(coverage2D);
0887: }
0888: }
0889: }
0890: /*
0891: * CRS assertions (for debugging purpose).
0892: */
0893: final CoordinateReferenceSystem sourceCRS;
0894: assert CRS.equalsIgnoreMetadata((sourceCRS = coverage
0895: .getCoordinateReferenceSystem()), CRSUtilities
0896: .getSubCRS(crs, 0, sourceCRS.getCoordinateSystem()
0897: .getDimension())) : sourceCRS + "\n\n" + crs;
0898: assert coverage.getNumSampleDimensions() == numSampleDimensions : coverage;
0899: return coverage;
0900: }
0901:
0902: /**
0903: * Loads a single image at the given index.
0904: *
0905: * @param index Index in {@link #elements} for the image to load.
0906: * @throws IOException if an error occured while loading image.
0907: */
0908: private void load(final int index) throws IOException {
0909: final Element element = elements[index];
0910: final NumberRange zRange = element.getZRange();
0911: logLoading(VocabularyKeys.LOADING_IMAGE_$1,
0912: new String[] { element.getName() });
0913: lower = upper = load(element);
0914: lowerZ = upperZ = getZ(zRange);
0915: lowerRange = upperRange = zRange;
0916: }
0917:
0918: /**
0919: * Loads images for the given elements.
0920: *
0921: * @throws IOException if an error occured while loading images.
0922: */
0923: private void load(final Element lowerElement,
0924: final Element upperElement) throws IOException {
0925: logLoading(VocabularyKeys.LOADING_IMAGES_$2, new String[] {
0926: lowerElement.getName(), upperElement.getName() });
0927: final NumberRange lowerRange = lowerElement.getZRange();
0928: final NumberRange upperRange = upperElement.getZRange();
0929: final Coverage lower = load(lowerElement);
0930: final Coverage upper = load(upperElement);
0931:
0932: this .lower = lower; // Set only when BOTH images are OK.
0933: this .upper = upper;
0934: this .lowerZ = getZ(lowerRange);
0935: this .upperZ = getZ(upperRange);
0936: this .lowerRange = lowerRange;
0937: this .upperRange = upperRange;
0938: }
0939:
0940: /**
0941: * Loads coverages required for a linear interpolation at the specified <var>z</var> value.
0942: * The loaded coverages will be stored in {@link #lower} and {@link #upper} fields. It is
0943: * possible that the same coverage is given to those two fields, if this method determine
0944: * that no interpolation is necessary.
0945: *
0946: * @param z The z value.
0947: * @return {@code true} if data were found.
0948: * @throws PointOutsideCoverageException if the <var>z</var> value is outside the allowed range.
0949: * @throws CannotEvaluateException if the operation failed for some other reason.
0950: */
0951: private boolean seek(final double z) throws CannotEvaluateException {
0952: assert Thread.holdsLock(this );
0953: /*
0954: * Check if currently loaded coverages
0955: * are valid for the requested z value.
0956: */
0957: if ((z >= lowerZ && z <= upperZ)
0958: || (Double.isNaN(z) && Double.isNaN(lowerZ) && Double
0959: .isNaN(upperZ))) {
0960: return true;
0961: }
0962: /*
0963: * Currently loaded coverages are not valid for the requested z value.
0964: * Search for the coverage to use as upper bounds ({@link #upper}).
0965: */
0966: final Number Z = new Double(z);
0967: int index;
0968: try {
0969: index = Arrays.binarySearch(elements, Z, COMPARATOR);
0970: } catch (UndeclaredThrowableException exception) {
0971: // TODO: localize
0972: throw new CannotEvaluateException(
0973: "Can't fetch coverage properties.",
0974: rethrow(exception));
0975: }
0976: try {
0977: if (index >= 0) {
0978: /*
0979: * An exact match has been found.
0980: * Load only this coverage and exit.
0981: */
0982: load(index);
0983: return true;
0984: }
0985: index = ~index; // Insertion point (note: ~ is NOT the minus sign).
0986: if (index == elements.length) {
0987: if (--index >= 0) { // Does this stack has at least 1 coverage?
0988: /*
0989: * The requested z is after the last coverage's central z.
0990: * Maybe it is not after the last coverage's upper z. Check...
0991: */
0992: if (elements[index].getZRange().contains(Z)) {
0993: load(index);
0994: return true;
0995: }
0996: }
0997: // fall through the exception at this method's end.
0998: } else if (index == 0) {
0999: /*
1000: * The requested z is before the first coverage's central z.
1001: * Maybe it is not before the first coverage's lower z. Check...
1002: */
1003: if (elements[index].getZRange().contains(Z)) {
1004: load(index);
1005: return true;
1006: }
1007: // fall through the exception at this method's end.
1008: } else {
1009: /*
1010: * An interpolation between two coverages seems possible.
1011: * Checks if there is not a z lag between both.
1012: */
1013: final Element lowerElement = elements[index - 1];
1014: final Element upperElement = elements[index];
1015: final NumberRange lowerRange = lowerElement.getZRange();
1016: final NumberRange upperRange = upperElement.getZRange();
1017: final double lowerEnd = lowerRange.getMaximum();
1018: final double upperStart = upperRange.getMinimum();
1019: if (lowerEnd + lagTolerance >= upperStart) {
1020: if (interpolationEnabled) {
1021: load(lowerElement, upperElement);
1022: } else {
1023: if (Math.abs(getZ(upperRange) - z) > Math.abs(z
1024: - getZ(lowerRange))) {
1025: index--;
1026: }
1027: load(index);
1028: }
1029: return true;
1030: }
1031: if (lowerRange.contains(Z)) {
1032: load(index - 1);
1033: return true;
1034: }
1035: if (upperRange.contains(Z)) {
1036: load(index);
1037: return true;
1038: }
1039: return false; // Missing data.
1040: }
1041: } catch (IOException exception) {
1042: String message = exception.getLocalizedMessage();
1043: if (message == null) {
1044: message = Utilities.getShortClassName(exception);
1045: }
1046: throw new CannotEvaluateException(message, exception);
1047: }
1048: final Object Zp;
1049: if (zCRS instanceof TemporalCRS) {
1050: Zp = DefaultTemporalCRS.wrap((TemporalCRS) zCRS).toDate(z);
1051: } else {
1052: Zp = Z;
1053: }
1054: throw new OrdinateOutsideCoverageException(Errors.format(
1055: ErrorKeys.ZVALUE_OUTSIDE_COVERAGE_$2, getName(), Zp),
1056: zDimension, getEnvelope());
1057: }
1058:
1059: /**
1060: * Returns a point with the same number of dimensions than the specified coverage.
1061: * The number of dimensions must be {@link #zDimensions} or {@code zDimensions+1}.
1062: */
1063: private final DirectPosition reduce(DirectPosition coord,
1064: final Coverage coverage) {
1065: final CoordinateReferenceSystem targetCRS = coverage
1066: .getCoordinateReferenceSystem();
1067: final int dimension = targetCRS.getCoordinateSystem()
1068: .getDimension();
1069: if (dimension == zDimension) {
1070: if (reducedPosition == null) {
1071: reducedPosition = new GeneralDirectPosition(zDimension);
1072: }
1073: for (int i = 0; i < dimension; i++) {
1074: reducedPosition.ordinates[i] = coord.getOrdinate(i);
1075: }
1076: coord = reducedPosition;
1077: } else {
1078: assert CRS.equalsIgnoreMetadata(crs, targetCRS) : targetCRS;
1079: }
1080: return coord;
1081: }
1082:
1083: /**
1084: * Returns a sequence of values for a given point in the coverage. The default implementation
1085: * delegates to the {@link #evaluate(DirectPosition, double[])} method.
1086: *
1087: * @param coord The coordinate point where to evaluate.
1088: * @return The value at the specified point.
1089: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1090: * @throws CannotEvaluateException if the computation failed for some other reason.
1091: */
1092: public Object evaluate(final DirectPosition coord)
1093: throws CannotEvaluateException {
1094: return evaluate(coord, (double[]) null);
1095: }
1096:
1097: /**
1098: * Returns a sequence of boolean values for a given point in the coverage.
1099: *
1100: * @param coord The coordinate point where to evaluate.
1101: * @param dest An array in which to store values, or {@code null} to create a new array.
1102: * @return The {@code dest} array, or a newly created array if {@code dest} was null.
1103: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1104: * @throws CannotEvaluateException if the computation failed for some other reason.
1105: */
1106: public synchronized boolean[] evaluate(final DirectPosition coord,
1107: boolean[] dest) throws CannotEvaluateException {
1108: final double z = coord.getOrdinate(zDimension);
1109: if (!seek(z)) {
1110: // Missing data
1111: if (dest == null) {
1112: dest = new boolean[numSampleDimensions];
1113: } else {
1114: Arrays.fill(dest, 0, numSampleDimensions, false);
1115: }
1116: return dest;
1117: }
1118: if (lower == upper) {
1119: return lower.evaluate(reduce(coord, lower), dest);
1120: }
1121: assert !(z < lowerZ || z > upperZ) : z; // Uses !(...) in order to accepts NaN.
1122: final Coverage coverage = (z >= 0.5 * (lowerZ + upperZ)) ? upper
1123: : lower;
1124: return coverage.evaluate(reduce(coord, coverage), dest);
1125: }
1126:
1127: /**
1128: * Returns a sequence of byte values for a given point in the coverage.
1129: *
1130: * @param coord The coordinate point where to evaluate.
1131: * @param dest An array in which to store values, or {@code null} to create a new array.
1132: * @return The {@code dest} array, or a newly created array if {@code dest} was null.
1133: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1134: * @throws CannotEvaluateException if the computation failed for some other reason.
1135: */
1136: public synchronized byte[] evaluate(final DirectPosition coord,
1137: byte[] dest) throws CannotEvaluateException {
1138: final double z = coord.getOrdinate(zDimension);
1139: if (!seek(z)) {
1140: // Missing data
1141: if (dest == null) {
1142: dest = new byte[numSampleDimensions];
1143: } else {
1144: Arrays.fill(dest, 0, numSampleDimensions, (byte) 0);
1145: }
1146: return dest;
1147: }
1148: if (lower == upper) {
1149: return lower.evaluate(reduce(coord, lower), dest);
1150: }
1151: byteBuffer = upper.evaluate(reduce(coord, upper), byteBuffer);
1152: dest = lower.evaluate(reduce(coord, lower), dest);
1153: assert !(z < lowerZ || z > upperZ) : z; // Uses !(...) in order to accepts NaN.
1154: final double ratio = (z - lowerZ) / (upperZ - lowerZ);
1155: for (int i = 0; i < byteBuffer.length; i++) {
1156: dest[i] = (byte) Math.round(dest[i] + ratio
1157: * (byteBuffer[i] - dest[i]));
1158: }
1159: return dest;
1160: }
1161:
1162: /**
1163: * Returns a sequence of integer values for a given point in the coverage.
1164: *
1165: * @param coord The coordinate point where to evaluate.
1166: * @param dest An array in which to store values, or {@code null} to create a new array.
1167: * @return The {@code dest} array, or a newly created array if {@code dest} was null.
1168: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1169: * @throws CannotEvaluateException if the computation failed for some other reason.
1170: */
1171: public synchronized int[] evaluate(final DirectPosition coord,
1172: int[] dest) throws CannotEvaluateException {
1173: final double z = coord.getOrdinate(zDimension);
1174: if (!seek(z)) {
1175: // Missing data
1176: if (dest == null) {
1177: dest = new int[numSampleDimensions];
1178: } else {
1179: Arrays.fill(dest, 0, numSampleDimensions, 0);
1180: }
1181: return dest;
1182: }
1183: if (lower == upper) {
1184: return lower.evaluate(reduce(coord, lower), dest);
1185: }
1186: intBuffer = upper.evaluate(reduce(coord, upper), intBuffer);
1187: dest = lower.evaluate(reduce(coord, lower), dest);
1188: assert !(z < lowerZ || z > upperZ) : z; // Uses !(...) in order to accepts NaN.
1189: final double ratio = (z - lowerZ) / (upperZ - lowerZ);
1190: for (int i = 0; i < intBuffer.length; i++) {
1191: dest[i] = (int) Math.round(dest[i] + ratio
1192: * (intBuffer[i] - dest[i]));
1193: }
1194: return dest;
1195: }
1196:
1197: /**
1198: * Returns a sequence of float values for a given point in the coverage.
1199: *
1200: * @param coord The coordinate point where to evaluate.
1201: * @param dest An array in which to store values, or {@code null} to create a new array.
1202: * @return The {@code dest} array, or a newly created array if {@code dest} was null.
1203: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1204: * @throws CannotEvaluateException if the computation failed for some other reason.
1205: */
1206: public synchronized float[] evaluate(final DirectPosition coord,
1207: float[] dest) throws CannotEvaluateException {
1208: final double z = coord.getOrdinate(zDimension);
1209: if (!seek(z)) {
1210: // Missing data
1211: if (dest == null) {
1212: dest = new float[numSampleDimensions];
1213: }
1214: Arrays.fill(dest, 0, numSampleDimensions, Float.NaN);
1215: return dest;
1216: }
1217: if (lower == upper) {
1218: return lower.evaluate(reduce(coord, lower), dest);
1219: }
1220: floatBuffer = upper.evaluate(reduce(coord, upper), floatBuffer);
1221: dest = lower.evaluate(reduce(coord, lower), dest);
1222: assert !(z < lowerZ || z > upperZ) : z; // Uses !(...) in order to accepts NaN.
1223: final double ratio = (z - lowerZ) / (upperZ - lowerZ);
1224: for (int i = 0; i < floatBuffer.length; i++) {
1225: final float lower = dest[i];
1226: final float upper = floatBuffer[i];
1227: float value = (float) (lower + ratio * (upper - lower));
1228: if (Float.isNaN(value)) {
1229: if (!Float.isNaN(lower)) {
1230: assert Float.isNaN(upper) : upper;
1231: if (contains(lowerRange, z)) {
1232: value = lower;
1233: }
1234: } else if (!Float.isNaN(upper)) {
1235: assert Float.isNaN(lower) : lower;
1236: if (contains(upperRange, z)) {
1237: value = upper;
1238: }
1239: }
1240: }
1241: dest[i] = value;
1242: }
1243: return dest;
1244: }
1245:
1246: /**
1247: * Returns a sequence of double values for a given point in the coverage.
1248: *
1249: * @param coord The coordinate point where to evaluate.
1250: * @param dest An array in which to store values, or {@code null} to create a new array.
1251: * @return The {@code dest} array, or a newly created array if {@code dest} was null.
1252: * @throws PointOutsideCoverageException if {@code coord} is outside coverage.
1253: * @throws CannotEvaluateException if the computation failed for some other reason.
1254: */
1255: public synchronized double[] evaluate(final DirectPosition coord,
1256: double[] dest) throws CannotEvaluateException {
1257: final double z = coord.getOrdinate(zDimension);
1258: if (!seek(z)) {
1259: // Missing data
1260: if (dest == null) {
1261: dest = new double[numSampleDimensions];
1262: }
1263: Arrays.fill(dest, 0, numSampleDimensions, Double.NaN);
1264: return dest;
1265: }
1266: if (lower == upper) {
1267: return lower.evaluate(reduce(coord, lower), dest);
1268: }
1269: doubleBuffer = upper.evaluate(reduce(coord, upper),
1270: doubleBuffer);
1271: dest = lower.evaluate(reduce(coord, lower), dest);
1272: assert !(z < lowerZ || z > upperZ) : z; // Uses !(...) in order to accepts NaN.
1273: final double ratio = (z - lowerZ) / (upperZ - lowerZ);
1274: for (int i = 0; i < doubleBuffer.length; i++) {
1275: final double lower = dest[i];
1276: final double upper = doubleBuffer[i];
1277: double value = lower + ratio * (upper - lower);
1278: if (Double.isNaN(value)) {
1279: if (!Double.isNaN(lower)) {
1280: assert Double.isNaN(upper) : upper;
1281: if (contains(lowerRange, z)) {
1282: value = lower;
1283: }
1284: } else if (!Double.isNaN(upper)) {
1285: assert Double.isNaN(lower) : lower;
1286: if (contains(upperRange, z)) {
1287: value = upper;
1288: }
1289: }
1290: }
1291: dest[i] = value;
1292: }
1293: return dest;
1294: }
1295:
1296: /**
1297: * Returns the coverages to be used for the specified <var>z</var> value. Special cases:
1298: * <p>
1299: * <ul>
1300: * <li>If there is no coverage available for the specified <var>z</var> value, returns
1301: * an {@linkplain Collections#EMPTY_LIST empty list}.</li>
1302: * <li>If there is only one coverage available, or if the specified <var>z</var> value
1303: * falls exactly in the middle of the {@linkplain Element#getZRange range value}
1304: * (i.e. no interpolation are needed), or if {@linkplain #setInterpolationEnabled
1305: * interpolations are disabled}, then this method returns a
1306: * {@linkplain Collections#singletonList singleton}.</li>
1307: * <li>Otherwise, this method returns a list containing at least 2 coverages, one before
1308: * and one after the specified <var>z</var> value.</li>
1309: * </ul>
1310: *
1311: * @param z The z value for the coverages to be returned.
1312: * @return The coverages for the specified values. May contains 0, 1 or 2 elements.
1313: *
1314: * @since 2.3
1315: */
1316: public synchronized List/*<Coverage>*/coveragesAt(final double z) {
1317: if (!seek(z)) {
1318: return Collections.EMPTY_LIST;
1319: }
1320: if (lower == upper) {
1321: return Collections.singletonList(lower);
1322: }
1323: return Arrays.asList(new Coverage[] { lower, upper });
1324: }
1325:
1326: /**
1327: * Returns {@code true} if interpolation are enabled in the <var>z</var> value dimension.
1328: * Interpolations are enabled by default.
1329: */
1330: public boolean isInterpolationEnabled() {
1331: return interpolationEnabled;
1332: }
1333:
1334: /**
1335: * Enable or disable interpolations in the <var>z</var> value dimension.
1336: */
1337: public synchronized void setInterpolationEnabled(final boolean flag) {
1338: lower = null;
1339: upper = null;
1340: lowerZ = Double.POSITIVE_INFINITY;
1341: upperZ = Double.NEGATIVE_INFINITY;
1342: interpolationEnabled = flag;
1343: }
1344:
1345: /**
1346: * Adds an {@link IIOReadWarningListener} to the list of registered warning listeners.
1347: */
1348: public void addIIOReadWarningListener(
1349: final IIOReadWarningListener listener) {
1350: listeners.addIIOReadWarningListener(listener);
1351: }
1352:
1353: /**
1354: * Removes an {@link IIOReadWarningListener} from the list of registered warning listeners.
1355: */
1356: public void removeIIOReadWarningListener(
1357: final IIOReadWarningListener listener) {
1358: listeners.removeIIOReadWarningListener(listener);
1359: }
1360:
1361: /**
1362: * Adds an {@link IIOReadProgressListener} to the list of registered progress listeners.
1363: */
1364: public void addIIOReadProgressListener(
1365: final IIOReadProgressListener listener) {
1366: listeners.addIIOReadProgressListener(listener);
1367: }
1368:
1369: /**
1370: * Removes an {@link IIOReadProgressListener} from the list of registered progress listeners.
1371: */
1372: public void removeIIOReadProgressListener(
1373: final IIOReadProgressListener listener) {
1374: listeners.removeIIOReadProgressListener(listener);
1375: }
1376:
1377: /**
1378: * Invoked automatically when an image is about to be loaded. The default implementation
1379: * logs the message in the {@code "org.geotools.coverage"} logger. Subclasses can override
1380: * this method if they wants a different logging.
1381: *
1382: * @param record The log record. The message contains information about the images to load.
1383: */
1384: protected void logLoading(final LogRecord record) {
1385: Logging.getLogger("org.geotools.coverage").log(record);
1386: }
1387:
1388: /**
1389: * Prepares a log record about an image to be loaded, and put the log record in a stack.
1390: * The record will be effectively logged only when image loading really beging.
1391: */
1392: private void logLoading(final int key, final Object[] parameters) {
1393: final Locale locale = null;
1394: final LogRecord record = Vocabulary.getResources(locale)
1395: .getLogRecord(Level.INFO, key);
1396: record.setSourceClassName(CoverageStack.class.getName());
1397: record.setSourceMethodName("evaluate");
1398: record.setParameters(parameters);
1399: if (readListener == null) {
1400: readListener = new Listeners();
1401: addIIOReadProgressListener(readListener);
1402: }
1403: readListener.record = record;
1404: }
1405:
1406: /**
1407: * A listener for monitoring image loading. The purpose for this listener is to
1408: * log a message when an image is about to be loaded.
1409: *
1410: * @version $Id: CoverageStack.java 27862 2007-11-12 19:51:19Z desruisseaux $
1411: * @author Martin Desruisseaux
1412: */
1413: private final class Listeners extends IIOReadProgressAdapter {
1414: /**
1415: * The record to log.
1416: */
1417: public LogRecord record;
1418:
1419: /**
1420: * Reports that an image read operation is beginning.
1421: */
1422: public void imageStarted(ImageReader source, int imageIndex) {
1423: if (record != null) {
1424: logLoading(record);
1425: source.removeIIOReadProgressListener(this);
1426: record = null;
1427: }
1428: }
1429: }
1430: }
|