ucar.nc2.dataset.VariableDS Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/
package ucar.nc2.dataset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.dataset.NetcdfDataset.Enhance;
import ucar.nc2.filter.*;
import ucar.nc2.internal.dataset.CoordinatesHelper;
import ucar.nc2.util.CancelTask;
import ucar.nc2.util.Misc;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
/**
* A wrapper around a Variable, creating an "enhanced" Variable. The original Variable is used for the I/O.
* There are several distinct uses:
*
* - Handle scale/offset/missing/enum/unsigned conversion; this can change DataType and data values
* - Container for coordinate system information
* - NcML modifications to underlying Variable
*
*
* @author caron
* @see NetcdfDataset
*/
public class VariableDS extends Variable implements VariableEnhanced, EnhanceScaleMissingUnsigned {
/**
* Constructor when there's no underlying variable.
* You must also set the values by doing one of:
*
* - set the values with setCachedData()
* - set a proxy reader with setProxyReader()
*
* Otherwise, it is assumed to have constant values (using the fill value)
*
* @param ds the containing dataset
* @param group the containing group
* @param parentStructure the containing Structure (may be null)
* @param shortName the (short) name
* @param dataType the data type
* @param dims list of dimension names, these must already exist in the Group; empty String = scalar
* @param units String value of units, may be null
* @param desc String value of description, may be null
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
public VariableDS(NetcdfDataset ds, Group group, Structure parentStructure, String shortName, DataType dataType,
String dims, String units, String desc) {
super(ds, group, parentStructure, shortName);
setDataType(dataType);
setDimensions(dims);
this.orgDataType = dataType;
if (dataType == DataType.STRUCTURE)
throw new IllegalArgumentException("VariableDS must not wrap a Structure; name=" + shortName);
if (units != null)
addAttribute(new Attribute(CDM.UNITS, units.trim()));
if (desc != null)
addAttribute(new Attribute(CDM.LONG_NAME, desc));
this.enhanceProxy = new EnhancementsImpl(this, units, desc);
}
/**
* Make a new VariableDS, delegate data reading to the original variable, but otherwise
* dont take any info from it. This is used by NcML explicit mode.
*
* @param group the containing group; may not be null
* @param parent parent Structure, may be null
* @param shortName variable shortName, must be unique within the Group
* @param orgVar the original Variable to wrap. The original Variable is not modified.
* Must not be a Structure, use StructureDS instead.
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
public VariableDS(Group group, Structure parent, String shortName, Variable orgVar) {
super(null, group, parent, shortName);
setDimensions(getDimensionsString()); // reset the dimensions
if (orgVar instanceof Structure)
throw new IllegalArgumentException("VariableDS must not wrap a Structure; name=" + orgVar.getFullName());
// dont share cache, iosp : all IO is delegated
this.ncfile = null;
this.spiObject = null;
createNewCache();
this.orgVar = orgVar;
this.orgDataType = orgVar.getDataType();
this.enhanceProxy = new EnhancementsImpl(this);
}
/**
* Wrap the given Variable, making it into a VariableDS.
* Delegate data reading to the original variable.
* Take all metadata from original variable.
* Does not share cache, iosp.
*
* @param g logical container, if null use orgVar's group
* @param orgVar the original Variable to wrap. The original Variable is not modified.
* Must not be a Structure, use StructureDS instead.
* @param enhance if true, use NetcdfDataset.defaultEnhanceMode to define what enhancements are made.
* Note that this can change DataType and data values.
* You can also call enhance() later. If orgVar is VariableDS, then enhance is inherited from there,
* and this parameter is ignored.
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
public VariableDS(Group g, Variable orgVar, boolean enhance) {
super(orgVar);
if (g != null)
setParentGroup(g); // otherwise super() sets group; this affects the long name and the dimensions.
setDimensions(getDimensionsString()); // reset the dimensions
if (orgVar instanceof Structure)
throw new IllegalArgumentException("VariableDS must not wrap a Structure; name=" + orgVar.getFullName());
// dont share cache, iosp : all IO is delegated
this.ncfile = null;
this.spiObject = null;
createNewCache();
this.orgVar = orgVar;
this.orgDataType = orgVar.getDataType();
this.orgFileTypeId = orgVar.getFileTypeId();
this.enhanceProxy = new EnhancementsImpl(this);
if (enhance) {
enhance(NetcdfDataset.getDefaultEnhanceMode());
}
// Add this so that old VariableDS units agrees with new VariableDS units.
String units = orgVar.getUnitsString();
if (units != null) {
setUnitsString(units.trim());
}
}
/**
* Copy constructor, for subclasses.
* Used by copy() and CoordinateAxis
* Share everything except the coord systems.
*
* @param vds copy from here.
* @param isCopy called from copy()
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
protected VariableDS(VariableDS vds, boolean isCopy) {
super(vds);
this.orgVar = vds;
this.orgDataType = vds.orgDataType;
this.orgName = vds.orgName;
this.enhanceMode = vds.enhanceMode;
this.enhanceProxy = new EnhancementsImpl(this); // decouple coordinate systems
this.unsignedConversion = vds.unsignedConversion;
this.scaleOffset = vds.scaleOffset;
this.convertMissing = vds.convertMissing;
this.fillValue = vds.getFillValue();
this.hasFillValue = vds.hasFillValue();
// Add this so that old VariableDS units agrees with new VariableDS units.
String units = vds.getUnitsString();
if (units != null) {
setUnitsString(units.trim());
}
if (!isCopy) {
createNewCache(); // dont share cache unless its a copy
}
}
@Override
public NetcdfFile getNetcdfFile() {
// TODO can group really be null? Variable says no.
return group == null ? null : group.getNetcdfFile();
}
// for section and slice
/** @deprecated Use {@link #toBuilder()} */
@Deprecated
@Override
protected VariableDS copy() {
return new VariableDS(this, true);
}
/**
* Remove coordinate system info.
*
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
@Override
public void clearCoordinateSystems() {
this.enhanceProxy = new EnhancementsImpl(this, getUnitsString(), getDescription());
}
/**
* Calculate scale/offset/missing/enum/unsigned value info. This may change the DataType.
*
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
@Override
public void enhance(Set enhancements) {
this.enhanceMode = EnumSet.copyOf(enhancements);
// this.enhanceMode will only contain enhancements not already applied to orgVar.
if (orgVar instanceof VariableDS) {
for (Enhance orgVarEnhancement : ((VariableDS) orgVar).getEnhanceMode()) {
this.enhanceMode.remove(orgVarEnhancement);
}
}
// enhance() may have been called previously, with a different enhancement set.
// So, we need to reset to default before we process this new set.
// LOOK this seems bogus
if (orgDataType != null) {
setDataType(orgDataType);
}
createEnhancements();
if (convertMissing != null) {
fillValue = unpackVal(convertMissing.getFillValue());
hasFillValue = true;
} else {
fillValue = ConvertMissing.getFillValueOrDefault(this);
hasFillValue = !Double.isNaN(fillValue);
}
}
boolean needConvert() {
Set enhancements = getEnhanceMode();
return enhancements.contains(Enhance.ConvertEnums) || enhancements.contains(Enhance.ConvertUnsigned)
|| enhancements.contains(Enhance.ApplyScaleOffset) || enhancements.contains(Enhance.ConvertMissing);
}
Array convert(Array data) {
return convert(data, enhanceMode);
}
Array convert(Array data, Set enhancements) {
if (enhancements.contains(Enhance.ConvertEnums)
&& (dataType.isEnum() || (orgDataType != null && orgDataType.isEnum()))) {
// Creates STRING data. As a result, we can return here, because the other conversions don't apply to STRING.
return convertEnums(data);
} else {
// TODO: make work for isVariableLength; i thought BUFR worked?
if (this.isVariableLength) {
return data;
}
if (!dataType.isNumeric()) {
return data;
}
// datatype of the result depends on what enhancements were applied
DataType convertedType = data.getDataType();
// TODO: change to a provider for extensible Enhancements
List toApply = new ArrayList<>();
if (enhancements.contains(Enhance.ConvertUnsigned) && unsignedConversion != null) {
toApply.add(unsignedConversion);
convertedType = unsignedConversion.getOutType();
}
if (enhancements.contains(Enhance.ConvertMissing) && convertMissing != null
&& (dataType == DataType.FLOAT || dataType == DataType.DOUBLE)) {
toApply.add(convertMissing);
}
if (enhancements.contains(Enhance.ApplyScaleOffset) && scaleOffset != null) {
toApply.add(scaleOffset);
convertedType = scaleOffset.getScaledOffsetType();
}
if (enhancements.contains(Enhance.ApplyStandardizer) && standardizer != null) {
toApply.add(standardizer);
}
if (enhancements.contains(Enhance.ApplyNormalizer) && normalizer != null) {
toApply.add(normalizer);
}
if (enhancements.contains(Enhance.ApplyClassifier) && classifier != null) {
toApply.add(classifier);
}
double[] dataArray = (double[]) data.get1DJavaArray(DataType.DOUBLE);
dataArray = Arrays.stream(dataArray).parallel().map((num) -> {
for (Enhancement e : toApply) {
num = e.convert(num);
}
return num;
}).toArray();
Array out = Array.factory(convertedType, data.getShape());
IndexIterator iterOut = out.getIndexIterator();
for (int i = 0; i < data.getSize(); i++) {
iterOut.setObjectNext(dataArray[i]);
}
return out;
}
}
private Array convertEnums(Array values) {
if (!values.getDataType().isIntegral()) {
return values; // Nothing to do!
}
Array result = Array.factory(DataType.STRING, values.getShape());
IndexIterator ii = result.getIndexIterator();
values.resetLocalIterator();
while (values.hasNext()) {
String sval = lookupEnumString(values.nextInt());
ii.setObjectNext(sval);
}
return result;
}
/**
* Returns the enhancements applied to this variable. If this variable wraps another variable, the returned set will
* also contain the enhancements applied to the nested variable, recursively.
*
* @return the enhancements applied to this variable.
*/
public Set getEnhanceMode() {
if (!(orgVar instanceof VariableDS)) {
return Collections.unmodifiableSet(enhanceMode);
} else {
VariableDS orgVarDS = (VariableDS) orgVar;
return Sets.union(enhanceMode, orgVarDS.getEnhanceMode());
}
}
/**
* Add {@code enhancement} to this variable. It may result in a change of data type.
*
* @param enhancement the enhancement to add.
* @return {@code true} if the set of enhancements changed as a result of the call.
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
public boolean addEnhancement(Enhance enhancement) {
if (enhanceMode.add(enhancement)) {
enhance(enhanceMode);
return true;
} else {
return false;
}
}
/**
* Remove {@code enhancement} from this variable. It may result in a change of data type.
*
* @param enhancement the enhancement to remove.
* @return {@code true} if the set of enhancements changed as a result of the call.
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
public boolean removeEnhancement(Enhance enhancement) {
if (enhanceMode.remove(enhancement)) {
enhance(enhanceMode);
return true;
} else {
return false;
}
}
/**
* A VariableDS usually wraps another Variable.
*
* @return original Variable or null
*/
@Override
public Variable getOriginalVariable() {
return orgVar;
}
/**
* Set the Variable to wrap. Used by NcML explicit mode.
*
* @param orgVar original Variable, must not be a Structure
* @deprecated Use NetcdfDataset.builder()
*/
@Deprecated
@Override
public void setOriginalVariable(Variable orgVar) {
if (orgVar instanceof Structure)
throw new IllegalArgumentException("VariableDS must not wrap a Structure; name=" + orgVar.getFullName());
this.orgVar = orgVar;
}
/**
* When this wraps another Variable, get the original Variable's DataType.
*
* @return original Variable's DataType, or current data type if it doesnt wrap another variable
*/
public DataType getOriginalDataType() {
return orgDataType != null ? orgDataType : getDataType();
}
/**
* When this wraps another Variable, get the original Variable's name.
*
* @return original Variable's name
*/
@Override
public String getOriginalName() {
return orgName;
}
@Override
public String lookupEnumString(int val) {
if (dataType.isEnum())
return super.lookupEnumString(val);
return orgVar.lookupEnumString(val);
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public String setName(String newName) {
this.orgName = getShortName();
super.setShortName(newName);
return newName;
}
@Override
public String toStringDebug() {
return (orgVar != null) ? orgVar.toStringDebug() : "";
}
@Override
public String getDatasetLocation() {
String result = super.getDatasetLocation();
if (result != null)
return result;
if (orgVar != null)
return orgVar.getDatasetLocation();
return null;
}
/** @deprecated Removed in v6 */
@Deprecated
public boolean hasCachedDataRecurse() {
return super.hasCachedData() || ((orgVar != null) && orgVar.hasCachedData());
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public void setCaching(boolean caching) {
if (caching && orgVar != null)
orgVar.setCaching(true); // propagate down only if true
}
@Override
protected Array _read() throws IOException {
Array result;
// check if already cached - caching in VariableDS only done explicitly by app
if (hasCachedData()) {
result = super._read();
} else {
result = proxyReader.reallyRead(this, null);
}
return convert(result);
}
// section of regular Variable
@Override
protected Array _read(Section section) throws IOException, InvalidRangeException {
// really a full read
if ((null == section) || section.computeSize() == getSize()) {
return _read();
}
Array result;
if (hasCachedData()) {
result = super._read(section);
} else {
result = proxyReader.reallyRead(this, section, null);
}
return convert(result);
}
// do not call directly
@Override
public Array reallyRead(Variable client, CancelTask cancelTask) throws IOException {
if (orgVar == null)
return getMissingDataArray(shape);
return orgVar.read();
}
// do not call directly
@Override
public Array reallyRead(Variable client, Section section, CancelTask cancelTask)
throws IOException, InvalidRangeException {
// see if its really a full read
if ((null == section) || section.computeSize() == getSize())
return reallyRead(client, cancelTask);
if (orgVar == null)
return getMissingDataArray(section.getShape());
return orgVar.read(section);
}
@Override
public long readToStream(Section section, OutputStream out) throws IOException, InvalidRangeException {
if (orgVar == null)
return super.readToStream(section, out);
return orgVar.readToStream(section, out);
}
/**
* Return Array with missing data
*
* @param shape of this shape
* @return Array with given shape
*/
public Array getMissingDataArray(int[] shape) {
Object storage;
switch (getDataType()) {
case BOOLEAN:
storage = new boolean[1];
break;
case BYTE:
case UBYTE:
case ENUM1:
storage = new byte[1];
break;
case CHAR:
storage = new char[1];
break;
case SHORT:
case USHORT:
case ENUM2:
storage = new short[1];
break;
case INT:
case UINT:
case ENUM4:
storage = new int[1];
break;
case LONG:
case ULONG:
storage = new long[1];
break;
case FLOAT:
storage = new float[1];
break;
case DOUBLE:
storage = new double[1];
break;
default:
storage = new Object[1];
}
Array array = Array.factoryConstant(getDataType(), shape, storage);
if (hasFillValue) {
array.setObject(0, fillValue);
}
return array;
}
/**
* public for debugging
*
* @param f put info here
*/
public void showScaleMissingProxy(Formatter f) {
f.format("has missing = %s%n", convertMissing != null);
if (convertMissing != null) {
if (convertMissing.hasMissing()) {
if (convertMissing.hasMissingValue()) {
f.format(" missing value(s) = ");
for (double d : convertMissing.getMissingValues())
f.format(" %f", d);
f.format("%n");
}
if (convertMissing.hasFillValue())
f.format(" fillValue = %f%n", convertMissing.getFillValue());
if (convertMissing.hasValidData())
f.format(" valid min/max = [%f,%f]%n", convertMissing.getValidMin(), convertMissing.getValidMax());
}
f.format("FillValue or default = %s%n", convertMissing.getFillValue());
}
f.format("%nhas scale/offset = %s%n", scaleOffset != null);
if (scaleOffset != null) {
double offset = scaleOffset.applyScaleOffset(0.0);
double scale = scaleOffset.applyScaleOffset(1.0) - offset;
f.format(" scale_factor = %f add_offset = %f%n", scale, offset);
}
f.format("original data type = %s%n", orgDataType);
f.format("converted data type = %s%n", getDataType());
}
////////////////////////////////////////////// Enhancements //////////////////////////////////////////////
@Override
public String getDescription() {
return enhanceProxy.getDescription();
}
@Override
public String getUnitsString() {
return enhanceProxy.getUnitsString();
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public void setUnitsString(String units) {
enhanceProxy.setUnitsString(units);
}
@Override
public ImmutableList getCoordinateSystems() {
return enhanceProxy.getCoordinateSystems();
}
/** @deprecated Use VariableDS.builder() */
@Deprecated
@Override
public void addCoordinateSystem(CoordinateSystem cs) {
enhanceProxy.addCoordinateSystem(cs);
}
/** @deprecated Use VariableDS.builder() */
@Deprecated
@Override
public void removeCoordinateSystem(CoordinateSystem cs) {
enhanceProxy.removeCoordinateSystem(cs);
}
//////////////////////////////////////////// EnhanceScaleMissingUnsigned ////////////////////////////////////////////
@Override
public boolean hasScaleOffset() {
return scaleOffset != null;
}
@Override
public double getScaleFactor() {
return scaleOffset != null ? scaleOffset.getScaleFactor() : 1;
}
@Override
public double getOffset() {
return scaleOffset != null ? scaleOffset.getOffset() : 0;
}
@Override
public boolean hasMissing() {
return convertMissing != null && convertMissing.hasMissing();
}
@Override
public boolean isMissing(double val) {
if (Double.isNaN(val)) {
return true;
}
return convertMissing != null && convertMissing.isMissing(packVal(val));
}
@Override
public boolean hasValidData() {
return convertMissing != null && convertMissing.hasMissingValue();
}
@Override
public double getValidMin() {
if (convertMissing == null) {
return -Double.MAX_VALUE;
}
if (scaleOffset == null) {
return convertMissing.getValidMin();
}
return scaleOffset.getScaleFactor() < 0 ? unpackVal(convertMissing.getValidMax())
: unpackVal(convertMissing.getValidMin());
}
@Override
public double getValidMax() {
if (convertMissing == null) {
return Double.MAX_VALUE;
}
if (scaleOffset == null) {
return convertMissing.getValidMax();
}
return scaleOffset.getScaleFactor() < 0 ? unpackVal(convertMissing.getValidMin())
: unpackVal(convertMissing.getValidMax());
}
@Override
public boolean isInvalidData(double val) {
return convertMissing != null && convertMissing.isInvalidData(packVal(val));
}
@Override
public boolean hasFillValue() {
return hasFillValue;
}
@Override
public double getFillValue() {
return fillValue;
}
@Override
public boolean isFillValue(double val) {
return hasFillValue && Misc.nearlyEquals(val, fillValue, Misc.defaultMaxRelativeDiffFloat);
}
@Override
public boolean hasMissingValue() {
return convertMissing != null && convertMissing.hasMissingValue();
}
@Override
public double[] getMissingValues() {
if (convertMissing == null) {
return new double[] {0};
}
double[] missingVals = convertMissing.getMissingValues();
double[] vals = new double[missingVals.length];
for (int i = 0; i < missingVals.length; i++) {
vals[i] = unpackVal(missingVals[i]);
}
return vals;
}
@Override
public boolean isMissingValue(double val) {
return convertMissing != null && convertMissing.isMissingValue(packVal(val));
}
private double unpackVal(double val) {
return scaleOffset != null && !Double.isNaN(val) ? scaleOffset.convert(val) : val;
}
private double packVal(double val) {
return scaleOffset != null && !Double.isNaN(val) ? scaleOffset.applyScaleOffset(val) : val;
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public void setFillValueIsMissing(boolean b) {
if (convertMissing != null) {
convertMissing.setFillValueIsMissing(b);
}
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public void setInvalidDataIsMissing(boolean b) {
if (convertMissing != null) {
convertMissing.setInvalidDataIsMissing(b);
}
}
public boolean missingDataIsMissing() {
return builder().missingDataIsMissing;
}
public boolean fillValueIsMissing() {
return builder().fillValueIsMissing;
}
public boolean invalidDataIsMissing() {
return builder().invalidDataIsMissing;
}
/** @deprecated Use NetcdfDataset.builder() */
@Deprecated
@Override
public void setMissingDataIsMissing(boolean b) {
if (convertMissing != null) {
convertMissing.setMissingDataIsMissing(b);
}
}
@Nullable
@Override
public DataType getScaledOffsetType() {
return scaleOffset != null ? scaleOffset.getScaledOffsetType() : dataType;
}
@Override
public DataType getUnsignedConversionType() {
return unsignedConversion != null ? unsignedConversion.getOutType() : dataType;
}
@Override
public DataType.Signedness getSignedness() {
return unsignedConversion != null ? unsignedConversion.getSignedness() : dataType.getSignedness();
}
@Override
public double applyScaleOffset(Number value) {
return scaleOffset != null ? scaleOffset.convert(value.doubleValue()) : value.doubleValue();
}
@Override
public Array applyScaleOffset(Array data) {
return scaleOffset != null ? scaleOffset.convert(data) : data;
}
@Override
public Number convertUnsigned(Number value) {
return unsignedConversion != null ? unsignedConversion.convert(value.doubleValue()) : value;
}
public Number convertUnsigned(Number value, DataType dataType) {
return unsignedConversion != null ? unsignedConversion.convertUnsigned(value, dataType) : value;
}
@Override
public Array convertUnsigned(Array in) {
return unsignedConversion != null ? unsignedConversion.convertUnsigned(in) : in;
}
@Override
public Number convertMissing(Number value) {
return convertMissing != null ? convertMissing.convert(value.doubleValue()) : value;
}
@Override
public Array convertMissing(Array in) {
return (convertMissing != null && (dataType == DataType.FLOAT || dataType == DataType.DOUBLE))
? convertMissing.convertMissing(in)
: in;
}
/**
* @deprecated use implementations in filters package
*/
@Override
@Deprecated
public Array convert(Array in, boolean convertUnsigned, boolean applyScaleOffset, boolean convertMissing) {
return convertMissing(applyScaleOffset(convertUnsigned(in)));
}
////////////////////////////////////////////////////////////////////////////////////////////
// TODO remove in version 6.
private EnhancementsImpl enhanceProxy;
private List coordSysNames;
// TODO make immutable in version 6
private UnsignedConversion unsignedConversion;
private ScaleOffset scaleOffset;
private Standardizer standardizer;
private Normalizer normalizer;
private Classifier classifier;
private ConvertMissing convertMissing;
private Set enhanceMode = EnumSet.noneOf(Enhance.class); // The set of enhancements that were made.
protected Variable orgVar; // wrap this Variable : use it for the I/O
protected DataType orgDataType; // keep separate for the case where there is no orgVar.
protected String orgName; // in case Variable was renamed, and we need to keep track of the original name
String orgFileTypeId; // the original fileTypeId.
private boolean hasFillValue = false;
private double fillValue = Double.MAX_VALUE;
protected VariableDS(Builder> builder, Group parentGroup) {
super(builder, parentGroup);
this.enhanceMode = builder.enhanceMode;
this.orgVar = builder.orgVar;
this.orgDataType = builder.orgDataType;
this.orgName = builder.orgName;
if (this.orgDataType == null) {
this.orgDataType = dataType;
}
// Make sure that units has been trimmed.
// Replace with correct case
// TODO Can simplify when doesnt have to agree with old VariableDS
Attribute units = builder.getAttributeContainer().findAttributeIgnoreCase(CDM.UNITS);
if (units != null && units.isString()) {
builder.getAttributeContainer()
.addAttribute(Attribute.builder(CDM.UNITS).setStringValue(units.getStringValue().trim()).build());
}
this.orgFileTypeId = builder.orgFileTypeId;
this.enhanceProxy = new EnhancementsImpl(this, builder.units, builder.getDescription());
this.scaleOffset = builder.scaleOffset;
createEnhancements();
if (convertMissing != null) {
fillValue = unpackVal(convertMissing.getFillValue());
hasFillValue = true;
} else {
fillValue = ConvertMissing.getFillValueOrDefault(this);
hasFillValue = !Double.isNaN(fillValue);
}
// We have to complete this after the NetcdfDataset is built.
this.coordSysNames = builder.coordSysNames;
}
private void createEnhancements() {
// Enhancement Objects are created if it is contained in Enhance
if (this.enhanceMode.contains(Enhance.ConvertEnums) && dataType.isEnum()) {
this.dataType = DataType.STRING;
}
if (this.enhanceMode.contains(Enhance.ConvertUnsigned)) {
this.unsignedConversion = UnsignedConversion.createFromVar(this);
this.dataType = unsignedConversion != null ? unsignedConversion.getOutType() : dataType;
}
if (this.enhanceMode.contains(Enhance.ConvertMissing)) {
this.convertMissing = ConvertMissing.createFromVariable(this);
}
if (this.enhanceMode.contains(Enhance.ApplyScaleOffset) && (dataType.isNumeric() || dataType == DataType.CHAR)) {
if (this.scaleOffset == null) {
this.scaleOffset = ScaleOffset.createFromVariable(this);
}
this.dataType = scaleOffset != null ? scaleOffset.getScaledOffsetType() : this.dataType;
}
Attribute standardizerAtt = findAttribute(CDM.STANDARDIZE);
if (standardizerAtt != null && this.enhanceMode.contains(Enhance.ApplyStandardizer) && dataType.isFloatingPoint()) {
this.standardizer = Standardizer.createFromVariable(this);
}
Attribute normalizerAtt = findAttribute(CDM.NORMALIZE);
if (normalizerAtt != null && this.enhanceMode.contains(Enhance.ApplyNormalizer) && dataType.isFloatingPoint()) {
this.normalizer = Normalizer.createFromVariable(this);
}
Attribute classifierAtt = findAttribute(CDM.CLASSIFY);
if (classifierAtt != null && this.enhanceMode.contains(Enhance.ApplyClassifier) && dataType.isNumeric()) {
this.classifier = Classifier.createFromVariable(this);
}
}
public Builder> toBuilder() {
return addLocalFieldsToBuilder(builder());
}
// Add local fields to the passed - in builder.
protected Builder> addLocalFieldsToBuilder(Builder extends Builder>> builder) {
builder.setOriginalVariable(this.orgVar).setOriginalDataType(this.orgDataType).setOriginalName(this.orgName)
.setOriginalFileTypeId(this.orgFileTypeId).setEnhanceMode(this.enhanceMode).setUnits(this.enhanceProxy.units)
.setDesc(this.enhanceProxy.desc);
builder.scaleOffset = this.scaleOffset;
if (this.coordSysNames != null) {
this.coordSysNames.stream().forEach(s -> builder.addCoordinateSystemName(s));
}
return (VariableDS.Builder>) super.addLocalFieldsToBuilder(builder);
}
/** @deprecated do not use */
@Deprecated
void setCoordinateSystems(CoordinatesHelper coords) {
for (String name : this.coordSysNames) {
coords.findCoordSystem(name).ifPresent(cs -> this.enhanceProxy.addCoordinateSystem(cs));
}
}
/**
* Get Builder for this class that allows subclassing.
*
* @see "https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses"
*/
public static Builder> builder() {
return new Builder2();
}
private static class Builder2 extends Builder {
@Override
protected Builder2 self() {
return this;
}
}
public static abstract class Builder> extends Variable.Builder {
public Set enhanceMode = EnumSet.noneOf(Enhance.class);
public Variable orgVar; // wrap this Variable : use it for the I/O
public DataType orgDataType; // keep separate for the case where there is no orgVar.
public String orgFileTypeId; // the original fileTypeId.
String orgName; // in case Variable was renamed, and we need to keep track of the original name
private String units;
private String desc;
public List coordSysNames = new ArrayList<>();
private boolean invalidDataIsMissing = NetcdfDataset.invalidDataIsMissing;
private boolean fillValueIsMissing = NetcdfDataset.fillValueIsMissing;
private boolean missingDataIsMissing = NetcdfDataset.missingDataIsMissing;
private ScaleOffset scaleOffset;
private boolean built;
protected abstract T self();
public T setEnhanceMode(Set enhanceMode) {
this.enhanceMode = enhanceMode;
return self();
}
public T addEnhanceMode(Set enhanceMode) {
this.enhanceMode.addAll(enhanceMode);
return self();
}
public T setOriginalVariable(Variable orgVar) {
this.orgVar = orgVar;
return self();
}
public T setOriginalDataType(DataType orgDataType) {
this.orgDataType = orgDataType;
return self();
}
public T setOriginalName(String orgName) {
this.orgName = orgName;
return self();
}
public T setOriginalFileTypeId(String orgFileTypeId) {
this.orgFileTypeId = orgFileTypeId;
return self();
}
public T setUnits(String units) {
this.units = units;
if (units != null) {
this.units = units.trim();
addAttribute(new Attribute(CDM.UNITS, this.units));
}
return self();
}
public T setDesc(String desc) {
this.desc = desc;
if (desc != null) {
addAttribute(new Attribute(CDM.LONG_NAME, desc));
}
return self();
}
public void addCoordinateSystemName(String coordSysName) {
coordSysNames.add(coordSysName);
}
public void setFillValueIsMissing(boolean b) {
this.fillValueIsMissing = b;
}
public void setInvalidDataIsMissing(boolean b) {
this.invalidDataIsMissing = b;
}
public void setMissingDataIsMissing(boolean b) {
this.missingDataIsMissing = b;
}
/** Copy of this builder. */
@Override
public Variable.Builder> copy() {
return new Builder2().copyFrom(this);
}
/** Copy metadata from orgVar. */
@Override
public T copyFrom(Variable orgVar) {
super.copyFrom(orgVar);
setSPobject(null);
// resetCache();
setOriginalVariable(orgVar);
setOriginalDataType(orgVar.getDataType());
setOriginalName(orgVar.getShortName());
this.orgFileTypeId = orgVar.getFileTypeId();
return self();
}
public T copyFrom(VariableDS.Builder> builder) {
super.copyFrom(builder);
builder.coordSysNames.forEach(name -> this.addCoordinateSystemName(name));
setDesc(builder.desc);
setEnhanceMode(builder.enhanceMode);
setFillValueIsMissing(builder.fillValueIsMissing);
setInvalidDataIsMissing(builder.invalidDataIsMissing);
setMissingDataIsMissing(builder.missingDataIsMissing);
this.scaleOffset = builder.scaleOffset;
this.orgVar = builder.orgVar;
this.orgDataType = builder.orgDataType;
this.orgFileTypeId = builder.orgFileTypeId;
this.orgName = builder.orgName;
setUnits(builder.units);
return self();
}
public String getUnits() {
String result = units;
if (result == null) {
result = getAttributeContainer().findAttributeString(CDM.UNITS, null);
}
if (result == null && orgVar != null) {
result = orgVar.attributes().findAttributeString(CDM.UNITS, null);
}
return (result == null) ? null : result.trim();
}
public String getDescription() {
String result = desc;
if (result == null) {
result = getAttributeContainer().findAttributeString(CDM.LONG_NAME, null);
}
if (result == null && orgVar != null) {
result = orgVar.attributes().findAttributeString(CDM.LONG_NAME, null);
}
return (result == null) ? null : result.trim();
}
/** Normally this is called by Group.build() */
public VariableDS build(Group parentGroup) {
if (built)
throw new IllegalStateException("already built");
built = true;
return new VariableDS(this, parentGroup);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy