use of org.opengis.metadata.spatial.Georectified in project geotoolkit by Geomatys.
the class AsciiGridWriter method prepareHeader.
/**
* Fills the given {@code header} map with values extracted from the given image metadata.
* The {@code "NCOLS"} and {@code "NROWS"} attributes are already defined when this method
* is invoked. This method is responsible for filling the remaining attributes.
*
* @param metadata The metadata.
* @param header The map in which to store the (<var>key</var>, <var>value</var>) pairs
* to be written.
* @return The fill value, or {@code Double#NaN} if none.
* @throws IOException If the metadata can not be prepared.
*/
private String prepareHeader(final SpatialMetadata metadata, final Map<String, String> header, final ImageWriteParam param) throws IOException {
final MetadataHelper helper = new MetadataHelper(this);
final Georectified spatialRp = metadata.getInstanceForType(Georectified.class);
final RectifiedGrid domain = metadata.getInstanceForType(RectifiedGrid.class);
final PixelOrientation ptInPixel = (spatialRp != null) ? spatialRp.getPointInPixel() : null;
final AffineTransform gridToCRS = helper.getAffineTransform(domain, param);
String xll = "XLLCORNER";
String yll = "YLLCORNER";
// reverted (i.e. the corresponding value in OffsetVectors is negative).
if (ptInPixel != null && !ptInPixel.equals(PixelOrientation.UPPER_LEFT)) {
if (ptInPixel.equals(PixelOrientation.CENTER)) {
xll = "XLLCENTER";
yll = "YLLCENTER";
} else if (ptInPixel.equals(PixelOrientation.valueOf("UPPER"))) {
yll = "YLLCENTER";
} else if (ptInPixel.equals(PixelOrientation.valueOf("LEFT"))) {
xll = "XLLCENTER";
} else {
throw new ImageMetadataException(Warnings.message(this, Errors.Keys.IllegalParameterValue_2, "pointInPixel", ptInPixel));
}
}
header.put(xll, String.valueOf(gridToCRS.getTranslateX()));
header.put(yll, String.valueOf(gridToCRS.getTranslateY()));
/*
* Use the CELLSIZE attribute if the pixels are square, or the DX, DY attibutes
* if they are rectangular and we are allowed to use those non-standard attributes.
*/
try {
header.put("CELLSIZE", String.valueOf(helper.getCellSize(gridToCRS)));
} catch (IIOException e) {
final Dimension2D size;
if (strictCellSize || (size = helper.getCellDimension(gridToCRS)) == null) {
throw e;
}
Warnings.log(this, null, AsciiGridWriter.class, "writeHeader", e);
header.put("DX", String.valueOf(size.getWidth()));
header.put("DY", String.valueOf(size.getHeight()));
}
/*
* Get the fill sample value, which is optional. The default defined by
* the ASCII grid format is -9999.
*/
String fillValue = DEFAULT_FILL;
final List<SampleDimension> dimensions = metadata.getListForType(SampleDimension.class);
if (!isNullOrEmpty(dimensions)) {
final SampleDimension dim = dimensions.get(0);
if (dim != null) {
final double[] fillValues = dim.getFillSampleValues();
if (fillValues != null && fillValues.length != 0) {
final double value = fillValues[0];
if (!Double.isNaN(value)) {
fillValue = CharSequences.trimFractionalPart(String.valueOf(value)).toString();
header.put("NODATA_VALUE", fillValue);
}
}
}
}
return fillValue;
}
use of org.opengis.metadata.spatial.Georectified 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.opengis.metadata.spatial.Georectified 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.opengis.metadata.spatial.Georectified in project geotoolkit by Geomatys.
the class GeoTiffMetaDataWriter method fillMetadata.
/**
* Complete the TIFF metadata tree with geotiff informations.
*/
public void fillMetadata(Node tiffTree, final SpatialMetadata spatialMD) throws ImageMetadataException, IOException, FactoryException, TransformException {
ArgumentChecks.ensureNonNull("tiffTree", tiffTree);
ArgumentChecks.ensureNonNull("spatialMD", spatialMD);
// container for informations which will be written
final GeoTiffMetaDataStack stack = new GeoTiffMetaDataStack(tiffTree);
// fill geotiff crs information
final CoordinateReferenceSystem coverageCRS = spatialMD.getInstanceForType(CoordinateReferenceSystem.class);
final GeoTiffCRSWriter crsWriter = new GeoTiffCRSWriter();
crsWriter.fillCRSMetaDatas(stack, CRSUtilities.getCRS2D(coverageCRS));
// fill the transformation information
final RectifiedGrid domain = spatialMD.getInstanceForType(RectifiedGrid.class);
AffineTransform gridToCrs = MetadataHelper.INSTANCE.getAffineTransform(domain, null);
// readjust gridToCRS to be the pixel corner
final Georectified georect = spatialMD.getInstanceForType(Georectified.class);
final CellGeometry cell = georect.getCellGeometry();
PixelOrientation orientation = georect.getPointInPixel();
if (orientation == null)
orientation = PixelOrientation.CENTER;
if (CellGeometry.POINT.equals(cell)) {
stack.addShort(GTRasterTypeGeoKey, RasterPixelIsPoint);
if (!orientation.equals(PixelOrientation.CENTER)) {
AffineTransform2D trs = new AffineTransform2D(gridToCrs);
gridToCrs = (AffineTransform) PixelTranslation.translate(trs, orientation, PixelOrientation.CENTER, 0, 1);
}
} else {
// consider all other as Area
stack.addShort(GTRasterTypeGeoKey, RasterPixelIsArea);
if (!orientation.equals(PixelOrientation.UPPER_LEFT)) {
AffineTransform2D trs = new AffineTransform2D(gridToCrs);
gridToCrs = (AffineTransform) PixelTranslation.translate(trs, orientation, PixelOrientation.UPPER_LEFT, 0, 1);
}
}
// -- find a date from crs
final int tempOrdinate = getTemporalOrdinate(coverageCRS);
if (tempOrdinate >= 0) {
// -- add temporal tag
final GridDomainAccessor gda = new GridDomainAccessor(spatialMD);
final double[] origin = gda.getAttributeAsDoubles("origin", false);
final double date = origin[tempOrdinate];
final Date dat = DefaultTemporalCRS.castOrCopy(CommonCRS.Temporal.JAVA.crs()).toDate(date);
stack.setDate(dat);
}
fillTransform(stack, gridToCrs, domain.getExtent());
// fill NoData values
fillSampleDimensionProperties(stack, spatialMD);
// write in the metadata tree
stack.flush();
}
Aggregations