/*
* 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.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.measure.unit.Unit;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.io.CoverageAccess;
import org.geotools.coverage.io.CoverageSource;
import org.geotools.coverage.io.CoverageStore;
import org.geotools.coverage.io.driver.DefaultFileDriver;
import org.geotools.coverage.io.driver.Driver;
import org.geotools.coverage.io.hdf4.HDF4ProductFieldType.Product;
import org.geotools.coverage.io.impl.DefaultCoverageAccess;
import org.geotools.coverage.io.impl.range.DefaultRangeType;
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.DefaultServiceInfo;
import org.geotools.data.Parameter;
import org.geotools.data.ServiceInfo;
import org.geotools.factory.Hints;
import org.geotools.feature.NameImpl;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.imageio.SliceDescriptor;
import org.geotools.imageio.SpatioTemporalImageReader;
import org.geotools.imageio.metadata.Band;
import org.geotools.imageio.metadata.RectifiedGrid;
import org.geotools.imageio.metadata.SpatioTemporalMetadata;
import org.geotools.referencing.operation.transform.ProjectiveTransform;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.util.NullProgressListener;
import org.geotools.util.SimpleInternationalString;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.feature.type.Name;
import org.opengis.geometry.BoundingBox;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.temporal.TemporalGeometricPrimitive;
import org.opengis.util.InternationalString;
import org.opengis.util.ProgressListener;
/**
* {@link CoverageAccess} implementation for HDF4 Data format.
*
* @author Romagnoli Daniele, GeoSolutions
*
*
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/coverage-experiment/hdf4/src/main/java/org/geotools/coverage/io/hdf4/HDF4Access.java $
*/
public class HDF4Access extends DefaultCoverageAccess {
private final static Set<AccessType> allowedAccessTypes;
private final static Map<String, Parameter<?>> accessParameters;
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(HDF4Access.class.toString());
private int numberOfCoverages;
Map<Integer, SliceDescriptor> sliceDescriptorsMap = new HashMap<Integer, SliceDescriptor>();
Map<Integer, SpatioTemporalMetadata> spatioTemporalMetadataMap = new HashMap<Integer, SpatioTemporalMetadata>();
/**
* Name of this coverage data product.
*/
ArrayList<Name> coverageNames = new ArrayList<Name>();
private Map<String, Serializable> connectionParameters;
HashMap<Name, GeneralEnvelope> envelopesMap = new HashMap<Name, GeneralEnvelope>();
HashMap<Name, GeneralEnvelope> coverageEnvelope2DMap = new HashMap<Name, GeneralEnvelope>();
HashMap<BoundingBox, Name> coverages2DMap = new HashMap<BoundingBox, Name>();
HashMap<Name, CoordinateReferenceSystem> crsMap = new HashMap<Name, CoordinateReferenceSystem>();
HashMap<Name, MathTransform> raster2ModelMap = new HashMap<Name, MathTransform>();
HashMap<Name, Set<TemporalGeometricPrimitive>> temporalExtentMap = new HashMap<Name, Set<TemporalGeometricPrimitive>>();
// HashMap<Name, Set<NumberRange<Double>>> verticalExtentMap = new HashMap<Name, Set<NumberRange<Double>>>();
//
// HashMap <String, Name> verticalCRSMap = new HashMap <String, Name>();
/** Highest resolution available. */
HashMap<Name, double[]> highestResMap = new HashMap<Name, double[]>();
/** The base envelope 2D */
HashMap<Name, Envelope2D> baseEnvelope2DMap = new HashMap<Name, Envelope2D>();
HashMap<Name, BoundingBox> boundingBoxesMap = new HashMap<Name, BoundingBox>();
/** WGS84 envelope 2D for this coverage */
HashMap<Name, Envelope2D> wgs84BaseEnvelope2DMap = new HashMap<Name, Envelope2D>();
/** The CRS related to the base envelope 2D */
HashMap<Name, CoordinateReferenceSystem> spatialReferenceSystem2DMap = new HashMap<Name, CoordinateReferenceSystem>();
private URL input;
HashMap<Name, RangeType> rangeMap = new HashMap<Name, RangeType>();
HashMap<Name, GridGeometry2D> gridGeometry2DMap = new HashMap<Name, GridGeometry2D>();
static {
final Set<AccessType> types = new HashSet<AccessType>();
types.add(AccessType.READ_ONLY);
allowedAccessTypes = Collections.unmodifiableSet(types);
final Map<String, Parameter<?>> parameters = new HashMap<String, Parameter<?>>();
parameters.put(DefaultFileDriver.URL.key, DefaultFileDriver.URL);
accessParameters = Collections.unmodifiableMap(parameters);
}
HDF4Access(Driver driver, URL source, Map<String, Serializable> additionalParameters, Hints hints,
ProgressListener listener) throws IOException {
super(driver);
if (source == null) {
if (additionalParameters.containsKey(DefaultFileDriver.URL.key)) {
source = (URL) additionalParameters.get(DefaultFileDriver.URL.key);
}
}
if (source == null)
throw new IllegalArgumentException();
connectionParameters = new HashMap<String, Serializable>();
if (additionalParameters != null) {
connectionParameters.putAll(additionalParameters);
}
connectionParameters.put(DefaultFileDriver.URL.key, source);
// get the protocol
final String protocol = source.getProtocol();
// file
if (protocol.equalsIgnoreCase("file")) {
// convert to file
final File sourceFile = DefaultFileDriver.urlToFile(source);
// check that it is a file,exists and can be at least read
if (!sourceFile.exists() || !sourceFile.isFile() || !sourceFile.canRead())
throw new IllegalArgumentException("Invalid input");
// set the class type
// this.inputClass = File.class;
this.input = source;
// initialize
this.init();
return;
}
}
private void init() {
// get the needed info from them to set the extent
try {
final SpatioTemporalImageReader reader = (SpatioTemporalImageReader) HDF4Driver.spi.createReaderInstance(this.input);
reader.setInput(this.input);
int numCoverages = 0;
// Setting the name
final Name mainCoverageName = Utilities.buildCoverageName(input);
Name coverageName = mainCoverageName;
// TODO: Add more checks on Vertical/Temporal dimension availability
final int numImages = reader.getNumImages(false);
// //
//
// Setting Envelope and Extents
//
// //
Map<Name, Map<String, HDF4ProductFieldType>> fieldsMap = new HashMap<Name, Map<String, HDF4ProductFieldType>>();
for (int imageIndex = 0; imageIndex < numImages; imageIndex++) {
final SpatioTemporalMetadata metadata = reader.getSpatioTemporalMetadata(imageIndex);
final SliceDescriptor sd = reader.getSliceDescriptor(imageIndex);
if (sd == null)
throw new IllegalStateException("unable to get the required sliceDescriptor");
// //
//
// Getting slice extent
//
// //
// VerticalExtent ve = sd.getVerticalExtent();
// CoordinateReferenceSystem crs = sd.getCoordinateReferenceSystem();
TemporalGeometricPrimitive time = sd.getTemporalExtent();
BoundingBox boundingBox = sd.getHorizontalExtent();
// String referenceID = "";
// if (crs instanceof CompoundCRS){
// List<CoordinateReferenceSystem> list = ((CompoundCRS) crs).getCoordinateReferenceSystems();
// if (list!=null && !list.isEmpty()){
// for (CoordinateReferenceSystem crsElement : list){
// if (crsElement instanceof VerticalCRS){
// referenceID = crsElement.getName().getCode();
// break;
// }
// }
// }
// }
Band band = metadata.getBand(0);
Set<TemporalGeometricPrimitive> temporalExtent;
// Set<NumberRange<Double>> verticalExtent;
Map<String, HDF4ProductFieldType> fields;
// //
//
// Firstly, group coverages having the same bounding Box.
// Some datasources may contain data coming from acquisitions
// on different areas. We divide them in different groups.
//
// //
// if (!verticalCRSMap.containsKey(referenceID)) {
if (!coverages2DMap.containsKey(boundingBox)) {
// //
//
// This is the first occurrence. Setting coverageName,
// as well as the extents.
//
// //
coverageName = new NameImpl(mainCoverageName.getLocalPart() + "_" + Integer.toString(numCoverages++));
coverageNames.add(coverageName);
// verticalCRSMap.put(referenceID, coverageName);
coverages2DMap.put(boundingBox, coverageName);
temporalExtent = new LinkedHashSet<TemporalGeometricPrimitive>();
// verticalExtent = new LinkedHashSet<NumberRange<Double>>();
fields = new LinkedHashMap<String, HDF4ProductFieldType>();
temporalExtentMap.put(coverageName, temporalExtent);
// verticalExtentMap.put(coverageName, verticalExtent);
fieldsMap.put(coverageName, fields);
} else {
// coverageName = verticalCRSMap.get(referenceID);
coverageName = coverages2DMap.get(boundingBox);
}
if (!envelopesMap.containsKey(coverageName)) {
// //
//
// In case this coverage's properties haven't been
// initialized yet, init them.
//
// //
initOriginalEnvelopeAndCRS(coverageName, sd);
final GridGeometry2D gridGeometry2D = buildGridGeometry2D(coverageName, metadata);
gridGeometry2DMap.put(coverageName, gridGeometry2D);
} else {
GeneralEnvelope envelope = envelopesMap.get(coverageName);
envelope.add(sd.getGeneralEnvelope());
envelopesMap.put(coverageName, envelope);
}
// //
//
// Update the temporal, vertical extent for this coverage
//
// //
temporalExtent = (LinkedHashSet<TemporalGeometricPrimitive>) temporalExtentMap.get(coverageName);
// verticalExtent = (LinkedHashSet<NumberRange<Double>>) verticalExtentMap.get(coverageName);
// if (ve != null) {
// NumberRange<Double> verticalEnvelope = NumberRange.create(ve.getMinimumValue().doubleValue(), ve.getMaximumValue().doubleValue());
// if (!verticalExtent.contains(verticalEnvelope))
// verticalExtent.add(verticalEnvelope);
// }
if (time != null) {
if (!temporalExtent.contains(time))
temporalExtent.add(time);
}
// //
//
// Update the fields for this coverage
//
// //
fields = (LinkedHashMap<String, HDF4ProductFieldType>) fieldsMap.get(coverageName);
String elementName = band.getName();
if (!fields.containsKey(elementName)) {
Product product = HDF4ProductFieldType.getProduct(elementName);
Unit unit = Unit.ONE;
if (product != null) {
unit = product.getUoM();
} else {
String uOm = band.getUoM();
if(uOm == null)
unit = Unit.ONE.alternate(uOm);
else
try {
unit = Unit.valueOf(uOm);
} catch (IllegalArgumentException iae) {
try {
unit = Unit.ONE.alternate(uOm);
} catch (IllegalArgumentException iae2) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "Unable to parse the provided unit " + uOm, iae2);
}
} catch (UnsupportedOperationException uoe) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"Unable to parse the provided unit "
+ uOm, uoe);
}
}
}
}
final Name nameImpl = new NameImpl(coverageName.getLocalPart(), elementName);
final InternationalString description = new SimpleInternationalString(product!=null?product.getDescription():elementName);
// setting bands names.
final GridSampleDimension gridBand = Utilities.buildBands(band, elementName, unit);
final HDF4ProductFieldType fd = new HDF4ProductFieldType(nameImpl, description, gridBand);
fields.put(elementName, fd);
}
// //
//
// Updating the sliceDescriptor Map
//
// //
sliceDescriptorsMap.put(imageIndex, sd);
spatioTemporalMetadataMap.put(imageIndex, metadata);
}
// //
//
// Checking sets
//
// //
for (Name covName : coverageNames) {
// for (Name covName : verticalExtentMap.keySet()) {
// Set<NumberRange<Double>> verticalExtent = verticalExtentMap.get(covName);
// if (verticalExtent.size() == 0) {
// verticalExtent = Collections.emptySet();
// verticalExtentMap.put(covName, verticalExtent);
// }
Set<TemporalGeometricPrimitive> temporalExtent = temporalExtentMap.get(covName);
if (temporalExtent.size() == 0) {
temporalExtent = Collections.emptySet();
temporalExtentMap.put(covName, temporalExtent);
}
// //
//
// Setting a proper RangeType using the FieldTypes found
//
// //
Map<String, HDF4ProductFieldType> fields = (LinkedHashMap<String, HDF4ProductFieldType>) fieldsMap.get(covName);
if (fields != null && !fields.isEmpty()) {
final Set<FieldType> fieldTypes = new LinkedHashSet<FieldType>(fields.size());
final StringBuilder sb = new StringBuilder();
for (HDF4ProductFieldType fd : fields.values()) {
fieldTypes.add(fd);
final InternationalString description = fd.getDescription();
sb.append(description != null ? description.toString()+ "," : "");
}
String description = sb.toString();
int pos = -1;
if (description.length() > 0 && (pos = description.lastIndexOf(",")) != -1) {
description = description.substring(0, pos);
}
DefaultRangeType range = new DefaultRangeType(covName,new SimpleInternationalString(description),fieldTypes);
rangeMap.put(covName, range);
}
}
numberOfCoverages = numCoverages;
// dispose the reader
reader.dispose();
} catch (IOException e) {
this.numberOfCoverages = 1;
} catch (FactoryException fe) {
this.numberOfCoverages = 1;
} catch (TransformException fe) {
this.numberOfCoverages = 1;
}
}
public CoverageSource access(Name name, Map<String, Serializable> params,
AccessType accessType, Hints hints, ProgressListener listener)
throws IOException {
if (listener == null)
listener = new NullProgressListener();
listener.started();
try {
return new HDF4Source(this, name);
} finally {
listener.complete();
}
}
public boolean canCreate(Name name, Map<String, Serializable> params,
Hints hints, ProgressListener listener) throws IOException {
return false;
}
public boolean canDelete(Name name, Map<String, Serializable> params,
Hints hints) throws IOException {
return false;
}
public CoverageStore create(Name name, Map<String, Serializable> params,
Hints hints, ProgressListener listener) throws IOException {
throw new UnsupportedOperationException("HDF4Access only provides read access from a Coverage Source");
}
public boolean delete(Name name, Map<String, Serializable> params, Hints hints) throws IOException {
return false;
}
public void dispose() {
input = null;
}
public Map<String, Parameter<?>> getAccessParameterInfo(AccessType accessType) {
return accessParameters;
}
public Map<String, Serializable> getConnectParameters() {
return Collections.unmodifiableMap(connectionParameters);
}
public Driver getDriver() {
return new HDF4Driver();
}
public ServiceInfo getInfo(ProgressListener listener) {
if (listener == null)
listener = new NullProgressListener();
listener.started();
final DefaultServiceInfo info = new DefaultServiceInfo();
info.setTitle(coverageNames.get(0).toString());
try {
info.setSource(input.toURI());
} catch (URISyntaxException e1) {
} finally {
listener.complete();
}
return info;
}
public Envelope getExtent(Name coverageName, ProgressListener listener) {
if (listener == null)
listener = new NullProgressListener();
listener.started();
try {
return envelopesMap.get(coverageName).clone();
} finally {
listener.complete();
}
}
public List<Name> getNames(ProgressListener listener) {
if (listener == null)
listener = new NullProgressListener();
listener.started();
try {
return Collections.unmodifiableList(this.coverageNames);
} finally {
listener.complete();
}
}
public int getNumCoverages(ProgressListener listener) {
if (listener == null)
listener = new NullProgressListener();
listener.started();
try {
return numberOfCoverages;
} finally {
listener.complete();
}
}
public Set<AccessType> getSupportedAccessTypes() {
return allowedAccessTypes;
}
public boolean isCreateSupported() {
return false;
}
public boolean isDeleteSupported() {
return false;
}
private GridGeometry2D buildGridGeometry2D(Name coverageName, SpatioTemporalMetadata metadata) {
GridGeometry2D g2d = null;
if (metadata != null) {
// //
//
// Getting the rectifiedGrid node
//
// //
final RectifiedGrid rg = metadata.getRectifiedGrid();
final AffineTransform at = Utilities.getAffineTransform(rg);
final GridEnvelope gridRange = Utilities.getGridRange(rg);
final MathTransform raster2Model = ProjectiveTransform.create(at);
raster2ModelMap.put(coverageName, raster2Model);
final double[] highestRes = CoverageUtilities.getResolution((AffineTransform) raster2Model);
highestResMap.put(coverageName, highestRes);
g2d = new GridGeometry2D(gridRange, raster2Model,spatialReferenceSystem2DMap.get(coverageName));
}
return g2d;
}
private void initOriginalEnvelopeAndCRS(Name coverageName,
SliceDescriptor sd) throws FactoryException, TransformException {
if (sd == null)
throw new IllegalArgumentException("Provided Slice Descriptor is null");
final GeneralEnvelope envelope = sd.getGeneralEnvelope().clone();
envelopesMap.put(coverageName, envelope);
crsMap.put(coverageName, sd.getCoordinateReferenceSystem());
final BoundingBox bb = sd.getHorizontalExtent();
boundingBoxesMap.put(coverageName, bb);
final GeneralEnvelope coverageEnvelope2D = new GeneralEnvelope(bb);
coverageEnvelope2DMap.put(coverageName, coverageEnvelope2D);
spatialReferenceSystem2DMap.put(coverageName, coverageEnvelope2D.getCoordinateReferenceSystem());
final Envelope2D wgs84BaseEnvelope2D = (Envelope2D) Utilities.getEnvelopeAsWGS84(coverageEnvelope2D, true);
wgs84BaseEnvelope2DMap.put(coverageName, wgs84BaseEnvelope2D);
baseEnvelope2DMap.put(coverageName, new Envelope2D(coverageEnvelope2D));
}
public URL getInput() {
return input;
}
}