1、NC文件
NetCDF全称为network Common Data Format,中文译法为“网络通用数据格式”,对程序员来说,它和zip、jpeg、bmp文件格式类似,都是一种文件格式的标准。
- netcdf文件开始的目的是用于存储气象科学中的数据,现在已经成为许多数据采集软件的生成文件的格式。
- 从数学上来说,netcdf存储的数据就是一个多自变量的单值函数。用公式来说就是f(x,y,z,…)=value, 函数的自变量x,y,z等在netcdf中叫做维(dimension)或坐标轴(axix),函数值value在netcdf中叫做变量(Variables).而自变量和函数值在物理学上的一些性质,比如计量单位(量纲)、物理学名称等等在netcdf中就叫属性(Attributes).
netcdf文件的内容
- 变量(Variables)
- 变量对应着真实的物理数据。
- 维(dimension)
- 一个维对应着函数中的某个自变量,或者说函数图象中的一个坐标轴
- 属性(Attribute)
- 属性对变量值和维的具体物理含义的注释或者说解释。
2、netcdf包
通过Java读取nc文件,目前比较活的是netcdf包,提供了非常全面的nc文件读写。但许多方法的使用并不容易理解。这里我在netcdf包的基础上,提供了一些便于使用的读取nc文件的工具类
netcdf包可以通过Maven仓库获取
pom.xml
<!-- 读取 .nc 文件 -->
<dependency>
<groupId>edu.ucar</groupId>
<artifactId>netcdf4</artifactId>
<version>4.5.5</version>
</dependency>
3、NcUtil
NcUtil.java
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.Variable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public interface NcUtils {
/**
* 读取变量中的全部数据,对于 3 维及以上的数据,是切片的数据
* 即某一变量的某一层数据
*
* @return 如果是 3 维数据,取 0 维 0 层切片;如果是 4 维数据,取 0 维 0 层、 1 维 0 层切片;
* @throws IOException 变量读取数据{@code variable.read()}时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
* @param variable
*/
NcData getDataOfSlice(Variable variable) throws IOException, InvalidRangeException;
/**
* 读取变量中的全部数据,对于 3 维及以上的数据,是切片的数据
* 即某一变量的某一层数据
*
* @param variable 具体的某一变量
* @param positionOf0 如果是 3 维数据,该参数代表第 0 维切片层数;如果是 4 维数据,该参数代表第 1 维切片层数
* @return 如果是 3 维数据,取 0 维 positionOf0 层切片;如果是 4 维数据,取 0 维 0 层、1 维 positionOf0 层切片
* @throws IOException 变量读取数据{@code variable.read()}时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
NcData getDataOfSlice(Variable variable, @Nullable Integer positionOf0) throws IOException, InvalidRangeException;
/**
* 读取变量中的全部数据,对于 3 维及以上的数据,是切片的数据
* 即某一变量的某一层数据
*
* @param variable 具体的某一变量
* @param positionOf0 如果是 3 维数据,该参数代表第 0 维切片层数;如果是 4 维数据,该参数代表第 1 维切片层数
* @param positionOf1 如果是 3 维数据,不会使用该参数;如果是 4 维数据,该参数代表第 0 维切片层数
* @return 如果是 3 维数据,取 0 维 positionOf0 层切片;如果是 4 维数据,取 0 维 positionOf1 层、1 维 positionOf0 层切片
* @throws IOException 变量读取数据{@code variable.read()}时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
NcData getDataOfSlice(Variable variable, @Nullable Integer positionOf0, @Nullable Integer positionOf1) throws IOException, InvalidRangeException;
/**
* 读取变量集合中的全部数据,对于 3 维及以上的数据,是切片的数据
*
* @param variables 变量列表
* @return 如果是 3 维数据,取 0 维 0 层切片;如果是 4 维数据,取 0 维 0 层、 1 维 0 层切片;
* @throws IOException 变量读取数据 {@code variable.read()} 时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
Map<String, NcData> getAllDataOfSlice(List<Variable> variables) throws IOException, InvalidRangeException;
/**
* 读取变量集合中的全部数据,对于 3 维及以上的数据,是切片的数据
*
* @param variables 变量列表
* @param positionOf0 如果是 3 维数据,该参数代表第 0 维切片层数;如果是 4 维数据,该参数代表第 1 维切片层数
* @return 如果是 3 维数据,取 0 维 positionOf0 层切片;如果是 4 维数据,取 0 维 0 层、1 维 positionOf0 层切片
* @throws IOException 变量读取数据 {@code variable.read()} 时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
Map<String, NcData> getAllDataOfSlice(List<Variable> variables, @Nullable Integer positionOf0) throws IOException, InvalidRangeException;
/**
* 读取变量集合中的全部数据,对于 3 维及以上的数据,是切片的数据
*
* @param variables 变量列表
* @param positionOf0 如果是 3 维数据,该参数代表第 0 维切片层数;如果是 4 维数据,该参数代表第 1 维切片层数
* @param positionOf1 如果是 3 维数据,不会使用该参数;如果是 4 维数据,该参数代表第 0 维切片层数
* @return 如果是 3 维数据,取 0 维 positionOf0 层切片;如果是 4 维数据,取 0 维 positionOf1 层、1 维 positionOf0 层切片
* @throws IOException 变量读取数据 {@code variable.read()} 时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
Map<String, NcData> getAllDataOfSlice(List<Variable> variables, @Nullable Integer positionOf0, @Nullable Integer positionOf1) throws IOException, InvalidRangeException;
/**
* 设置维度条件,读取变量中的数据
* 该方法至多设置3个维度条件
*
* @param variable 具体的某一变量
* @param ncDimensions 维度实体类
* @return 返回带有限制参数的维度对应变量nc数据,以及变量nc数据
* @throws IOException 变量读取数据{@code variable.read()}时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
NcData getDataWithLimit(@NotNull Variable variable,@NotNull NcDimension... ncDimensions) throws IOException, InvalidRangeException;
/**
* 设置维度条件,读取变量集合中的所有数据
* 该方法至多设置3个维度条件
*
* @param variables 变量列表
* @param ncDimensions 维度实体类
* @return 返回带有限制参数的维度对应变量nc数据,以及变量列表nc数据
* @throws IOException 变量读取数据{@code variable.read()}时抛出的异常
* @throws InvalidRangeException 切片过程中,重新生成 Range 对象时抛出的范围异常,已做保护措施,应该不会抛出该异常
*/
Map<String,NcData> getAllDataWithLimit(@NotNull List<Variable> variables,@NotNull NcDimension... ncDimensions) throws IOException, InvalidRangeException;
}
NcUtilImpl.java
package util.nc;
import ucar.ma2.DataType;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Range;
import ucar.nc2.Dimension;
import ucar.nc2.Variable;
import util.MathUtils;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NcUtilsImpl implements NcUtils {
@Override
public NcData getDataOfSlice(Variable variable) throws IOException, InvalidRangeException {
return getDataOfSlice(variable, null);
}
@Override
public NcData getDataOfSlice(Variable variable, Integer positionOf0) throws IOException, InvalidRangeException {
return getDataOfSlice(variable, null, null);
}
@Override
public NcData getDataOfSlice(Variable variable, Integer positionOf0, Integer positionOf1) throws IOException, InvalidRangeException {
if (variable == null)
return null;
NcData ncData = new NcData();
ncData.setDataType(variable.getDataType());
int rank = variable.getRank(); //return shape.length,返回维度个数
ncData.setDimensions(rank); //设置维度
/**
* 根据维度和变量数据类型的不同,返回不同的数组
*/
switch (rank) {
case 1:
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData1Df((float[]) variable.read().getStorage());
else
ncData.setData1Dd((double[]) variable.read().getStorage());
break;
case 2:
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData2Df((float[][]) variable.read().copyToNDJavaArray());
else
ncData.setData2Dd((double[][]) variable.read().copyToNDJavaArray());
break;
case 3:
//variable.getRanges(),返回决定该变量的各个维度的长度
List<Range> ranges3D = new ArrayList<>(variable.getRanges());
Range range3D = ranges3D.get(0); //返回第 0 维度的长度
positionOf0 = positionOf0 == null ? 0 : MathUtils.middle(positionOf0, 0, range3D.length() - 1);
ranges3D.set(0, new Range(range3D.getName(), positionOf0, positionOf0, range3D.stride())); //重新设置第0维度的长度,即获取第0维度的positionOf0层
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData2Df((float[][]) variable.read(ranges3D).reduce().copyToNDJavaArray());
else
ncData.setData2Dd((double[][]) variable.read(ranges3D).reduce().copyToNDJavaArray());
break;
case 4:
List<Range> ranges4D = new ArrayList<>(variable.getRanges());
Range range4D0 = ranges4D.get(0);
positionOf1 = positionOf1 == null ? 0 : MathUtils.middle(positionOf1, 0, range4D0.length() - 1);
ranges4D.set(0, new Range(range4D0.getName(), positionOf1, positionOf1, range4D0.stride()));
Range range4D1 = ranges4D.get(1);
positionOf0 = positionOf0 == null ? 0 : MathUtils.middle(positionOf0, 0, range4D1.length() - 1);
ranges4D.set(1, new Range(range4D1.getName(), positionOf0, positionOf0, range4D1.stride()));
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData2Df((float[][]) variable.read(ranges4D).reduce().copyToNDJavaArray());
else
ncData.setData2Dd((double[][]) variable.read(ranges4D).reduce().copyToNDJavaArray());
break;
}
return ncData;
}
@Override
public Map<String, NcData> getAllDataOfSlice(List<Variable> variables) throws IOException, InvalidRangeException {
return getAllDataOfSlice(variables, null);
}
@Override
public Map<String, NcData> getAllDataOfSlice(List<Variable> variables, Integer positionOf0) throws IOException, InvalidRangeException {
return getAllDataOfSlice(variables, null, null);
}
@Override
public Map<String, NcData> getAllDataOfSlice(List<Variable> variables, Integer positionOf0, Integer positionOf1) throws IOException, InvalidRangeException {
Map<String, NcData> map = new HashMap<>();
for (Variable variable : variables) {
map.put(variable.getFullName(), getDataOfSlice(variable, positionOf0, positionOf1));
}
return map;
}
@Override
public NcData getDataWithLimit(Variable variable, NcDimension... ncDimensions) throws IOException, InvalidRangeException {
if (variable == null)
return null;
NcData ncData = new NcData();
ncData.setDataType(variable.getDataType()); //设置nc实体类的数据类型
int limitLength = ncDimensions.length; //有限制条件的维度数
int rank = variable.getRank(); //return shape.length,返回维度个数
ncData.setDimensions(rank); //设置维度
int[] origin = new int[rank];
int[] s = new int[rank];
for (int i = 0; i < s.length; i++) {
s[i] = 1;
}
setOriginAndSection(variable, origin, s, ncDimensions); //重新给origin和s赋值
//获取有限制条件的维度集合
switch (rank) {
case 1:
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData1Df((float[]) variable.read(origin, s).getStorage());
else
ncData.setData1Dd((double[]) variable.read(origin, s).getStorage());
break;
case 2:
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData2Df((float[][]) variable.read(origin, s).copyToNDJavaArray());
else
ncData.setData2Dd((double[][]) variable.read(origin, s).copyToNDJavaArray());
break;
case 3:
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData3Df((float[][][]) variable.read(origin, s).copyToNDJavaArray());
else
ncData.setData3Dd((double[][][]) variable.read(origin, s).copyToNDJavaArray());
break;
case 4:
//仅降低一个维度,转为三维数组
for (int i = 0; i < s.length; i++) {
if (s[i] == 1) {
if (ncData.getDataType() == DataType.FLOAT)
ncData.setData3Df((float[][][]) variable.read(origin, s).reduce(i).copyToNDJavaArray());
else
ncData.setData3Dd((double[][][]) variable.read(origin, s).reduce(i).copyToNDJavaArray());
break;
}
}
break;
}
return ncData;
}
@Override
public Map<String, NcData> getAllDataWithLimit(List<Variable> variables, NcDimension... ncDimensions) throws IOException, InvalidRangeException {
Map<String, NcData> map = new HashMap<>();
for (Variable variable : variables) {
map.put(variable.getFullName(), getDataWithLimit(variable, ncDimensions));
}
return map;
}
private void setOriginAndSection(@NotNull Variable variable, int[] origin, int[] s, @NotNull NcDimension... ncDimensions) {
int startIndex = 0;
for (NcDimension ncDimension : ncDimensions) {
Dimension dimension = ncDimension.getDimension();
int dimensionIndex = variable.findDimensionIndex(dimension.getFullName());
//判断variable是否存在该dimension
if (dimensionIndex != -1) {
startIndex = ncDimension.getStartIndex();
origin[dimensionIndex] = MathUtils.middle(startIndex, 0, dimension.getLength() - 1);
s[dimensionIndex] = MathUtils.middle(ncDimension.getSection(), 1, dimension.getLength() - 1 - startIndex);
}
}
}
}
NcData.java(实体类)
package util.nc;
import ucar.ma2.DataType;
import java.util.Arrays;
/**
* nc数据的实体类
*/
public class NcData {
/**
* 维度
*/
private int dimensions;
/**
* 数据类型
* nc基本数据类型有六种:boolean,byte,char,short,int,long,float,double
* 用的是{@link ucar.ma2.DataType}
*/
private DataType dataType;
private float[] data1Df; //float类型的一维数组
private float[][] data2Df; //float类型的二维数组
private float[][][] data3Df; //float类型的三维数组
private double[] data1Dd; //double类型的一维数组
private double[][] data2Dd; //double类型的二维数组
private double[][][] data3Dd; //double类型的三维数组
@Override
public String toString() {
if (dataType == DataType.FLOAT) {
return "NcData{" +
"dimensions=" + dimensions +
", dataType=" + dataType +
", data1Df=" + Arrays.toString(data1Df) +
", data2Df=" + Arrays.deepToString(data2Df) +
", data3Df=" + Arrays.deepToString(data3Df) +
'}';
} else {
return "NcData{" +
"dimensions=" + dimensions +
", dataType=" + dataType +
", data1Dd=" + Arrays.toString(data1Dd) +
", data2Dd=" + Arrays.deepToString(data2Dd) +
", data3Dd=" + Arrays.deepToString(data3Dd) +
'}';
}
}
public int getDimensions() {
return dimensions;
}
public void setDimensions(int dimensions) {
this.dimensions = dimensions;
}
public DataType getDataType() {
return dataType;
}
public void setDataType(DataType dataType) {
this.dataType = dataType;
}
public float[] getData1Df() {
return data1Df;
}
public void setData1Df(float[] data1Df) {
this.data1Df = data1Df;
}
public float[][] getData2Df() {
return data2Df;
}
public void setData2Df(float[][] data2Df) {
this.data2Df = data2Df;
}
public float[][][] getData3Df() {
return data3Df;
}
public void setData3Df(float[][][] data3Df) {
this.data3Df = data3Df;
}
public double[] getData1Dd() {
return data1Dd;
}
public void setData1Dd(double[] data1Dd) {
this.data1Dd = data1Dd;
}
public double[][] getData2Dd() {
return data2Dd;
}
public void setData2Dd(double[][] data2Dd) {
this.data2Dd = data2Dd;
}
public double[][][] getData3Dd() {
return data3Dd;
}
public void setData3Dd(double[][][] data3Dd) {
this.data3Dd = data3Dd;
}
}
NcDimension.java
package util.nc;
import ucar.ma2.DataType;
import ucar.nc2.Dimension;
/**
* nc文件维度实体类
*/
public class NcDimension {
private Dimension dimension; //维度变量
private DataType dataType; //维度变量数据类型
private int dimensionIndex; //维度的下标
private int startIndex; //维度变量的起始下标
private int section; //维度变量从起始下标位置,获取的长度
public NcDimension() {
}
public NcDimension(Dimension dimension, int startIndex, int section) {
this.dimension = dimension;
this.startIndex = startIndex;
this.section = section;
}
public Dimension getDimension() {
return dimension;
}
public void setDimension(Dimension dimension) {
this.dimension = dimension;
}
public DataType getDataType() {
return dataType;
}
public void setDataType(DataType dataType) {
this.dataType = dataType;
}
public int getDimensionIndex() {
return dimensionIndex;
}
public void setDimensionIndex(int dimensionIndex) {
this.dimensionIndex = dimensionIndex;
}
public int getStartIndex() {
return startIndex;
}
public void setStartIndex(int startIndex) {
this.startIndex = startIndex;
}
public int getSection() {
return section;
}
public void setSection(int section) {
this.section = section;
}
}
相关资源下载:读取nc文件的工具包