ucar.nc2.ft.point.standard.Table Maven / Gradle / Ivy
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.nc2.ft.point.standard;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Formatter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ucar.ma2.Array;
import ucar.ma2.ArrayChar;
import ucar.ma2.ArraySequence;
import ucar.ma2.ArrayStructure;
import ucar.ma2.ArrayStructureMA;
import ucar.ma2.ArrayStructureW;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Section;
import ucar.ma2.StructureData;
import ucar.ma2.StructureDataIterator;
import ucar.ma2.StructureDataIteratorMediated;
import ucar.ma2.StructureDataMediator;
import ucar.ma2.StructureDataProxy;
import ucar.ma2.StructureDataW;
import ucar.ma2.StructureMembers;
import ucar.nc2.*;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.StructureDS;
import ucar.nc2.dataset.StructurePseudo2Dim;
import ucar.nc2.dataset.StructurePseudoDS;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.ft.point.StructureDataIteratorIndexed;
import ucar.nc2.ft.point.StructureDataIteratorLinked;
/**
* A generalization of a Structure. Main function is to return a StructureDataIterator,
* iterating over its table rows
*
* @author caron
* @since Jan 20, 2009
*/
public abstract class Table {
static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Table.class);
public enum CoordName {
Lat, Lon, Elev, Time, TimeNominal, StnId, StnDesc, WmoId, StnAlt, FeatureId, MissingVar
}
public enum Type {
ArrayStructure, Construct, Contiguous, LinkedList,
MultidimInner, MultidimInner3D, MultidimInnerPsuedo, MultidimInnerPsuedo3D, MultidimStructure,
NestedStructure, ParentId, ParentIndex, Singleton, Structure, Top
}
public static Table factory(NetcdfDataset ds, TableConfig config) {
switch (config.type) {
case ArrayStructure: // given array of StructureData, stored in config.as
return new TableArrayStructure(ds, config);
case Construct: // construct the table from its children - theres no seperate station table, stn info is duplicated in the obs structure.
return new TableConstruct(ds, config);
case Contiguous: // contiguous list of child record, using indexes
return new TableContiguous(ds, config);
case LinkedList: // linked list of child records, using indexes
return new TableLinkedList(ds, config);
case MultidimInner: // the inner struct of a 2D multdim(outer, inner) with unlimited dimension
return new TableMultidimInner(ds, config);
case MultidimInner3D: // the inner struct of a 3D multdim(outer, middle, inner) with unlimited dimension
return new TableMultidimInner3D(ds, config);
case MultidimStructure: // the outer struct of a multidim structure
return new TableMultidimStructure(ds, config);
case MultidimInnerPsuedo: // the inner struct of a 2D multdim(outer, inner) without the unlimited dimension
// the middle struct of a 3D multdim(outer, middle, inner) without the unlimited dimension
return new TableMultidimInnerPsuedo(ds, config);
case MultidimInnerPsuedo3D: // the inner struct of a 3D multdim(outer, middle, inner) without the unlimited dimension
return new TableMultidimInnerPsuedo3D(ds, config);
case NestedStructure: // Structure or Sequence is nested in the parent
return new TableNestedStructure(ds, config);
case ParentId: // child record has an id for the parent.
return new TableParentId(ds, config);
case ParentIndex: // child record has the record index of the parent.
return new TableParentIndex(ds, config);
case Singleton: // singleton row, with given StructureData
return new TableSingleton(ds, config);
case Structure: // Structure or PsuedoStructure
return new TableStructure(ds, config);
case Top: // singleton consisting of top variables and constants
return new TableTop(ds, config);
}
throw new IllegalStateException("Unimplemented Table type = " + config.type);
}
////////////////////////////////////////////////////////////////////////////////////////
String name;
FeatureType featureType;
Table parent, child;
List extraJoins;
String lat, lon, elev, time, timeNominal;
String stnId, stnDesc, stnNpts, stnWmoId, stnAlt, limit;
String feature_id, missingVar;
Map cols = new HashMap<>(); // all variables
Set nondataVars = new HashSet<>(); // exclude these from the getDataVariables() list
protected Table(NetcdfDataset ds, TableConfig config) {
this.name = config.name;
this.featureType = config.featureType;
this.lat = config.lat;
this.lon = config.lon;
this.elev = config.elev;
this.time = config.time;
this.timeNominal = config.timeNominal;
this.stnId = config.stnId;
this.stnDesc = config.stnDesc;
this.stnNpts = config.stnNpts;
this.stnWmoId = config.stnWmoId;
this.stnAlt = config.stnAlt;
this.limit = config.limit;
this.feature_id = config.feature_id;
this.missingVar = config.missingVar;
if (config.parent != null) {
parent = Table.factory(ds, config.parent);
parent.child = this;
}
this.extraJoins = config.extraJoin;
// try to exclude coordinate vars and "structural data" from the list of data variables
addNonDataVariable(config.time);
addNonDataVariable(config.lat);
addNonDataVariable(config.lon);
addNonDataVariable(config.elev);
addNonDataVariable(config.timeNominal);
addNonDataVariable(config.stnId);
addNonDataVariable(config.stnDesc);
addNonDataVariable(config.stnWmoId);
addNonDataVariable(config.stnAlt);
addNonDataVariable(config.stnNpts);
addNonDataVariable(config.limit);
addNonDataVariable(config.feature_id);
addNonDataVariable(config.parentIndex);
addNonDataVariable(config.start);
addNonDataVariable(config.next);
addNonDataVariable(config.numRecords);
}
protected void addNonDataVariable(String name) {
if (name != null)
nondataVars.add(name);
}
// change shape of the data variables
protected void replaceDataVars(StructureMembers sm) {
for (StructureMembers.Member m : sm.getMembers()) {
VariableSimpleIF org = this.cols.get(m.getName());
int rank = org.getRank();
List orgDims = org.getDimensions();
// only keep the last n
int n = m.getShape().length;
List dims = orgDims.subList(rank-n, rank);
VariableSimpleImpl result = new VariableSimpleImpl(org.getShortName(), org.getDescription(), org.getUnitsString(), org.getDataType(), dims);
for (Attribute att : org.getAttributes()) result.add(att);
this.cols.put(m.getName(), result);
}
}
/**
* Iterate over the rows of this table. Subclasses must implement this.
*
* @param cursor state of comlpete iteration. Table implementations may not modify.
* @return iterater over the rows of this table.
* @throws IOException on read error
*/
abstract public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException;
String findCoordinateVariableName(CoordName coordName) {
switch (coordName) {
case Elev:
return elev;
case Lat:
return lat;
case Lon:
return lon;
case Time:
return time;
case TimeNominal:
return timeNominal;
case StnId:
return stnId;
case StnDesc:
return stnDesc;
case WmoId:
return stnWmoId;
case StnAlt:
return stnAlt;
case FeatureId:
return feature_id;
case MissingVar:
return missingVar;
}
return null;
}
///////////////////////////////////////////////////////
/**
* A Structure, PsuedoStructure, or Sequence.
*
* Structure: defined by config.structName.
* if config.vars if not null restricts to list of vars, must be members.
*
* PsuedoStructure: defined by variables with outer dimension = config.dim
* So we find all Variables with signature v(outDim, ...) and make them into
*
* Structure {
* v1(...);
* v2(...);
* } s
*
* config.vars if not null restricts to list of vars, must be members.
*/
public static class TableStructure extends Table {
StructureDS struct;
Dimension dim, outer;
TableConfig.StructureType stype;
TableStructure(NetcdfDataset ds, TableConfig config) {
super(ds, config);
this.stype = config.structureType;
switch (config.structureType) {
case Structure:
struct = (StructureDS) ds.findVariable(config.structName);
if (struct == null)
throw new IllegalStateException("Cant find Structure " + config.structName);
dim = struct.getDimension(0);
if (config.vars != null)
struct = (StructureDS) struct.select(config.vars); // limit to list of vars
break;
case PsuedoStructure:
this.dim = ds.findDimension(config.dimName);
assert dim != null;
String name = config.structName == null ? "anon" : config.structName;
struct = new StructurePseudoDS(ds, dim.getGroup(), name, config.vars, this.dim);
break;
case PsuedoStructure2D:
this.dim = ds.findDimension(config.dimName);
this.outer = ds.findDimension(config.outerName);
assert dim != null;
assert config.outerName != null;
struct = new StructurePseudo2Dim(ds, dim.getGroup(), config.structName, config.vars, this.dim, this.outer);
break;
}
config.vars = new ArrayList<>();
for (Variable v : struct.getVariables()) {
// remove substructures
if (v.getDataType() == DataType.STRUCTURE) {
if (config.structureType == TableConfig.StructureType.PsuedoStructure)
struct.removeMemberVariable(v);
} else {
this.cols.put(v.getShortName(), v);
config.vars.add(v.getShortName());
}
}
}
@Override
protected void showTableExtraInfo(String indent, Formatter f) {
f.format("%sstruct=%s, dim=%s type=%s%n", indent, struct.getNameAndDimensions(), dim.getShortName(), struct.getClass().getName());
}
@Override
public VariableDS findVariable(String axisName) {
String structPrefix = struct.getShortName() + ".";
if (axisName.startsWith(structPrefix))
axisName = axisName.substring(structPrefix.length());
return (VariableDS) struct.findVariable(axisName);
}
@Override
public String showDimension() {
return dim.getShortName();
}
@Override
public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException {
return new StructureDataIteratorMediated(struct.getStructureIterator(), new RestrictToColumns());
}
@Override
public String getName() {
return stype.toString() + "(" + struct.getShortName() + ")";
}
}
private class RestrictToColumns implements StructureDataMediator {
StructureMembers members;
@Override
public StructureData modify(StructureData sdata) {
// make members restricted to column names
if (members == null) {
StructureMembers orgMembers = sdata.getStructureMembers();
members = new StructureMembers(orgMembers.getName() + "RestrictToColumns");
for (String colName : cols.keySet()) {
StructureMembers.Member m = orgMembers.findMember(colName);
if (m == null)
throw new IllegalStateException("Cant find " + colName);
members.addMember(m);
}
}
return new StructureDataProxy(members, sdata);
}
}
///////////////////////////////////////////////////////
/**
* ArrayStructure is passed in config.as
* Used by
* UnidataPointFeature: type StationProfile (removed now)
*/
public static class TableArrayStructure extends Table {
ArrayStructure as;
Dimension dim;
TableArrayStructure(NetcdfDataset ds, TableConfig config) {
super(ds, config);
assert (config.as != null);
this.as = config.as;
this.dim = new Dimension(config.structName, (int) config.as.getSize(), false);
for (StructureMembers.Member m : config.as.getStructureMembers().getMembers())
cols.put(m.getName(), new VariableSimpleAdapter(m));
}
@Override
protected void showTableExtraInfo(String indent, Formatter f) {
f.format("%sArrayStruct=%s, dim=%s%n", indent, new Section(as.getShape()), dim.getShortName());
}
@Override
public String showDimension() {
return dim.getShortName();
}
@Override
public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException {
return as.getStructureDataIterator();
}
@Override
public String getName() {
return "ArrayStructure(" + name + ")";
}
}
///////////////////////////////////////////////////////
/**
* When theres no seperate station table, but info is duplicated in the obs structure.
* Must have a ParentId child table
* No variables are added to cols.
*
* Used by:
* BufrCdm StationProfile type
*/
public static class TableConstruct extends Table {
ArrayStructure as; // injected by TableParentId
TableConstruct(NetcdfDataset ds, TableConfig config) {
super(ds, config);
}
@Override
protected void showTableExtraInfo(String indent, Formatter f) {
}
@Override
public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException {
return as.getStructureDataIterator();
}
@Override
public String getName() {
return "Constructed";
}
}
///////////////////////////////////////////////////////
/**
* Contiguous children, using start and numRecords variables in the parent. This assumes column store.
* TableContiguous is the children, config.struct describes the cols.
*
* Used by:
* UnidataPointObs
* CFPointObs
*/
public static class TableContiguous extends TableStructure {
private String startVarName; // variable name holding the starting index in parent
private String numRecordsVarName; // variable name holding the number of children in parent
private int[] startIndex, numRecords;
private NetcdfDataset ds;
private boolean isInit;
TableContiguous(NetcdfDataset ds, TableConfig config) {
super(ds, config);
this.ds = ds;
startVarName = config.getStart();
numRecordsVarName = config.getNumRecords();
addNonDataVariable(startVarName);
addNonDataVariable(numRecordsVarName);
}
private void init() {
if (startVarName == null) { // read numRecords when startVar is not known LOOK this should be deffered
try {
Variable v = ds.findVariable(numRecordsVarName);
Array numRecords = v.read();
int n = (int) numRecords.getSize();
// construct the start variable
this.numRecords = new int[n];
this.startIndex = new int[n];
int i = 0;
int count = 0;
while (numRecords.hasNext()) {
this.startIndex[i] = count;
this.numRecords[i] = numRecords.nextInt();
count += this.numRecords[i];
i++;
}
isInit = true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
protected void showTableExtraInfo(String indent, Formatter f) {
f.format("%sstart=%s, numRecords=%s%n", indent, startVarName, numRecordsVarName);
}
@Override
public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException {
if (!isInit) init();
int firstRecno, numrecs;
StructureData parentStruct = cursor.getParentStructure();
if (startIndex != null) {
int parentIndex = cursor.getParentRecnum();
firstRecno = startIndex[parentIndex];
numrecs = numRecords[parentIndex];
} else {
firstRecno = parentStruct.getScalarInt(startVarName);
numrecs = parentStruct.getScalarInt(numRecordsVarName);
}
return new StructureDataIteratorLinked(struct, firstRecno, numrecs, null);
}
@Override
public String getName() {
return "Contig(" + numRecordsVarName + ")";
}
}
///////////////////////////////////////////////////////
/**
* The children have a field containing the index of the parent.
* For efficiency, we scan this data and construct an IndexMap( parentIndex -> list of children),
* i.e. we compute the inverse link, parent -> children.
* TableParentIndex is the children, config.struct describes the cols.
*
* Used by:
* CFPointObs
*/
public static class TableParentIndex extends TableStructure {
private Map> indexMap;
private String parentIndexName;
TableParentIndex(NetcdfDataset ds, TableConfig config) {
super(ds, config);
this.parentIndexName = config.parentIndex;
// construct the map
try {
Variable rpIndex = ds.findVariable(config.parentIndex);
Array index = rpIndex.read();
int childIndex = 0;
this.indexMap = new HashMap<>((int) (2 * index.getSize()));
while (index.hasNext()) {
int parent = index.nextInt();
List list = indexMap.get(parent);
if (list == null) {
list = new ArrayList<>();
indexMap.put(parent, list);
}
list.add(childIndex);
childIndex++;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
addNonDataVariable(config.parentIndex);
}
@Override
protected void showTableExtraInfo(String indent, Formatter f) {
f.format("%sparentIndexName=%s, indexMap.size=%d%n", indent, parentIndexName, indexMap.size());
}
@Override
public StructureDataIterator getStructureDataIterator(Cursor cursor) throws IOException {
int parentIndex = cursor.getParentRecnum();
List index = indexMap.get(parentIndex);
if (index == null) index = new ArrayList<>();
return new StructureDataIteratorIndexed(struct, index);
}
@Override
public String getName() {
return "Indexed(" + parentIndexName + ")";
}
}
///////////////////////////////////////////////////////
/**
* The children have a field containing the id of the parent.
* For efficiency, we scan this data and construct an IndexMap( parentIndex -> list of children),
* i.e. we compute the inverse link, parent -> children.
* TableParentIndex is the children, config.struct describes the cols.
*
* Used by:
* CFPointObs
*/
public static class TableParentId extends TableStructure {
private ParentInfo[] indexMap;
private String parentIdName;
TableParentId(NetcdfDataset ds, TableConfig config) {
super(ds, config);
this.parentIdName = config.parentIndex;
// construct the hash of unique parents, based on the id variable
Map