in common/src/main/java/org/apache/sedona/common/raster/netcdf/NetCdfReader.java [158:323]
private static GridCoverage2D getRasterHelper(
NetcdfFile netcdfFile,
Variable recordVariable,
Variable lonVariable,
Variable latVariable,
boolean findCoordIndices)
throws IOException, FactoryException {
// assert(netcdfFile.getFileTypeVersion().equalsIgnoreCase("1")); //CDF - 1
ImmutableList<Attribute> globalAttributes = netcdfFile.getGlobalAttributes();
Group rootGroup = netcdfFile.getRootGroup();
Array lonData, latData;
double minX = 0, maxY = 0, translateX, translateY, skewX = 0, skewY = 0;
int width, height;
boolean isIncreasing = false;
HashMap<String, List<String>> varMetadata = new HashMap<>();
// ========================= Set Raster geo-reference (width, height, scaleX, scaleY
// =========================
lonData = lonVariable.read();
width = lonData.getShape()[0];
double firstVal, lastVal;
firstVal = getElement(lonData, 0);
lastVal = getElement(lonData, width - 1);
isIncreasing = firstVal < lastVal;
minX = isIncreasing ? firstVal : lastVal;
translateX = Math.abs(lastVal - firstVal) / (width - 1);
latData = latVariable.read();
height = latData.getShape()[0];
firstVal = getElement(latData, 0);
lastVal = getElement(latData, height - 1);
isIncreasing = firstVal < lastVal;
maxY = isIncreasing ? lastVal : firstVal;
translateY = Math.abs(lastVal - firstVal) / (height - 1);
// ======================================================================================================
String lonVariableName = lonVariable.getShortName();
String latVariableName = latVariable.getShortName();
List<Dimension> recordDimensions = recordVariable.getDimensions();
int numRecordDimensions = recordDimensions.size();
// ==================== Re-check existence of dimensions if necessary ====================
int lonIndex = numRecordDimensions - 1;
int latIndex = numRecordDimensions - 2;
if (findCoordIndices) {
lonIndex = recordVariable.findDimensionIndex(lonVariableName);
latIndex = recordVariable.findDimensionIndex(latVariableName);
if (latIndex == -1 || lonIndex == -1) {
throw new IllegalArgumentException(NetCdfConstants.COORD_IDX_NOT_FOUND);
}
}
// read recordVariable data
Array recordData = recordVariable.read();
// Set noDataValue if available, null otherwise
Double noDataValue = null;
if (recordVariable.attributes().hasAttribute(NetCdfConstants.MISSING_VALUE)) {
Attribute noDataAttribute = recordVariable.findAttribute(NetCdfConstants.MISSING_VALUE);
if (!Objects.isNull(noDataAttribute)) noDataValue = getAttrDoubleValue(noDataAttribute);
}
int[] strides = recordData.getIndex().getShape();
int[] pointers = recordData.getIndex().getCurrentCounter();
int numBands = 1;
int numValuesPerBand = 1;
for (int i = 0; i < numRecordDimensions; i++) {
Dimension recordDimension = recordDimensions.get(i);
String dimensionShortName = recordDimension.getShortName();
// TODO: Maybe replace this string comparison with index comparison since we already have lon
// and lat dimension indices stored?
if (dimensionShortName.equalsIgnoreCase(latVariableName)
|| dimensionShortName.equalsIgnoreCase(lonVariableName)) {
numValuesPerBand *= strides[i];
} else {
numBands *= strides[i];
}
}
Array[] dimensionVars = new Array[numRecordDimensions];
double scaleFactor = 1, offset = 0;
for (int i = 0; i < numRecordDimensions; i++) {
if (i == lonIndex) dimensionVars[i] = lonData;
else if (i == latIndex) dimensionVars[i] = latData;
else {
Dimension recordDimension = recordDimensions.get(i);
String dimensionShortName = recordDimension.getShortName();
// TODO: This leads to tree traversal for dimensions, can we do this in one go?
Variable recordDimVar =
findVariableRecursive(
dimensionShortName, rootGroup); // rootGroup.findVariableLocal(dimensionShortName);
if (Objects.isNull(recordDimVar))
throw new IllegalArgumentException(
String.format(NetCdfConstants.COORD_VARIABLE_NOT_FOUND, dimensionShortName));
AttributeContainer recordDimAttrs = recordDimVar.attributes();
varMetadata.computeIfAbsent(dimensionShortName, k -> new ArrayList<>());
for (Attribute attr : recordDimAttrs) {
varMetadata.get(dimensionShortName).add(attr.getStringValue());
if (attr.getShortName().equalsIgnoreCase(NetCdfConstants.SCALE_FACTOR)) {
Array values = attr.getValues();
if (!Objects.isNull(values)) scaleFactor = getElement(attr.getValues(), 0);
}
if (attr.getShortName().equalsIgnoreCase(NetCdfConstants.ADD_OFFSET)) {
Array values = attr.getValues();
if (!Objects.isNull(values)) offset = getElement(attr.getValues(), 0);
}
}
dimensionVars[i] = recordDimVar.read();
}
}
double[][] allBandValues = new double[numBands][numValuesPerBand];
// check for offset in the record variable
List<List<String>> bandMetaData = new ArrayList<>();
int currBandNum = 0;
while (currBandNum < numBands) {
if (dimensionVars.length > 2) {
// start from the bottom 3rd dimension going up to form metadata
List<String> currBandMetadata = new ArrayList<>();
for (int metadataDim = dimensionVars.length - 3; metadataDim >= 0; metadataDim--) {
double data = getElement(dimensionVars[metadataDim], pointers[metadataDim]);
String metadata = recordDimensions.get(metadataDim).getShortName() + " : " + data;
currBandMetadata.add(metadata);
}
bandMetaData.add(currBandMetadata);
}
// int dataIndex = currBandNum;
for (int j = height - 1; j >= 0; j--) {
for (int i = 0; i < width; i++) {
int index = (j * width + i);
int dataIndex = currBandNum * (width * height) + ((height - 1 - j) * width + i);
double currRecord = scaleFactor * getElement(recordData, dataIndex) + offset;
allBandValues[currBandNum][index] = currRecord;
}
}
boolean addCarry = true;
for (int currDim = dimensionVars.length - 3; currDim >= 0; currDim--) {
int maxIndex = strides[currDim];
if (addCarry) {
pointers[currDim]++;
addCarry = false;
}
if (pointers[currDim] == maxIndex) {
pointers[currDim] = 0;
addCarry = true;
}
}
currBandNum++;
}
Map<String, List<String>> rasterProperties =
getRasterProperties(globalAttributes, varMetadata, bandMetaData);
return RasterConstructors.makeNonEmptyRaster(
numBands,
width,
height,
minX,
maxY,
translateX,
-translateY,
skewX,
skewY,
0,
allBandValues,
rasterProperties,
noDataValue,
PixelInCell.CELL_CENTER);
}