/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wcs2_0.response; import java.io.IOException; import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.geoserver.catalog.CoverageInfo; import org.geoserver.catalog.DimensionInfo; import org.geoserver.catalog.MetadataMap; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.util.ReaderDimensionsAccessor; import org.geoserver.util.ISO8601Formatter; import org.geoserver.wcs2_0.exception.WCS20Exception; import org.geoserver.wcs2_0.util.NCNameResourceCodec; import org.geotools.coverage.grid.io.DimensionDescriptor; import org.geotools.coverage.grid.io.GridCoverage2DReader; import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader; import org.geotools.util.Utilities; import org.vfny.geoserver.wcs.WcsException; /** * Provides support to build the coverage description for time/elevation/additional dim based data * * @author Andrea Aime - GeoSolutions * @author Daniele Romagnoli - GeoSolutions */ public class WCSDimensionsHelper { /** * Duration in ms of well know time periods */ static final BigDecimal[] DURATIONS = new BigDecimal[] { new BigDecimal(31536000000L), new BigDecimal(2628000000L), new BigDecimal(86400000L), new BigDecimal(3600000L), new BigDecimal(60000), new BigDecimal(1000L) }; /** * Labels for the above time periods */ static final String[] DURATION_UNITS = new String[] { "year", "month", "day", "hour", "minute", "second" }; /** * Quick access fields for timeDimension and elevationDimension */ DimensionInfo timeDimension; DimensionInfo elevationDimension; /** * Additional dimensions map */ Map<String, DimensionInfo> additionalDimensions; ReaderDimensionsAccessor accessor; ISO8601Formatter formatter = new ISO8601Formatter(); String elevationResolutionUnit; double elevationResolutionValue; String timeResolutionUnit; long timeResolutionValue; String coverageId; /** * Base constructor which only deals with timeDimension. It is used by WCS-EO classes which * deals with up to timeDimensions * * @param timeDimension * @param reader * @param coverageId * @throws IOException */ public WCSDimensionsHelper(CoverageInfo ci) throws IOException { this.coverageId = NCNameResourceCodec.encode(ci); this.accessor = new ReaderDimensionsAccessor((GridCoverage2DReader) ci.getGridCoverageReader(null, null)); Map<String, DimensionInfo> dimensions = new HashMap<String, DimensionInfo>(); for (Map.Entry<String, Serializable> entry : ci.getMetadata().entrySet()) { if(entry.getValue() instanceof DimensionInfo) { dimensions.put(entry.getKey(), (DimensionInfo) entry.getValue()); } } if (!dimensions.isEmpty()) { initDimensions(dimensions); } } /** * Base constructor which only deals with timeDimension. It is used by WCS-EO classes which * deals with up to timeDimensions * * @param timeDimension * @param reader * @param coverageId * @throws IOException */ public WCSDimensionsHelper(final DimensionInfo timeDimension, final GridCoverage2DReader reader, final String coverageId) throws IOException { this(new HashMap<String, DimensionInfo>() { { put(ResourceInfo.TIME, timeDimension); } }, reader, coverageId); } public WCSDimensionsHelper(final Map<String, DimensionInfo> dimensions, final GridCoverage2DReader reader, final String coverageId) throws IOException { this.accessor = new ReaderDimensionsAccessor(reader); this.coverageId = coverageId; if (dimensions != null && !dimensions.isEmpty()) { initDimensions(dimensions); } } /** * Initialize dimensions * * @param updatedDimensions */ private void initDimensions(Map<String, DimensionInfo> dimensions) { Utilities.ensureNonNull("dimensions", dimensions); Map<String, DimensionInfo> updatedDimensions = new HashMap<String, DimensionInfo>(); updatedDimensions.putAll(dimensions); // Initialize Time dimensions if (updatedDimensions.containsKey(ResourceInfo.TIME)) { timeDimension = updatedDimensions.remove(ResourceInfo.TIME) ; if (timeDimension != null) { final BigDecimal resolution = timeDimension.getResolution(); if (resolution != null) { setupTimeResolution(resolution); } } } // Initialize Elevation Dimensions if (updatedDimensions.containsKey(ResourceInfo.ELEVATION)) { elevationDimension = updatedDimensions.remove(ResourceInfo.ELEVATION) ; final BigDecimal resolution = elevationDimension.getResolution(); if (resolution != null) { elevationResolutionValue = resolution.doubleValue(); elevationResolutionUnit = elevationDimension.getUnitSymbol(); } } // Remaining dimensions are custom dimensions this.additionalDimensions = updatedDimensions; } private void setupTimeResolution(BigDecimal resolution) { for (int i = 0; i < DURATIONS.length; i++) { BigDecimal duration = DURATIONS[i]; if (resolution.remainder(duration).longValue() == 0) { timeResolutionValue = resolution.divide(duration).longValue(); timeResolutionUnit = DURATION_UNITS[i]; return; } } // uh oh? it's a value in milliseconds? throw new WcsException( "Dimension's resolution requires milliseconds for full representation, " + "but this cannot be represented in WCS 2.0 describe coverage output"); } public DimensionInfo getTimeDimension() { return timeDimension; } public DimensionInfo getElevationDimension() { return elevationDimension; } public Map<String, DimensionInfo> getAdditionalDimensions() { return additionalDimensions; } public TreeSet<Object> getTimeDomain() throws IOException { return accessor.getTimeDomain(); } public TreeSet<Object> getElevationDomain() throws IOException { return accessor.getElevationDomain(); } public List<String> getDomain(final String domainName) throws IOException { return accessor.getDomain(domainName); } /** * Returns the minimum time, formatted according to ISO8601 * @throws IOException */ public String getBeginTime() throws IOException { Date minTime = accessor.getMinTime(); return format(minTime); } /** * Returns the maximum time, formatted according to ISO8601 * @throws IOException */ public String getEndTime() throws IOException { Date maxTime = accessor.getMaxTime(); return format(maxTime); } /** * Returns the minimum elevation * @throws IOException */ public String getBeginElevation() throws IOException { Double minElevation = accessor.getMinElevation(); return minElevation.toString(); } /** * Returns the maximum elevation * @throws IOException */ public String getEndElevation() throws IOException { Double maxElevation = accessor.getMaxElevation(); return maxElevation.toString(); } /** * Return the default value of the specified additional domain * * @param domainName * * @throws IOException */ public String getDefaultValue(String domainName) throws IOException { if (additionalDimensions != null && !additionalDimensions.isEmpty() && additionalDimensions.containsKey(domainName)) { return accessor.getCustomDomainDefaultValue(domainName); } return null; } /** * Formats a Date into ISO86011 * @param time * */ public String format(Date time) { if (time != null) { return formatter.format(time); } else { return null; } } /** * Returns the time resolution unit, choosing among "year", "month", "day", "hour", "minute", * "second" * * */ public String getTimeResolutionUnit() { return timeResolutionUnit; } /** * The time resolution value, expressed in the unit returned by {@link #getTimeResolutionUnit()} * * */ public long getTimeResolutionValue() { return timeResolutionValue; } /** * Returns the elevation resolution unit * * */ public String getElevationResolutionUnit() { return elevationResolutionUnit; } /** * The elevation resolution value * */ public double getElevationResolutionValue() { return elevationResolutionValue; } /** * The coverage identifier * */ public String getCoverageId() { return coverageId; } /** * Scan the metadataMap looking for resources related to {@link DimensionInfo} objects and * return a dimensions Map. Return an empty map if no dimensions are found. * * @param metadata * */ public static Map<String, DimensionInfo> getDimensionsFromMetadata(MetadataMap metadata) { Map<String, DimensionInfo> dimensionsMap = new HashMap<String, DimensionInfo>(); if (metadata != null && !metadata.isEmpty()) { final Set<String> metadataKeys = metadata.keySet(); final Iterator<String> metadataIterator = metadataKeys.iterator(); // loop over metadata keys while (metadataIterator.hasNext()) { String key = metadataIterator.next(); if (isADimension(key)) { // Check whether the specified metadata is related to an enabled Dimension DimensionInfo dimension = metadata.get(key, DimensionInfo.class); if (dimension != null && dimension.isEnabled()) { if (key.startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)) { key = key.substring(ResourceInfo.CUSTOM_DIMENSION_PREFIX.length()); } dimensionsMap.put(key, dimension); } } } } return dimensionsMap; } public static DimensionDescriptor getDimensionDescriptor(final StructuredGridCoverage2DReader reader, final String coverageName, final String dimensionName) { try { List<DimensionDescriptor> descriptors = reader.getDimensionDescriptors(coverageName); for (DimensionDescriptor dd : descriptors) { if (dd.getName().equalsIgnoreCase(dimensionName)) { return dd; } } return null; } catch(IOException e) { throw new WCS20Exception("Failed to locate the reader's " + dimensionName + " dimension descriptor", e); } } /** * Return {@code true} in case the specified Key refers to a Dimension. * @param key * */ private final static boolean isADimension(final String key) { return key != null && (key.equals(ResourceInfo.TIME) || key.equals(ResourceInfo.ELEVATION) || key .startsWith(ResourceInfo.CUSTOM_DIMENSION_PREFIX)); } }