/*
* Copyright (C) 2015 by Array Systems Computing Inc. http://www.array.ca
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.s1tbx.io.netcdf;
import com.bc.ceres.core.ProgressMonitor;
import org.esa.snap.core.dataio.AbstractProductWriter;
import org.esa.snap.core.dataio.ProductWriterPlugIn;
import org.esa.snap.core.datamodel.Band;
import org.esa.snap.core.datamodel.GeoCoding;
import org.esa.snap.core.datamodel.MetadataAttribute;
import org.esa.snap.core.datamodel.MetadataElement;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductData;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.TiePointGeoCoding;
import org.esa.snap.core.datamodel.TiePointGrid;
import org.esa.snap.core.datamodel.VirtualBand;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.core.util.io.FileUtils;
import ucar.ma2.Array;
import ucar.ma2.ArrayDouble;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.Group;
import ucar.nc2.NetcdfFileWriteable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class NetCDFWriter extends AbstractProductWriter {
private File outputFile = null;
private NetcdfFileWriteable netCDFWriteable = null;
/**
* Construct a new instance of a product writer for the given product writer plug-in.
*
* @param writerPlugIn the given product writer plug-in, must not be <code>null</code>
*/
public NetCDFWriter(final ProductWriterPlugIn writerPlugIn) {
super(writerPlugIn);
}
private static float[] getLonData(final Product product, final String lonGridName) {
final int size = product.getSceneRasterWidth();
final TiePointGrid lonGrid = product.getTiePointGrid(lonGridName);
if (lonGrid != null)
return lonGrid.getPixels(0, 0, size, 1, (float[]) null);
return null;
}
private static float[] getLatData(final Product product, final String latGridName) {
final int size = product.getSceneRasterHeight();
final TiePointGrid latGrid = product.getTiePointGrid(latGridName);
if (latGrid != null)
return latGrid.getPixels(0, 0, 1, size, (float[]) null);
return null;
}
private static float[][] getTiePointGridData(final TiePointGrid tpg) {
int gridWidth = tpg.getGridWidth();
int gridHeight = tpg.getGridHeight();
final float[][] data = new float[gridHeight][gridWidth];
final ProductData gridData = tpg.getGridData();
for (int y = 0; y < gridHeight; ++y) {
final int stride = y * gridWidth;
for (int x = 0; x < gridWidth; ++x) {
data[y][x] = gridData.getElemFloatAt(stride + x);
}
}
return data;
}
/**
* Writes the in-memory representation of a data product. This method was called by <code>writeProductNodes(product,
* output)</code> of the AbstractProductWriter.
*
* @throws IllegalArgumentException if <code>output</code> type is not one of the supported output sources.
* @throws java.io.IOException if an I/O error occurs
*/
@Override
protected void writeProductNodesImpl() throws IOException {
outputFile = null;
final File file;
if (getOutput() instanceof String) {
file = new File((String) getOutput());
} else {
file = (File) getOutput();
}
outputFile = FileUtils.ensureExtension(file, NetcdfConstants.NETCDF_FORMAT_FILE_EXTENSIONS[0]);
deleteOutput();
final Product product = getSourceProduct();
netCDFWriteable = NetcdfFileWriteable.createNew(outputFile.getAbsolutePath(), true);
netCDFWriteable.addDimension(NetcdfConstants.LON_VAR_NAMES[0], product.getSceneRasterWidth());
netCDFWriteable.addDimension(NetcdfConstants.LAT_VAR_NAMES[0], product.getSceneRasterHeight());
final Group rootGroup = netCDFWriteable.getRootGroup();
netCDFWriteable.addVariable(NetcdfConstants.LAT_VAR_NAMES[0], DataType.FLOAT,
new Dimension[]{rootGroup.findDimension(NetcdfConstants.LAT_VAR_NAMES[0])});
netCDFWriteable.addVariableAttribute(NetcdfConstants.LAT_VAR_NAMES[0], "units", "degrees_north (+N/-S)");
netCDFWriteable.addVariable(NetcdfConstants.LON_VAR_NAMES[0], DataType.FLOAT,
new Dimension[]{rootGroup.findDimension(NetcdfConstants.LON_VAR_NAMES[0])});
netCDFWriteable.addVariableAttribute(NetcdfConstants.LON_VAR_NAMES[0], "units", "degrees_east (+E/-W)");
for (Band band : product.getBands()) {
final String name = StringUtils.createValidName(band.getName(), new char[]{'_'}, '_');
netCDFWriteable.addVariable(name, DataType.DOUBLE,
new Dimension[]{rootGroup.findDimension(NetcdfConstants.LAT_VAR_NAMES[0]),
rootGroup.findDimension(NetcdfConstants.LON_VAR_NAMES[0])}
);
if (band.getDescription() != null)
netCDFWriteable.addVariableAttribute(name, "description", band.getDescription());
if (band.getUnit() != null)
netCDFWriteable.addVariableAttribute(name, "unit", band.getUnit());
}
for (TiePointGrid tpg : product.getTiePointGrids()) {
final String name = tpg.getName();
netCDFWriteable.addDimension(name + 'x', tpg.getGridWidth());
netCDFWriteable.addDimension(name + 'y', tpg.getGridHeight());
netCDFWriteable.addVariable(name, DataType.FLOAT,
new Dimension[]{rootGroup.findDimension(name + 'y'), rootGroup.findDimension(name + 'x')});
if (tpg.getDescription() != null)
netCDFWriteable.addVariableAttribute(name, "description", tpg.getDescription());
if (tpg.getUnit() != null)
netCDFWriteable.addVariableAttribute(name, "unit", tpg.getUnit());
}
addMetadata(product);
netCDFWriteable.create();
final GeoCoding sourceGeoCoding = product.getSceneGeoCoding();
String latGridName = "latitude";
String lonGridName = "longitude";
if (sourceGeoCoding instanceof TiePointGeoCoding) {
final TiePointGeoCoding geoCoding = (TiePointGeoCoding) sourceGeoCoding;
latGridName = geoCoding.getLatGrid().getName();
lonGridName = geoCoding.getLonGrid().getName();
}
final float[] latData = getLatData(product, latGridName);
final float[] lonData = getLonData(product, lonGridName);
if (latData != null && lonData != null) {
final Array latNcArray = Array.factory(latData);
final Array lonNcArray = Array.factory(lonData);
try {
netCDFWriteable.write(NetcdfConstants.LAT_VAR_NAMES[0], latNcArray);
netCDFWriteable.write(NetcdfConstants.LON_VAR_NAMES[0], lonNcArray);
for (TiePointGrid tpg : product.getTiePointGrids()) {
final Array tpgArray = Array.factory(getTiePointGridData(tpg));
netCDFWriteable.write(tpg.getName(), tpgArray);
}
} catch (InvalidRangeException rangeE) {
rangeE.printStackTrace();
throw new RuntimeException(rangeE);
}
}
}
/**
* {@inheritDoc}
*/
public void writeBandRasterData(final Band sourceBand,
final int regionX,
final int regionY,
final int regionWidth,
final int regionHeight,
final ProductData regionData,
ProgressMonitor pm) throws IOException {
final int[] origin = new int[2];
origin[1] = regionX;
origin[0] = regionY;
try {
final ArrayDouble dataTemp = new ArrayDouble.D2(regionHeight, regionWidth);
final Index index = dataTemp.getIndex();
int i = 0;
for (int y = 0; y < regionHeight; ++y) {
for (int x = 0; x < regionWidth; ++x) {
index.set(y, x);
dataTemp.set(index, regionData.getElemDoubleAt(i));
++i;
}
}
netCDFWriteable.write(sourceBand.getName(), origin, dataTemp);
pm.worked(1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
/**
* Deletes the physically representation of the given product from the hard disk.
*/
public void deleteOutput() {
if (outputFile != null && outputFile.isFile()) {
outputFile.delete();
}
}
/**
* Closes all output streams currently open.
*
* @throws java.io.IOException on failure
*/
public void close() throws IOException {
netCDFWriteable.close();
}
/**
* Writes all data in memory to disk. After a flush operation, the writer can be closed safely
*
* @throws IOException on failure
*/
public void flush() throws IOException {
if (netCDFWriteable == null) {
return;
}
netCDFWriteable.flush();
}
/**
* Returns wether the given product node is to be written.
*
* @param node the product node
* @return <code>true</code> if so
*/
@Override
public boolean shouldWrite(ProductNode node) {
return !(node instanceof VirtualBand) && super.shouldWrite(node);
}
private void addMetadata(final Product product) {
final MetadataElement rootElem = product.getMetadataRoot();
final Group rootGroup = netCDFWriteable.getRootGroup();
addElements(rootElem, rootGroup);
addAttributes(rootElem, rootGroup);
}
private void addElements(final MetadataElement parentElem, final Group parentGroup) {
final Map<String, Integer> dupeCntElem = new HashMap<>();
for (int i = 0; i < parentElem.getNumElements(); i++) {
final MetadataElement subElement = parentElem.getElementAt(i);
final String name = subElement.getName();
boolean lastDupe = true;
for (int j = i + 1; j < parentElem.getNumElements(); j++) {
final MetadataElement dupeElement = parentElem.getElementAt(j);
if (dupeElement.getName().equals(name)) {
Integer cnt = dupeCntElem.get(name);
if (cnt == null)
dupeCntElem.put(name, 1);
else {
++cnt;
dupeCntElem.put(name, cnt);
}
lastDupe = false;
break;
}
}
if (dupeCntElem.get(name) != null) {
int cnt = dupeCntElem.get(name);
if (lastDupe)
++cnt;
subElement.setName(subElement.getName() + "." + cnt);
}
final Group newGroup = new Group(netCDFWriteable, parentGroup, subElement.getName());
addAttributes(subElement, newGroup);
// recurse
addElements(subElement, newGroup);
netCDFWriteable.addGroup(parentGroup, newGroup);
}
}
private void addAttributes(final MetadataElement elem, final Group newGroup) {
final Map<String, Integer> dupeCntAtrib = new HashMap<>();
for (int i = 0; i < elem.getNumAttributes(); i++) {
final MetadataAttribute attrib = elem.getAttributeAt(i);
final String name = attrib.getName();
boolean lastDupe = true;
for (int j = i + 1; j < elem.getNumAttributes(); j++) {
final MetadataAttribute dupeAtrib = elem.getAttributeAt(j);
if (dupeAtrib.getName().equals(name)) {
Integer cnt = dupeCntAtrib.get(name);
if (cnt == null)
dupeCntAtrib.put(name, 1);
else {
++cnt;
dupeCntAtrib.put(name, cnt);
}
lastDupe = false;
break;
}
}
if (dupeCntAtrib.get(name) != null) {
int cnt = dupeCntAtrib.get(name);
if (lastDupe)
++cnt;
attrib.setName(attrib.getName() + "." + cnt);
}
final int dataType = attrib.getDataType();
if (dataType == ProductData.TYPE_FLOAT32 || dataType == ProductData.TYPE_FLOAT64) {
newGroup.addAttribute(new Attribute(attrib.getName(), elem.getAttributeDouble(attrib.getName(), 0)));
} else if (dataType == ProductData.TYPE_UTC || attrib.getData() instanceof ProductData.UTC) {
newGroup.addAttribute(new Attribute(attrib.getName(), NetcdfConstants.UTC_TYPE + elem.getAttributeString(attrib.getName(), " ")));
} else if (dataType > ProductData.TYPE_INT8 && dataType < ProductData.TYPE_FLOAT32) {
newGroup.addAttribute(new Attribute(attrib.getName(), elem.getAttributeInt(attrib.getName(), 0)));
} else {
newGroup.addAttribute(new Attribute(attrib.getName(), elem.getAttributeString(attrib.getName(), " ")));
}
}
}
}