Efficient support for multiple coverages in GridCoverageReader - STEMLab/geotools GitHub Wiki

Description

The current implementation status of the GridCoveargeReader normally associates a single reader with a single coverage (e.g., TIFF, GTOPO30, and so on). The intention of this proposal is to break the 1-1 association and allow GridCoverageReader to expose multiple covearges in an efficient way, in order to properly deal with sources storing multiple coverages with different structures and features (e.g., not mosaics, which can deal with multiple coverages having some degree of uniformity).

The current GridCoverageReader interface already allows to expose multiple coverages, but in an inefficient and thread unsafe way, as it is intended to *read GridCoverages from the input stream in a sequential order *In order to access different coverages, once the reader is open, one needs to check if more coverages are available by checking hasMoreGridCoverages and then skip to the desired coverage, and get the name of current coverage through the getCurrentSubname method. Thus, there are two issues:

  • Need to open a new reader for each different coverage read, which is rather inefficient as normally the opening of a reader requires parsing the raster data header, or gathering network or database connections, something that is better done only once insteadstyle="font-size: 10.0pt;line-height: 13.0pt;">
  • Need to linearly scan the contents of the reader to reach the desired coverage, with no form of random access, hampering the usage of the current API in storages that contain a large amount of coverages

The idea is to deprecate this methods in favor of methods which allow explicitly specifying the coverage to be accessed, thus allowing random access to the various coverages and their descriptive information from a single reader instance. In order to preserve backwards compatibility the methods to get the coverage metadata will be maintained, and new methods taking the coverage name as an extra parameter will be added to allow clients selection of a specific coverage provided by the reader.

Finally, a simple observation of the code using reader will show that nowadays pretty much all code casts reader to AbstractGridCoverage2DReader in order to gain access to more meta information about coverages, in particular the grid coverage locacation and grid structure. In order to promote programming against interfaces a new GridCoverage2DReader interface will be created sporting the same common methods AbstractGridCoverage2DReader provides, along with variants that take the coverage name as a parameter.

Additional notes on base implementation changes:

Base class implementing GridCoverageReader is AbstractGridCoverage2DReader. The implementation of **AbstractGridCoverage2DReader **will be changed so that implementors of the current API won't be affected by the change, but allowing new multi-coverage readers to get full benefit from the new API

An outlook into the future

