/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2014 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.catalog; import it.geosolutions.imageio.maskband.DatasetLayout; import it.geosolutions.jaiext.range.NoDataContainer; import java.awt.Color; import java.awt.image.ColorModel; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.measure.unit.Unit; import javax.media.jai.ImageLayout; import javax.media.jai.PropertySource; import javax.media.jai.PropertySourceImpl; import org.geoserver.catalog.CoverageView.CoverageBand; import org.geoserver.catalog.impl.CoverageDimensionImpl; import org.geotools.coverage.Category; import org.geotools.coverage.GridSampleDimension; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.DimensionDescriptor; import org.geotools.coverage.grid.io.GranuleSource; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.coverage.grid.io.HarvestedSource; import org.geotools.coverage.grid.io.OverviewPolicy; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.data.ResourceInfo; import org.geotools.data.ServiceInfo; import org.geotools.factory.Hints; import org.geotools.geometry.GeneralEnvelope; import org.geotools.resources.Classes; import org.geotools.resources.coverage.CoverageUtilities; import org.geotools.util.NumberRange; import org.geotools.util.SimpleInternationalString; import org.geotools.util.logging.Logging; import org.opengis.coverage.ColorInterpretation; import org.opengis.coverage.PaletteInterpretation; import org.opengis.coverage.SampleDimension; import org.opengis.coverage.SampleDimensionType; import org.opengis.coverage.grid.Format; import org.opengis.coverage.grid.GridCoverageReader; import org.opengis.coverage.grid.GridEnvelope; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterValue; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.util.InternationalString; /** * A {@link GridCoverage2DReader} wrapper to customize the {@link CoverageDimensionInfo} associated * with a coverage by exposing configured values such as null values, band name, and * data ranges instead of the ones associated with the underlying coverage. * * @author Daniele Romagnoli - GeoSolutions SAS */ public class CoverageDimensionCustomizerReader implements GridCoverage2DReader { private static Logger LOGGER = Logging.getLogger(CoverageDimensionCustomizerReader.class); final static String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); private CoverageInfo info; private GridCoverage2DReader delegate; private String coverageName; static class CoverageDimensionCustomizerStructuredReader extends CoverageDimensionCustomizerReader implements StructuredGridCoverage2DReader{ private StructuredGridCoverage2DReader structuredDelegate; public CoverageDimensionCustomizerStructuredReader(StructuredGridCoverage2DReader delegate, String coverageName, CoverageInfo info) { super(delegate, coverageName, info); this.structuredDelegate = delegate; } public CoverageDimensionCustomizerStructuredReader(StructuredGridCoverage2DReader delegate, String coverageName, CoverageStoreInfo storeInfo) { super(delegate, coverageName, storeInfo); this.structuredDelegate = delegate; } @Override public GranuleSource getGranules(String coverageName, boolean readOnly) throws IOException, UnsupportedOperationException { return structuredDelegate.getGranules(coverageName, readOnly); } @Override public boolean isReadOnly() { return structuredDelegate.isReadOnly(); } @Override public void createCoverage(String coverageName, SimpleFeatureType schema) throws IOException, UnsupportedOperationException { structuredDelegate.createCoverage(coverageName, schema); } @Override public boolean removeCoverage(String coverageName) throws IOException, UnsupportedOperationException { return structuredDelegate.removeCoverage(coverageName); } @Override public boolean removeCoverage(String coverageName, boolean delete) throws IOException, UnsupportedOperationException { return structuredDelegate.removeCoverage(coverageName, delete); } @Override public void delete(boolean deleteData) throws IOException { structuredDelegate.delete(deleteData); } @Override public List<HarvestedSource> harvest(String defaultTargetCoverage, Object source, Hints hints) throws IOException, UnsupportedOperationException { return structuredDelegate.harvest(defaultTargetCoverage, source, hints); } @Override public List<DimensionDescriptor> getDimensionDescriptors(String coverageName) throws IOException { return structuredDelegate.getDimensionDescriptors(coverageName); } } /** * Wrap a {@link GridCoverage2DReader} into a {@link CoverageDimensionCustomizerReader}. * @param delegate the reader to be wrapped. * @param coverageName the specified coverageName. It may be null in case of {@link GridCoverage2DReader}s * with a single coverage, coming from an old catalog where no coverageName has been stored. * @param info the {@link CoverageStoreInfo} instance used to look for {@link CoverageInfo} instances. */ public static GridCoverageReader wrap(GridCoverage2DReader delegate, String coverageName, CoverageStoreInfo info) { GridCoverage2DReader reader = delegate; if (coverageName != null) { reader = SingleGridCoverage2DReader.wrap(delegate, coverageName); } if (reader instanceof StructuredGridCoverage2DReader) { return new CoverageDimensionCustomizerStructuredReader((StructuredGridCoverage2DReader) reader, coverageName, info); } else { return new CoverageDimensionCustomizerReader(reader, coverageName, info); } } /** * Wrap a {@link GridCoverage2DReader} into a {@link CoverageDimensionCustomizerReader}. * @param delegate the reader to be wrapped. * @param coverageName the specified coverageName. It may be null in case of {@link GridCoverage2DReader}s * with a single coverage, coming from an old catalog where no coverageName has been stored. * @param info the {@link CoverageInfo} instance used to look for {@link CoverageInfo} instances. */ public static GridCoverageReader wrap(GridCoverage2DReader delegate, String coverageName, CoverageInfo info) { GridCoverage2DReader reader = delegate; if (coverageName != null) { reader = SingleGridCoverage2DReader.wrap(delegate, coverageName); } if (reader instanceof StructuredGridCoverage2DReader) { return new CoverageDimensionCustomizerStructuredReader((StructuredGridCoverage2DReader) reader, coverageName, info); } else { return new CoverageDimensionCustomizerReader(reader, coverageName, info); } } public CoverageDimensionCustomizerReader(GridCoverage2DReader delegate, String coverageName, CoverageStoreInfo storeInfo) { this.delegate = delegate; this.coverageName = coverageName; this.info = ResourcePool.getCoverageInfo(coverageName, storeInfo); } public CoverageDimensionCustomizerReader(GridCoverage2DReader delegate, String coverageName, CoverageInfo info) { this.delegate = delegate; this.coverageName = coverageName; this.info = info; } public String getCoverageName() { return coverageName; } public CoverageInfo getCoverageInfo() { return info; } @Override public ServiceInfo getInfo() { return delegate.getInfo(); } @Override public ResourceInfo getInfo(String coverageName) { checkCoverageName(coverageName); return delegate.getInfo(coverageName); } @Override public GridCoverage2D read(GeneralParameterValue[] parameters) throws IllegalArgumentException, IOException { return read(this.coverageName, parameters); } @Override /** * This specific read operation is a customized one since we need to wrap the coverage properties * (null values, ranges, sampleDimensions...) */ public GridCoverage2D read(String coverageName, GeneralParameterValue[] parameters) throws IllegalArgumentException, IOException { final GridCoverage2D coverage = coverageName != null ? delegate.read(coverageName, parameters) : delegate.read(parameters); if (coverage == null) { return coverage; } final Map<String, Object> properties = coverage.getProperties(); final SampleDimension[] dims = coverage.getSampleDimensions(); GridSampleDimension[] wrappedDims = null; if (info != null) { List<CoverageDimensionInfo> dimensions = info.getDimensions(); // if no dimensions found, see if there is a coverage view providing them if(dimensions == null || dimensions.isEmpty()) { MetadataMap map = info.getMetadata(); if (map.containsKey(CoverageView.COVERAGE_VIEW)) { CoverageView coverageView = (CoverageView) map.get(CoverageView.COVERAGE_VIEW); dimensions = new ArrayList<>(); List<CoverageBand> coverageBands = coverageView.getCoverageBands(); for (CoverageBand band : coverageBands) { CoverageDimensionInfo dimensionInfo = new CoverageDimensionImpl(); dimensionInfo.setName(band.getDefinition()); dimensions.add(dimensionInfo); } } } int[] selectedBandIndexes = getSelectedBandIndexes(parameters); wrappedDims = wrapDimensions(dims, dimensions, selectedBandIndexes); } // Wrapping sample dimensions NoDataContainer noDataProperty = CoverageUtilities.getNoDataProperty(coverage); if (wrappedDims == null) { wrappedDims = (GridSampleDimension[]) dims; } else if (properties != null && noDataProperty != null) { // update the GC_NODATA property (if any) with the latest value, if we have any double[] wrappedNoDataValues = wrappedDims[0].getNoDataValues(); if (wrappedNoDataValues != null && wrappedNoDataValues.length > 0) { CoverageUtilities.setNoDataProperty(properties, wrappedNoDataValues[0]); } } // Wrap the coverage into a coverageWrapper to change its name and sampleDimensions return new GridCoverageWrapper(coverageName, coverage, wrappedDims, properties); } int[] getSelectedBandIndexes(GeneralParameterValue[] parameters) { // if the delegate cannot do band selection, don't bother if(delegate.getFormat() == null || !delegate.getFormat().getReadParameters().getDescriptor().descriptors().contains(AbstractGridFormat.BANDS)) { return null; }; // lookup the bands if possible if (parameters != null) { for (int i = 0; i < parameters.length; i++) { final ParameterValue param = (ParameterValue) parameters[i]; if (AbstractGridFormat.BANDS.getName().equals(param.getDescriptor().getName())) { int[] bandIndicesParam = (int[]) param.getValue(); return bandIndicesParam; } } } return null; } protected GridSampleDimension[] wrapDimensions(SampleDimension[] dims, List<CoverageDimensionInfo> storedDimensions, int[] bandIndexes) { GridSampleDimension[] wrappedDims = null; if (storedDimensions != null && storedDimensions.size() > 0) { int i = 0; wrappedDims = new GridSampleDimension[dims.length]; for (SampleDimension dim : dims) { CoverageDimensionInfo cdi; if(bandIndexes != null) { if(i >= bandIndexes.length) { // dynamically added alpha band cdi = buildAlphaChannelDimnsionInfo(storedDimensions.get(0)); } else { int idx = bandIndexes[i]; cdi = storedDimensions.get(idx); } } else { if(i >= storedDimensions.size()) { // dynamically added alpha band cdi = buildAlphaChannelDimnsionInfo(storedDimensions.get(0)); } else { cdi = storedDimensions.get(i); } } wrappedDims[i] = WrappedSampleDimension.build((GridSampleDimension) dim, cdi); i++; } } return wrappedDims; } private CoverageDimensionInfo buildAlphaChannelDimnsionInfo( CoverageDimensionInfo sample) { CoverageDimensionInfo result = new CoverageDimensionImpl(sample); result.setName("ALPHA_BAND"); result.setDescription("Alpha"); return result; } public Format getFormat() { return delegate.getFormat(); } public Object getSource() { return delegate.getSource(); } public String[] getMetadataNames() throws IOException { return delegate.getMetadataNames(); } public String[] getMetadataNames(String coverageName) throws IOException { checkCoverageName(coverageName); return delegate.getMetadataNames(coverageName); } public String getMetadataValue(String name) throws IOException { return delegate.getMetadataValue(name); } public String getMetadataValue(String coverageName, String name) throws IOException { checkCoverageName(coverageName); return delegate.getMetadataValue(coverageName, name); } public String[] listSubNames() throws IOException { return delegate.listSubNames(); } public String getCurrentSubname() throws IOException { return delegate.getCurrentSubname(); } public boolean hasMoreGridCoverages() throws IOException { return delegate.hasMoreGridCoverages(); } public void skip() throws IOException { delegate.skip(); } public void dispose() throws IOException { delegate.dispose(); } public GeneralEnvelope getOriginalEnvelope() { return delegate.getOriginalEnvelope(); } public GeneralEnvelope getOriginalEnvelope(String coverageName) { checkCoverageName(coverageName); return delegate.getOriginalEnvelope(coverageName); } public GridEnvelope getOriginalGridRange() { return delegate.getOriginalGridRange(); } public GridEnvelope getOriginalGridRange(String coverageName) { checkCoverageName(coverageName); return delegate.getOriginalGridRange(coverageName); } public MathTransform getOriginalGridToWorld(PixelInCell pixInCell) { return delegate.getOriginalGridToWorld(pixInCell); } public MathTransform getOriginalGridToWorld(String coverageName, PixelInCell pixInCell) { checkCoverageName(coverageName); return delegate.getOriginalGridToWorld(coverageName, pixInCell); } public CoordinateReferenceSystem getCoordinateReferenceSystem() { return delegate.getCoordinateReferenceSystem(); } public CoordinateReferenceSystem getCoordinateReferenceSystem(String coverageName) { checkCoverageName(coverageName); return delegate.getCoordinateReferenceSystem(coverageName); } public Set<ParameterDescriptor<List>> getDynamicParameters() throws IOException { return delegate.getDynamicParameters(); } public Set<ParameterDescriptor<List>> getDynamicParameters(String coverageName) throws IOException { checkCoverageName(coverageName); return delegate.getDynamicParameters(coverageName); } public double[] getReadingResolutions(OverviewPolicy policy, double[] requestedResolution) throws IOException { return delegate.getReadingResolutions(policy, requestedResolution); } public double[] getReadingResolutions(String coverageName, OverviewPolicy policy, double[] requestedResolution) throws IOException { checkCoverageName(coverageName); return delegate.getReadingResolutions(coverageName, policy, requestedResolution); } public String[] getGridCoverageNames() throws IOException { return delegate.getGridCoverageNames(); } public int getGridCoverageCount() throws IOException { return delegate.getGridCoverageCount(); } public int getNumOverviews() { return delegate.getNumOverviews(); } public int getNumOverviews(String coverageName) { checkCoverageName(coverageName); return delegate.getNumOverviews(coverageName); } public ImageLayout getImageLayout() throws IOException { return delegate.getImageLayout(); } public ImageLayout getImageLayout(String coverageName) throws IOException { checkCoverageName(coverageName); return delegate.getImageLayout(coverageName); } public double[][] getResolutionLevels() throws IOException { return delegate.getResolutionLevels(); } public double[][] getResolutionLevels(String coverageName) throws IOException { checkCoverageName(coverageName); return delegate.getResolutionLevels(coverageName); } /** * Checks the specified name is the one we are expecting * @param coverageName */ protected void checkCoverageName(String coverageName) { if (this.coverageName != null && !this.coverageName.equals(coverageName)) { throw new IllegalArgumentException("Unkonwn coverage named " + coverageName + ", the only valid value is: " + this.coverageName); } } /** * Utility class to wrap a GridCoverage by overriding its sampleDimensions and properties */ public static class GridCoverageWrapper extends GridCoverage2D { /** A custom propertySource allowing to redefine properties (since getProperties return a clone) */ private PropertySourceImpl wrappedPropertySource; /** Configured sampleDimensions */ private GridSampleDimension[] wrappedSampleDimensions; public GridCoverageWrapper(String name, GridCoverage2D coverage, GridSampleDimension[] sampleDimensions, Map properties) { super(name, coverage); this.wrappedSampleDimensions = sampleDimensions; wrappedPropertySource = new PropertySourceImpl(properties, coverage instanceof PropertySource ? (PropertySource) coverage : null); } @Override public GridSampleDimension getSampleDimension(int index) { return wrappedSampleDimensions[index]; } @Override public GridSampleDimension[] getSampleDimensions() { return wrappedSampleDimensions.clone(); } @Override public Map getProperties() { return wrappedPropertySource.getProperties(); } @Override public Object getProperty(String arg0) { return wrappedPropertySource.getProperty(arg0); } public static GridCoverage2D wrapCoverage(GridCoverage2D coverage, GridCoverage2D sourceCoverage, GridSampleDimension[] wrappedDimensions, Map properties, boolean forceWrapping) { if (coverage instanceof GridCoverageWrapper || forceWrapping) { return new GridCoverageWrapper(coverage.getName().toString(), coverage, wrappedDimensions == null ? coverage.getSampleDimensions() : wrappedDimensions, properties == null ? sourceCoverage.getProperties() : properties); } return coverage; } } /** * Wrap a GridSampleDimension by overriding categories, ranges, null values and name. */ static class WrappedSampleDimension extends GridSampleDimension implements SampleDimension { /** The original sample dimension */ private GridSampleDimension sampleDim; /** The custom categories */ private List<Category> customCategories; /** The custom noDataValues */ private double[] configuredNoDataValues; /** The custom unit */ private Unit<?> configuredUnit; /** The custom range */ private NumberRange<? extends Number> configuredRange; /** The custom name */ private String name; /** The custom description */ private InternationalString configuredDescription; public static WrappedSampleDimension build(GridSampleDimension sampleDim, CoverageDimensionInfo info) { String name = info.getName(); final InternationalString sampleDimDescription = sampleDim.getDescription(); InternationalString configuredDescription = (sampleDimDescription == null || !sampleDimDescription.toString() .equalsIgnoreCase(name)) ? new SimpleInternationalString(name) : sampleDimDescription; final List<Category> categories = sampleDim.getCategories(); NumberRange configuredRange = info.getRange(); final String uom = info.getUnit(); Unit defaultUnit = sampleDim.getUnits(); Unit unit = defaultUnit; try { if (uom != null) { unit = Unit.valueOf(uom); } } catch (IllegalArgumentException iae) { if (LOGGER.isLoggable(Level.WARNING) && defaultUnit != null) { LOGGER.warning("Unable to parse the specified unit (" + uom + "). Using the previous one: " + defaultUnit.toString()); } } Unit configuredUnit = unit; // custom null values final List<Double> nullValues = info.getNullValues(); double[] configuredNoDataValues; if (nullValues != null && nullValues.size() > 0) { final int size = nullValues.size(); configuredNoDataValues = new double[size]; for (int i = 0; i < size ; i++) { configuredNoDataValues[i] = nullValues.get(i); } } else { configuredNoDataValues = sampleDim.getNoDataValues(); } // Check if the nodata has been configured boolean nodataConfigured = configuredNoDataValues != null && configuredNoDataValues.length > 0; // custom categories int numCategories = 0; List<Category> customCategories = new ArrayList<Category>(numCategories); if (categories != null && (numCategories = categories.size()) > 0) { Category wrapped = null; for (Category category : categories) { wrapped = category; if (Category.NODATA.getName().equals(category.getName())) { if (category.isQuantitative()) { // Get minimum and maximum value double minimum = nodataConfigured ? configuredNoDataValues[0] : category.getRange().getMinimum(); double maximum = nodataConfigured ? configuredNoDataValues[0] : category.getRange().getMaximum(); if (Double.isNaN(minimum) && Double.isNaN(maximum)) { // Create a qualitative category wrapped = new Category(Category.NODATA.getName(), category.getColors()[0], minimum); } else { // Create the wrapped category wrapped = new Category(Category.NODATA.getName(), category.getColors(), NumberRange.create(minimum, maximum)); } } } customCategories.add(wrapped); } } // Adding the full data range, it's mandatory to have it if we want the configured range to survive a grid dimension copy // (but an infinite range is not valid, the Category creation will throw an exception) if(configuredRange != null && !Double.isInfinite(configuredRange.getMinimum()) && !Double.isInfinite(configuredRange.getMaximum())) { Class targetType = categories != null && !categories.isEmpty() ? categories.get(0).getRange().getElementClass() : Double.class; final NumberRange<?> dataRange = configuredRange.castTo(targetType); List<NumberRange<?>> dataRanges = new ArrayList<>(); dataRanges.add(dataRange); for (Category category : customCategories) { List<NumberRange<?>> newDataRanges = new ArrayList<>(); for (NumberRange<?> dr : dataRanges) { NumberRange<?>[] subtracted = dr.subtract(category.getRange()); for (NumberRange<?> range : subtracted) { if(!range.isEmpty()) { newDataRanges.add(range); } } } dataRanges = newDataRanges; } for (int i = 0; i < dataRanges.size(); i++) { customCategories.add(new Category("data" + i, (Color) null, dataRanges.get(i))); } } return new WrappedSampleDimension(name, configuredDescription, configuredRange, configuredUnit, configuredNoDataValues, (Category[]) customCategories.toArray(new Category[customCategories.size()]), sampleDim); } WrappedSampleDimension(String name, InternationalString configuredDescription, NumberRange<? extends Number> configuredRange, Unit<?> configuredUnit, double[] configuredNoDataValues, Category[] customCategories, GridSampleDimension sampleDim) { super(configuredDescription.toString(), customCategories, configuredUnit); this.configuredDescription = configuredDescription; this.configuredRange = configuredRange; this.configuredUnit = configuredUnit; this.configuredNoDataValues = configuredNoDataValues; this.customCategories = Arrays.asList(customCategories); this.sampleDim = sampleDim; } @Override public SampleDimensionType getSampleDimensionType() { return sampleDim.getSampleDimensionType(); } @Override public InternationalString getDescription() { return configuredDescription; } @Override public InternationalString[] getCategoryNames() throws IllegalStateException { return sampleDim.getCategoryNames(); } @Override public List<Category> getCategories() { return customCategories; } @Override public Category getCategory(double sample) { return sampleDim.getCategory(sample); } @Override public double[] getNoDataValues() throws IllegalStateException { return configuredNoDataValues; } @Override public double getMinimumValue() { NumberRange<? extends Number> range = getRange(); // Check if the range exists, otherwise use the sample dimension values if (range != null){ return range.getMinimum(); } else { return sampleDim.getMinimumValue(); } } @Override public double getMaximumValue() { NumberRange<? extends Number> range = getRange(); // Check if the range exists, otherwise use the sample dimension values if (range != null) { return range.getMaximum(); } else { return sampleDim.getMaximumValue(); } } @Override public NumberRange<? extends Number> getRange() { return configuredRange; } @Override public String getLabel(double value, Locale locale) { return sampleDim.getLabel(value, locale); } @Override public Unit<?> getUnits() { return configuredUnit; } @Override public double getOffset() throws IllegalStateException { return sampleDim.getOffset(); } @Override public double getScale() { return sampleDim.getScale(); } @Override public int[][] getPalette() { return sampleDim.getPalette(); } @Override public PaletteInterpretation getPaletteInterpretation() { return sampleDim.getPaletteInterpretation(); } @Override public ColorInterpretation getColorInterpretation() { return sampleDim.getColorInterpretation(); } @Override public ColorModel getColorModel() { return sampleDim.getColorModel(); } @Override public ColorModel getColorModel(int visibleBand, int numBands) { return sampleDim.getColorModel(visibleBand, numBands); } @Override public ColorModel getColorModel(int visibleBand, int numBands, int type) { return sampleDim.getColorModel(visibleBand, numBands, type); } @Override public int hashCode() { return sampleDim.hashCode(); } @Override public boolean equals(Object object) { return sampleDim.equals(object); } private StringBuilder formatRange(StringBuilder builder, final Locale locale) { final NumberRange range = getRange(); builder.append('['); if (range != null) { builder.append(range.getMinimum()).append(" ... ").append(range.getMaximum()); } else { final Unit<?> unit = getUnits(); if (unit != null) { builder.append(unit); } } builder.append(']'); return builder; } @Override public String toString() { if (customCategories != null) { StringBuilder builder = new StringBuilder(configuredDescription); builder.append('('); builder = formatRange(builder, null); builder.append(')').append(LINE_SEPARATOR); for (final Category category : customCategories) { builder.append(" ").append(' ').append(' ') .append(category).append(LINE_SEPARATOR); } return builder.toString(); } else { return sampleDim.toString(); } } private void parseUOM(StringBuilder label, Unit uom) { String uomString = uom.toString(); uomString = uomString.replaceAll("\u00B2", "^2"); uomString = uomString.replaceAll("\u00B3", "^3"); uomString = uomString.replaceAll("\u212B", "A"); uomString = uomString.replaceAll("�", ""); label.append(uomString); } private void buildDescription() { StringBuilder label = new StringBuilder("GridSampleDimension".intern()); final Unit uom = sampleDim.getUnits(); String uName = name.toUpperCase(); if (uom != null) { label.append("(".intern()); parseUOM(label, uom); label.append(")".intern()); } label.append("[".intern()); label.append(getMinimumValue()); label.append(",".intern()); label.append(getMaximumValue()); label.append("]".intern()); configuredDescription = new SimpleInternationalString(label.toString()); } } @Override public DatasetLayout getDatasetLayout() { return delegate.getDatasetLayout(); } @Override public DatasetLayout getDatasetLayout(String coverageName) { checkCoverageName(coverageName); return delegate.getDatasetLayout(coverageName); } }