use of org.apache.sis.coverage.grid.GridExtent in project sis by apache.
the class RasterResource method read.
/**
* Loads a subset of the grid coverage represented by this resource.
*
* @param domain desired grid extent and resolution, or {@code null} for reading 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.
* @throws DataStoreException if an error occurred while reading the grid coverage data.
*/
@Override
public GridCoverage read(final GridGeometry domain, final int... range) throws DataStoreException {
final long startTime = System.nanoTime();
final RangeArgument rangeIndices = validateRangeArgument(ranges.length, range);
final Variable first = data[bandDimension >= 0 ? 0 : rangeIndices.getFirstSpecified()];
final DataType dataType = first.getDataType();
if (bandDimension < 0) {
for (int i = 0; i < rangeIndices.getNumBands(); i++) {
final Variable variable = data[rangeIndices.getSourceIndex(i)];
if (!dataType.equals(variable.getDataType())) {
throw new DataStoreContentException(Resources.forLocale(getLocale()).getString(Resources.Keys.MismatchedVariableType_3, getFilename(), first.getName(), variable.getName()));
}
}
}
/*
* At this point the arguments and the state of this resource have been validated.
* There is three ways to read the data, determined by `bandDimension` value:
*
* • (bandDimension < 0): one variable per band (usual case).
* • (bandDimension = 0): one variable containing all bands, with bands in the first dimension.
* • (bandDimension > 0): one variable containing all bands, with bands in the last dimension.
*/
final GridGeometry targetDomain;
final DataBuffer imageBuffer;
final SampleDimension[] bands = new SampleDimension[rangeIndices.getNumBands()];
// By default, all bands start at index 0.
int[] bandOffsets = null;
try {
final GridDerivation targetGeometry = gridGeometry.derive().rounding(GridRoundingMode.ENCLOSING).subgrid((domain != null) ? domain : gridGeometry);
// Pixel indices of data to read.
GridExtent areaOfInterest = targetGeometry.getIntersection();
// Slice to read or subsampling to apply.
int[] subsampling = targetGeometry.getSubsampling();
// By default, one variable per band.
int numBuffers = bands.length;
// Adjust user-specified domain to data geometry.
targetDomain = targetGeometry.build();
if (bandDimension >= 0) {
areaOfInterest = rangeIndices.insertBandDimension(areaOfInterest, bandDimension);
subsampling = rangeIndices.insertSubsampling(subsampling, bandDimension);
if (bandDimension == 0) {
// Will be set to non-zero values later.
bandOffsets = new int[numBuffers];
}
// One variable for all bands.
numBuffers = 1;
}
/*
* Iterate over netCDF variables in the order they appear in the file, not in the order requested
* by the `range` argument. The intent is to perform sequential I/O as much as possible, without
* seeking backward. In the (uncommon) case where bands are one of the variable dimension instead
* than different variables, the reading of the whole variable occurs during the first iteration.
*/
Buffer[] sampleValues = new Buffer[numBuffers];
synchronized (lock) {
for (int i = 0; i < bands.length; i++) {
// In strictly increasing order.
int indexInResource = rangeIndices.getSourceIndex(i);
int indexInRaster = rangeIndices.getTargetIndex(i);
Variable variable = getVariable(indexInResource);
SampleDimension b = ranges[indexInResource];
if (b == null) {
ranges[indexInResource] = b = createSampleDimension(rangeIndices.builder(), variable, i);
}
bands[indexInRaster] = b;
if (bandOffsets != null) {
bandOffsets[indexInRaster] = i;
// Pixels interleaved in one bank: sampleValues.length = 1.
indexInRaster = 0;
}
if (i < numBuffers)
try {
// Optional.orElseThrow() below should never fail since Variable.read(…) wraps primitive array.
sampleValues[indexInRaster] = variable.read(areaOfInterest, subsampling).buffer().get();
} catch (ArithmeticException e) {
throw variable.canNotComputePosition(e);
}
}
}
/*
* The following block is executed only if all bands are in a single variable, and the bands dimension is
* the last one (in "natural" order). In such case, the sample model to construct is a BandedSampleModel.
* Contrarily to PixelInterleavedSampleModel (the case when the band dimension is first), banded sample
* model force us to split the buffer in a buffer for each band.
*/
if (bandDimension > 0) {
// Really > 0, not >= 0.
final int stride = Math.toIntExact(data[0].getBandStride());
Buffer values = sampleValues[0].limit(stride);
sampleValues = new Buffer[bands.length];
for (int i = 0; i < sampleValues.length; i++) {
if (i != 0) {
values = JDK9.duplicate(values);
final int p = values.limit();
values.position(p).limit(Math.addExact(p, stride));
}
sampleValues[i] = values;
}
}
/*
* Convert NIO Buffer into Java2D DataBuffer. May throw various RuntimeException.
*/
imageBuffer = RasterFactory.wrap(dataType.rasterDataType, sampleValues);
} catch (IOException | RuntimeException e) {
throw canNotRead(getFilename(), domain, e);
}
/*
* At this point the I/O operation is completed and sample values have been stored in a NIO buffer.
* Provide to `Raster` all information needed for building a `RenderedImage` when requested.
*/
if (imageBuffer == null) {
throw new DataStoreContentException(Errors.getResources(getLocale()).getString(Errors.Keys.UnsupportedType_1, dataType.name()));
}
final Variable main = data[visibleBand];
final Raster raster = new Raster(targetDomain, UnmodifiableArrayList.wrap(bands), imageBuffer, String.valueOf(identifier), rangeIndices.getPixelStride(), bandOffsets, visibleBand, main.decoder.convention().getColors(main));
logReadOperation(location, targetDomain, startTime);
return raster;
}
use of org.apache.sis.coverage.grid.GridExtent in project sis by apache.
the class Variable method getBandStride.
/**
* Returns the number of sample values between two bands.
* This method is meaningful only if {@link #bandDimension} ≥ 0.
*/
final long getBandStride() throws IOException, DataStoreException {
long length = 1;
final GridExtent extent = getGridGeometry().getExtent();
for (int i = bandDimension; --i >= 0; ) {
length = Math.multiplyExact(length, extent.getSize(i));
}
return length;
}
use of org.apache.sis.coverage.grid.GridExtent in project sis by apache.
the class Variable method getGridGeometry.
/**
* Returns the grid geometry for this variable, or {@code null} if this variable is not a data cube.
* Not all variables have a grid geometry. For example collections of features do not have such grid.
* The same grid geometry may be shared by many variables.
* The grid may have fewer {@linkplain Grid#getDimensions() dimensions} than this variable,
* in which case the additional {@linkplain #getGridDimensions() variable dimensions} can be considered as bands.
*
* @return the grid geometry for this variable, or {@code null} if none.
* @throws IOException if an error occurred while reading the data.
* @throws DataStoreException if a logical error occurred.
*/
public final GridGeometry getGridGeometry() throws IOException, DataStoreException {
if (!gridDetermined) {
// Set first so we don't try twice in case of failure.
gridDetermined = true;
final GridMapping gridMapping = GridMapping.forVariable(this);
final GridAdjustment adjustment = new GridAdjustment();
final Grid info = getGrid(adjustment);
if (info != null) {
/*
* This variable may have more dimensions than the grid. We need to reduce the list to the same
* dimensions than the ones in the grid. We can not take Grid.getDimensions() directly because
* those dimensions may not have the same length (this mismatch is handled in the next block).
*/
// In netCDF order.
List<Dimension> dimensions = getGridDimensions();
final int dataDimension = dimensions.size();
if (dataDimension > info.getSourceDimensions()) {
boolean copied = false;
// Also in netCDF order.
final List<Dimension> toKeep = info.getDimensions();
final int numToKeep = toKeep.size();
for (int i = 0; i < numToKeep; i++) {
Dimension expected = toKeep.get(i);
expected = adjustment.gridToVariable.getOrDefault(expected, expected);
/*
* At this point, `expected` is a dimension of the variable that we expect to find at
* current index `i`. If we do not find that dimension, then the unexpected dimension
* is assumed to be a band. We usually remove at most one element. If removal results
* in a list too short, it would be a bug in the way we computed `toKeep`.
*/
while (!expected.equals(dimensions.get(i))) {
if (!copied) {
copied = true;
dimensions = new ArrayList<>(dimensions);
}
/*
* It is possible that we never reach this point if the unexpected dimension is last.
* However in such case the dimension to declare is the last one in netCDF order,
* which corresponds to the first dimension (i.e. dimension 0) in "natural" order.
* Since the `bandDimension` field is initialized to zero, its value is correct.
*/
// Convert netCDF order to "natural" order.
bandDimension = dataDimension - 1 - i;
dimensions.remove(i);
for (int j = dimensions.size(); --j >= i; ) {
dimensions.set(j, dimensions.get(j).decrementIndex());
}
if (dimensions.size() < numToKeep) {
// Should not happen (see above comment).
throw new InternalDataStoreException();
}
}
}
/*
* At this point `dimensions` may still be longer than `toKeep` but it does not matter.
* We only need that for any index i < numToKeep, dimensions.get(i) corresponds to the
* dimension at the same index in the grid.
*/
}
/*
* Compare the size of the variable with the size of the localization grid.
* If they do not match, then there is a scale factor between the two that
* needs to be applied.
*/
GridGeometry grid = info.getGridGeometry(decoder);
if (grid != null) {
if (grid.isDefined(GridGeometry.EXTENT)) {
GridExtent extent = grid.getExtent();
final long[] sizes = new long[extent.getDimension()];
boolean needsResize = false;
for (int i = sizes.length; --i >= 0; ) {
// Convert "natural order" index into netCDF index.
final int d = (sizes.length - 1) - i;
sizes[i] = dimensions.get(d).length();
if (!needsResize) {
needsResize = (sizes[i] != extent.getSize(i));
}
}
if (needsResize) {
final double[] dataToGridIndices = adjustment.dataToGridIndices();
if (dataToGridIndices == null || dataToGridIndices.length < sizes.length) {
warning(Variable.class, "getGridGeometry", Resources.Keys.ResamplingIntervalNotFound_2, getFilename(), getName());
return null;
}
extent = extent.resize(sizes);
grid = GridAdjustment.scale(grid, extent, info.getAnchor(), dataToGridIndices);
}
}
/*
* At this point we finished to build a grid geometry from the information provided by axes.
* If there is grid mapping attributes (e.g. "EPSG_code", "ESRI_pe_string", "GeoTransform",
* "spatial_ref", etc.), substitute some parts of the grid geometry by the parts built from
* those attributes.
*/
if (gridMapping != null) {
grid = gridMapping.adaptGridCRS(this, grid, info.getAnchor());
}
}
gridGeometry = grid;
} else if (gridMapping != null) {
gridGeometry = gridMapping.createGridCRS(this);
}
}
return gridGeometry;
}
use of org.apache.sis.coverage.grid.GridExtent in project sis by apache.
the class MapCanvas method repaint.
/**
* Invoked when the map content needs to be rendered again.
* It may be because the map has new content, or because the viewed region moved or has been zoomed.
* This method starts the rendering process immediately, unless a rendering is already in progress.
*
* @see #requestRepaint()
*/
final void repaint() {
assert Platform.isFxApplicationThread();
/*
* If a rendering is already in progress, do not send a new request now.
* Wait for current rendering to finish; a new one will be automatically
* requested if content changes are detected after the rendering.
*/
if (renderingInProgress != null) {
if (renderingInProgress instanceof Delayed) {
renderingInProgress.cancel(true);
renderingInProgress = null;
} else {
contentChangeCount++;
return;
}
}
hasError = false;
// Avoid that `requestRepaint(…)` trig new paints.
isRendering.set(true);
renderingStartTime = System.nanoTime();
try {
/*
* If a new canvas size is known, inform the parent `PlanarCanvas` about that.
* It may cause a recomputation of the "objective to display" transform.
*/
if (sizeChanged) {
sizeChanged = false;
final Pane view = floatingPane;
Envelope2D bounds = new Envelope2D(null, view.getLayoutX(), view.getLayoutY(), view.getWidth(), view.getHeight());
if (bounds.isEmpty())
return;
setDisplayBounds(bounds);
}
/*
* Compute the `objectiveToDisplay` only before the first rendering, because the display
* bounds may not be known before (it may be zero at the time `MapCanvas` is initialized).
* This code is executed only once for a new map.
*/
if (invalidObjectiveToDisplay) {
final Envelope2D target = getDisplayBounds();
if (target == null) {
// Bounds are still unknown. Another repaint event will happen when they will become known.
return;
}
invalidObjectiveToDisplay = false;
final GridExtent extent = new GridExtent(null, new long[] { Math.round(target.getMinX()), Math.round(target.getMinY()) }, new long[] { Math.round(target.getMaxX()), Math.round(target.getMaxY()) }, false);
/*
* If `setObjectiveBounds(…)` has been invoked (as it should be), initialize the affine
* transform to values which will allow this canvas to contain fully the objective bounds.
* Otherwise the transform is initialized to an identity transform (should not happen often).
* If a CRS is present, it is used for deciding if we need to swap or flip axes.
*/
CoordinateReferenceSystem objectiveCRS;
final LinearTransform crsToDisplay;
if (objectiveBounds != null) {
objectiveCRS = objectiveBounds.getCoordinateReferenceSystem();
final MatrixSIS m;
if (objectiveCRS != null) {
AxisDirection[] srcAxes = CoordinateSystems.getAxisDirections(objectiveCRS.getCoordinateSystem());
m = Matrices.createTransform(objectiveBounds, srcAxes, target, toDisplayDirections(srcAxes));
} else {
m = Matrices.createTransform(objectiveBounds, target);
}
Matrices.forceUniformScale(m, 0, new double[] { target.getCenterX(), target.getCenterY() });
crsToDisplay = MathTransforms.linear(m);
if (objectiveCRS == null) {
objectiveCRS = extent.toEnvelope(crsToDisplay.inverse()).getCoordinateReferenceSystem();
/*
* Above code tried to provide a non-null CRS on a "best effort" basis. The objective CRS
* may still be null, there is no obvious answer against that. It is not the display CRS
* if the "display to objective" transform is not identity. A grid CRS is not appropriate
* neither, otherwise `extent.toEnvelope(…)` would have found it.
*/
}
} else {
objectiveCRS = getDisplayCRS();
crsToDisplay = MathTransforms.identity(BIDIMENSIONAL);
}
setGridGeometry(new GridGeometry(extent, PixelInCell.CELL_CORNER, crsToDisplay.inverse(), objectiveCRS));
transform.setToIdentity();
}
} catch (TransformException | RenderException ex) {
restoreCursorAfterPaint();
isRendering.set(false);
errorOccurred(ex);
return;
}
/*
* If a temporary zoom, rotation or translation has been applied using JavaFX transform API,
* replace that temporary transform by a "permanent" adjustment of the `objectiveToDisplay`
* transform. It allows SIS to get new data for the new visible area and resolution.
* Do not reset `transform` to identity now; we need to continue accumulating gestures
* that may happen while the rendering is done in a background thread.
*/
changeInProgress.setToTransform(transform);
if (!transform.isIdentity()) {
transformDisplayCoordinates(new AffineTransform(transform.getMxx(), transform.getMyx(), transform.getMxy(), transform.getMyy(), transform.getTx(), transform.getTy()));
}
/*
* Invoke `createWorker(…)` only after we finished above configuration, because that method
* may take a snapshot of current canvas state in preparation for use in background threads.
* Take the value of `contentChangeCount` only now because above code may have indirect calls
* to `requestRepaint()`.
*/
renderedContentStamp = contentChangeCount;
final Renderer context = createRenderer();
if (context != null && context.initialize(floatingPane)) {
final Task<?> worker = createWorker(context);
assert renderingInProgress == null;
BackgroundThreads.execute(worker);
// Set after we know that the task has been scheduled.
renderingInProgress = worker;
if (!isMouseChangeScheduled) {
DelayedExecutor.schedule(new CursorChange());
isMouseChangeScheduled = true;
}
} else {
if (!hasError) {
clearError();
}
isRendering.set(false);
restoreCursorAfterPaint();
}
}
use of org.apache.sis.coverage.grid.GridExtent in project sis by apache.
the class ImageRequest method configure.
/**
* Configures the given status bar with the geometry of the grid coverage we have just read.
* This method is invoked in JavaFX thread after {@link GridView#setImage(ImageRequest)}
* loaded in background thread a new image, successfully or not.
*/
final void configure(final StatusBar bar) {
final Long id = LogHandler.loadingStart(resource);
try {
final GridCoverage cv = coverage;
final GridExtent ex = sliceExtent;
bar.applyCanvasGeometry(cv != null ? cv.getGridGeometry() : null);
/*
* By `GridCoverage.render(GridExtent)` contract, the `RenderedImage` pixel coordinates are relative
* to the requested `GridExtent`. Consequently we need to translate the image coordinates so that it
* become the coordinates of the original `GridGeometry` before to apply `gridToCRS`. It is okay to
* modify `StatusBar.localToObjectiveCRS` because we do not associate it to a `MapCanvas`, so it will
* not be overwritten by gesture events (zoom, pan, etc).
*/
if (ex != null) {
final double[] origin = new double[ex.getDimension()];
for (int i = 0; i < origin.length; i++) {
origin[i] = ex.getLow(i);
}
bar.localToObjectiveCRS.set(MathTransforms.concatenate(MathTransforms.translation(origin), bar.localToObjectiveCRS.get()));
}
} finally {
LogHandler.loadingStop(id);
}
}
Aggregations