/* (c) 2014-2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.wcs.responses; import java.sql.Timestamp; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.geoserver.wcs2_0.response.DimensionBean; import org.geoserver.wcs2_0.response.DimensionBean.DimensionType; import org.geotools.coverage.io.netcdf.crs.NetCDFCoordinateReferenceSystemType.NetCDFCoordinate; import org.geotools.imageio.netcdf.utilities.NetCDFUtilities; import org.geotools.util.DateRange; import org.geotools.util.NumberRange; import ucar.ma2.Array; import ucar.ma2.DataType; import ucar.ma2.Index; import ucar.nc2.Dimension; /** * Provides mapping between a Coverage {@link DimensionBean}, a NetCDF {@link Dimension} * as well as the related dimension values (the coordinates). * * @author Daniele Romagnoli, GeoSolutions SAS * */ class NetCDFDimensionsManager { /** * A dimension mapping between dimension names and dimension mapper * instances We use a Linked map to preserve the dimension order */ private Map<String, NetCDFDimensionMapping> netcdfDimensions = new LinkedHashMap<String, NetCDFDimensionMapping>(); public final int getNumDimensions() { return netcdfDimensions.keySet().size(); } public void add(String name, NetCDFDimensionMapping mapper) { netcdfDimensions.put(name, mapper); } public Collection<NetCDFDimensionMapping> getDimensions() { return netcdfDimensions.values(); } public void addDimensions (Map<String, NetCDFDimensionMapping> mapping) { netcdfDimensions.putAll(mapping); } public void dispose() { if (netcdfDimensions != null) { netcdfDimensions.clear(); netcdfDimensions = null; } } /** * A NetCDFDimensionMapping class to associate to a dimension: * - the input coverageDimension, * - the output netCDFDimension, * - the available values for the coordinate variable of that dimension */ static class NetCDFDimensionMapping { public NetCDFDimensionMapping(String name) { super(); this.name = name; } /** * The available (sorted) values for a Dimension */ private DimensionValues dimensionValues; /** * The input coverage Dimension (a {@link DimensionBean} instance) */ private DimensionBean coverageDimension; /** * The output netCDF dimension (a {@link Dimension} instance) */ private Dimension netCDFDimension; /** * the name of this dimension manager */ private String name; @Override public String toString() { return "NetCDFDimensionMapping [name=" + name + " coverageDimension=" + coverageDimension + ", netCDFDimension=" + netCDFDimension + "]"; } public DimensionValues getDimensionValues() { return dimensionValues; } public void setDimensionValues(DimensionValues dimensionValues) { this.dimensionValues = dimensionValues; } public DimensionBean getCoverageDimension() { return coverageDimension; } public void setCoverageDimension(DimensionBean coverageDimension) { this.coverageDimension = coverageDimension; } public Dimension getNetCDFDimension() { return netCDFDimension; } public void setNetCDFDimension(Dimension netCDFDimension) { this.netCDFDimension = netCDFDimension; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * A simple interface to deal with the values of a Dimension */ interface DimensionValues { abstract Object getValues(); abstract void addValue(Object object); abstract int getSize(); } /** * A DimensionValues based on Set of objects * */ static class DimensionValuesSet implements DimensionValues { Set<Object> values; public DimensionValuesSet(Set<Object> set) { values = set; } @Override public Set getValues() { return values; } @Override public void addValue(Object object) { values.add(object); } @Override public int getSize() { return values.size(); } } /** * A DimensionValues based on Array */ static class DimensionValuesArray implements DimensionValues { Array values; public DimensionValuesArray(Array data) { values = data; } @Override public Array getValues() { return (Array) values; } @Override public void addValue(Object object) { throw new UnsupportedOperationException(); } @Override public int getSize() { return (int) values.getSize(); } } public void dispose() { dimensionValues = null; netCDFDimension = null; } /** * Get the values for a Dimension wrapped by its specific DimensionManager and return them as * a NetCDF Array object * * @param rangeValues specify whether the data should be returned as a 1D array or a 2D array * (the latter for dimensions having ranges) * @param netCDFCoordinates used to check whether a dimension is related to a coordinate. In that case, * just return the coordinate values. * */ public Array getDimensionData(final boolean rangeValues, NetCDFCoordinate[] netCDFCoordinates) { final String dimensionName = getName(); // Special management for latitude and logitude boolean is2DCoordinate = false; if (netCDFCoordinates != null && netCDFCoordinates.length > 0) { for (NetCDFCoordinate coordinate : netCDFCoordinates) { if (dimensionName.equalsIgnoreCase(coordinate.getDimensionName())) { is2DCoordinate = true; break; } } } if (is2DCoordinate) { return (Array) getDimensionValues().getValues(); } else { // Get Dimension information DimensionBean bean = getCoverageDimension(); DimensionType type = bean.getDimensionType(); final String dataType = bean.getDatatype(); boolean isTime = false; if (type == DimensionType.TIME || NetCDFUtilities.isATime(dataType)) { isTime = true; } // Get Dimension values final DimensionValues dimensionValues = getDimensionValues(); final Set<Object> values = (Set<Object>) dimensionValues.getValues(); final int numElements = values.size(); final String dimensionDataType = getCoverageDimension().getDatatype(); final DataType netCDFDataType = NetCDFUtilities.getNetCDFDataType(dimensionDataType); // Get a proper array to contain the dimension values final int[] dimensionSize = rangeValues ? new int[] { numElements, 2 } : new int[] {numElements}; final Array data = NetCDFUtilities.getArray(dimensionSize, netCDFDataType); final Index index = data.getIndex(); final Iterator<Object> valuesIterator = values.iterator(); final int indexing[] = new int[rangeValues ? 2: 1]; // Setting array values for (int pos = 0; pos < numElements; pos++) { indexing[0] = pos; Object value = valuesIterator.next(); data.setObject(index.set(indexing), getValue(value, isTime, false)); if (rangeValues) { indexing[1] = 1; data.setObject(index.set(indexing), getValue(value, isTime, rangeValues)); indexing[1] = 0; } } return data; } } /** * Get the value from the input object. Take care of time elements since they need * to be referred to the time origin * * @param input * @param isTime does this object represents a temporal entity? * @param endValue specify whether it needs to return the second value of a range * */ private Object getValue(Object input, boolean isTime, boolean endValue) { if (isTime) { return getTime(input, endValue); } else if (input instanceof NumberRange) { NumberRange range = (NumberRange) input; return endValue ? range.getMaxValue() : range.getMinValue(); } //Simply return back the value return input; } /** * Return the time value for this object. Note that times are referred with respect to * an origin {@link NetCDFUtilities#START_TIME}. * @param input * @param endTime specify whether it needs to return the second value of a time range * */ private Double getTime(Object input, boolean endTime) { long time = 0; if (input instanceof Timestamp) { time = ((Timestamp) input).getTime(); } else if (input instanceof DateRange) { if (!endTime) { time = ((DateRange) input).getMinValue().getTime(); } else { time = ((DateRange) input).getMaxValue().getTime(); } } else if (input instanceof Date){ time = ((Date)input).getTime(); } else { throw new IllegalArgumentException("Unsupported time"); } // Convert to seconds since START_TIME return ((double)(time - NetCDFUtilities.START_TIME))/1000d; } } }