This work is meant as a stepping stone to allow a smooth transition between old style coverage APIs (based on current GridCoverageReader which doesn't have proper logic to deal with time/elevation domains) and new coverage-api currently living on unsupported/coverage-experiment, and loosely based on the DataAccess paradigm, as well as the WCS protocol, which allows for

  • proper geospatial dimensions management (time domain, vertical domain, custom domains) without having to deal with lists of strings
  • a single coverageAccess to deal with multiple underlying coverage sources

The idea is that bridges will be built so that implementations using the new coverage API can be wrapped in the GridCoverage2DReader one, and that similarly old implementations can be wrapped into the new one, allowing for a period of transition between the two programming styles.

Status

This proposal is under construction.

Voting has not started yet:

Tasks

  1. AA: API changed based on BEFORE / AFTER\

    • GridCoverageReader
    • GridCoverage2DReader extends GridCoverageReader
    • AbstractGridCoverage2DReader
  2. AA: Update code to use the new interface instead of casting to AbstractGridCoverage2DReader (there will be a similar follow up in GeoServer)

  3. JG: Update documentation to use the new interface

    • Fix examples to use interface (rather than cast to style="color: rgb(0,0,0);">AbstractGridCoverage2DReader)
    • Make up an example on how to access more than one coverage

GridCoverageReader

BEFORE:

public interface GridCoverageReader {
  Format getFormat();
  Object getSource();
  void dispose() throws IOException;
  GridCoverage read(GeneralParameterValue[] parameters) throws IllegalArgumentException, IOException;
  String[] getMetadataNames() throws IOException;
  String getMetadataValue(String name) throws IOException;
  String getCurrentSubname() throws IOException;
  boolean hasMoreGridCoverages() throws IOException;
  void skip() throws IOException;
  String[] listSubNames() throws IOException;
 }

AFTER:

public interface GridCoverageReader {
    /**
     * Returns the format handled by this {@code GridCoverageReader}.
     */
    Format getFormat();
    /**
     * Returns the input source. It can be a
     * {@link java.lang.String}, an {@link java.io.InputStream}, a
     * {@link java.nio.channels.FileChannel}, whatever.
     */
    Object getSource();
    /**
     * Returns the list of metadata keywords associated with the {@linkplain #getSource
     * input source} as a whole (not associated with any particular grid coverage).
     * If no metadata is available, the array will be empty.
     *
     * @return The list of metadata keywords for the input source.
     * @throws IOException if an error occurs during reading.
     *
     * @todo This javadoc may not apply thats well in the iterator scheme.
     */
    String[] getMetadataNames() throws IOException;
    
    /**
     * Returns the list of metadata keywords associated with a specific gridCoverage 
     * referred by name.
     * If no metadata is available, the array will be empty.
     *
     * @return The list of metadata keywords for the input source.
     * @throws IOException if an error occurs during reading.
     *
     * @todo This javadoc may not apply thats well in the iterator scheme.
     */
    String[] getMetadataNames(String coverageName) throws IOException;
    /**
     * Retrieve the metadata value for a given metadata name.
     *
     * @param  name Metadata keyword for which to retrieve metadata.
     * @return The metadata value for the given metadata name. Should be one of
     *         the name returned by {@link #getMetadataNames}.
     * @throws IOException if an error occurs during reading.
     *
     * @todo This javadoc may not apply thats well in the iterator scheme.
     */
    String getMetadataValue(String name) throws IOException;
    
    /**
     * Retrieve the metadata value for a given metadata name for a specified coverage.
     *
     * @param  name Metadata keyword for which to retrieve metadata.
     * @return The metadata value for the given metadata name. Should be one of
     *         the name returned by {@link #getMetadataNames}.
     * @throws IOException if an error occurs during reading.
     *
     * @todo This javadoc may not apply thats well in the iterator scheme.
     */
    String getMetadataValue(String coverageName, String name) throws IOException;
    /**
     * Retrieve the list of grid coverages contained within the {@linkplain #getSource
     * input source}. Each grid can have a different coordinate system, number of dimensions
     * and grid geometry. For example, a HDF-EOS file (GRID.HDF) contains 6 grid coverages
     * each having a different projection. An empty array will be returned if no sub names
     * exist.
     *
     * @return The list of grid coverages contained within the input source.
     * @throws IOException if an error occurs during reading.
     *
     * @todo The javadoc should also be more explicit about hierarchical format.
     *       Should the names be returned as paths?
     *       Explain what to return if the GridCoverage are accessible by index
     *       only. A proposal is to name them "grid1", "grid2", etc.
     * @deprecated use {@link #getGridCoverageNames()}
     */
    String[] listSubNames() throws IOException;
    /**
     * Retrieve the list of coverages contained within the {@linkplain #getSource
     * input source}. Each grid can have a different coordinate system, number of dimensions
     * and grid geometry. For example, a HDF-EOS file (GRID.HDF) contains 6 grid coverages
     * each having a different projection. An empty array will be returned if no sub names
     * exist.
     *
     * @return The list of grid coverages contained within the input source.
     * @throws IOException if an error occurs during reading.
     *
     */
    String[] getGridCoverageNames() throws IOException;
    /**
     * Retrieve the number of coverages contained within the {@linkplain #getSource
     * input source}.
     *
     * @return The number of coverages contained within the input source.
     * @throws IOException if an error occurs during reading.
     *
     */
    int getGridCoverageCount() throws IOException;
    /**
     * Returns the name for the next grid coverage to be {@linkplain #read read} from the
     * {@linkplain #getSource input source}.
     *
     * @throws IOException if an error occurs during reading.
     * @todo Do we need a special method for that, or should it be a metadata?
     * 
     * @deprecated no replacement for that.
     */
    String getCurrentSubname() throws IOException;
    /**
     * Returns {@code true} if there is at least one more grid coverage
     * available on the stream.
     * 
     * @deprecated no replacement for that.
     */
    boolean hasMoreGridCoverages() throws IOException;
    /**
     * Read the only available grid coverage. Use {@link #read(String, GeneralParameterValue[])}
     * in case of multiple grid coverages available.
     *
     * @param  parameters An optional set of parameters. Should be any or all of the
     *         parameters returned by {@link Format#getReadParameters}.
     * @return A new {@linkplain GridCoverage grid coverage} from the input source.
     * @throws InvalidParameterNameException if a parameter in {@code parameters}
     *         doesn't have a recognized name.
     * @throws InvalidParameterValueException if a parameter in {@code parameters}
     *         doesn't have a valid value.
     * @throws ParameterNotFoundException if a parameter was required for the operation but was
     *         not provided in the {@code parameters} list.
     * @throws CannotCreateGridCoverageException if the coverage can't be created for a logical
     *         reason (for example an unsupported format, or an inconsistency found in the data).
     * @throws IOException if a read operation failed for some other input/output reason, including
     *         {@link FileNotFoundException} if no file with the given {@code name} can be
     *         found, or {@link javax.imageio.IIOException} if an error was thrown by the
     *         underlying image library.
     */
    GridCoverage read(GeneralParameterValue[] parameters)
            throws IllegalArgumentException, IOException;
    
    
    /**
     * Read the grid coverage specified by coverageName parameter.
     *
     * @param  parameters An optional set of parameters. Should be any or all of the
     *         parameters returned by {@link Format#getReadParameters}.
     * @return A new {@linkplain GridCoverage grid coverage} from the input source.
     * @throws InvalidParameterNameException if a parameter in {@code parameters}
     *         doesn't have a recognized name.
     * @throws InvalidParameterValueException if a parameter in {@code parameters}
     *         doesn't have a valid value.
     * @throws ParameterNotFoundException if a parameter was required for the operation but was
     *         not provided in the {@code parameters} list.
     * @throws CannotCreateGridCoverageException if the coverage can't be created for a logical
     *         reason (for example an unsupported format, or an inconsistency found in the data).
     * @throws IOException if a read operation failed for some other input/output reason, including
     *         {@link FileNotFoundException} if no file with the given {@code name} can be
     *         found, or {@link javax.imageio.IIOException} if an error was thrown by the
     *         underlying image library.
     */
    GridCoverage read(String coverageName, GeneralParameterValue[] parameters)
            throws IllegalArgumentException, IOException;
    /**
     * Skip the current grid coverage without reading it, and move the stream position to
     * the next grid coverage.
     *
     * @throws IOException if the operation failed.
     * @deprecated no replacement for that.
     */
    void skip() throws IOException;
    /**
     * Allows any resources held by this object to be released. The result of calling any other
     * method subsequent to a call to this method is undefined. It is important for applications
     * to call this method when they know they will no longer be using this {@code GridCoverageReader}.
     * Otherwise, the reader may continue to hold on to resources indefinitely.
     *
     * @throws IOException if an error occured while disposing resources (for example while closing
     *         a file).
     */
    void dispose() throws IOException;
}

GridCoverage2DReader (new interface extending GridCoverageReader)

public interface GridCoverage2DReader extends GridCoverageReader {
    /**
     * The time domain (comma separated list of values)
     */
    public static final String TIME_DOMAIN = "TIME_DOMAIN";
    /**
     * Time domain resolution (when using min/max/resolution)
     */
    public static final String TIME_DOMAIN_RESOLUTION = "TIME_DOMAIN_RESOLUTION";
    /**
     * If the time domain is available (or if a min/max/resolution approach has been chosen)
     */
    public static final String HAS_TIME_DOMAIN = "HAS_TIME_DOMAIN";
    /**
     * The time domain max value
     */
    public static final String TIME_DOMAIN_MAXIMUM = "TIME_DOMAIN_MAXIMUM";
    /**
     * The time domain min value
     */
    public static final String TIME_DOMAIN_MINIMUM = "TIME_DOMAIN_MINIMUM";
    /**
     * Whether the elevation is expressed as a full domain or min/max/resolution (true if domain
     * list available)
     */
    public static final String HAS_ELEVATION_DOMAIN = "HAS_ELEVATION_DOMAIN";
    /**
     * Elevation domain (comma separated list of values)
     */
    public static final String ELEVATION_DOMAIN = "ELEVATION_DOMAIN";
    /**
     * Elevation domain maximum value
     */
    public static final String ELEVATION_DOMAIN_MAXIMUM = "ELEVATION_DOMAIN_MAXIMUM";
    /**
     * Elevation domain minimum value
     */
    public static final String ELEVATION_DOMAIN_MINIMUM = "ELEVATION_DOMAIN_MINIMUM";
    /**
     * Elevation domain resolution
     */
    public static final String ELEVATION_DOMAIN_RESOLUTION = "ELEVATION_DOMAIN_RESOLUTION";
    /**
     * If a coverage has this property is means it been read straight out of a file without 
     * any sub-setting, it means the coverage represents the full contents of the file.
     * This can be used for different types of optimizations, such as avoiding reading
     * and re-encoding the original file when the original file would do.
     */
    public static final String FILE_SOURCE_PROPERTY = "OriginalFileSource";
    
    /**
     * Return the original {@link GeneralEnvelope} for the default coverage served by the underlying store.
     * 
     * @return the original {@link GeneralEnvelope} for the default coverage served by the underlying store.
     */
    GeneralEnvelope getOriginalEnvelope();
    /**
     * Return the original {@link GeneralEnvelope} for the specified coverageName.
     * 
     * @param coverageName the name of the coverage to work on.
     * @return the original {@link GeneralEnvelope} for the specified coverageName.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    GeneralEnvelope getOriginalEnvelope(String coverageName);
    /**
     * Retrieves the {@link CoordinateReferenceSystem} associated to the default coverage for this {@link GridCoverage2DReader}.
     * 
     * <p>
     * @return the {@link CoordinateReferenceSystem} mapped to the specified coverageName, or {@code null} if the provided coverageName does not map
     *         to a real coverage.
     * 
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     */
    CoordinateReferenceSystem getCoordinateReferenceSystem();
    /**
     * Retrieves the {@link CoordinateReferenceSystem} associated to this {@link GridCoverage2DReader} for the specified coverageName.
     * 
     * <p>
     * @return the {@link CoordinateReferenceSystem} mapped to the specified coverageName
     * 
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    CoordinateReferenceSystem getCoordinateReferenceSystem(String coverageName);
    /**
     * Retrieves the {@link GridEnvelope} associated to the default coverage for this {@link GridCoverage2DReader}.
     * <p>
     * The {@link GridEnvelope} describes the raster area (in pixels) covered by the coverage.
     * 
     * <p>
     * @return the {@link CoordinateReferenceSystem} mapped to the default coverageName
     */
    GridEnvelope getOriginalGridRange();
    /**
     * Retrieves the {@link GridEnvelope} associated to the specified coverageName for this {@link GridCoverage2DReader}.
     * <p>
     * The {@link GridEnvelope} describes the raster area (in pixels) covered by the coverage.
     * 
     * @param coverageName the name of the coverage to work with
     * @return the {@link GridEnvelope} mapped to the specified coverageName
     * 
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    GridEnvelope getOriginalGridRange(String coverageName);
    /**
     * Retrieves the {@link MathTransform} associated to the default coverage for this {@link GridCoverage2DReader}.
     * 
     * <p>
     * @return the {@link CoordinateReferenceSystem} mapped to the default coverageName
     */
    MathTransform getOriginalGridToWorld(PixelInCell pixInCell);
    /**
     * Retrieves the {@link MathTransform} associated to the requested coverageName for this {@link GridCoverage2DReader}.
     * 
     * @param coverageName the name of the coverage to work with
     * @return the {@link MathTransform} mapped to the specified coverageName
     * 
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    MathTransform getOriginalGridToWorld(String coverageName, PixelInCell pixInCell);
    /**
     * Created a {@link GridCoverage2D} out of this {@link GridCoverage2DReader} for the default coverage.
     * 
     * @param parameters an array of {@link GeneralParameterValue} that uses a subset of the available read params for this
     *        {@link GridCoverage2DReader} as specified by the {@link Format}
     * @return a {@link GridCoverage2D} for the underlying default coverage for this {@link GridCoverage2DReader} or <code>null</code> in case no
     *         {@link GridCoverage2D} can be read for the provided parameters.
     * @throws IOException in case an error happen during read time.
     */
    GridCoverage2D read(GeneralParameterValue[] parameters) throws IOException;
    /**
     * Retrieves the {@link GridEnvelope} associated to the specified coverageName for this {@link GridCoverage2DReader}.
     * 
     * @param coverageName the name of the coverage to work with
     * @param parameters an array of {@link GeneralParameterValue} that uses a subset of the available read params for this
     *        {@link GridCoverage2DReader} as specified by the {@link Format}
     * @return a {@link GridCoverage2D} for the underlying default coverage for this {@link GridCoverage2DReader} or <code>null</code> in case no
     *         {@link GridCoverage2D} can be read for the provided parameters.
     * 
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    GridCoverage2D read(String coverageName, GeneralParameterValue[] parameters) throws IOException;
    /**
     * Return the {@link Set} of dynamic read parameters supported by this {@link GridCoverage2DReader} for the default coverage.
     * 
     * @return the {@link Set} of dynamic read parameters supported by this {@link GridCoverage2DReader}.
     * @throws IOException in case an error occurs while creating the {@link Set} of dynamic parameters.
     */
    Set<ParameterDescriptor<List>> getDynamicParameters() throws IOException;
    /**
     * Return the {@link Set} of dynamic read parameters supported by this {@link GridCoverage2DReader} for the specified coverage.
     * 
     * @param coverageName the name of the coverage to work with
     * @return the {@link Set} of dynamic read parameters supported by this {@link GridCoverage2DReader}.
     * @throws IOException in case an error occurs while creating the {@link Set} of dynamic parameters.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    Set<ParameterDescriptor<List>> getDynamicParameters(String coverageName) throws IOException;
    /**
     * Return the resolution of the overview which would be picked out for the provided requested resolution
     * using the provided {@link OverviewPolicy}. This method works on the default coverage for this {@link GridCoverage2DReader}.
     *  
     * @param policy the {@link OverviewPolicy} to use during evaluation.
     * @param requestedResolution the requested resolution 
     * @return an array of 2 double with the resolution of the selected overview.
     * @throws IOException in case an error occurs.
     */
    double[] getReadingResolutions(OverviewPolicy policy, double[] requestedResolution) throws IOException;
    /**
     * Return the resolution of the overview which would be picked out for the provided requested resolution
     * using the provided {@link OverviewPolicy}. This method works on the specified coverage for this {@link GridCoverage2DReader}.
     *  
     * @param coverageName the name of the coverage to work on.
     * @param policy the {@link OverviewPolicy} to use during evaluation.
     * @param requestedResolution the requested resolution 
     * @return an array of 2 double with the resolution of the selected overview.
     * @throws IOException in case an error occurs.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    double[] getReadingResolutions(String coverageName, OverviewPolicy policy, double[] requestedResolution) throws IOException;
    /**
     * Number of predetermined overviews for the default coverage.
     * 
     * @return The number of predetermined overviews for the default coverage. Zero if none are available, -1 if infinite are available, otherwise a
     *         positive number.
     */
    int getNumOverviews();
    /**
     * Number of predetermined overviews for the specified coverage.
     * 
     * @param coverageName the name of the coverage for which we do want to get the number of overviews.
     * 
     * @return The number of predetermined overviews for the specified coverage. 0 if none are available, -1 if infinite are available, otherwise a
     *         positive number.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    int getNumOverviews(String coverageName);
    /**
     * Retrieve the {@link ImageLayout} for the default coverage.
     * <p>
     * Throw an {@link IllegalArgumentException} in case the name is wrong and/or no such a coverage exists.
     * 
     * @return an {@link ImageLayout} that is useful for actually knowing the {@link ColorModel}, the {@link SampleModel} as well as the tile grid for
     *         the default coverage.
     */
    ImageLayout getImageLayout()throws IOException;
    /**
     * Retrieve the {@link ImageLayout} for the specified coverage.
     * <p>
     * Throw an {@link IllegalArgumentException} in case the name is wrong and/or no such a coverage exists.
     * 
     * @param coverageName the name of the coverage for which we want to know the {@link GridEnvelope}.
     * @return an {@link ImageLayout} that is useful for actually knowing the {@link ColorModel}, the {@link SampleModel} as well as the tile grid for
     *         a certain coverage.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    ImageLayout getImageLayout(String coverageName)throws IOException;
    /**
     * Retrieve the resolution levels for the default coverage.
     * <p>
     * Throw an {@link IllegalArgumentException} in case the name is wrong and/or no such a coverage exists.
     * 
     * @return the resolution levels for the default coverage.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    double[][] getResolutionLevels() throws IOException;
    /**
     * Retrieve the resolution levels for the specified coverage.
     * <p>
     * Throw an {@link IllegalArgumentException} in case the name is wrong and/or no such a coverage exists.
     * 
     * @param coverageName the name of the coverage for which we want to know the resolution levels.
     * @return the resolution levels for the specified coverage.
     * @throws NullPointerException if the specified coverageName is <code>null</code>
     * @throws IllegalArgumentException if the specified coverageName does not exist
     */
    double[][] getResolutionLevels(String coverageName) throws IOException;
}

Default implementation changes

AbstractGridCoverage2DReader

BEFORE:

public String[] listSubNames() {
    throw new UnsupportedOperationException("Unsupported operation.");
}

public int getGridCoverageCount() {
    throw new UnsupportedOperationException("Unsupported operation.");
}

public final GeneralEnvelope getOriginalEnvelope() {
    ...
    return originalEnvelope;
 }

public final GridEnvelope getOriginalGridRange() {
    ...
    return originalGridRange; 
}

public final MathTransform getOriginalGridToWorld(final PixelInCell pixInCell) {
    ...
    return mathTransform;
}

public String[] getMetadataNames() {
    ...
    return metadataNames;
}

public String getMetadataValue(final String name) {
    ...
    return metadataValue;
}
  

AFTER:

/**
 * @deprecated use {@link #getGridCoverageNames()}
 */
public String[] listSubNames() {
    return getGridCoverageNames();
}

public String[] getGridCoverageNames() {
    return coverageNames;
}

public int getGridCoverageCount() {
    return coveragesNumber;
} 

public final GeneralEnvelope getOriginalEnvelope() {
    if (coveragesNumber > 1) {
        throw new IllegalArgumentException("You must specify a coverage name");
    } else { 
        // Backward compatibility
        ...
        return originalEnvelope;
    }
}
 
/**
 * Get the envelope of the specified coverage 
 */
public final GeneralEnvelope getOriginalEnvelope(final String coverageName) {
    ...
}

public final GridEnvelope getOriginalGridRange() {
    if (coveragesNumber > 1) {
        throw new IllegalArgumentException("You must specify a coverage name");
    } else { 
        // Backward compatibility
        ...
        return originalGridRange; 
    }
}

/**
 * Get the gridRange of the specified coverage 
 */
public final GridEnvelope getOriginalGridRange(final String coverageName) {
    ...
}

public final MathTransform getOriginalGridToWorld(final PixelInCell pixInCell) {
    if (coveragesNumber > 1) {
        throw new IllegalArgumentException("You must specify a coverage name");
    } else { 
        // Backward compatibility
        ...
        return mathTransform;
    }
}

/**
 * Get the grid to world transformation of the specified coverage 
 */
public final MathTransform getOriginalGridToWorld(final String coverageName, final PixelInCell pixInCell) {
    ...
}

public String[] getMetadataNames() {
    if (coveragesNumber > 1) {
        throw new IllegalArgumentException("You must specify a coverage name");
    } else { 
        // Backward compatibility
        ...
        return metadataNames;
    }
}

/**
 *  Get the metadata names of the specified coverage
 */
public String[] getMetadataNames(final String coverageName) {
    ...
}

public String[] getMetadataValue(final String name) {
    if (coveragesNumber > 1) {
        throw new IllegalArgumentException("You must specify a coverage name");
    } else { 
        // Backward compatibility
        ...
        return metadataValue;;
    }
}

/**
 *  Get the metadata value related to the specified metadataName for the specified coverage
 */
public String[] getMetadataValue(final String coverageName, final String name) {
    ...
}

Code Examples

BEFORE:

/** Sample code usage */
public void testSingleCoverageReader() {
    ...
    AbstractGridCoverage2DReader reader = ...; // Creating a reader

    // Getting the coverage's properties
    final GeneralEnvelope envelope = reader.getOriginalEnvelope();
    final GridEnvelope gridRange = reader.getOriginalGridRange();    
    ...

    // reading the coverage
    GridCoverage2D coverage = (GridCoverage2D) reader.read(null);
    ...
}
 

AFTER:

/** Sample code usage */
public void testMultipleCoverageReader() {
    ...
    AbstractGridCoverageReader reader = ...; // Creating a reader
    String[] coverageNames = reader.getGridCoverageNames();
    // At this point, coverageNames may contain, as an instance, "pressure,temperature,humidity"

    String requestedCoverageName = coverageNames[1]; // e..g, "temperature"

    // Getting selected coverage properties    
    final GeneralEnvelope envelope = reader.getOriginalEnvelope(requestedCoverageName );
    final GridEnvelope gridRange = reader.getOriginalGridRange(requestedCoverageName );  
    ...
    ...
 
    // Reading that coverage
    GridCoverage2D coverage = (GridCoverage2D) reader.read(requestedCoverageName , null);
    ...
}

Documentation Changes

list the pages effected by this proposal

⚠️ **GitHub.com Fallback** ⚠️