/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.coverage.io.hdf4;
import java.awt.Rectangle;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReadParam;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.io.CoverageCapabilities;
import org.geotools.coverage.io.CoverageSource;
import org.geotools.coverage.io.domain.RasterLayout;
import org.geotools.coverage.io.driver.DefaultFileDriver;
import org.geotools.coverage.io.impl.CoverageReadRequest;
import org.geotools.coverage.io.impl.CoverageResponse;
import org.geotools.coverage.io.impl.CoverageResponse.Status;
import org.geotools.coverage.io.metadata.MetadataNode;
import org.geotools.coverage.io.range.FieldType;
import org.geotools.coverage.io.range.RangeType;
import org.geotools.coverage.io.util.Utilities;
import org.geotools.data.Parameter;
import org.geotools.data.ResourceInfo;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.imageio.AbstractSliceDescriptor;
import org.geotools.imageio.SliceDescriptor;
import org.geotools.referencing.CRS;
import org.geotools.util.NumberRange;
import org.opengis.coverage.SampleDimension;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.type.Name;
import org.opengis.geometry.BoundingBox;
import org.opengis.metadata.extent.Extent;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.temporal.TemporalGeometricPrimitive;
import org.opengis.util.ProgressListener;
public class HDF4Source implements CoverageSource {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(HDF4Source.class.toString());
private class HDF4RasterDatasetDomainManager {
/**
* Implementation of temporal domain
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
private class HDF4TemporalDomain extends TemporalDomain{
private HDF4TemporalDomain() {
}
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
final CoordinateReferenceSystem crs = access.crsMap.get(HDF4Source.this.name);
return CRS.getTemporalCRS(crs);
}
@Override
public SortedSet<? extends TemporalGeometricPrimitive> getTemporalElements(final ProgressListener listener) throws IOException {
if(listener!=null)
listener.started();
if(listener!=null&&listener.isCanceled())
return new TreeSet<TemporalGeometricPrimitive>();
try{
final TreeSet<TemporalGeometricPrimitive> retVal = new TreeSet<TemporalGeometricPrimitive>(access.temporalExtentMap.get(name));
return retVal;
}catch (Throwable e) {
listener.exceptionOccurred(e);
return new TreeSet<TemporalGeometricPrimitive>();
}
finally{
if(listener!=null)
listener.complete();
}
}
}
private class HDF4VerticalDomain extends VerticalDomain{
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
final CoordinateReferenceSystem crs = access.crsMap.get(HDF4Source.this.name);
return CRS.getVerticalCRS(crs);
}
@Override
public SortedSet<? extends NumberRange<Double>> getVerticalElements(boolean overall, ProgressListener listener)
throws IOException {
if(listener!=null)
listener.started();
if(listener!=null&&listener.isCanceled())
return new TreeSet<NumberRange<Double>>();
// try{
// final TreeSet<NumberRange<Double>> retVal = new TreeSet<NumberRange<Double>>(access.verticalExtentMap.get(name));
// return retVal;
// }catch (Throwable e) {
// listener.exceptionOccurred(e);
// return new TreeSet<NumberRange<Double>>();
return new TreeSet<NumberRange<Double>>();
// }
// finally{
// if(listener!=null)
// listener.complete();
// }
}
}
private class HDF4HorizontalDomain extends HorizontalDomain{
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem2D() {
final CoordinateReferenceSystem crs = access.crsMap.get(HDF4Source.this.name);
return CRS.getHorizontalCRS(crs);
}
@Override
public MathTransform2D getGridToWorldTransform(ProgressListener listener) throws IOException {
if(listener!=null)
listener.started();
if(listener!=null&&listener.isCanceled())
return null;
try{
final MathTransform2D g2w=access.gridGeometry2DMap.get(HDF4Source.this.name).getGridToCRS2D(PixelOrientation.CENTER);
return g2w;
}catch (Throwable e) {
listener.exceptionOccurred(e);
return null;
}
finally{
if(listener!=null)
listener.complete();
}
}
@Override
public Set<? extends RasterLayout> getRasterElements(boolean overall, ProgressListener listener)
throws IOException {
if(listener!=null)
listener.started();
if(listener!=null&&listener.isCanceled())
return null;
try{
final Rectangle bounds=access.gridGeometry2DMap.get(HDF4Source.this.name).getGridRange2D().getBounds();
final RasterLayout rl= new RasterLayout(bounds);
return Collections.singleton(rl);
}catch (Throwable e) {
listener.exceptionOccurred(e);
return null;
}
finally{
if(listener!=null)
listener.complete();
}
}
@Override
public Set<? extends BoundingBox> getSpatialElements(boolean overall, ProgressListener listener)
throws IOException {
if(listener!=null)
listener.started();
if(listener!=null&&listener.isCanceled())
return null;
try{
return Collections.singleton(new ReferencedEnvelope((org.opengis.geometry.Envelope)access.gridGeometry2DMap.get(HDF4Source.this.name).getEnvelope2D()));
}catch (Throwable e) {
listener.exceptionOccurred(e);
return null;
}
finally{
if(listener!=null)
listener.complete();
}
}
}
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return access.crsMap.get(HDF4Source.this.name);
}
public Extent getExtent() {
return null;
}
public TemporalDomain getTemporalDomain() throws IOException {
final TemporalDomain td= new HDF4TemporalDomain();
return td;
}
public VerticalDomain getVerticalDomain() throws IOException {
final VerticalDomain vd= new HDF4VerticalDomain();
return vd;
}
public HorizontalDomain getHorizontalDomain() throws IOException {
final HorizontalDomain hd= new HDF4HorizontalDomain();
return hd;
}
}
private HDF4Access access;
private Name name;
private final static EnumSet<CoverageCapabilities> capabilities;
static {
capabilities = EnumSet.of(
CoverageCapabilities.READ_HORIZONTAL_DOMAIN_SUBSAMBLING,
CoverageCapabilities.READ_RANGE_SUBSETTING,
CoverageCapabilities.READ_REPROJECTION,
CoverageCapabilities.READ_SUBSAMPLING);
}
private GridCoverageFactory coverageFactory;
private HDF4RasterDatasetDomainManager domainManager;
public synchronized void dispose() {
this.access = null;
this.name = null;
}
HDF4Source(final HDF4Access access, final Name name) {
this.access = access;
this.name = name;
// TODO: Take hints from somewhere
coverageFactory = CoverageFactoryFinder.getGridCoverageFactory(null);
// create the domain manager
this.domainManager= new HDF4RasterDatasetDomainManager();
}
public ResourceInfo getInfo(ProgressListener listener) {
ensureNotDisposed();
return null;
}
public String[] getMetadataNames(ProgressListener listener)
throws IOException {
ensureNotDisposed();
return null;
}
public String getMetadataValue(String name, ProgressListener listener)
throws IOException {
ensureNotDisposed();
return null;
}
public Name getName(ProgressListener listener) {
ensureNotDisposed();
return name;
}
private boolean isDisposed() {
return name == null;
}
private void ensureNotDisposed() {
if (isDisposed())
throw new IllegalStateException("Disposed");
}
public CoverageResponse read(CoverageReadRequest request, ProgressListener listener) throws IOException {
ensureNotDisposed();
// //
//
// Checking the request and filling the missing fields
//
// //
checkRequest(request);
final BoundingBox requestedBoundingBox = request.getGeographicArea();
final Rectangle requestedRasterArea = request.getRasterArea();
final Set<TemporalGeometricPrimitive> temporalSubset = request.getTemporalSubset();
final Set<NumberRange<Double>> verticalSubset = request.getVerticalSubset();
final RangeType range = request.getRangeSubset();
final Set<FieldType> fieldTypes = range.getFieldTypes();
final boolean useJAI = false;
final CoverageResponse response = new CoverageResponse();
response.setRequest(request);
final Rectangle sourceRasterRegion = new Rectangle();
GeneralEnvelope adjustedRequestedEnvelope2D;
final GeneralEnvelope requestedEnvelope2D = new GeneralEnvelope(requestedBoundingBox);
final MathTransform2D grid2WorldTransform = request.getGridToWorldTransform();
final ImageReadParam imageReadParam = new ImageReadParam();
try {
// //
//
// Set envelope and source region
//
// //
adjustedRequestedEnvelope2D = Utilities.evaluateRequestedParams(
access.gridGeometry2DMap.get(this.name).getGridRange(),
access.baseEnvelope2DMap.get(this.name),
access.spatialReferenceSystem2DMap.get(this.name),
access.raster2ModelMap.get(this.name), requestedEnvelope2D,
sourceRasterRegion, requestedRasterArea,
grid2WorldTransform, access.wgs84BaseEnvelope2DMap.get(this.name));
// //
//
// Set specific imageIO parameters: type of read operation,
// imageReadParams
//
// //
if (adjustedRequestedEnvelope2D != null) {
final GeneralEnvelope req = (adjustedRequestedEnvelope2D.isEmpty()) ? requestedEnvelope2D
: adjustedRequestedEnvelope2D;
Utilities.setReadParameters(null /* OverviewPolicy */,
imageReadParam, req, requestedRasterArea,
access.highestResMap.get(this.name),
access.gridGeometry2DMap.get(this.name).getGridRange(),
PixelInCell.CELL_CORNER);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
adjustedRequestedEnvelope2D = null;
} catch (TransformException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
adjustedRequestedEnvelope2D = null;
}
if (adjustedRequestedEnvelope2D != null && sourceRasterRegion != null && !sourceRasterRegion.isEmpty()) {
imageReadParam.setSourceRegion(sourceRasterRegion);
}
// A transformation is requested in case the requested envelope has been
// adjusted
final boolean needTransformation = (adjustedRequestedEnvelope2D != null && !adjustedRequestedEnvelope2D.isEmpty());
// In case the adjusted requested envelope is null, no intersection
// between requested envelope and base envelope have been found. Hence,
// no valid coverage will be loaded and the request should be considered
// as producing an empty result.
final boolean emptyRequest = adjustedRequestedEnvelope2D == null;
// //
//
// adding GridCoverages to the results list
//
// //
for (FieldType fieldType : fieldTypes) {
final Name name = fieldType.getName();
// TODO: Fix this leveraging on the proper Band
Set<SampleDimension> sampleDims = fieldType.getSampleDimensions();
if (sampleDims == null || sampleDims.isEmpty()) {
final RangeType innerRange = access.rangeMap.get(this.name);
if (innerRange != null) {
final FieldType ft = innerRange.getFieldType(name.getLocalPart());
if (ft != null)
sampleDims = ft.getSampleDimensions();
}
}
final GridSampleDimension[] sampleDimensions = sampleDims.toArray(new GridSampleDimension[sampleDims.size()]);
final Collection<SliceDescriptor> sliceDescriptors = access.sliceDescriptorsMap.values();
for (SliceDescriptor sd : sliceDescriptors) {
if (name.getLocalPart().equals(sd.getElementName())) {
// //
//
// Has Time?
//
// //
if (!temporalSubset.isEmpty()) {
for (TemporalGeometricPrimitive time : temporalSubset) {
TemporalGeometricPrimitive sdTime = sd.getTemporalExtent();
if (isTimeAccepted(time, sdTime)) {
addCoverage(response, verticalSubset, sd, needTransformation, emptyRequest,
useJAI, imageReadParam, sampleDimensions);
}
}
} else {
addCoverage(response, verticalSubset, sd, needTransformation, emptyRequest, useJAI,
imageReadParam, sampleDimensions);
}
}
}
}
response.setStatus(Status.SUCCESS);
return response;
}
private void addCoverage(final CoverageResponse response, final Set<NumberRange<Double>> verticalSubset,
final SliceDescriptor sd, final boolean needTransformation, final boolean emptyRequest,
final boolean useJAI, final ImageReadParam imageReadParam, final GridSampleDimension[] sampleDimensions) throws IOException {
// if (!verticalSubset.isEmpty()) {
// for (NumberRange<Double> envelope : verticalSubset) {
// VerticalExtent ve = sd.getVerticalExtent();
// if (ve != null) {
// NumberRange<Double> verticalEnvelope = NumberRange.create(ve.getMinimumValue().doubleValue(), ve.getMaximumValue().doubleValue());
// if (verticalEnvelope.contains(envelope)) {// TODO check this, it might not be correct
// final int imageIndex = ((AbstractSliceDescriptor) sd).getImageIndex();
// GridCoverage gc = Utilities.compute(BaseFileDriver
// .urlToFile(access.getInput()), imageIndex,
// needTransformation, emptyRequest, useJAI,
// imageReadParam, false, sampleDimensions, HDF4Driver.spi, this.name.toString(), coverageFactory, access.raster2ModelMap.get(this.name),
// access.spatialReferenceSystem2DMap.get(this.name), access.coverageEnvelope2DMap.get(this.name));
// if (gc != null)
// response.addResult(gc);
// }
// }
// }
// } else {
final int imageIndex = ((AbstractSliceDescriptor) sd)
.getImageIndex();
GridCoverage gc = Utilities.compute(DefaultFileDriver.urlToFile(access
.getInput()), imageIndex, needTransformation, emptyRequest,
useJAI, imageReadParam, false, sampleDimensions, HDF4Driver.spi, this.name.toString(), coverageFactory, access.raster2ModelMap.get(this.name),
access.spatialReferenceSystem2DMap.get(this.name), access.coverageEnvelope2DMap.get(this.name));
if (gc != null)
response.addResult(gc);
// }
}
private void checkRequest(CoverageReadRequest request) {
BoundingBox requestedBoundingBox = request.getGeographicArea();
// //
//
// Checking RequestedRasterArea setting
//
// //
Rectangle requestedRasterArea = request.getRasterArea();
if (requestedRasterArea == null || requestedBoundingBox == null) {
if (requestedRasterArea == null) {
// requestedRasterArea =
// access.coverageGeometries.get(access.coverageNames.get(0))
// .getGridRange2D().getBounds();
requestedRasterArea = access.gridGeometry2DMap.get(this.name)
.getGridRange2D().getBounds();
}
if (requestedBoundingBox == null) {
requestedBoundingBox = (BoundingBox) new ReferencedEnvelope(access.gridGeometry2DMap.get(this.name).getEnvelope());
}
request.setDomainSubset(requestedRasterArea, ReferencedEnvelope.reference(requestedBoundingBox));
}
// //
//
// Checking TemporalSubset setting
//
// //
SortedSet<TemporalGeometricPrimitive> temporalSubset = request.getTemporalSubset();
if (temporalSubset.isEmpty()) {
Set<TemporalGeometricPrimitive> temporalExtent = access.temporalExtentMap.get(this.name);
temporalSubset = new TreeSet<TemporalGeometricPrimitive>(temporalExtent);
request.setTemporalSubset(temporalSubset);
}
// // //
// //
// // Checking VerticalSubset setting
// //
// // //
// Set<NumberRange<Double>> verticalSubset = request.getVerticalSubset();
// if (verticalSubset.isEmpty()) {
// Set<NumberRange<Double>> verticalExtent = access.verticalExtentMap.get(this.name);
// if (verticalExtent != null)
// verticalSubset = new HashSet<NumberRange<Double>>(verticalExtent);
// request.setVerticalSubset(verticalSubset);
// } else {
// final Set<NumberRange<Double>> availableSet = access.verticalExtentMap.get(this.name);
// final NumberRange<Double> requestedEnvelope = verticalSubset.iterator().next();
// if (!availableSet.contains(requestedEnvelope)) {//TODO is this correct?
// // TODO: Actually, support for a single vertical element search
// // Find the nearest vertical Envelope
// NumberRange<Double> nearestEnvelope = availableSet.iterator().next();
//
// double minimumDistance = Math.abs(nearestEnvelope.getMinimum()- requestedEnvelope.getMinimum());
// for (NumberRange<Double> env : availableSet) {
// double distance = Math.abs(env.getMinimum()- requestedEnvelope.getMinimum());
// if (distance < minimumDistance) {
// nearestEnvelope = env;
// minimumDistance = distance;
// }
// }
// verticalSubset = new HashSet<NumberRange<Double>>(1);
// verticalSubset.add(nearestEnvelope);
// request.setVerticalSubset(verticalSubset);
// }
// }
// //
//
// Checking RangeSubset setting
//
// //
RangeType range = request.getRangeSubset();
if (range == null)
request.setRangeSubset(access.rangeMap.get(this.name));
}
public EnumSet<CoverageCapabilities> getCapabilities() {
return EnumSet.copyOf(capabilities);
}
public Map<String, Parameter<?>> getReadParameterInfo() {
return null;
}
public RangeType getRangeType(ProgressListener listener) throws IOException {
ensureNotDisposed();
return access.rangeMap.get(this.name);
}
/**
* Move these methods to an Utility Class and improve the logic.
*
* @param first
* @param second
* @return
*/
private boolean isTimeAccepted(final TemporalGeometricPrimitive first, final TemporalGeometricPrimitive second) {
boolean takeThis = Utilities.contains(first, second);
if (!takeThis)
takeThis = Utilities.contains(second, first);
return takeThis;
}
public MetadataNode getMetadata(String metadataDomain,
ProgressListener listener) {
return null;
}
public Set<Name> getMetadataDomains() {
return null;
}
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
return this.domainManager.getCoordinateReferenceSystem();
}
public Extent getExtent() {
return null;
}
public HorizontalDomain getHorizontalDomain() throws IOException {
return this.domainManager.getHorizontalDomain();
}
public TemporalDomain getTemporalDomain() throws IOException {
return domainManager.getTemporalDomain();
}
public VerticalDomain getVerticalDomain() throws IOException {
return this.domainManager.getVerticalDomain();
}
public List<? extends RasterLayout> getOverviewsLayouts(ProgressListener listener)
throws IOException {
return Collections.emptyList();
}
public int getOverviewsNumber(ProgressListener listener) throws IOException {
return 0;
}
}