El formato NetCDF ( del inglés Network Common Data Format) fue creado por UNIDATA. Es un formato binario orientado a arreglos (arrays) de datos, que facilita la manipulación de datos espaciales a través de varias dimensiones de forma eficiente. El formato incluye la información necesaria para determinar las dimensiones y variables almacenadas, así como sus valores, tipos de datos y atributos adicionales que son agregados en el momento de creación del archivo. Es un formato portable que puede ser manipulado desde varios lenguajes de programación con las bibliotecas NetCDF (C/C++, Fortran, Java, Perl, Python, etc.). En nuestro caso veremos como manipular este formato de archivos empleando las bibliotecas HDF-Java.
Para más información acerca del formato NetCDF se puede consultar: The NetCDF Users' Guide.
Para más información acerca del formato NetCDF se puede consultar: The NetCDF Users' Guide.
Leer archivos NetCDF desde Java
Antes de comenzar a trabajar con los archivos NetCDF añadimos a nuestro proyecto las bibliotecas HDF-Java como se explicó en la entrada anterior. En la clase donde vamos a trabajar con los archivos NetCDF es necesario agregar los siguientes import:
import java.util.List;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.Index;
import ucar.ma2.Range;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.Group;
import ucar.nc2.Variable;
import ucar.nc2.NetcdfFile;
Para abrir un archivo NetCDF empleamos la clase NetcdfFile de la siguiente forma:
NetcdfFile ncdf = NetcdfFile.open("/home/millo/Documents/NetCDF/netCDFTest.nc");
Una vez abierto el archivo NetCDF podemos acceder a toda la información que contiene el archivo. Para acceder a los atributos generales declarados cuando se creó el archivo NetCDF ejecutamos el siguiente fragmento de código:
List<Attribute> attrs = ncdf.getGlobalAttributes();
System.out.println("Total de atributos "+attrs.size());
for (Attribute attr: attrs){
System.out.print(" Nombre: " + attr.getName() + " (Tipo: " + attr.getDataType().name() + ", Valor: ");
if(attr.getDataType()==DataType.DOUBLE)
System.out.println(attr.getNumericValue()+")");
else
System.out.println(attr.getStringValue()+")");
}
Los datos dentro de un archivo NetCDF pueden estar divididos por grupos. Los elementos que conforman al archivo NetCDF pueden ser accedidos directamente desde el objeto NetcdfFile o a través del objeto de tipo Group que representa a cada grupo. Para acceder a los grupos de un archivo NetCDF podemos ejecutar el siguiente fragmento de código:
List<Group> groups=ncdf.getRootGroup().getGroups();
System.out.println("Total de grupos: "+groups.size());
for(Group group : groups){
System.out.println(" Nombre: " + group.getNameAndAttributes());
}
Para el trabajo con el formato NetCDF trabajaremos accediendo los elementos del formato directamente desde el objeto NetcdfFile, ya que en caso de ser necesario manipular los grupos de datos, cada uno de los elementos del archivo permite determinar el grupo al cual pertenece.
Las dimensiones y variables de un archivo NetCDF son dos elementos que están estrechamente relacionados. Todas las variables de una sola dimensión les corresponde un objeto de tipo Dimension, mientras que a las variables multidimensionales no. El objeto Dimension permite obtener el rango de las dimensiones que componen una variable. Para obtener todas las dimensiones de un archivo NetCDF ejecutamos el siguiente fragmento de código:
List<Dimension> dims =ncdf.getDimensions();
System.out.println("Total de dimensiones: "+dims.size());
for (Dimension dim : dims){
System.out.println(" Nombre: " + dim.getName() + " ( Tamaño: " + dim.getLength()+")");
}
Los datos del archivo NetCDF se obtienen a través de los objetos de tipo Variable. Estos objetos a su ver tienen un conjunto de atributos y propiedades, entres las que se encuentran las dimensiones de la variables. Una variable unidimensional responde a un sólo objeto Dimension, mientras que una variable multidimensional responde a tantos objetos Dimension como dimensiones tenga. Las variables también tienen asociada una forma o shape, determinada por el tamaño de cada una de las dimensiones a las que responde la variable. Para obtener todas las variables de un archivo NetCDF ejecutamos el siguiente fragmento de código:
List<Variable> vars = ncdf.getVariables();
System.out.println("Total de variables "+vars.size());
for( Variable var: vars){
System.out.print(" Nombre: " + var.getName() + " (Tipo: " + var.getDataType().name() + ", Dimensiones: " + var.getDimensionsString() + ", Tamaño:");
int [] size = var.getShape();
for(int i=0; i<size.length;++i){
System.out.print(" " +size[i]);
}
System.out.println(")");
}
De igual forma que consultamos los atributos generales del archivo NetCDF podemos consultar los atributos de las variables ejecutando el siguiente fragmento de código:
Variable var = ...; // Obtenemos una variable
List<Attribute> attrs = var.getAttributes();
System.out.println("Total de atributos "+attrs.size());
for (Attribute attr: attrs){
System.out.print(" Nombre: " + attr.getName() + " (Tipo: " + attr.getDataType().name() + ", Valor: ");
if(attr.getDataType()==DataType.DOUBLE)
System.out.println(attr.getNumericValue()+")");
else
System.out.println(attr.getStringValue()+")");
}
Cuando accedemos a los datos del archivo del formato NetCDF es necesario tener en cuenta las dimensiones que tiene la variable a la cual queremos acceder, pues por lo general, las variables con datos geográficos son de grandes dimensiones, y esto puede provocar la excepción OutOfMemory en Java. Una variante para corregir este imprevisto, es asignarle más memoria a la máquina virtual de Java, pero aún puede seguir ocurriendo la misma excepción. La otra forma más práctica de resolver este problema es no cargar en memoria toda la información de la variable, o sea, cargar la información de forma parcial. Cargar de forma parcial la información puede provocar en nuestra aplicación un pequeño retraso por las lecturas sucesivas desde el archivo, pero por lo general este retraso es poco notable, siendo mucho más notable la diferencia del consumo de memoria de la máquina virtual de Java.
Para acceder a un subconjunto de los datos de una variable, empleamos los objetos Range, los cuales permiten definir el rango de valores que se desea leer para cada dimensión de la variable. Por ejemplo, supongamos que tenemos un archivo NetCDF con una variable "tmn". Esta variable "tmn" de tipo real, está responde a tres dimensiones: "tiempo", "latitud" y "longitud", con tamaños 120, 360 y 720 respectivamente. Esto le daría a la variable "tmn" una forma tridimensional de 120x360x720, lo cual equivale a 31104000 valores reales que se deben almacenar en memoria (este valor puede resultar un poco pequeño comparado con el tamaño de otros archivos NetCDF). Si tratamos de leer todos los valores de esta variable en una PC con 1GB de RAM con los valores por defecto de la máquina virtual de Java, nos produce la excepción OutOfMemory. Sin embargo, si leemos estos valores para cada instante de la dimensión "tiempo", lo cual correspondería a 259200 (360x720) valores reales, la aplicación funcionaría correctamente incluso en PC con menos de 1GB de RAM. Tomando como base la definición de la variable "tmn", el siguiente fragmento de código lee los datos del archivo NetCDF para cada instante de la dimensión "tiempo", y luego accede a cada uno de los valores para procesarlos:
Variable tmn = ...; //Obtenemos la variable tmn
// Toma los rangos de las variables, de solo lectura
List<Range> ranges = tmn.getRanges();
// Crea una estructura con los rangos leidos
ArrayList<Range> newRanges = new ArrayList<Range>();
newRanges.add(ranges.get(0));
newRanges.add(ranges.get(1));
newRanges.add(ranges.get(2));
// Recorre todos los instantes de tiempo
for (int tm = 0; tm < 120; ++tm) {
// Actualiza el rango de tiempo
newRanges.set(0, new Range(tm, tm));
// Lee los datos para cada instante de tiempo
Array arr = tmn.read(newRanges);
// Toma el índice del arreglo de datos
Index idx = arr.getIndex();
// Recorre todas las longitudes
for(int lat = 0; lat < 360; ++lat) {
for (int lon = 0; lon < 720; ++lon) {
idx.set(0, lat, lon); // tiempo, latitud, longitud
double val = arr.getDouble(idx);
// PROCESAMOS EL VALOR LEIDO
}
}
}
De igual forma se pueden modificar otros o varios de los rangos para acceder a subconjuntos de datos más pequeños. Una vez que hemos finalizado el trabajo con el archivo NetCDF es necesario cerrarlo ejecutando el siguiente fragmento de código:
ncdf.close();
No hay comentarios.:
Publicar un comentario