use of org.geotoolkit.image.io.SpatialImageReader in project geotoolkit by Geomatys.
the class WorldFileImageReader method createMetadata.
/**
* Creates a new stream or image metadata. This method first delegates to the main reader as
* documented in the {@linkplain ImageReaderAdapter#createMetadata(int) super-class method},
* then completes the metadata with information read from the <cite>World File</cite> and
* <cite>Map Projection</cite> files.
* <p>
* The <cite>World File</cite> and <cite>Map Projection</cite> files are determined by calls
* to the {@link #createInput(String)} method with {@code "tfw"} and {@code "prj"} argument
* values. Subclasses can override the later method if they want to specify different files
* to be read.
*/
@Override
protected SpatialMetadata createMetadata(final int imageIndex) throws IOException {
SpatialMetadata metadata = super.createMetadata(imageIndex);
if (imageIndex >= 0) {
AffineTransform gridToCRS = null;
CoordinateReferenceSystem crs = null;
Object in = getVerifiedInput("tfw");
if (in != null) {
gridToCRS = SupportFiles.parseTFW(IOUtilities.open(in), in);
}
in = getVerifiedInput("prj");
if (in != null) {
crs = PrjFiles.read(IOUtilities.open(in), true);
}
/*
* If we have found information in TFW or PRJ files, complete metadata.
*/
if (gridToCRS != null || crs != null) {
// -- if exist some metadata from sub reader complete them, else create new spatial metadata
if (main instanceof SpatialImageReader) {
metadata = ((SpatialImageReader) main).getImageMetadata(imageIndex);
} else {
metadata = new SpatialMetadata(false, this, null);
}
if (gridToCRS != null) {
final int width = getWidth(imageIndex);
final int height = getHeight(imageIndex);
new GridDomainAccessor(metadata).setAll(gridToCRS, new Rectangle(width, height), null, PixelOrientation.UPPER_LEFT);
}
if (crs != null) {
new ReferencingBuilder(metadata).setCoordinateReferenceSystem(crs);
}
}
}
return metadata;
}
use of org.geotoolkit.image.io.SpatialImageReader in project geotoolkit by Geomatys.
the class ImageCoverageReader method getGridGeometry.
/**
* Returns the grid geometry for the {@link GridCoverage2D} to be read at the given index.
* The default implementation performs the following:
* <p>
* <ul>
* <li>The {@link GridExtent} is determined from the
* {@linkplain SpatialImageReader#getGridEnvelope(int) spatial image reader}
* if possible, or from the image {@linkplain ImageReader#getWidth(int) width}
* and {@linkplain ImageReader#getHeight(int) height} otherwise.</li>
* <li>The {@link CoordinateReferenceSystem} and the "<cite>grid to CRS</cite>" conversion
* are determined from the {@link SpatialMetadata} if any.</li>
* </ul>
*
* @return The grid geometry for the {@link GridCoverage} at the specified index.
* @throws IllegalStateException If the input source has not been set.
* @throws IndexOutOfBoundsException If the supplied index is out of bounds.
* @throws CoverageStoreException If an error occurs while reading the information from the input source.
* @throws CancellationException If {@link #abort()} has been invoked in an other thread during
* the execution of this method.
*
* @see ImageReader#getWidth(int)
* @see ImageReader#getHeight(int)
*/
public GridGeometry getGridGeometry() throws DataStoreException {
final int index = 0;
GridGeometry gridGeometry = getCached(gridGeometries, index);
if (gridGeometry == null) {
// Protect from changes.
final ImageReader imageReader = this.imageReader;
if (imageReader == null) {
throw new IllegalStateException(formatErrorMessage(Errors.Keys.NoImageInput));
}
/*
* Get the required information from the SpatialMetadata, if any.
* For now we just collect them - they will be processed later.
*/
CoordinateReferenceSystem crs = null;
MathTransform gridToCRS = null;
PixelOrientation pointInPixel = null;
final int width, height;
try {
width = imageReader.getWidth(index);
height = imageReader.getHeight(index);
final SpatialMetadata metadata = getImageMetadata(imageReader, index);
if (metadata != null) {
crs = metadata.getInstanceForType(CoordinateReferenceSystem.class);
if (crs == null) {
crs = PredefinedCRS.GRID_2D;
}
if (crs instanceof GridGeometry) {
// Some formats (e.g. NetCDF) do that.
gridToCRS = ((GridGeometry) crs).getGridToCRS(PixelInCell.CELL_CENTER);
} else {
final RectifiedGrid grid = metadata.getInstanceForType(RectifiedGrid.class);
if (grid != null) {
gridToCRS = getMetadataHelper().getGridToCRS(grid);
}
final Georectified georect = metadata.getInstanceForType(Georectified.class);
if (georect != null) {
pointInPixel = georect.getPointInPixel();
}
}
}
} catch (IOException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
/*
* If any metadata are still null, replace them by their default values. Those default
* values are selected in order to be as neutral as possible: An ImageCRS which is not
* convertible to GeodeticCRS, an identity "grid to CRS" conversion, a PixelOrientation
* equivalent to performing no shift at all in the "grid to CRS" conversion.
*/
if (crs == null) {
crs = PredefinedCRS.GRID_2D;
}
final int dimension = crs.getCoordinateSystem().getDimension();
if (gridToCRS == null) {
gridToCRS = MathTransforms.identity(dimension);
}
if (pointInPixel == null) {
pointInPixel = PixelOrientation.CENTER;
}
/*
* Now build the grid geometry. Note that the grid extent spans shall be set to 1
* for all dimensions other than X and Y, even if the original file has more data,
* since this is a GridGeometry2D requirement.
*/
final long[] lower = new long[dimension];
final long[] upper = new long[dimension];
Arrays.fill(upper, 1);
upper[X_DIMENSION] = width;
upper[Y_DIMENSION] = height;
final GridExtent gridExtent = new GridExtent(null, lower, upper, false);
final PixelInCell pixelInCell = pointInPixel == PixelOrientation.CENTER ? PixelInCell.CELL_CENTER : PixelInCell.CELL_CORNER;
gridGeometry = new GridGeometry(gridExtent, pixelInCell, gridToCRS, crs);
Map.Entry<Map<Integer, GridGeometry>, GridGeometry> entry = setCached(gridGeometry, gridGeometries, index);
gridGeometries = entry.getKey();
gridGeometry = entry.getValue();
}
return gridGeometry;
}
use of org.geotoolkit.image.io.SpatialImageReader in project geotoolkit by Geomatys.
the class ImageCoverageReader method read.
/**
* Converts geodetic parameters to image parameters, reads the image and wraps it in a
* grid coverage. First, this method creates an initially empty block of image parameters
* by invoking the {@link #createImageReadParam(int)} method. The image parameter
* {@linkplain ImageReadParam#setSourceRegion source region},
* {@linkplain ImageReadParam#setSourceSubsampling source subsampling} and
* {@linkplain ImageReadParam#setSourceBands source bands} are computed from the
* parameter given to this {@code read} method. Then, the following image parameters
* are set (if the image parameter class allows such settings):
* <p>
* <ul>
* <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
* setSampleConversionAllowed}({@linkplain SampleConversionType#REPLACE_FILL_VALUES
* REPLACE_FILL_VALUES}, true)</code> in order to allow the replacement of
* fill values by {@link Float#NaN NaN}.</li>
*
* <li><code>{@linkplain SpatialImageReadParam#setSampleConversionAllowed
* setSampleConversionAllowed}({@linkplain SampleConversionType#SHIFT_SIGNED_INTEGERS
* SHIFT_SIGNED_INTEGERS}, true)</code> if the sample dimensions declare an unsigned
* range of sample values.</li>
*
* <li><code>{@linkplain MosaicImageReadParam#setSubsamplingChangeAllowed
* setSubsamplingChangeAllowed}(true)</code> in order to allow {@link MosaicImageReader}
* to use a different resolution than the requested one. This is crucial from a
* performance point of view. Since the {@code GridCoverageReader} contract does not
* guarantee that the grid geometry of the returned coverage is the requested geometry,
* we are allowed to do that.</li>
* </ul>
* <p>
* Finally, the image is read and wrapped in a {@link GridCoverage2D} using the
* information provided by {@link #getGridGeometry(int)} and {@link #getSampleDimensions(int)}.
*
* /!\ If {@link org.geotoolkit.coverage.io.GridCoverageReadParam#setDeferred(boolean)} parameter is set to true, the
* returned coverage will rely on the current reader to cache it's data on the fly, so you CANNOT dispose of the current
* reader while your using the resulting coverage.
*
* @param param Optional parameters used to control the reading process, or {@code null}.
* @return The {@link GridCoverage} at the specified index.
* @throws IllegalStateException if the input source has not been set.
* @throws IndexOutOfBoundsException if the supplied index is out of bounds.
* @throws CoverageStoreException If an error occurs while reading the information from the input source.
* @throws CancellationException If {@link #abort()} has been invoked in an other thread during
* the execution of this method.
*
* @see ImageReader#read(int)
*/
public GridCoverage2D read(final GridCoverageReadParam param) throws DataStoreException, CancellationException {
final int index = 0;
final boolean loggingEnabled = isLoggable();
long fullTime = (loggingEnabled) ? System.nanoTime() : 0;
ignoreGridTransforms = !loggingEnabled;
/*
* Parameters check.
*/
abortRequested = false;
// Protect from changes.
final ImageReader imageReader = this.imageReader;
if (imageReader == null) {
throw new IllegalStateException(formatErrorMessage(Errors.Keys.NoImageInput));
}
GridGeometry gridGeometry = getGridGeometry();
final int gridDimensionX, gridDimensionY;
if (gridGeometry.getDimension() == 2) {
gridDimensionX = 0;
gridDimensionY = 1;
} else if (gridGeometry.isDefined(GridGeometry.EXTENT)) {
final int[] subSpace = gridGeometry.getExtent().getSubspaceDimensions(2);
gridDimensionX = subSpace[0];
gridDimensionY = subSpace[1];
} else
throw new DataStoreException("Cannot determine X and Y axis of the coverage");
checkAbortState();
final ImageReadParam imageParam;
try {
imageParam = createImageReadParam(0);
} catch (IOException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
final int[] srcBands;
final int[] dstBands;
MathTransform2D destToExtractedGrid = null;
boolean supportBandSelection = true;
if (param != null) {
srcBands = param.getSourceBands();
dstBands = param.getDestinationBands();
if (srcBands != null && dstBands != null && srcBands.length != dstBands.length) {
throw new IllegalArgumentException(Errors.getResources(locale).getString(Errors.Keys.MismatchedArrayLength_2, "sourceBands", "destinationBands"));
}
/*
* Convert geodetic envelope and resolution to pixel coordinates.
* Store the result of the above conversions in the ImageReadParam object.
*/
destToExtractedGrid = geodeticToPixelCoordinates(gridGeometry, param, imageParam, false);
/*
* Conceptually we could compute right now:
*
* AffineTransform change = new AffineTransform();
* change.translate(sourceRegion.x, sourceRegion.y);
* change.scale(xSubsampling, ySubsampling);
*
* However this implementation will scale only after the image has been read,
* because the MosaicImageReader may have changed the subsampling to more
* efficient values if it was authorized to make such change.
*/
if (srcBands != null) {
// -- particulaity case for tiff image reader which does not support setbands indexes.
try {
imageParam.setSourceBands(srcBands);
} catch (UnsupportedOperationException ex) {
LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any source bands.");
supportBandSelection = false;
}
}
if (dstBands != null) {
try {
imageParam.setDestinationBands(dstBands);
} catch (UnsupportedOperationException ex) {
LOGGER.log(Level.WARNING, ex.getMessage() + "Read coverage without set any destination bands.");
}
}
} else {
srcBands = null;
dstBands = null;
}
/*
* Next, check if we should allow the image reader to add an offset to signed intergers
* in order to make them unsigned. We will allow such offset if the SampleDimensions
* declare unsigned range of sample values.
*/
boolean usePaletteFactory = false;
final SampleDimension[] bands = getSampleDimensions(index, supportBandSelection ? srcBands : null, supportBandSelection ? dstBands : null);
if (imageParam instanceof SpatialImageReadParam) {
final SpatialImageReadParam sp = (SpatialImageReadParam) imageParam;
if (!isRangeSigned(bands)) {
sp.setSampleConversionAllowed(SampleConversionType.SHIFT_SIGNED_INTEGERS, true);
}
sp.setSampleConversionAllowed(SampleConversionType.REPLACE_FILL_VALUES, true);
/*
* If the image does not have its own color palette, provides a palette factory
* which will create the IndexColorModel (if needed) from the SampleDimension.
*/
if (bands != null && imageReader instanceof SpatialImageReader)
try {
usePaletteFactory = !((SpatialImageReader) imageReader).hasColors(index);
} catch (IOException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
/*
* If there is supplemental dimensions (over the usual 2 dimensions) and the subclass
* implementation did not defined explicitely some dimension slices, then convert the
* envelope bounds in those supplemental dimensions to slices index.
*
* TODO: there is some duplication between this code and the work done in the parent
* class. We need to refactor geodeticToPixelCoordinates(…) in a new helper
* class for making easier to divide the work in smaller parts.
*/
if (param != null && !sp.hasDimensionSlices()) {
final int gridDim = gridGeometry.getDimension();
if (gridDim > 2) {
// max(X_DIMENSION, Y_DIMENSION) + 1
final CoordinateReferenceSystem crs = gridGeometry.getCoordinateReferenceSystem();
final int geodeticDim = crs.getCoordinateSystem().getDimension();
if (geodeticDim > 2) {
Envelope envelope = param.getEnvelope();
if (envelope != null && envelope.getDimension() > 2)
try {
if (crs instanceof CompoundCRS) {
envelope = CRSUtilities.appendMissingDimensions(envelope, (CompoundCRS) crs);
}
envelope = Envelopes.transform(envelope, crs);
final double[] median = new double[geodeticDim];
for (int i = 0; i < geodeticDim; i++) {
median[i] = envelope.getMedian(i);
}
final double[] indices = new double[gridDim];
gridGeometry.getGridToCRS(PixelInCell.CELL_CENTER).inverse().transform(median, 0, indices, 0, 1);
final GridExtent gridExtent;
if (crs instanceof GridGeometry) {
gridExtent = ((GridGeometry) crs).getExtent();
} else {
// We can not fallback on gridGeometry.getExtent(), because
// GridGeometry2D contract forces all extra dimensions to have
// a span of 1.
gridExtent = null;
}
for (int i = 0; i < gridDim; i++) {
if (i != gridDimensionX && i != gridDimensionY) {
final double sliceIndex = indices[i];
if (!Double.isNaN(sliceIndex)) {
final DimensionSlice slice = sp.newDimensionSlice();
slice.addDimensionId(i);
slice.setSliceIndex((int) Math.round(Math.max(gridExtent != null ? gridExtent.getLow(i) : Integer.MIN_VALUE, Math.min(gridExtent != null ? gridExtent.getHigh(i) : Integer.MAX_VALUE, sliceIndex))));
}
}
}
} catch (TransformException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
}
}
}
}
}
checkAbortState();
/*
* Read the image using the ImageReader.read(...) method. We could have used
* ImageReader.readAsRenderedImage(...) instead in order to give the reader a
* chance to return a tiled image, but experience with some formats suggests
* that it requires to keep the ImageReader with its input stream open.
*/
final String name;
RenderedImage image;
try {
if (usePaletteFactory) {
SampleDimensionPalette.BANDS.set(bands);
((SpatialImageReadParam) imageParam).setPaletteFactory(SampleDimensionPalette.FACTORY);
}
if (param != null && param.isDeferred()) {
image = new LargeRenderedImage(imageReader.getOriginatingProvider(), imageParam, imageReader.getInput(), index, null);
} else {
image = imageReader.read(index, imageParam);
}
} catch (IOException | IllegalArgumentException e) {
throw new CoverageStoreException(formatErrorMessage(e), e);
} finally {
if (usePaletteFactory) {
SampleDimensionPalette.BANDS.remove();
}
}
/*
* If the grid geometry changed as a result of subsampling or reading a smaller region,
* update the grid geometry. The (xmin, ymin) values are usually (0,0), but we take
* them in account anyway as a paranoiac safety (a previous version of this code used
* the 'readAsRenderedImage(...)' method, which could have shifted the image).
*/
if (param != null) {
final Rectangle sourceRegion = imageParam.getSourceRegion();
final AffineTransform change = AffineTransform.getTranslateInstance(sourceRegion.x, sourceRegion.y);
change.scale(imageParam.getSourceXSubsampling(), imageParam.getSourceYSubsampling());
final int xmin = image.getMinX();
final int ymin = image.getMinY();
final int xi = gridDimensionX;
final int yi = gridDimensionY;
final MathTransform gridToCRS = gridGeometry.getGridToCRS(PixelInCell.CELL_CORNER);
MathTransform newGridToCRS = gridToCRS;
if (!change.isIdentity()) {
final int gridDimension = gridToCRS.getSourceDimensions();
final Matrix matrix = Matrices.createIdentity(gridDimension + 1);
matrix.setElement(xi, xi, change.getScaleX());
matrix.setElement(yi, yi, change.getScaleY());
matrix.setElement(xi, gridDimension, change.getTranslateX() - xmin);
matrix.setElement(yi, gridDimension, change.getTranslateY() - ymin);
newGridToCRS = MathTransforms.concatenate(MathTransforms.linear(matrix), gridToCRS);
}
final GridExtent gridExtent = gridGeometry.getExtent();
final long[] low = GridGeometryIterator.getLow(gridExtent);
final long[] high = GridGeometryIterator.getHigh(gridExtent);
low[xi] = xmin;
high[xi] = xmin + image.getWidth() - 1;
low[yi] = ymin;
high[yi] = ymin + image.getHeight() - 1;
if (imageParam instanceof SpatialImageReadParam) {
for (final DimensionSlice slice : ((SpatialImageReadParam) imageParam).getDimensionSlices()) {
for (final Object id : slice.getDimensionIds()) {
if (id instanceof Integer) {
final int dim = (Integer) id;
low[dim] = high[dim] = slice.getSliceIndex();
}
}
}
}
final GridExtent newGridRange = new GridExtent(null, low, high, true);
if (newGridToCRS != gridToCRS || !newGridRange.equals(gridExtent)) {
gridGeometry = new GridGeometry(newGridRange, PixelInCell.CELL_CORNER, newGridToCRS, gridGeometry.getCoordinateReferenceSystem());
}
}
final GridCoverage2D coverage = new org.apache.sis.coverage.grid.GridCoverage2D(gridGeometry, Arrays.asList(bands), image);
if (loggingEnabled) {
fullTime = System.nanoTime() - fullTime;
final Level level = getLogLevel(fullTime);
if (LOGGER.isLoggable(level)) {
ImageCoverageStore.logOperation(level, locale, ImageCoverageReader.class, false, input, index, coverage, null, null, destToExtractedGrid, fullTime);
}
}
return coverage;
}
Aggregations