天天看點

c++讀取NetCdf檔案

netcdf入門

作者:laomai

稽核者: ybb

轉載時請注明出處: http://blog.csdn.net/laomai

最近在做一個和資料采集有關的項目,裡面用到了netcdf庫,大緻看了一下,這裡把心得寫出來,供

後來者參考。

一、概述

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的下載下傳

netcdf的是官方網站為http://www.unidata.ucar.edu/software/netcdf/。

在本文中,我們主要讨論在windows平台上使用netcdf軟體庫。我們将要從這個網站上

下載下傳如下資源

⑴netcdf的源代碼,目前的位址為

ftp://ftp.unidata.ucar.edu/pub/netcdf/netcdf-4/netcdf-beta.tar.gz

⑵netcdf的在windows平台預編譯好的dll,位址為

ftp://ftp.unidata.ucar.edu/pub/netcdf/contrib/win32/netcdf-3.6.1-win32.zip

解壓後裡面有如下東西

netcdf.dll 為編譯好的dll

ncgen.exe 為生成netcdf檔案的工具

ncdump.exe 為讀取netcdf檔案的工具

netcdf.lib 和 netcdf.exp在程式設計時會用到,後面會講。

⑶netcdf的相關文檔,包括

①netcdf的使用者手冊,下載下傳位址為http://www.unidata.ucar.edu/software/netcdf/docs/netcdf.pdf

②netcdf的入門教程, 下載下傳位址為http://www.unidata.ucar.edu/software/netcdf/docs/netcdf-tutorial.pdf

③netcdf的c接口api手冊,下載下傳位址為http://www.unidata.ucar.edu/software/netcdf/docs/netcdf-c.pdf

下面我們來看netcdf檔案的具體内容。

三、netcdf檔案的内容

一個netcdf檔案的結構包括以下對象:

1、變量(Variables)

   變量對應着真實的實體資料。比如我們家裡的電表,每個時刻顯示的讀數表示使用者的到該時刻的耗電量。

這個讀數值就可以用netcdf裡的變量來表示。它是一個以時間為自變量(或者說自變量個數為一維)的單值

函數。再比如在氣象學中要作出一個氣壓圖,就是“東經xx度,北緯yy度的點的大氣壓值為多少帕”,這是

一個二維單值函數,兩維分别是經度和緯度。函數值為大氣壓。

   從上面的例子可以看出,netcdf中的變量就是一個N維數組,數組的維數就是實際問題中的自變量個數,

數 組的值就是觀測得到的實體值。變量(數組值)在netcdf中的存儲類型有六種,ascii字元(char) ,位元組(byte), 短整型(short), 整型(int), 浮點(float), 雙精度(double). 顯然這些類型和c中的類型一緻,搞C的朋友應該很快就能明白。

2、維(dimension)

   一個維對應着函數中的某個自變量,或者說函數圖象中的一個坐标軸,線上性代數中就是一個N維向量

的一個分量(這也是維這個名稱的由來)。在netcdf中,一個維具有一個名字和範圍(或者說長度,也就

是數學上所說的定義域,可以是離散的點集合或者連續的區間)。在netcdf中,維的長度基本都是有限的,

最多隻能有一個具有無限長度的維。

3、屬性(Attribute)

   屬性對變量值和維的具體實體含義的注釋或者說解釋。因為變量和維在netcdf中都隻是無量綱的數字,

要想讓人們明白這些數字的具體含義,就得靠屬性這個對象了。

   在netcdf中,屬性由一個屬性名和一個屬性值(一般為字元串)組成。比如,在某個cdl檔案

(cdl檔案的具體格式在下一節中講述)中有這樣的代碼段

   temperature:units = "celsius" ;   

   前面的temperature是一個已經定義好的變量(Variable),即溫度,冒号後面的units就是屬性名,

表示實體機關,=後面的就是units這個屬性的值,為“celsius” ,即攝氏度,整個一行代碼的意思就是

溫度這個實體量的機關為celsius,很好了解。

三、CDL結構

   CDL全稱為network Common data form Description Language,它是用來描述netcdf檔案

的結構的一種文法格式。它包括前面所說的三種netcdf對象(變量、維、屬性)的具體定義。

看一個具體例子(這個例子cdl檔案是從netcdf教程中的2.1 節The simple xy Example摘出來的)

netcdf simple_xy {

dimensions:

x = 6 ;

y = 12 ;

variables:

int data(x, y) ;

data:

data =

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,

12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,

24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,

36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,

48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,

60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 ;

}

上面的代碼定義了一個符合netcdf格式的結構simple_xy,

這個結構包括三個部分

1、維的定義,以dimensions:關鍵字開頭

   dimensions:

x = 6 ;

y = 12 ;

定義了兩個軸(或者說兩維),名字分别為x和y,x軸的長度(準确的說是坐标點的個數)為6,

y軸的長度為12。

2、變量的定義:以variables:開頭

variables:

   int data(x, y);

   定義了一個以x軸和y軸為自變量的函數data,數學公式就是f(x,y)=data;

   注意維出現的順序是有序的,它決定data段中的具體指派結果.

