use of org.geotoolkit.image.io.metadata.SpatialMetadata in project geotoolkit by Geomatys.
the class AsciiGridReaderTest method testMetadata.
/**
* Tests the metadata of the {@link "grid.asc"} file.
*
* @throws IOException if an error occurred while reading the file.
*/
@Test
public void testMetadata() throws IOException {
prepareImageReader(true);
assertEquals(20, reader.getWidth(0));
assertEquals(42, reader.getHeight(0));
assertNull(reader.getStreamMetadata());
final SpatialMetadata metadata = (SpatialMetadata) reader.getImageMetadata(0);
assertNotNull(metadata);
assertMultilinesEquals(decodeQuotes(GEOTK_FORMAT_NAME + '\n' + "├───RectifiedGridDomain\n" + "│ ├───origin=“-9500.0 20500.0”\n" + "│ ├───OffsetVectors\n" + "│ │ ├───OffsetVector\n" + "│ │ │ └───values=“1000.0 0.0”\n" + "│ │ └───OffsetVector\n" + "│ │ └───values=“0.0 -1000.0”\n" + "│ └───Limits\n" + "│ ├───low=“0 0”\n" + "│ └───high=“19 41”\n" + "├───SpatialRepresentation\n" + "│ ├───numberOfDimensions=“2”\n" + "│ ├───centrePoint=“0.0 0.0”\n" + "│ └───pointInPixel=“center”\n" + "└───ImageDescription\n" + " └───Dimensions\n" + " └───Dimension\n" + " ├───minValue=“-1.893”\n" + " ├───maxValue=“31.14”\n" + " └───fillSampleValues=“-9999.0”\n"), metadata.toString());
/*
* Forces a scan of pixel values and test again.
*/
final DimensionAccessor helper = new DimensionAccessor(metadata);
assertFalse("Pixels scan should not be needed.", helper.isScanSuggested(reader, 0));
// Scan anyway, even if the above returned 'false'.
metadata.setReadOnly(false);
helper.scanValidSampleValue(reader, 0);
assertFalse("Pixels scan should not be needed.", helper.isScanSuggested(reader, 0));
assertMultilinesEquals(decodeQuotes(GEOTK_FORMAT_NAME + '\n' + "├───RectifiedGridDomain\n" + "│ ├───origin=“-9500.0 20500.0”\n" + "│ ├───OffsetVectors\n" + "│ │ ├───OffsetVector\n" + "│ │ │ └───values=“1000.0 0.0”\n" + "│ │ └───OffsetVector\n" + "│ │ └───values=“0.0 -1000.0”\n" + "│ └───Limits\n" + "│ ├───low=“0 0”\n" + "│ └───high=“19 41”\n" + "├───SpatialRepresentation\n" + "│ ├───numberOfDimensions=“2”\n" + "│ ├───centrePoint=“0.0 0.0”\n" + "│ └───pointInPixel=“center”\n" + "└───ImageDescription\n" + " └───Dimensions\n" + " └───Dimension\n" + " ├───fillSampleValues=“-9999.0”\n" + " └───validSampleValues=“[-1.893 … 31.139999]”\n"), metadata.toString());
}
use of org.geotoolkit.image.io.metadata.SpatialMetadata in project geotoolkit by Geomatys.
the class TextRecordImageReaderTest method testMetadata.
/**
* Tests the metadata of the {@link "records.txt"} file.
*
* @throws IOException if an error occurred while reading the file.
*/
@Test
public void testMetadata() throws IOException {
prepareImageReader(true);
assertEquals(20, reader.getWidth(0));
assertEquals(42, reader.getHeight(0));
assertNull(reader.getStreamMetadata());
final SpatialMetadata metadata = (SpatialMetadata) reader.getImageMetadata(0);
assertNotNull(metadata);
assertMultilinesEquals(decodeQuotes(GEOTK_FORMAT_NAME + '\n' + "├───RectifiedGridDomain\n" + "│ ├───origin=“-19000.0 12690.0”\n" + "│ ├───OffsetVectors\n" + "│ │ ├───OffsetVector\n" + "│ │ │ └───values=“2000.0 0.0”\n" + "│ │ └───OffsetVector\n" + "│ │ └───values=“0.0 -619.0243902439024”\n" + "│ └───Limits\n" + "│ ├───low=“0 0”\n" + "│ └───high=“19 41”\n" + "├───SpatialRepresentation\n" + "│ ├───numberOfDimensions=“2”\n" + "│ ├───centrePoint=“0.0 0.0”\n" + "│ └───pointInPixel=“center”\n" + "└───ImageDescription\n" + " └───Dimensions\n" + " └───Dimension\n" + " ├───minValue=“-1.893”\n" + " ├───maxValue=“31.14”\n" + " └───fillSampleValues=“-9999.0”\n"), metadata.toString());
}
use of org.geotoolkit.image.io.metadata.SpatialMetadata in project geotoolkit by Geomatys.
the class ImageCoverageReader method getMetadata.
/**
* Returns the ISO 19115 metadata object associated with the input source as a whole
* and each coverages. The default implementation constructs the metadata from the
* {@linkplain #getStreamMetadata() stream metadata} and the
* {@linkplain #getCoverageMetadata(int) coverage metadata},
* eventually completed by the {@link #getGridGeometry(int)}.
* <p>
* Since the relationship between Image I/O metadata and ISO 19115 is not always a
* "<cite>one-to-one</cite>" relationship, this method works on a best effort basis.
*
* @return The ISO 19115 metadata (never {@code null}).
* @throws CoverageStoreException If an error occurs while reading the information from the input source.
*
* @see <a href="../../image/io/metadata/SpatialMetadataFormat.html#default-formats">Metadata formats</a>
*
* @since 3.18
*/
public Metadata getMetadata() throws DataStoreException {
final SpatialMetadata streamMetadata = getStreamMetadata();
final DefaultMetadata metadata = createMetadata(streamMetadata);
/*
* Extract all information available from the stream metadata, provided that metadata
* elements were not already provided by the above call to createMetadata(...). Since
* createMetadata(...) typically get its information from the stream metadata as well,
* we assume that creating here new objects from stream metadata would be redundant.
*/
DataIdentification identification = null;
if (streamMetadata != null) {
final Collection<DataQuality> quality = metadata.getDataQualityInfo();
if (quality.isEmpty()) {
addIfNonNull(quality, streamMetadata.getInstanceForType(DataQuality.class));
}
final Collection<AcquisitionInformation> acquisition = metadata.getAcquisitionInformation();
if (acquisition.isEmpty()) {
addIfNonNull(acquisition, streamMetadata.getInstanceForType(AcquisitionInformation.class));
}
/*
* Get the existing identification info if any, or create a new one otherwise.
* If an identification info is found, remove it from the metadata (it will be
* added back at the end of this method, or a copy of it will be added).
*/
final Iterator<Identification> it = metadata.getIdentificationInfo().iterator();
while (it.hasNext()) {
final Identification candidate = it.next();
if (candidate instanceof DataIdentification) {
identification = (DataIdentification) candidate;
it.remove();
break;
}
}
if (identification == null) {
identification = streamMetadata.getInstanceForType(DataIdentification.class);
}
}
/*
* Check if we should complete the extents and resolutions. We will do so only
* if the vertical/temporal extent, geographic bounding box and resolution are
* not already provided in the metadata. If the geographic extent is declared
* by an other kind of object than GeographicBoundingBox, we will still add the
* bounding box because the existing extent could be only a textual description.
*/
// For logging warning only once.
boolean failed = false;
// 'false' if extents are already present.
boolean computeExtents = true;
// 'false' is resolutions are already present.
boolean computeResolutions = true;
// The extent to compute, if needed.
DefaultExtent extent = null;
// The extents already provided in the metadata.
List<Extent> extents = null;
// The resolutions to compute, if needed.
Set<Resolution> resolutions = null;
if (identification != null) {
computeResolutions = isNullOrEmpty(identification.getSpatialResolutions());
final Collection<? extends Extent> existings = identification.getExtents();
if (!isNullOrEmpty(existings)) {
extents = new ArrayList<>(existings);
extent = UniqueExtents.getIncomplete(extents);
if (extent == null) {
// The plugin-provided Metadata instance seems to contain Extents
// that are complete enough, so we will not try to complete them.
computeExtents = false;
extents = null;
}
}
}
/*
* Check if we should complete the content info and the spatial representation info.
* If the plugin-provided metadata declare explicitly such information, we will not
* compute them in this method (the plugin information will have precedence).
*/
final Collection<ContentInformation> contentInfo = metadata.getContentInfo();
final Collection<SpatialRepresentation> spatialInfo = metadata.getSpatialRepresentationInfo();
final boolean computeContent = (contentInfo != null) && contentInfo.isEmpty();
final boolean computeSpatial = (spatialInfo != null) && spatialInfo.isEmpty();
if (computeContent || computeSpatial || computeResolutions || computeExtents) {
final GenericName coverageName = getCoverageName();
if (computeContent || computeSpatial) {
CoverageDescription ci = null;
final SpatialMetadata coverageMetadata = getCoverageMetadata();
if (coverageMetadata != null) {
if (computeContent) {
ci = coverageMetadata.getInstanceForType(ImageDescription.class);
if (ci != null) {
contentInfo.add(ci);
}
}
if (computeSpatial) {
final Georectified rectified = coverageMetadata.getInstanceForType(Georectified.class);
if (rectified != null) {
metadata.getSpatialRepresentationInfo().add(rectified);
}
}
}
/*
* Get or create the content info to store sample dimensions
*/
if (ci == null) {
// get or create it
if (contentInfo.size() > 0) {
CoverageDescription cd = contentInfo.stream().limit(1).filter(CoverageDescription.class::isInstance).map(CoverageDescription.class::cast).findFirst().orElse(null);
if (cd instanceof ModifiableMetadata && ((ModifiableMetadata) cd).state() != ModifiableMetadata.State.FINAL) {
ci = cd;
}
} else {
ci = new DefaultCoverageDescription();
contentInfo.add(ci);
}
}
if (ci != null && ci.getAttributeGroups() != null && ci.getAttributeGroups().isEmpty() && ci.getDimensions().isEmpty()) {
final List<SampleDimension> sampleDimensions = getSampleDimensions();
if (sampleDimensions != null) {
final MetadataBuilder mb = new MetadataBuilder();
for (int idx = 0, n = sampleDimensions.size(); idx < n; idx++) {
SampleDimension gsd = sampleDimensions.get(idx).forConvertedValues(true);
final Unit<? extends Quantity<?>> units = gsd.getUnits().orElse(null);
mb.newSampleDimension();
mb.setBandIdentifier(Names.createMemberName(null, null, "" + idx, Integer.class));
mb.addBandDescription(gsd.getName().toString());
if (units != null)
mb.setSampleUnits(units);
mb.addMinimumSampleValue(SampleDimensionUtils.getMinimumValue(gsd));
mb.addMaximumSampleValue(SampleDimensionUtils.getMaximumValue(gsd));
gsd = gsd.forConvertedValues(false);
gsd.getTransferFunctionFormula().ifPresent((f) -> {
mb.setTransferFunction(f.getScale(), f.getOffset());
});
}
final DefaultMetadata meta = mb.build();
final CoverageDescription imgDesc = (CoverageDescription) meta.getContentInfo().iterator().next();
ci.getAttributeGroups().addAll((Collection) imgDesc.getAttributeGroups());
}
}
}
if (computeResolutions || computeExtents) {
/*
* Resolution along the horizontal axes only, ignoring all other axes. For linear units (feet,
* kilometres, etc.), we convert the units to metres for compliance with a current limitation
* of Apache SIS, which can handle only metres. For angular resolution (typically in degrees),
* we perform an APPROXIMATE conversion to metres using the nautical mile definition. This
* conversion is only valid along the latitudes axis (the number is wrong along the longitude
* axis), and more accurate for mid-latitude (the numbers are differents close to equator or
* to the poles).
*/
final GridGeometry gg = getGridGeometry();
if (computeResolutions && gg.isDefined(GridGeometry.CRS)) {
double[] res = null;
try {
res = gg.getResolution(false);
} catch (IncompleteGridGeometryException ex) {
}
final Quantity<?> m = CRSUtilities.getHorizontalResolution(gg.getCoordinateReferenceSystem(), res);
if (m != null) {
double measureValue = m.getValue().doubleValue();
final Unit<?> unit = m.getUnit();
Unit<?> standardUnit = null;
double scaleFactor = 1;
if (Units.isAngular(unit)) {
standardUnit = Units.DEGREE;
// From definition of nautical miles.
scaleFactor = (1852 * 60);
} else if (Units.isLinear(unit)) {
standardUnit = Units.METRE;
}
if (standardUnit != null)
try {
measureValue = unit.getConverterToAny(standardUnit).convert(measureValue) * scaleFactor;
final DefaultResolution resolution = new DefaultResolution();
resolution.setDistance(measureValue);
if (resolutions == null) {
resolutions = new LinkedHashSet<>();
}
resolutions.add(resolution);
} catch (IncommensurableException e) {
// In case of failure, do not create a Resolution object.
Logging.recoverableException(LOGGER, ImageCoverageReader.class, "getMetadata", e);
}
}
}
/*
* Horizontal, vertical and temporal extents. The horizontal extents is
* represented as a geographic bounding box, which may require a reprojection.
*/
if (computeExtents && gg.isDefined(GridGeometry.ENVELOPE)) {
if (extent == null) {
extent = new UniqueExtents();
}
try {
extent.addElements(gg.getEnvelope());
} catch (TransformException e) {
// Not a big deal if we fail. We will just let the identification section unchanged.
if (!failed) {
// Log only once.
failed = true;
Logging.recoverableException(LOGGER, ImageCoverageReader.class, "getMetadata", e);
}
}
}
}
}
/*
* At this point, we have computed extents and resolutions from every images
* in the stream. Now store the result. Note that we unconditionally create
* a copy of the identification info, even if the original object was already
* an instance of DefaultDataIdentification, because the original object may
* be cached in the ImageReader.
*/
if (extent != null || resolutions != null) {
final DefaultDataIdentification copy = new DefaultDataIdentification(identification);
if (extent != null) {
if (extents != null) {
copy.setExtents(extents);
} else {
copy.getExtents().add(extent);
}
}
if (resolutions != null) {
copy.setSpatialResolutions(resolutions);
}
identification = copy;
}
if (identification != null) {
metadata.getIdentificationInfo().add(identification);
}
return metadata;
}
use of org.geotoolkit.image.io.metadata.SpatialMetadata 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.metadata.SpatialMetadata in project geotoolkit by Geomatys.
the class TiffImageReader method read.
/**
* Reads the image at the given index.
*
* @param imageIndex The index of the image to read.
* @param param Parameters used to control the reading process, or {@code null}.
* @return The image.
* @throws IOException If an error occurred while reading the image.
*/
@Override
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
checkLayers();
BufferedImage img = readLayer(getLayerIndex(imageIndex), param);
// check if we are in float or double type
final int dataType = img.getRaster().getDataBuffer().getDataType();
rewrite: if (DataBuffer.TYPE_FLOAT == dataType || DataBuffer.TYPE_DOUBLE == dataType) {
final SpatialMetadata sm = getImageMetadata(imageIndex);
final DimensionAccessor accessor = new DimensionAccessor(sm);
final List<SampleDimension> sampleDimensions = accessor.getSampleDimensions();
if (sampleDimensions == null || sampleDimensions.size() != 1)
break rewrite;
// check if we have a numeric noData value
final SampleDimension sd = sampleDimensions.get(0);
final double[] noDataValues = SampleDimensionUtils.getNoDataValues(sd);
if (noDataValues == null || noDataValues.length != 1)
break rewrite;
boolean replace = false;
for (double d : noDataValues) {
if (!Double.isNaN(d)) {
replace = true;
break;
}
}
if (!replace)
break rewrite;
final WritablePixelIterator writer = WritablePixelIterator.create(img);
while (writer.next()) {
double v = writer.getSampleDouble(0);
if (v == noDataValues[0]) {
writer.setSample(0, Double.NaN);
}
}
writer.close();
}
return img;
}
Aggregations