/*
* Copyright 1998-2014 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package thredds.server.opendap;
import opendap.dap.DAPNode;
import ucar.nc2.*;
import ucar.ma2.DataType;
import opendap.servers.*;
import opendap.dap.BaseType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.NetcdfDataset;
import java.util.*;
/**
* NcDDS is a specialization of ServerDDS for netcdf files.
* This creates a ServerDDS from the netcdf file.
*
* @author jcaron
*/
public class NcDDS extends ServerDDS {
// Handle the case of potential grids when the array has duplicate dims
static protected final boolean HANDLE_DUP_DIM_GRIDS = true;
static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NcDDS.class);
//private HashMap<String, BaseType> coordHash = new HashMap<String, BaseType>(50); // non grid coordinate variables
// Track various subsets of the variables
private Map<String, Variable> coordvars = new HashMap<>(50);
private List<Variable> ddsvars = new ArrayList<>(50); // list of currently active variables
private Map<String, Variable> gridarrays = new HashMap<>(50);
private Map<String, Variable> used = new HashMap<>(50);
private Variable findVariable(String name) {
for (Variable v : ddsvars) {
if (v.getFullName().equals(name)) return v;
}
return null;
}
/**
* Constructor
*
* @param name name of the dataset, at bottom of DDS
* @param ncfile create DDS from this
*/
public NcDDS(String name, NetcdfFile ncfile) {
super((name));
if (ncfile instanceof NetcdfDataset) {
NetcdfDataset ncd = (NetcdfDataset) ncfile;
if (ncd.getEnhanceMode().contains(NetcdfDataset.Enhance.CoordSystems)) {
createFromDataset((NetcdfDataset) ncfile);
return;
}
}
createFromFile(ncfile);
}
private void createFromFile(NetcdfFile ncfile) {
// dup the variable set
ddsvars = new ArrayList<>(ncfile.getVariables());
// get coordinate variables
for (Object o : ncfile.getDimensions()) {
Dimension dim = (Dimension) o;
Variable cv = findVariable(dim.getShortName());
if ((cv != null) && cv.isCoordinateVariable()) {
coordvars.put(dim.getShortName(), cv);
if (log.isDebugEnabled())
log.debug(" NcDDS adding coordinate variable " + cv.getFullName() + " for dimension " + dim.getShortName());
}
}
// collect grid array variables and set of used (in grids) coordinate variables
for (Variable v : ddsvars) {
boolean isgridarray = (v.getRank() > 1) && (v.getDataType() != DataType.STRUCTURE) && (v.getParentStructure() == null);
if (!isgridarray) continue;
List<Dimension> dimset = v.getDimensions();
int rank = dimset.size();
for (int i = 0; isgridarray && i < rank; i++) {
Dimension dim = dimset.get(i);
if (dim.getShortName() == null)
isgridarray = false;
else {
Variable gv = coordvars.get(dim.getShortName());
if (gv == null)
isgridarray = false;
}
if (HANDLE_DUP_DIM_GRIDS) {
// Check for duplicate dims
for (int j = i + 1; isgridarray && j < rank; j++) {
if (dimset.get(j) == dim)
isgridarray = false;
}
}
}
if (isgridarray) {
gridarrays.put(v.getFullName(), v);
for (Dimension dim : dimset) {
Variable gv = coordvars.get(dim.getShortName());
if (gv != null)
used.put(gv.getFullName(), gv);
}
}
}
// remove the used coord vars from ddsvars (wrong for now; keep so that coord vars are top-level also)
// for(Variable v: used.values()) ddsvars.remove(v);
// Create the set of variables
for (Object o1 : ddsvars) {
Variable cv = (Variable) o1;
BaseType bt;
if (false && cv.isCoordinateVariable()) {
if ((cv.getDataType() == DataType.CHAR))
bt = (cv.getRank() > 1) ? new NcSDCharArray(cv) : new NcSDString(cv);
else
bt = new NcSDArray(cv, createScalarVariable(ncfile, cv));
}
//if (bt == null)
bt = createVariable(ncfile, cv);
addVariable(bt);
}
}
// take advantage of the work already done by NetcdfDataset
private void createFromDataset(NetcdfDataset ncd) {
// get coordinate variables, disjunct from variables
for (CoordinateAxis axis : ncd.getCoordinateAxes()) {
coordvars.put(axis.getShortName(), axis);
}
// dup the variable set
ddsvars = new ArrayList<>(50);
// collect grid array variables and set of coordinate variables used in grids
for (Variable v : ncd.getVariables()) {
if (coordvars.containsKey(v.getShortName())) continue; // skip coordinate variables
ddsvars.add(v);
boolean isgridarray = (v.getRank() > 1) && (v.getDataType() != DataType.STRUCTURE) && (v.getParentStructure() == null);
if (!isgridarray) continue;
List<Dimension> dimset = v.getDimensions();
int rank = dimset.size();
for (int i = 0; isgridarray && i < rank; i++) {
Dimension dim = dimset.get(i);
if (dim.getShortName() == null)
isgridarray = false;
else {
Variable gv = coordvars.get(dim.getShortName());
if (gv == null)
isgridarray = false;
}
}
if (isgridarray) {
gridarrays.put(v.getFullName(), v);
for (Dimension dim : dimset) {
Variable gv = coordvars.get(dim.getShortName());
if (gv != null)
used.put(gv.getFullName(), gv);
}
}
}
// Create the set of coordinates
for (Variable cv : ncd.getCoordinateAxes()) {
BaseType bt = createVariable(ncd, cv);
addVariable(bt);
}
// Create the set of variables
for (Variable cv : ddsvars) {
BaseType bt = createVariable(ncd, cv);
addVariable(bt);
}
}
// turn Variable into opendap variable
private BaseType createVariable(NetcdfFile ncfile, Variable v) {
BaseType bt;
if (v.getRank() == 0) // scalar
bt = createScalarVariable(ncfile, v);
else if (v.getDataType() == DataType.CHAR) {
if (v.getRank() > 1)
bt = new NcSDCharArray(v);
else
bt = new NcSDString(v);
} else if (v.getDataType() == DataType.STRING) {
if (v.getRank() == 0)
bt = new NcSDString(v);
else
bt = new NcSDArray(v, new NcSDString(v));
} else // non-char multidim array
bt = createArray(ncfile, v);
return bt;
}
private BaseType createScalarVariable(NetcdfFile ncfile, Variable v) {
DataType dt = v.getDataType();
if (dt == DataType.DOUBLE)
return new NcSDFloat64(v);
else if (dt == DataType.FLOAT)
return new NcSDFloat32(v);
else if (dt == DataType.INT)
return v.isUnsigned() ? new NcSDUInt32(v) : new NcSDInt32(v);
else if (dt == DataType.SHORT)
return v.isUnsigned() ? new NcSDUInt16(v) : new NcSDInt16(v);
else if (dt == DataType.BYTE)
return new NcSDByte(v);
else if (dt == DataType.CHAR)
return new NcSDString(v);
else if (dt == DataType.STRING)
return new NcSDString(v);
else if (dt == DataType.STRUCTURE)
return createStructure(ncfile, (Structure) v);
else
throw new UnsupportedOperationException("NcDDS Variable data type = " + dt);
}
private BaseType createArray(NetcdfFile ncfile, Variable v) {
// all dimensions must have coord vars to be a grid, also must have the same name
// no dim can be duplicated.
boolean isGrid = (gridarrays.get(v.getFullName()) != null);
NcSDArray arr = new NcSDArray(v, createScalarVariable(ncfile, v));
if (isGrid) {
ArrayList<BaseType> list = new ArrayList<>();
list.add(arr); // Array is first element in the list
List<Dimension> dimset = v.getDimensions();
for (Dimension dim : dimset) {
Variable v1 = used.get(dim.getShortName());
assert (v1 != null);
BaseType bt;
if ((v1.getDataType() == DataType.CHAR))
bt = (v1.getRank() > 1) ? new NcSDCharArray(v1) : new NcSDString(v1);
else
bt = new NcSDArray(v1, createScalarVariable(ncfile, v1));
list.add(bt);
}
return new NcSDGrid(v.getShortName(), list);
} else
return arr;
}
private BaseType createStructure(NetcdfFile ncfile, Structure s) {
ArrayList<BaseType> list = new ArrayList<>();
for (Object o : s.getVariables()) {
Variable nested = (Variable) o;
list.add(createVariable(ncfile, nested));
}
return new NcSDStructure(s, list);
}
/**
* Returns a clone of this <code>?</code>.
* See BaseType.cloneDAG()
*
* @param map track previously cloned nodes
* @return a clone of this object.
*/
public DAPNode cloneDAG(CloneMap map)
throws CloneNotSupportedException {
NcDDS d = (NcDDS) super.cloneDAG(map);
d.coordvars = coordvars;
return d;
}
}