package org.esa.beam.smos.ee2netcdf;
import com.bc.ceres.binio.CompoundData;
import org.apache.commons.lang.StringUtils;
import org.esa.beam.dataio.netcdf.nc.NFileWriteable;
import org.esa.beam.dataio.netcdf.nc.NVariable;
import org.esa.beam.dataio.smos.SmosFile;
import org.esa.beam.dataio.smos.SmosProductReader;
import org.esa.beam.dataio.smos.dddb.Dddb;
import org.esa.beam.dataio.smos.dddb.Family;
import org.esa.beam.dataio.smos.dddb.MemberDescriptor;
import org.esa.beam.framework.datamodel.MetadataAttribute;
import org.esa.beam.framework.datamodel.MetadataElement;
import org.esa.beam.framework.datamodel.Product;
import org.esa.beam.framework.datamodel.ProductData;
import org.esa.beam.smos.DateTimeUtils;
import org.esa.beam.smos.ee2netcdf.geometry.GeometryFilter;
import org.esa.beam.smos.ee2netcdf.geometry.GeometryFilterFactory;
import org.esa.beam.smos.ee2netcdf.variable.VariableDescriptor;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import java.io.IOException;
import java.util.*;
abstract class AbstractFormatExporter implements FormatExporter {
private Family<MemberDescriptor> memberDescriptors;
protected int gridPointCount;
protected SmosFile explorerFile;
protected Map<String, VariableDescriptor> variableDescriptors;
protected ArrayList<Integer> gpIndexList;
@Override
public void initialize(Product product, ExportParameter exportParameter) throws IOException {
explorerFile = getSmosFile(product);
gridPointCount = explorerFile.getGridPointCount();
memberDescriptors = Dddb.getInstance().getMemberDescriptors(explorerFile.getHeaderFile());
createVariableDescriptors(exportParameter);
}
@Override
public void prepareGeographicSubset(NFileWriteable nFileWriteable, ExportParameter exportParameter) throws IOException {
if (exportParameter.getRegion() != null) {
final GeometryFilter geometryFilter = GeometryFilterFactory.create(exportParameter.getRegion());
gpIndexList = new ArrayList<>(gridPointCount);
for (int i = 0; i < gridPointCount; i++) {
final CompoundData gridPointData = explorerFile.getGridPointData(i);
if (geometryFilter.accept(gridPointData)) {
gpIndexList.add(i);
}
}
gridPointCount = gpIndexList.size();
}
}
@Override
public void addGlobalAttributes(NFileWriteable nFileWriteable, MetadataElement metadataRoot, ExportParameter exportParameter) throws IOException {
final String institution = exportParameter.getInstitution();
if (StringUtils.isNotBlank(institution)) {
nFileWriteable.addGlobalAttribute("institution", institution);
}
final String contact = exportParameter.getContact();
if (StringUtils.isNotBlank(contact)) {
nFileWriteable.addGlobalAttribute("contact", contact);
}
nFileWriteable.addGlobalAttribute("creation_date", DateTimeUtils.toFixedHeaderFormat(new Date()));
nFileWriteable.addGlobalAttribute("total_number_of_grid_points", Integer.toString(gridPointCount));
final Properties fileMetadata = extractMetadata(metadataRoot);
final Set<String> metaKeys = fileMetadata.stringPropertyNames();
for (final String key : metaKeys) {
final String value = fileMetadata.getProperty(key);
nFileWriteable.addGlobalAttribute(key, value);
}
}
@Override
public void addVariables(NFileWriteable nFileWriteable, ExportParameter exportParameter) throws IOException {
final Set<String> variableNameKeys = variableDescriptors.keySet();
final String[] outputBandNames = exportParameter.getVariableNames();
for (final String ncVariableName : variableNameKeys) {
if (!mustExport(ncVariableName, outputBandNames)) {
continue;
}
final VariableDescriptor variableDescriptor = variableDescriptors.get(ncVariableName);
final NVariable nVariable = nFileWriteable.addVariable(ncVariableName,
variableDescriptor.getDataType(),
true,
null,
variableDescriptor.getDimensionNames(),
exportParameter.getCompressionLevel());
final String unitValue = variableDescriptor.getUnit();
if (StringUtils.isNotBlank(unitValue)) {
nVariable.addAttribute("units", unitValue);
}
if (variableDescriptor.isFillValuePresent()) {
nVariable.addAttribute("_FillValue", variableDescriptor.getFillValue());
}
if (variableDescriptor.isValidMinPresent()) {
nVariable.addAttribute("valid_min", variableDescriptor.getValidMin());
}
if (variableDescriptor.isValidMaxPresent()) {
nVariable.addAttribute("valid_max", variableDescriptor.getValidMax());
}
final short[] flagMasks = variableDescriptor.getFlagMasks();
if (flagMasks != null) {
nVariable.addAttribute("flag_masks", Array.factory(flagMasks));
}
final short[] flagValues = variableDescriptor.getFlagValues();
if (flagValues != null) {
nVariable.addAttribute("flag_values", Array.factory(flagValues));
}
final String flagMeanings = variableDescriptor.getFlagMeanings();
if (StringUtils.isNotBlank(flagMeanings)) {
nVariable.addAttribute("flag_meanings", flagMeanings);
}
if (variableDescriptor.isScaleFactorPresent() || variableDescriptor.isScaleOffsetPresent()) {
nVariable.addAttribute("scale_factor", variableDescriptor.getScaleFactor());
nVariable.addAttribute("scale_offset", variableDescriptor.getScaleOffset());
}
if (variableDescriptor.isUnsigned()) {
nVariable.addAttribute("_Unsigned", "true");
}
}
}
@Override
abstract public void addDimensions(NFileWriteable nFileWriteable) throws IOException;
void createVariableDescriptors(ExportParameter exportParameter) {
variableDescriptors = new HashMap<>();
final String[] outputBandNames = exportParameter.getVariableNames();
final List<MemberDescriptor> memberDescriptorList = memberDescriptors.asList();
for (final MemberDescriptor memberDescriptor : memberDescriptorList) {
final String memberDescriptorName = memberDescriptor.getName();
if (mustExport(memberDescriptorName, outputBandNames)) {
final String dimensionNames = memberDescriptor.getDimensionNames();
final int numDimensions = getNumDimensions(dimensionNames);
final String variableName = ensureNetCDFName(memberDescriptorName);
final VariableDescriptor variableDescriptor = new VariableDescriptor(variableName,
memberDescriptor.isGridPointData(),
DataType.OBJECT,
dimensionNames,
numDimensions == 2,
memberDescriptor.getMemberIndex());
setDataType(variableDescriptor, memberDescriptor.getDataTypeName());
variableDescriptor.setUnit(memberDescriptor.getUnit());
variableDescriptor.setFillValue(memberDescriptor.getFillValue());
final float scalingFactor = memberDescriptor.getScalingFactor();
if (scalingFactor != 1.0) {
variableDescriptor.setScaleFactor(scalingFactor);
}
final float scalingOffset = memberDescriptor.getScalingOffset();
if (scalingOffset != 0.0) {
variableDescriptor.setScaleOffset(memberDescriptor.getScalingOffset());
}
final short[] flagMasks = memberDescriptor.getFlagMasks();
if (flagMasks != null) {
variableDescriptor.setFlagMasks(memberDescriptor.getFlagMasks());
variableDescriptor.setFlagValues(memberDescriptor.getFlagValues());
variableDescriptor.setFlagMeanings(memberDescriptor.getFlagMeanings());
}
variableDescriptors.put(variableName, variableDescriptor);
}
}
}
// package access for testing only tb 2014-07-01
static Properties extractMetadata(MetadataElement root) {
final Properties properties = new Properties();
extractAttributes(root, properties, "");
return properties;
}
// package access for testing only tb 2014-08-01
static boolean mustExport(String bandName, String[] outputBandNames) {
if (outputBandNames.length == 0) {
return true;
}
for (final String outputBandName : outputBandNames) {
if (outputBandName.equalsIgnoreCase(bandName)) {
return true;
}
}
return false;
}
private static SmosFile getSmosFile(Product product) {
final SmosProductReader smosReader = (SmosProductReader) product.getProductReader();
return (SmosFile) smosReader.getProductFile();
}
private static void extractAttributes(MetadataElement root, Properties properties, String prefix) {
final MetadataAttribute[] attributes = root.getAttributes();
for (MetadataAttribute attribute : attributes) {
addAttributeTo(properties, prefix, attribute);
}
final MetadataElement[] elements = root.getElements();
final HashMap<String, List<MetadataElement>> uniqueNamedElements = getListWithUniqueNamedElements(elements);
final Set<String> nameSet = uniqueNamedElements.keySet();
for (final String elementName : nameSet) {
final List<MetadataElement> elementsWithSameName = uniqueNamedElements.get(elementName);
if (elementsWithSameName.size() == 1) {
final MetadataElement metadataElement = elementsWithSameName.get(0);
final String nextRecursionPrefix = prefix + metadataElement.getName() + ":";
extractAttributes(metadataElement, properties, nextRecursionPrefix);
} else {
int index = 0;
for (final MetadataElement metadataElement : elementsWithSameName) {
final String nextRecursionPrefix = prefix + metadataElement.getName() + "_" + Integer.toString(index) + ":";
extractAttributes(metadataElement, properties, nextRecursionPrefix);
++index;
}
}
}
}
private static void addAttributeTo(Properties properties, String prefix, MetadataAttribute attribute) {
final String attributeName = prefix + attribute.getName();
final ProductData data = attribute.getData();
properties.setProperty(attributeName, data.getElemString());
}
private static HashMap<String, List<MetadataElement>> getListWithUniqueNamedElements(MetadataElement[] elements) {
final HashMap<String, List<MetadataElement>> uniqueNamedElements = new HashMap<>(elements.length);
for (final MetadataElement element : elements) {
final String elementName = element.getName();
final List<MetadataElement> elementList = uniqueNamedElements.get(elementName);
if (elementList == null) {
final ArrayList<MetadataElement> uniqueNamedElementsList = new ArrayList<>();
uniqueNamedElementsList.add(element);
uniqueNamedElements.put(elementName, uniqueNamedElementsList);
} else {
elementList.add(element);
}
}
return uniqueNamedElements;
}
// package access for testing only tb 2014-07-30
static void setDataType(VariableDescriptor variableDescriptor, String dataTypeName) {
if (StringUtils.isBlank(dataTypeName)) {
throw new IllegalStateException("datatype not set for '" + variableDescriptor.getName() + "'");
}
if ("uint".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.INT);
variableDescriptor.setUnsigned(true);
} else if ("int".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.INT);
} else if ("float".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.FLOAT);
} else if ("ubyte".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.BYTE);
variableDescriptor.setUnsigned(true);
} else if ("ushort".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.SHORT);
variableDescriptor.setUnsigned(true);
} else if ("short".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.SHORT);
} else if ("ulong".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.LONG);
variableDescriptor.setUnsigned(true);
} else if ("double".equalsIgnoreCase(dataTypeName)) {
variableDescriptor.setDataType(DataType.DOUBLE);
} else {
throw new IllegalArgumentException("unsupported datatype: '" + dataTypeName + "'");
}
}
// package access for testing only tb 2014-07-30
static int getNumDimensions(String dimensionNames) {
if (StringUtils.isBlank(dimensionNames)) {
throw new IllegalArgumentException("empty dimension names");
}
final String[] splittednames = StringUtils.split(dimensionNames, ' ');
return splittednames.length;
}
static String ensureNetCDFName(String variableName) {
return variableName.replace(".", "_");
}
}