use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.
the class ImageRequest method load.
/**
* Loads the image. If the coverage has more than {@value #BIDIMENSIONAL} dimensions,
* only two of them are taken for the image; for all other dimensions, only the values
* at lowest index will be read.
*
* <p>If the {@link #coverage} field was null, it will be initialized as a side-effect.
* No other fields will be modified.</p>
*
* <h4>Thread safety</h4>
* This class does not need to be thread-safe because it should be used only once in a well-defined life cycle.
* We nevertheless synchronize as a safety (e.g. user could give the same {@code ImageRequest} to two different
* {@link CoverageExplorer} instances). In such case the {@link GridCoverage} will be loaded only once,
* but no caching is done for the {@link RenderedImage} (because usually not needed).
*
* @param task the task invoking this method (for checking for cancellation).
* @return the image loaded from the source given at construction time, or {@code null}
* if the task has been cancelled.
* @throws DataStoreException if an error occurred while loading the grid coverage.
*/
final synchronized RenderedImage load(final FutureTask<?> task) throws DataStoreException {
GridCoverage cv = coverage;
final Long id = LogHandler.loadingStart(resource);
try {
if (cv == null) {
cv = MultiResolutionImageLoader.getInstance(resource, null).getOrLoad(domain, range);
}
coverage = cv = cv.forConvertedValues(true);
if (task.isCancelled()) {
return null;
}
GridExtent ex = sliceExtent;
if (ex == null) {
final GridGeometry gg = cv.getGridGeometry();
if (gg.getDimension() > MultiResolutionImageLoader.BIDIMENSIONAL) {
ex = MultiResolutionImageLoader.slice(gg.derive(), gg.getExtent()).getIntersection();
}
}
return cv.render(ex);
} finally {
LogHandler.loadingStop(id);
}
}
use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.
the class MultiResolutionCoverageLoader method getOrLoad.
/**
* Returns the coverage at the given level if it is present in the cache, or loads and caches it otherwise.
*
* @param level pyramid level of the desired coverage.
* @return the coverage at the specified level (never null).
* @throws DataStoreException if an error occurred while loading the coverage.
*/
public final GridCoverage getOrLoad(final int level) throws DataStoreException {
synchronized (coverages) {
final Reference<GridCoverage> ref = coverages[level];
if (ref != null) {
final GridCoverage coverage = ref.get();
if (coverage != null)
return coverage;
coverages[level] = null;
}
}
GridGeometry domain = null;
if (resolutionSquared.length != 0) {
final double[] resolutions = resolutionSquared[level].clone();
for (int i = 0; i < resolutions.length; i++) {
resolutions[i] = Math.sqrt(resolutions[i]);
}
final MathTransform gridToCRS = MathTransforms.scale(resolutions);
domain = new GridGeometry(PixelInCell.CELL_CORNER, gridToCRS, areaOfInterest, GridRoundingMode.ENCLOSING);
}
final GridCoverage coverage = resource.read(getReadDomain(domain), readRanges);
/*
* Cache and return the coverage. The returned coverage may be a different instance
* if another coverage has been cached concurrently for the same level.
*/
synchronized (coverages) {
final Reference<GridCoverage> ref = coverages[level];
if (ref != null) {
final GridCoverage c = ref.get();
if (c != null)
return c;
}
coverages[level] = new SoftReference<>(coverage);
}
return coverage;
}
use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.
the class RenderingData method ensureImageLoaded.
/**
* Fetches the rendered image if {@linkplain #data} is null. This method needs to be invoked at least
* once after {@link #setCoverageSpace(GridGeometry, List)}. The {@code coverage} given in argument
* may be the value returned by {@link #ensureCoverageLoaded(LinearTransform, DirectPosition)}.
*
* @param coverage the coverage from which to read data, or {@code null} if the coverage did not changed.
* @param sliceExtent a subspace of the 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 whether the {@linkpalin #data} changed.
* @throws FactoryException if the CRS changed but the transform from old to new CRS can not be determined.
* @throws TransformException if an error occurred while transforming coordinates from old to new CRS.
*/
public final boolean ensureImageLoaded(GridCoverage coverage, final GridExtent sliceExtent) throws FactoryException, TransformException {
if (data != null || coverage == null) {
return false;
}
coverage = coverage.forConvertedValues(true);
final GridGeometry old = dataGeometry;
final List<SampleDimension> ranges = coverage.getSampleDimensions();
final RenderedImage image = coverage.render(sliceExtent);
final Object value = image.getProperty(PlanarImage.GRID_GEOMETRY_KEY);
final GridGeometry domain = (value instanceof GridGeometry) ? (GridGeometry) value : new ImageRenderer(coverage, sliceExtent).getImageGeometry(BIDIMENSIONAL);
setCoverageSpace(domain, ranges);
data = image;
/*
* Update the transforms in a way that preserve the current zoom level, translation, etc.
* We compute the change in the "data grid to objective CRS" transforms caused by the change
* in data grid geometry, then we concatenate that change to the existing transforms.
* That way, the objective CRS is kept unchanged.
*/
if (old != null && cornerToObjective != null && objectiveToCenter != null) {
MathTransform toNew = null, toOld = null;
if (old.isDefined(GridGeometry.CRS) && domain.isDefined(GridGeometry.CRS)) {
final CoordinateReferenceSystem oldCRS = old.getCoordinateReferenceSystem();
final CoordinateReferenceSystem newCRS = dataGeometry.getCoordinateReferenceSystem();
if (newCRS != oldCRS) {
// Quick check for the vast majority of cases.
/*
* Transform computed below should always be the identity transform,
* but we check anyway as a safety. A non-identity transform would be
* a pyramid where the CRS changes according the pyramid level.
*/
final GeographicBoundingBox areaOfInterest = Extents.union(dataGeometry.getGeographicExtent().orElse(null), old.getGeographicExtent().orElse(null));
toNew = CRS.findOperation(oldCRS, newCRS, areaOfInterest).getMathTransform();
toOld = toNew.inverse();
}
}
final MathTransform forward = concatenate(PixelInCell.CELL_CORNER, dataGeometry, old, toOld);
final MathTransform inverse = concatenate(PixelInCell.CELL_CENTER, old, dataGeometry, toNew);
cornerToObjective = MathTransforms.concatenate(forward, cornerToObjective);
objectiveToCenter = MathTransforms.concatenate(objectiveToCenter, inverse);
}
return true;
}
use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.
the class RasterReader method readAsCoverage.
/**
* Parses a raster from the given input stream and returns as a coverage.
*
* @param input source of bytes to read.
* @return the raster as a coverage, or {@code null} if the raster is empty.
* @throws Exception in an error occurred while reading from the given input or creating the coverage.
* Exception type may be I/O, SQL, factory, data store, arithmetic, raster format, <i>etc.</i>,
* too numerous for enumerating them all.
*/
public GridCoverage readAsCoverage(final ChannelDataInput input) throws Exception {
final BufferedImage image = readAsImage(input);
if (image == null) {
return null;
}
CoordinateReferenceSystem crs = null;
final int srid = getSRID();
if (spatialRefSys != null) {
crs = spatialRefSys.fetchCRS(srid);
} else if (srid > 0) {
crs = CRS.forCode(Constants.EPSG + ':' + srid);
}
if (crs == null) {
crs = defaultCRS;
}
final GridExtent extent = new GridExtent(image.getWidth(), image.getHeight());
final GridGeometry domain = new GridGeometry(extent, ANCHOR, getGridToCRS(), crs);
/*
* Create pseudo-categories with a transfer function if we need to specify "no data" value,
* or the sign of stored data do not match the sign of expected values.
*/
List<SampleDimension> range = null;
if (needsTransferFunction()) {
final SampleDimension[] sd = new SampleDimension[bands.length];
final SampleDimension.Builder builder = new SampleDimension.Builder();
for (int b = 0; b < sd.length; b++) {
final Band band = bands[b];
if ((band.getDataBufferType() & OPPOSITE_SIGN) != 0) {
// See `Band.OPPOSITE_SIGN` javadoc for more information on this limitation.
throw new RasterFormatException("Data type not yet supported.");
}
sd[b] = builder.setName(b + 1).setBackground(band.noDataValue).build();
builder.clear();
}
range = Arrays.asList(sd);
}
return new GridCoverage2D(domain, range, image);
}
use of org.apache.sis.coverage.grid.GridGeometry in project sis by apache.
the class MemoryGridResource method read.
/**
* 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 range 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 range.
*/
@Override
public GridCoverage read(GridGeometry domain, final int... range) {
List<SampleDimension> bands = coverage.getSampleDimensions();
final RangeArgument rangeIndices = validateRangeArgument(bands.size(), range);
/*
* 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 range 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 = coverage.getGridGeometry();
if (domain == null) {
domain = source;
} else {
intersection = source.derive().rounding(GridRoundingMode.ENCLOSING).subgrid(domain).getIntersection();
if (intersection.equals(source.getExtent())) {
// Will request the whole image.
intersection = null;
domain = source;
}
}
/*
* Quick check before to invoke the potentially costly `coverage.render(…)` method.
*/
final boolean sameBands = rangeIndices.isIdentity();
if (sameBands && intersection == null) {
return coverage;
}
/*
* 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 = coverage.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; ) {
// We need only the dimensions that may change.
changes[i] = intersection.getSize(i);
}
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 than the source coverage,
* we may be able to return that coverage directly.
*/
if (intersection.equals(source.getExtent())) {
if (sameBands) {
return coverage;
}
domain = source;
} else {
domain = new GridGeometry(intersection, PixelInCell.CELL_CORNER, source.getGridToCRS(PixelInCell.CELL_CORNER), source.getCoordinateReferenceSystem());
}
}
if (!sameBands) {
data = new ImageProcessor().selectBands(data, range);
bands = UnmodifiableArrayList.wrap(rangeIndices.select(bands));
}
return new GridCoverageBuilder().setValues(data).setRanges(bands).setDomain(domain).build();
}
Aggregations