/* * 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.sentinel1; import org.esa.s1tbx.io.netcdf.NetCDFUtils; import org.esa.snap.core.datamodel.Band; 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.util.SystemUtils; import org.esa.snap.dataio.netcdf.util.MetadataUtils; import org.esa.snap.engine_utilities.datamodel.AbstractMetadata; import ucar.ma2.Array; import ucar.ma2.InvalidRangeException; import ucar.nc2.Dimension; import ucar.nc2.NetcdfFile; import ucar.nc2.Variable; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * NetCDF reader for Level-2 OCN products */ public class Sentinel1OCNReader { // A NetCDF file consists of // 1) attributes // Global Attributes are added to Product as attributes in // Metadata --> Original_Product_Metadata --> annotation --> <filename of MDS>.nc // 2) dimensions // Dimensions are added to Product as attributes in // Metadata --> Original_Product_Metadata --> annotation --> <filename of MDS>.nc // 3) variables // - measurement data // If rank is 1, it is added as "Values" in annotation for that variable, e.g., see oswK // If rank > 1 but it is a vector of values, it is added as "values" in annotation for that variable // If rank is 2, it is added as a band // If rank is 3, it is added as a band. An "outer" grid of cells and then each cell is a single column of values. // If rank is 4, it is added as a band. An "outer" grid of cells and then each cell is an "inner" grid of bins. // - annotations // scalar or 1D array with same name as variable // Added to product as attributes in // Metadata --> Original_Product_Metadata --> annotation --> <filename of MDS>.nc // // MDS = Measurement Data Set // This maps the MDS .nc file name to the NetcdfFile private final Map<String, NCFileData> bandNCFileMap = new HashMap<>(1); private static class NCFileData { String name; NetcdfFile netcdfFile; NCFileData(String name, NetcdfFile netcdfFile) { this.name = name; this.netcdfFile = netcdfFile; } } private final Sentinel1Level2Directory dataDir; private String mode; // For WV, there can be more than one MDS .nc file. See Table 4-3 in Product Spec v2/7 (S1-RS-MDA-52-7441). // Each MDS has the same variables, so we want unique band names for variables of same name from different .nc file. // Given a band name, we want to map back to the .nc file. private final Map<String, NetcdfFile> bandNameNCFileMap = new HashMap<>(1); public Sentinel1OCNReader(final Sentinel1Level2Directory dataDir) { this.dataDir = dataDir; } public void addImageFile(final File file, final String name) throws IOException { // The image file here is the MDS .nc file. String imgNum = name.substring(name.lastIndexOf("-")+1, name.length()); final NetcdfFile netcdfFile = NetcdfFile.open(file.getPath()); bandNCFileMap.put(imgNum, new NCFileData(name, netcdfFile)); } public void addNetCDFMetadata(final MetadataElement annotationElement) { List<String> keys = new ArrayList<>(bandNCFileMap.keySet()); Collections.sort(keys); for (String imgNum : keys) { // for each MDS which is a .nc file //System.out.println("Sentinel1OCNReader.addNetCDFMetadataAndBands: file = " + file); final NCFileData data = bandNCFileMap.get(imgNum); final NetcdfFile netcdfFile = data.netcdfFile; final String file = data.name; // Add Global Attributes as Metadata final MetadataElement bandElem = NetCDFUtils.addAttributes(annotationElement, file, netcdfFile.getGlobalAttributes()); // Add dimensions as Metadata final MetadataElement dimElem = new MetadataElement("Dimensions"); bandElem.addElement(dimElem); List<Dimension> dimensionList = netcdfFile.getDimensions(); for (Dimension d : dimensionList) { ProductData productData; productData = ProductData.createInstance(ProductData.TYPE_UINT32, 1); productData.setElemUInt(d.getLength()); final MetadataAttribute metadataAttribute = new MetadataAttribute(d.getFullName(), productData, true); dimElem.addAttribute(metadataAttribute); } final List<Variable> variableList = netcdfFile.getVariables(); // Add attributes inside variables as Metadata for (Variable variable : variableList) { bandElem.addElement(MetadataUtils.createMetadataElement(variable, 1000)); } for (Variable variable : variableList) { if (variableIsVector(variable) && variable.getRank() > 1) { // If rank is 1 then it has already been taken care of by // MetadataUtils.createMetadataElement() final MetadataElement elem = bandElem.getElement(variable.getFullName()); final MetadataElement valuesElem = new MetadataElement("Values"); elem.addElement(valuesElem); MetadataUtils.addAttribute(variable, valuesElem, 1000); } } /* for (Dimension d : dimensionList) { int len = d.getLength(); String name = d.getFullName(); System.out.println("Sentinel1OCNReader.addNetCDFMetadata: dim name = " + name + " len = " + len); } for (Variable variable : variableList) { int[] varShape = variable.getShape(); System.out.print("Sentinel1OCNReader.addNetCDFMetadata: variable name = " + variable.getFullName() + " "); for (int i = 0; i < varShape.length; i++) { System.out.print(varShape[i] + " "); } System.out.println(); } */ } } public void addNetCDFBands(final Product product) { final MetadataElement absRoot = AbstractMetadata.getAbstractedMetadata(product); mode = absRoot.getAttributeString(AbstractMetadata.ACQUISITION_MODE); List<String> keys = new ArrayList<>(bandNCFileMap.keySet()); Collections.sort(keys); for (String imgNum : keys) { // for each MDS which is a .nc file final NCFileData data = bandNCFileMap.get(imgNum); final NetcdfFile netcdfFile = data.netcdfFile; final String file = data.name; // Add bands to product... int idx = file.indexOf("-ocn-"); final String pol = file.substring(idx + 5, idx + 7); idx = file.lastIndexOf('-'); final String imageNum = file.substring(idx + 1, idx + 4); final List<Variable> variableList = netcdfFile.getVariables(); for (Variable variable : variableList) { if (variableIsVector(variable) && variable.getRank() > 1) { continue; } String bandName = pol + "_" + imageNum + "_"; final int[] shape = variable.getShape(); switch (variable.getRank()) { case 1: // The data has been added as part of annotation for the variable under "Values". break; case 2: { bandName += variable.getFullName(); addBand(product, bandName, variable, shape[1], shape[0]); bandNameNCFileMap.put(bandName, netcdfFile); if (bandName.contains("owiNrcs")) { product.setQuicklookBandName(bandName); } } break; case 3: // When the rank is 3, there is an "outer" grid of cells and each cell contains a vector of values. // The "outer" grid is oswAzSize (rows) by oswRaSize (cols) of cells. // Each cell is a vector of values. // For owsSpecRes, the dimensions are oswAzSize x oswRaSize x oswAngularBinSize // So it is more natural to have the band be (oswAzSize*oswAngularBinSize) rows by oswRaSize columns. // All other rank 3 variables are oswAzSize x oswRaSize x oswPartitions // To be consistent, the bands will be (oswAzSize*oswPartitions) rows by oswRaSize columns. { for(int swath = 1; swath <= shape[2]; ++swath) { String bandNameSwath = bandName + mode + swath + "_" + variable.getFullName(); // Tbe band will have dimensions: shape[0]*shape[2] (rows) by shape[1] (cols). // So band width = shape[1] and band height = shape[0]*shape[2] addBand(product, bandNameSwath, variable, shape[1], shape[0]);// * shape[2]); bandNameNCFileMap.put(bandNameSwath, netcdfFile); } } break; case 4: // When the rank is 4, there is an "outer" grid of cells and each cell contains an "inner" grid of bins. // The "outer" grid is oswAzSize (rows) by oswRaSize (cols) of cells. // Each cell is oswAngularBinSize (rows) by oswWaveNumberBinSize (cols) of bins. // shape[0] is height of "outer" grid. // shape[1] is width of "outer" grid. // shape[2] is height of "inner" grid. // shape[3] is width of "inner" grid. { bandName += variable.getFullName(); // Tbe band will have dimensions: shape[0]*shape[2] (rows) by shape[1]*shape[3] (cols). // So band width = shape[1]*shape[3] and band height = shape[0]*shape[2] addBand(product, bandName, variable, shape[1] * shape[3], shape[0] * shape[2]); bandNameNCFileMap.put(bandName, netcdfFile); /* if (bandName.contains("oswPolSpec")) { dumpVariableValues(variable, bandName); } */ } break; default: SystemUtils.LOG.severe("SentinelOCNReader.addNetCDFMetadataAndBands: ERROR invalid variable rank " + variable.getRank() + " for " + variable.getFullName()); break; } } } } public void addGeoCodingToBands(final Product product) { /* final Band[] bands = product.getBands(); for (Band band : bands) { final String bandName = band.getName(); if (bandName.substring(7).equals("rvlRadVel") || bandName.substring(7).equals("owiWindSpeed") ) { final String bandNamePrefix = bandName.substring(0,10); final Band latBand = product.getBand(bandNamePrefix + "Lat"); final Band lonBand = product.getBand(bandNamePrefix + "Lon"); if (latBand == null || lonBand == null) { System.out.println("Sentinel1OCNReader.addDisplayBands: missing " + bandName + " Lat and/or Lon: latBand is " + latBand + " lonBand is " + lonBand); continue; } final int searchRadius = 5; // TODO No idea what this should be PixelGeoCoding pixGeoCoding = new PixelGeoCoding(latBand, lonBand, null, searchRadius); band.setGeoCoding(pixGeoCoding); } } */ } private void addBand(final Product product, String bandName, final Variable variable, final int width, final int height) { final Band band = NetCDFUtils.createBand(variable, width, height); band.setName(bandName); product.addBand(band); } public void readData(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Band destBand, int destOffsetX, int destOffsetY, int destWidth, int destHeight, ProductData destBuffer) { /* System.out.println("Sentinel1OCNReader.readData: sourceOffsetX = " + sourceOffsetX + " sourceOffsetY = " + sourceOffsetY + " sourceWidth = " + sourceWidth + " sourceHeight = " + sourceHeight + " sourceStepX = " + sourceStepX + " sourceStepY = " + sourceStepY + " destOffsetX = " + destOffsetX + " destOffsetY = " + destOffsetY + " destWidth = " + destWidth + " destHeight = " + destHeight + " destBuffer.getNumElems() = " + destBuffer.getNumElems()); */ // Can source and destination have different height and width? TODO if (sourceWidth != destWidth || sourceHeight != destHeight) { SystemUtils.LOG.severe("Sentinel1OCNReader.readData: ERROR sourceWidth = " + sourceWidth + " sourceHeight = " + sourceHeight); return; } // It looks like this will be called once for the entire band at the beginning to fill up the display // and then when we slide the cursor over each pixel, this is called again just for that pixel. // In the former case, we see this print statement... // Sentinel1OCNReader.readData: sourceOffsetX = 0 sourceOffsetY = 0 sourceWidth = 80 sourceHeight = 10 sourceStepX = 1 sourceStepY = 1 destOffsetX = 0 destOffsetY = 0 destWidth = 80 destHeight = 10 destBuffer.getNumElems() = 800 // In the latter case, we see this print statement... // Sentinel1OCNReader.readData: sourceOffsetX = 32 sourceOffsetY = 4 sourceWidth = 1 sourceHeight = 1 sourceStepX = 1 sourceStepY = 1 destOffsetX = 32 destOffsetY = 4 destWidth = 1 destHeight = 1 destBuffer.getNumElems() = 1 // So it looks like we can ignore destOffsetX and destOffsetY. final String bandName = destBand.getName(); final String varFullName = bandName.substring(bandName.lastIndexOf('_') + 1); //System.out.println("Sentinel1OCNReader.readData: bandName = " + bandName + " varFullName = " + varFullName); final NetcdfFile netcdfFile = bandNameNCFileMap.get(bandName); final Variable var = netcdfFile.findVariable(varFullName); switch (var.getRank()) { case 2: readDataForRank2Variable(sourceOffsetX, sourceOffsetY, sourceWidth, sourceHeight, sourceStepX, sourceStepY, var, destWidth, destHeight, destBuffer); break; case 3: readDataForRank3Variable(bandName, sourceOffsetX, sourceOffsetY, sourceWidth, sourceHeight, sourceStepX, sourceStepY, var, destWidth, destHeight, destBuffer); break; case 4: readDataForRank4Variable(sourceOffsetX, sourceOffsetY, sourceWidth, sourceHeight, sourceStepX, sourceStepY, var, destWidth, destHeight, destBuffer); break; } } public synchronized void readDataForRank2Variable(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Variable var, int destWidth, int destHeight, ProductData destBuffer) { final int[] origin = {sourceOffsetY, sourceOffsetX}; final int[] shape = {sourceHeight, sourceWidth}; /* System.out.println(":::::" + sourceOffsetX + " " + sourceOffsetY); System.out.println(":::::" + sourceStepX + " " + sourceStepY); System.out.println(":::::" + sourceWidth + " " + sourceHeight); System.out.println(":::::" + destWidth + " " + destHeight); */ try { final Array srcArray = var.read(origin, shape); for (int i = 0; i < destHeight; i++) { final int srcStride = i * sourceWidth; final int dstStride = i * destWidth; for (int j = 0; j < destWidth; j++) { destBuffer.setElemFloatAt(dstStride + j, srcArray.getFloat(srcStride + j)); //destBuffer.setElemFloatAt(dstStride +destWidth - j - 1, srcArray.getFloat(srcStride + j)); } } } catch (IOException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank2Variable: IOException when reading variable " + var.getFullName()); } catch (InvalidRangeException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank2Variable: InvalidRangeException when reading variable " + var.getFullName()); } } private synchronized void readDataForRank3Variable(final String bandName, int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Variable var, int destWidth, int destHeight, ProductData destBuffer) { final int swath = getSwathNumber(bandName); final int[] shape0 = var.getShape(); shape0[2] = 1; // shape0[0] is height of "outer" grid. // shape0[1] is width of "outer" grid. // shape0[2] is height of the column in each cell in the "outer" grid. final int[] origin = {sourceOffsetY, sourceOffsetX, swath}; final int outerYEnd = (sourceOffsetY + (sourceHeight - 1) * sourceStepY); final int outerXEnd = (sourceOffsetX + (sourceWidth - 1) * sourceStepX); final int[] shape = {outerYEnd - origin[0] + 1, outerXEnd - origin[1] + 1, 1}; try { final Array srcArray = var.read(origin, shape); final int length = destBuffer.getNumElems(); for(int i=0; i< length; ++i) { destBuffer.setElemFloatAt(i, srcArray.getFloat(i)); } } catch (IOException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank3Variable: IOException when reading variable " + var.getFullName()); } catch (InvalidRangeException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank3Variable: InvalidRangeException when reading variable " + var.getFullName()); } } private int getSwathNumber(final String bandName) { if(mode.equals("IW")) { if(bandName.contains("IW2")) return 1; else if(bandName.contains("IW3")) return 2; } return 0; } private synchronized void readDataForRank4Variable(int sourceOffsetX, int sourceOffsetY, int sourceWidth, int sourceHeight, int sourceStepX, int sourceStepY, Variable var, int destWidth, int destHeight, ProductData destBuffer) { final int[] shape0 = var.getShape(); // shape0[0] is height of "outer" grid. // shape0[1] is width of "outer" grid. // shape0[2] is height of "inner" grid. // shape0[3] is width of "inner" grid. final int[] origin = {sourceOffsetY / shape0[2], sourceOffsetX / shape0[3], 0, 0}; //System.out.println("sourceOffsetY = " + sourceOffsetY + " shape0[2] = " + shape0[2] + " sourceOffsetX = " + sourceOffsetX + " shape0[3] = " + shape0[3]); //System.out.println("origin " + origin[0] + " " + origin[1]); final int outerYEnd = (sourceOffsetY + (sourceHeight - 1) * sourceStepY) / shape0[2]; final int outerXEnd = (sourceOffsetX + (sourceWidth - 1) * sourceStepX) / shape0[3]; //System.out.println("sourceHeight = " + sourceHeight + " sourceStepY = " + sourceStepY + " outerYEnd = " + outerYEnd); //System.out.println("sourceWidth = " + sourceWidth + " sourceStepX = " + sourceStepX + " outerXEnd = " + outerXEnd); final int[] shape = {outerYEnd - origin[0] + 1, outerXEnd - origin[1] + 1, shape0[2], shape0[3]}; try { final Array srcArray = var.read(origin, shape); final int[] idx = new int[4]; for (int i = 0; i < destHeight; i++) { // srcY is wrt to what is read in srcArray final int srcY = (sourceOffsetY - shape0[2] * origin[0]) + i * sourceStepY; idx[0] = srcY / shape[2]; for (int j = 0; j < destWidth; j++) { // srcX is wrt to what is read in srcArray final int srcX = (sourceOffsetX - shape0[3] * origin[1]) + j * sourceStepX; idx[1] = srcX / shape[3]; idx[2] = srcY - idx[0] * shape[2]; idx[3] = srcX - idx[1] * shape[3]; final int srcIdx = (idx[0] * shape[1] * shape[2] * shape[3]) + (idx[1] * shape[2] * shape[3]) + (idx[2] * shape[3]) + idx[3]; final int destIdx = i * destWidth + j; destBuffer.setElemFloatAt(destIdx, srcArray.getFloat(srcIdx)); } } } catch (IOException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank4Variable: IOException when reading variable " + var.getFullName()); } catch (InvalidRangeException e) { SystemUtils.LOG.severe("Sentinel1OCNReader.readDataForRank4Variable: InvalidRangeException when reading variable " + var.getFullName()); } } private static boolean variableIsVector(Variable variable) { final int[] shape = variable.getShape(); int cnt = 0; for (int i : shape) { if (i == 1) { cnt++; } } return cnt + 1 >= shape.length; } private void dumpVariableValues(final Variable variable, final String bandName) { try { Array arr = variable.read(); for (int i = 0; i < arr.getSize(); i++) { System.out.println("Sentinel1OCNReader: " + variable.getFullName() + "[" + i + "] = " + arr.getFloat(i)); } } catch (IOException e) { System.out.println("Sentinel1OCNReader: failed to read variable " + variable.getFullName() + " for band " + bandName); } } }