blob: e947a208746dc1889660ce14e2aab160c1b698ec [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.sis.coverage.grid;
import java.util.List;
import java.util.Collection;
import java.util.Locale;
import java.awt.image.RenderedImage;
import org.opengis.geometry.DirectPosition;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.apache.sis.internal.util.UnmodifiableArrayList;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.SubspaceNotSpecifiedException;
import org.apache.sis.util.collection.DefaultTreeTable;
import org.apache.sis.util.collection.TableColumn;
import org.apache.sis.util.collection.TreeTable;
import org.apache.sis.util.resources.Vocabulary;
import org.apache.sis.util.ArgumentChecks;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Debug;
/**
* Base class of coverages with domains defined as a set of grid points.
* The essential property of coverage is to be able to generate a value for any point within its domain.
* Since a grid coverage is represented by a grid of values, the value returned by the coverage for a point
* is that of the grid value whose location is nearest the point.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Johann Sorel (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
public abstract class GridCoverage {
/**
* The grid extent, coordinate reference system (CRS) and conversion from cell indices to CRS.
*
* @see #getGridGeometry()
*/
private final GridGeometry gridGeometry;
/**
* List of sample dimension (band) information for the grid coverage. Information include such things
* as description, the no data values, minimum and maximum values, <i>etc</i>. A coverage must have
* at least one sample dimension. The content of this array shall never be modified.
*
* @see #getSampleDimensions()
*/
private final SampleDimension[] sampleDimensions;
/**
* Constructs a grid coverage using the specified grid geometry and sample dimensions.
*
* @param grid the grid extent, CRS and conversion from cell indices to CRS.
* @param bands sample dimensions for each image band.
*/
protected GridCoverage(final GridGeometry grid, final Collection<? extends SampleDimension> bands) {
ArgumentChecks.ensureNonNull("grid", grid);
ArgumentChecks.ensureNonNull("bands", bands);
gridGeometry = grid;
sampleDimensions = bands.toArray(new SampleDimension[bands.size()]);
for (int i=0; i<sampleDimensions.length; i++) {
ArgumentChecks.ensureNonNullElement("bands", i, sampleDimensions[i]);
}
}
/**
* Returns the coordinate reference system to which the values in grid domain are referenced.
* This is the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
* This coordinate reference system is usually different than the coordinate system of the grid.
* It is the target coordinate reference system of the {@link GridGeometry#getGridToCRS gridToCRS}
* math transform.
*
* <p>The default implementation delegates to {@link GridGeometry#getCoordinateReferenceSystem()}.</p>
*
* @return the CRS used when accessing a coverage with the {@code evaluate(…)} methods.
* @throws IncompleteGridGeometryException if the grid geometry has no CRS.
*/
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return gridGeometry.getCoordinateReferenceSystem();
}
/**
* Returns information about the <cite>domain</cite> of this grid coverage.
* Information includes the grid extent, CRS and conversion from cell indices to CRS.
* {@code GridGeometry} can also provide derived information like bounding box and resolution.
*
* @return grid extent, CRS and conversion from cell indices to CRS.
*
* @see org.apache.sis.storage.GridCoverageResource#getGridGeometry()
*/
public GridGeometry getGridGeometry() {
return gridGeometry;
}
/**
* Returns information about the <cite>range</cite> of this grid coverage.
* Information include names, sample value ranges, fill values and transfer functions for all bands in this grid coverage.
* The length of the returned list should be equal to the {@linkplain java.awt.image.SampleModel#getNumBands() number of
* bands} in the rendered image.
*
* @return names, value ranges, fill values and transfer functions for all bands in this grid coverage.
*
* @see org.apache.sis.storage.GridCoverageResource#getSampleDimensions()
*/
public List<SampleDimension> getSampleDimensions() {
return UnmodifiableArrayList.wrap(sampleDimensions);
}
/**
* Returns a grid coverage that contains real values or sample values, depending if {@code converted} is {@code true}
* or {@code false} respectively. If there is no {@linkplain SampleDimension#getTransferFunction() transfer function}
* defined by the {@linkplain #getSampleDimensions() sample dimensions}, then this method returns {@code this}.
* In all cases, the returned grid coverage <var>r</var> has the following properties:
*
* <ul>
* <li>The list returned by {@code r.getSampleDimensions()} is equal to the list returned by
* <code>this.{@linkplain #getSampleDimensions()}</code> with each element <var>e</var> replaced by
* <code>e.{@linkplain SampleDimension#forConvertedValues(boolean) forConvertedValues}(converted)</code>.</li>
* <li>The {@link RenderedImage} produced by {@code r.render(extent)} is equivalent to the image returned by
* <code>this.{@linkplain #render(GridExtent) render}(extent)</code> with all sample values converted
* using the transfer function if {@code converted} is {@code true}, or the inverse of transfer function
* if {@code converted} is {@code false}.</li>
* </ul>
*
* @param converted {@code true} for a coverage containing converted values,
* or {@code false} for a coverage containing packed values.
* @return a coverage containing converted or packed values, depending on {@code converted} argument value.
* May be {@code this} but never {@code null}.
*
* @see SampleDimension#forConvertedValues(boolean)
*/
public abstract GridCoverage forConvertedValues(boolean converted);
/**
* Returns a two-dimensional slice of grid data as a rendered image. The given {@code sliceExtent} argument specifies
* the coordinates of the slice in all dimensions that are not in the two-dimensional image. For example if this grid
* coverage has <i>(<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>)</i> dimensions and we want to render an image
* of data in the <i>(<var>x</var>,<var>y</var>)</i> dimensions, then the given {@code sliceExtent} shall contain the
* <i>(<var>z</var>,<var>t</var>)</i> coordinates of the desired slice. Those coordinates are specified in a grid extent
* where {@linkplain GridExtent#getLow(int) low coordinate} = {@linkplain GridExtent#getHigh(int) high coordinate} in the
* <var>z</var> and <var>t</var> dimensions. The two dimensions of the data to be shown (<var>x</var> and <var>y</var>
* in our example) shall be the only dimensions having a {@linkplain GridExtent#getSize(int) size} greater than 1 cell.
*
* <p>If the {@code sliceExtent} argument is {@code null}, then the default value is
* <code>{@linkplain #getGridGeometry()}.{@linkplain GridGeometry#getExtent() getExtent()}</code>.
* This means that {@code gridExtent} is optional for two-dimensional grid coverages or grid coverages where all dimensions
* except two have a size of 1 cell. If the grid extent contains more than 2 dimensions with a size greater than one cell,
* then a {@link SubspaceNotSpecifiedException} is thrown.</p>
*
* <div class="note"><p><b>How to compute a slice extent from a slice point in "real world" coordinates</b></p>
* The {@code sliceExtent} is specified to this method as grid indices. If the <var>z</var> and <var>t</var> values
* are not grid indices but are relative to some Coordinate Reference System (CRS) instead, then the slice extent can
* be computed as below. First, a <cite>slice point</cite> containing the <var>z</var> and <var>t</var> coordinates
* should be constructed as a {@link DirectPosition} in one of the following ways:
*
* <ul>
* <li>The {@code slicePoint} has a CRS with two dimensions less than this grid coverage CRS.</li>
* <li>The {@code slicePoint} has the same CRS than this grid coverage, but the two coordinates to
* exclude are set to {@link Double#NaN}.</li>
* </ul>
*
* Then:
*
* <blockquote><code>sliceExtent = {@linkplain #getGridGeometry()}.{@link GridGeometry#derive()
* derive()}.{@linkplain GridDerivation#slice(DirectPosition)
* slice}(slicePoint).{@linkplain GridDerivation#getIntersection() getIntersection()};</code></blockquote>
*
* If the {@code slicePoint} CRS is different than this grid coverage CRS (except for the number of dimensions),
* a coordinate transformation will be applied as needed.</div>
*
* <div class="section">Characteristics of the returned image</div>
* Image dimensions <var>x</var> and <var>y</var> map to the first and second dimension respectively of
* the two-dimensional {@code sliceExtent} {@linkplain GridExtent#getSubspaceDimensions(int) subspace}.
* The coordinates given by {@link RenderedImage#getMinX()} and {@link RenderedImage#getMinY() getMinY()}
* will be the image location <em>relative to</em> the location specified in {@code sliceExtent}
* {@linkplain GridExtent#getLow(int) low coordinates}.
* For example in the case of image {@linkplain RenderedImage#getMinX() minimum X coordinate}:
*
* <ul class="verbose">
* <li>A value of 0 means that the image left border is exactly where requested by {@code sliceExtent.getLow(xDimension)}.</li>
* <li>A positive value means that the returned image is shifted to the right compared to specified extent.
* This implies that the image has less data than requested on left side.
* It may happen if the specified extent is partially outside grid coverage extent.</li>
* <li>A negative value means that the returned image is shifted to the left compared to specified extent.
* This implies that the image has more data than requested on left side. It may happen if the image is tiled,
* the specified {@code sliceExtent} covers many tiles, and expanding the specified extent is necessary
* for returning an integer amount of tiles.</li>
* </ul>
*
* Similar discussion applies to the {@linkplain RenderedImage#getMinY() minimum Y coordinate}.
* The {@linkplain RenderedImage#getWidth() image width} and {@linkplain RenderedImage#getHeight() height} will be
* the {@code sliceExtent} {@linkplain GridExtent#getSize(int) sizes} if this method can honor exactly the request,
* or otherwise may be adjusted for the same reasons than <var>x</var> and <var>y</var> location discussed above.
*
* <p>Implementations should return a view as much as possible, without copying sample values.
* {@code GridCoverage} subclasses can use the {@link ImageRenderer} class as a helper tool for that purpose.
* This method does not mandate any behavior regarding tiling (size of tiles, their numbering system, <i>etc.</i>).
* Some implementations may defer data loading until {@linkplain RenderedImage#getTile(int, int) a tile is requested}.</p>
*
* @param sliceExtent a subspace of this grid coverage extent where all dimensions except two have a size of 1 cell.
* May be {@code null} if this grid coverage has only two dimensions with a size greater than 1 cell.
* @return the grid slice as a rendered image. Image location is relative to {@code sliceExtent}.
* @throws SubspaceNotSpecifiedException if the given argument is not sufficient for reducing the grid to a two-dimensional slice.
* @throws DisjointExtentException if the given extent does not intersect this grid coverage.
* @throws RuntimeException if this method can not produce the rendered image for another reason.
*/
public abstract RenderedImage render(GridExtent sliceExtent);
/**
* Returns a string representation of this grid coverage for debugging purpose.
* The returned string is implementation dependent and may change in any future version.
* Current implementation is equivalent to the following, where {@code <default flags>}
* is the same set of flags than {@link GridGeometry#toString()}.
*
* {@preformat java
* return toTree(Locale.getDefault(), <default flags>).toString();
* }
*
* @return a string representation of this grid coverage for debugging purpose.
*/
@Override
public String toString() {
return toTree(Locale.getDefault(), GridGeometry.defaultFlags()).toString();
}
/**
* Returns a tree representation of some elements of this grid coverage.
* The tree representation is for debugging purpose only and may change
* in any future SIS version.
*
* @param locale the locale to use for textual labels.
* @param bitmask combination of {@link GridGeometry} flags.
* @return a tree representation of the specified elements.
*
* @see GridGeometry#toTree(Locale, int)
*/
@Debug
public TreeTable toTree(final Locale locale, final int bitmask) {
ArgumentChecks.ensureNonNull("locale", locale);
final Vocabulary vocabulary = Vocabulary.getResources(locale);
final TableColumn<CharSequence> column = TableColumn.VALUE_AS_TEXT;
final TreeTable tree = new DefaultTreeTable(column);
final TreeTable.Node root = tree.getRoot();
root.setValue(column, Classes.getShortClassName(this));
TreeTable.Node branch = root.newChild();
branch.setValue(column, vocabulary.getString(Vocabulary.Keys.CoverageDomain));
gridGeometry.formatTo(locale, vocabulary, bitmask, branch);
branch = root.newChild();
branch.setValue(column, vocabulary.getString(Vocabulary.Keys.SampleDimensions));
branch.newChild().setValue(column, SampleDimension.toString(locale, sampleDimensions));
return tree;
}
}