3、資料的定義,以data:開頭

data:

data =

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,

12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,

24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,

36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,

48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,

60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 ;

這個段資料用數學的函數公式f(x,y)=data來看,

就是 x=0,y=0時,data = 0;

     x=0,y=1時,data = 1;

     x=5,y=11是,data=71;

要注意的是,

1、指派順序:

我們給出的是c格式的cdl檔案,是以這裡的指派順序和c語言中的一緻,也就是通常所說的“行式指派”,

而fortran語言中則是“列式指派”,是以在fortran格式的cdl檔案中,data段的數值順序和這裡正好

行列互換。    

2、自變量的預設取值和坐标變量

   如果隻給出維的長度,那麼維的值預設從0開始,然後自動加1,到(長度-1)停止,

   很多情況下我們要自己給出每個點的坐标值,這時就需要用到netcdf裡的坐标變量

   "coordinate varibles":增加一個和隻和維相關的一進制函數(自變量)并給出它的取值範圍。

   比如下面的cdl檔案(摘自netcdf教程中的2.2 The sfc pres temp Example)

   netcdf sfc_pres_temp {

dimensions:

latitude = 6 ;        //緯度軸

longitude = 12 ;      //經度軸

variables:

float latitude(latitude) ;    //坐标變量,存儲具體緯度

latitude:units = "degrees_north" ;

float longitude(longitude) ; //坐标變量,存儲具體緯度

longitude:units = "degrees_east" ;

float pressure(latitude, longitude) ;   //某個點(經度和緯度的交點)的大氣壓值

pressure:units = "hPa" ;           //大氣壓的機關為

float temperature(latitude, longitude) ; //某個點(經度和緯度的交點)的溫度值

temperature:units = "celsius" ;    //溫度的機關為

data:

latitude = 25, 30, 35, 40, 45, 50 ;

longitude = -125, -120, -115, -110, -105, -100, -95, -90, -85, -80, -75, -70 ;

pressure =

900, 906, 912, 918, 924, 930, 936, 942, 948, 954, 960, 966,

901, 907, 913, 919, 925, 931, 937, 943, 949, 955, 961, 967,

902, 908, 914, 920, 926, 932, 938, 944, 950, 956, 962, 968,

903, 909, 915, 921, 927, 933, 939, 945, 951, 957, 963, 969,

904, 910, 916, 922, 928, 934, 940, 946, 952, 958, 964, 970,

905, 911, 917, 923, 929, 935, 941, 947, 953, 959, 965, 971 ;

temperature =

9, 10.5, 12, 13.5, 15, 16.5, 18, 19.5, 21, 22.5, 24, 25.5,

9.25, 10.75, 12.25, 13.75, 15.25, 16.75, 18.25, 19.75, 21.25, 22.75, 24.25,

25.75,

9.5, 11, 12.5, 14, 15.5, 17, 18.5, 20, 21.5, 23, 24.5, 26,

9.75, 11.25, 12.75, 14.25, 15.75, 17.25, 18.75, 20.25, 21.75, 23.25, 24.75,

26.25,

10, 11.5, 13, 14.5, 16, 17.5, 19, 20.5, 22, 23.5, 25, 26.5,

10.25, 11.75, 13.25, 14.75, 16.25, 17.75, 19.25, 20.75, 22.25, 23.75,

25.25

對于上面的資料,就是

latitude = 25,longitude = -125時,pressure = 900,temperature = 9;

latitude = 25,longitude = -120時,pressure = 906,temperature = 10.5;

以此類推。

四、netcdf檔案的讀寫

   “學以緻用” ,前面講的都是netcdf的基本知識,都是為了本節的核心問題——讀寫netcdf格式的檔案

做鋪墊之用,下面我們就來看看如何建立一個netcdf格式檔案,以及如何再讀出它的内容。

1、在指令行下讀寫netcdf檔案

⑴建立一個simple_xy.cdl檔案,内容就是上一節“CDL結構”中的第一個例子。

⑵用ncgen.exe工具(下載下傳位址見前面的第二節)建立netcdf檔案

①将ncgen所在目錄加到系統path變量中或者直接将ncgen.exe拷到simple_xy.cdl所在目錄下

②執行ncgen -o simple_xy.nc simple_xy.cdl生成netcdf格式檔案simple_xy.nc

⑶生成的simple_xy.nc是一個二進制檔案,要想從這個檔案中還原出資料資訊,就要用ncdump工具

     ①将ncdump所在目錄加到系統path變量中或者直接将ncdump.exe拷到simple_xy.nc所在目錄下

     ②在指令行下執行ncdump simple_xy.nc,這時螢幕的輸出和simple_xy.cdl内容完全一樣。說明

     我們的檔案讀寫操作都是正确的。

2、程式設計讀寫netcdf檔案

前面我們知道如何手工去建立和讀取netddf檔案,下面我們來看看如何在程式中用代碼實作netcdf

檔案的建立和分析。我的程式設計環境為win2000+vc6.0 并安裝了vc sp6更新檔包。例子代碼選自

netcdf教程中的2.1節The simple xy Example

⑴将netcdf的源代碼解壓。我們将用到裡面的libsrc/netcdf.h頭檔案

⑵在vc6中建立一個空的win32控制台項目.名字為SimpleXyWrite,這個項目用來建立netcdf檔案

⑶把如下檔案拷貝到項目目錄中

①netcdf源代碼中的libsrc/netcdf.h頭檔案

②netcdf源代碼中的examples/C/simple_xy_wr.c檔案,并改名為simple_xy_wr.cpp

③netcdf預編譯包中的netcdf.dll檔案和 netcdf.lib檔案

⑷把netcdf.h檔案和simple_xy_wr.cpp加入到項目的檔案清單中

(具體菜單操作project->add to project->files)

⑸把netcdf.lib加入到項目的lib清單中

(具體菜單操作project->add to project->settings->Link->object/library modules)

⑹編譯并運作這個項目,會在項目目錄下生成一個simple_xy.nc檔案,其内容和我們手工生成的

檔案内容完全一樣。

simple_xy_wr.c檔案是建立netcdf檔案的c代碼,而examples/C/simple_xy_rd.c檔案則是分析

netcdf檔案的代碼,讀者可以用和剛才類似的步驟在vc6中編譯這個檔案。運作時把把剛才生成的

simple_xy.nc拷貝到項目的目錄下,如果檔案格式沒錯誤,會提示

*** SUCCESS reading example file simple_xy.nc!

然後退出。

3、用ncgen指令自動生成c代碼

給定了simple_xy.cdl檔案後,可以用

ncgen -c simple_xy.cdl

指令直接在螢幕上輸出c代碼.

不過,這個辦法隻限于cdl的資料比較簡單時才可以采用。對于真正的項目,

是需要我們自己去編寫代碼的。

五、小結

通過以上内容的介紹,相信讀者對netcdf已經有了大緻的了解。總體來說,

netcdf的核心内容就是通過cdl描述文法來建立一個netcdf格式檔案。抓住了這一點,

讀者再繼續深入看netcdf的其他資料時,應該就沒什麼太大的難度了。如是,作者的

目的也就達到了。

六、附錄

為便于讀者對照,現本文中用到的幾個檔案的内容在此列出

1、simple_xy.cdl檔案的内容

netcdf simple_xy {

dimensions:

x = 6 ;

y = 12 ;

variables:

int data(x, y) ;

data:

data =

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,

12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,

24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,

36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,

48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,

60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71 ;

}

2、simple_xy-wr.c檔案的内容

#include <stdlib.h>

#include <stdio.h>

#include <netcdf.h>

#define FILE_NAME "simple_xy.nc"

#define NDIMS 2

#define NX 6

#define NY 12

#define ERRCODE 2

#define ERR(e) {printf("Error: %s/n", nc_strerror(e)); exit(ERRCODE);}

int

main()

{

   int ncid, x_dimid, y_dimid, varid;

   int dimids[NDIMS];

   int data_out[NX][NY];

   int x, y, retval;

   for (x = 0; x < NX; x++)

      for (y = 0; y < NY; y++)

data_out[x][y] = x * NY + y;

   if ((retval = nc_create(FILE_NAME, NC_CLOBBER, &ncid)))

      ERR(retval);

   if ((retval = nc_def_dim(ncid, "x", NX, &x_dimid)))

      ERR(retval);

   if ((retval = nc_def_dim(ncid, "y", NY, &y_dimid)))

      ERR(retval);

   dimids[0] = x_dimid;

   dimids[1] = y_dimid;

   if ((retval = nc_def_var(ncid, "data", NC_INT, NDIMS,

       dimids, &varid)))

      ERR(retval);

   if ((retval = nc_enddef(ncid)))

      ERR(retval);

   if ((retval = nc_put_var_int(ncid, varid, &data_out[0][0])))

      ERR(retval);

   if ((retval = nc_close(ncid)))

      ERR(retval);

   printf("*** SUCCESS writing example file simple_xy.nc!/n");

   return 0;

}

3、simple_xy_rd.c檔案的内容

#include <stdlib.h>

#include <stdio.h>

#include <netcdf.h>

#define FILE_NAME "simple_xy.nc"

#define NX 6

#define NY 12

#define ERRCODE 2

#define ERR(e) {printf("Error: %s/n", nc_strerror(e)); exit(ERRCODE);}

int

main()

{

   int ncid, varid;

   int data_in[NX][NY];

   int x, y, retval;

   if ((retval = nc_open(FILE_NAME, NC_NOWRITE, &ncid)))

      ERR(retval);

   if ((retval = nc_inq_varid(ncid, "data", &varid)))

      ERR(retval);

   if ((retval = nc_get_var_int(ncid, varid, &data_in[0][0])))

      ERR(retval);

   for (x = 0; x < NX; x++)

      for (y = 0; y < NY; y++)

if (data_in[x][y] != x * NY + y)

     return ERRCODE;

   if ((retval = nc_close(ncid)))

      ERR(retval);

   printf("*** SUCCESS reading example file %s!/n", FILE_NAME);

   return 0;

}

繼續閱讀