blob: 25256f4457d1bd73695bb024a931b10225f9d90f [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.storage.base;
import java.util.List;
import java.util.Arrays;
import java.util.Objects;
import java.awt.image.RenderedImage;
import org.opengis.referencing.datum.PixelInCell;
import org.apache.sis.coverage.SampleDimension;
import org.apache.sis.coverage.grid.GridCoverage;
import org.apache.sis.coverage.grid.GridCoverageBuilder;
import org.apache.sis.coverage.grid.GridCoverageProcessor;
import org.apache.sis.coverage.grid.GridExtent;
import org.apache.sis.coverage.grid.GridGeometry;
import org.apache.sis.coverage.grid.GridRoundingMode;
import org.apache.sis.storage.AbstractGridCoverageResource;
import org.apache.sis.storage.event.StoreListeners;
/**
* A {@link org.apache.sis.storage.GridCoverageResource} in memory.
* This resource wraps an arbitrary {@link GridCoverage} specified at construction time.
* Metadata can be specified by overriding {@link #createMetadata()}.
*
* @author Johann Sorel (Geomatys)
* @author Martin Desruisseaux (Geomatys)
*/
public final class MemoryGridResource extends AbstractGridCoverageResource {
/**
* The grid coverage specified at construction time.
*/
public final GridCoverage coverage;
/**
* The grid coverage processor to use for selecting bands.
* It may be configured with a colorizer for determining the color models.
*/
private final GridCoverageProcessor processor;
/**
* Creates a new coverage stored in memory.
*
* @param parent listeners of the parent resource, or {@code null} if none.
* @param coverage stored coverage retained as-is (not copied). Cannot be null.
* @param processor the grid coverage processor for selecting bands, or {@code null} for default.
*/
public MemoryGridResource(final StoreListeners parent, final GridCoverage coverage, final GridCoverageProcessor processor) {
super(parent, false);
this.coverage = Objects.requireNonNull(coverage);
this.processor = (processor != null) ? processor : new GridCoverageProcessor();
}
/**
* Returns information about the <i>domain</i> of wrapped grid coverage.
*
* @return extent of grid coordinates together with their mapping to "real world" coordinates.
*/
@Override
public GridGeometry getGridGeometry() {
return coverage.getGridGeometry();
}
/**
* Returns information about the <i>ranges</i> of wrapped grid coverage.
*
* @return ranges of sample values together with their mapping to "real values".
*/
@Override
public List<SampleDimension> getSampleDimensions() {
return coverage.getSampleDimensions();
}
/**
* Returns a subset of the wrapped grid coverage. If a non-null grid geometry is specified, then
* this method tries to return a grid coverage matching the given grid geometry on a best-effort basis.
* In current implementation this is either a {@link org.apache.sis.coverage.grid.GridCoverage2D} or
* the original grid coverage.
*
* @param domain desired grid extent and resolution, or {@code null} for the whole domain.
* @param ranges 0-based indices of sample dimensions to read, or {@code null} or an empty sequence for reading them all.
* @return the grid coverage for the specified domain and ranges.
*/
@Override
public GridCoverage read(GridGeometry domain, final int... ranges) {
/*
* If the user requested a subset of the bands, select the bands on the grid coverage.
* It is slightly more expensive than selecting the bands on the image produced at the
* end of this method, except if the coverage is a `BandAggregateGridCoverage` because
* the latter will do rendering only on the selected coverage components.
*/
GridCoverage subset = coverage;
if (ranges != null && ranges.length != 0) {
subset = processor.selectSampleDimensions(subset, ranges);
}
/*
* The given `domain` may use arbitrary `gridToCRS` and `CRS` properties.
* For this simple implementation we need the same `gridToCRS` and `CRS`
* than the wrapped coverage; only domain `extent` is allowed to differ.
* Subsampling is ignored for now because it is an expensive operation.
* Clipping and ranges selection are light and do not copy any data.
*
* TODO: a future implementation may apply subsampling efficiently,
* by adjusting the pixel stride in SampleModel.
*/
GridExtent intersection = null;
final GridGeometry source = subset.getGridGeometry();
if (domain == null) {
domain = source;
} else {
intersection = source.derive()
.rounding(GridRoundingMode.ENCLOSING)
.subgrid(domain).getIntersection(); // Take in account the change of CRS if needed.
if (intersection.equals(source.getExtent())) {
intersection = null; // Will request the whole image.
domain = source;
}
}
/*
* Quick check before to invoke the potentially costly `coverage.render(…)` method.
*/
if (intersection == null) {
return subset;
}
/*
* After `render(…)` execution, the (minX, minY) image coordinates are the differences between
* the extent that we requested and the one that we got. If that differences is not zero, then
* we need to translate the `GridExtent` in order to make it matches what we got. But before to
* apply that translation, we adjust the grid size (because it may add another translation).
*/
RenderedImage data = subset.render(intersection);
if (intersection != null) {
final int[] sd = intersection.getSubspaceDimensions(2);
final int dimX = sd[0];
final int dimY = sd[1];
final long ox = intersection.getLow(dimX);
final long oy = intersection.getLow(dimY);
final long[] changes = new long[Math.max(dimX, dimY) + 1];
for (int i = changes.length; --i >= 0;) {
changes[i] = intersection.getSize(i); // We need only the dimensions that may change.
}
changes[dimX] = data.getWidth();
changes[dimY] = data.getHeight();
intersection = intersection.resize(changes);
/*
* Apply the translation after we resized the grid extent, because the resize operation
* may have caused an additional translation. We cancel that translation with terms that
* restore the (ox,oy) lower coordinates before to add the data minimum X,Y.
*/
Arrays.fill(changes, 0);
changes[dimX] = Math.addExact(ox - intersection.getLow(dimX), data.getMinX());
changes[dimY] = Math.addExact(oy - intersection.getLow(dimY), data.getMinX());
intersection = intersection.translate(changes);
/*
* If the result is the same intersection as the source coverage,
* we can return that coverage directly.
*/
if (intersection.equals(source.getExtent())) {
return subset;
} else {
domain = new GridGeometry(intersection, PixelInCell.CELL_CORNER,
source.getGridToCRS(PixelInCell.CELL_CORNER),
source.getCoordinateReferenceSystem());
}
}
return new GridCoverageBuilder()
.setValues(data)
.setDomain(domain)
.setRanges(subset.getSampleDimensions())
.build();
}
}