C/C++語言讀取SEGY檔案(二)
SEGY IO (2D)
本文件將介紹SEGY的讀取與寫入過程,即SEGY檔案的複製,並且在實現過程採用取樣點×道數二維陣列的形式讀寫。
新建標頭檔案SegyDataIO2D.h與C++檔案SegyDataIO2D.cpp,以及主程式main.cpp。
1 編寫標頭檔案SegyDataIO2D.h
1.1 程式描述、呼叫、宣告、定義
/********************************************************************** * Copyright(C) 2018,Company All Rights Reserved (1)版權說明 * * @file : SegyDataIO2D.cpp (2) 檔名 * * @brief : 實現地震資料的讀、寫操作 (3) 該檔案主要功能簡介 * * @version : 1.0 (4) 版本資訊 * * @author : Fan XinRan (5) 建立作者 * * @date : 2022/2/9 星期三 (6) 建立時間 * * Others : (7) 備註、改動資訊等 **********************************************************************/ //呼叫需要的C標頭檔案 #include<stdio.h> //C Language header file #include<stdlib.h> #include<string.h> #include<math.h> //呼叫需要的C++標頭檔案 #include<iostream> // C++ header file #include<vector> #include<algorithm> //呼叫非標準庫 #include"alloc.h" // 用於建立多維陣列 #include"segy.h" // 包含segy與bhed結構體,用於提取卷頭和道頭中採集、儲存的資訊 #define PI 3.141592654 //Constant Number Definition #define EPS 0.0000001 using namespace std; // 宣告名稱空間
1.2 宣告函式
輸入和輸出均為檔案指標。
bool copySeismicData2D(const char *filenameInput, const char *filenameOutput); //Copy seismic data from Inputfile to Outputfile
完整程式碼
/********************************************************************** * Copyright(C) 2018,Company All Rights Reserved * * @file : SegyDataIO2D.cpp * * @brief : 實現地震資料的讀、寫操作 * * @version : 1.0 * * @author : Fan XinRan * * @date : 2022/2/9 星期三 * * Others : **********************************************************************/ //(1)呼叫需要的C標頭檔案 #include<stdio.h> // C Language header file #include<stdlib.h> #include<string.h> #include<math.h> //(2)呼叫需要的C++標頭檔案 #include<iostream> // C++ header file #include<vector> #include<algorithm> //(3)呼叫需要的非標準庫標頭檔案 #include"alloc.h" // project header file #include"segy.h" //(4)定義全域性常量 #define PI 3.141592654 // Constant Number Definition #define EPS 0.0000001 //(5)宣告名稱空間 using namespace std; //(6)宣告函式名、輸入、輸出及其型別 bool copySeismicData2D(const char *filenameInput, const char *filenameOutput); //Copy seismic data from Inputfile to Outputfile
2 編寫C++檔案SegyDataIO2D.cpp
2.1 必要的說明
/************************************************************************************************************* Function: copySeismicData2D (1)函式名 Description: copy segy file from input data to output data (2)簡要描述其功能 Input: const char *filenameInput [in] input filename (.segy) (3)輸入變數及輸入檔案型別 Output: const char *filenameOutput[out] output filename (.segy) (4)輸出變數及輸出檔案型別 Return: bool true program success bool false program failed (5)返回值及其說明 Author: Fan XinRan (6)建立作者 Date : 2022/2/9 (7)建立時間 Others: (8)備註、改動資訊等 *************************************************************************************************************/
2.2 定義讀、寫函式
#include "SegyDataIO2D.h"
bool copySeismicData2D(const char *filenameInput, const char *filenameOutput){
//實現程式碼
...
}
(1) 定義待使用的結構體變數、數值型變數
bhed
與 segy
均為定義在segy.h中的結構體(structure),分別包含了二進位制卷頭資訊與道頭資訊,使用成員訪問運算子.
獲取其內容;
使用unsigned int
宣告整型變數,使用long long
或__int64
宣告segy檔案位元組數、地震道位元組數,防止資料量超出範圍,並且儘可能初始化變數。
bhed fileheader; // file header 卷頭
segy traceheader; // trace header 道頭
unsigned int nt=0; // number of sample 取樣點數
unsigned int sizefileheader=sizeof(fileheader); // size of fileheader;
unsigned int sizetraceheader=sizeof(traceheader); // size of traceheader;
unsigned int ncdp = 0; // number of cdp 道數
long long size_file = 0; //size of input file
long long size_trace = 0; //size of per-trace
(2)新建指標變數
在讀、寫地震道資料這一任務中,需要用到輸入指標、輸出指標、地震道資料指標以及道頭指標,四個指標變數。與1-1 SEGY IO
相比多定義了一個關於traceheader
的指標變數,用於存放道頭資訊。
FILE *fpinput = NULL; // input file pointer
FILE *fpoutput = NULL; //output file pointer
float **dataInput = NULL; //input data pointer 注意此處**dataInput前有兩個"*",表示的是二級指標
segy *traceheaderArray = NULL; //traceheader pointer
(3)開啟輸入、輸出檔案指標並判空
fpinput = fopen(filenameInput, "rb"); //open input file pointer
fpoutput = fopen(filenameOutput,"wb"); //open output file pointer
if(fpinput==NULL){ //如果檔案指標為NULL
printf("Cannot open %s file\n", filenameInput); //列印“檔案開啟失敗”
return false; //結束程式
}
if(fpoutput==NULL){
printf("Cannot open %s file\n", filenameOutput);
return false;
}
//讀寫操作
...
//fopen()與fclose()成對出現,在對檔案的操作完成後切記關閉檔案
fclose(fpinput); //close input file pointer
fclose(fpoutput); //close output file pointer
(4)讀取/計算卷、道資訊
fread(&fileheader,sizefileheader,1,fpinput); // 從輸入流(fpinput)中讀取卷頭資訊到指定地址---->fileheader
nt = fileheader.hns; //從卷頭資訊中獲取取樣點數
_fseeki64(fpinput,0,SEEK_END); // 從檔案末尾偏移這個結構體0個長度給檔案指標fpinput,即fpinput此時指向檔案尾
size_file = _ftelli64(fpinput); // 返回當前檔案位置,即檔案總位元組數
size_trace = nt*sizeof(float)+sizetraceheader; // 每一道的位元組數 = 取樣點位元組數+道頭位元組數
ncdp = (size_file - (long long)sizefileheader)/size_trace; // 道數 = (檔案總位元組數 - 卷頭位元組數)/每一道的位元組數
_fseeki64(fpinput,sizefileheader,SEEK_SET); // 從檔案開頭偏移sizefileheader(卷頭位元組數)個長度給指標fpinput,即fpinput此時指向第一道的開始
fwrite(&fileheader, sizefileheader, 1, fpoutput); // 先寫入卷頭
-
fread()
從給定流讀取資料到指標所指向的陣列中;fread(*ptr, size, nmemb,*stream)
- ptr -- 指向帶有最小尺寸 size*nmemb 位元組的記憶體塊的指標;
- size -- 要讀取的每個元素的大小,以位元組為單位。
- nmemb -- 元素的個數,每個元素的大小為 size 位元組;
- stream -- 指向 FILE 物件的指標。
-
fwrite(*ptr, size, nmemb,*stream)
引數與fread()
相同,把ptr
所指向的陣列中的資料寫入到給定流stream
中; -
_fseeki64
的用法與fseek
相同,表示從檔案指定位置偏移一定位元組數;前者具有更高的相容性; -
_ftelli64
與ftell
同理,返回給定流的當前檔案位置;_fseeki64(*stream, offset, whence)
-
stream -- 指向 FILE 物件的指標;
-
offset -- 相對 whence 的偏移量,以位元組為單位;
-
whence -- 表示開始新增偏移 offset 的位置。一般定義為
SEEK_SET
(檔案開頭)、SEEK_CUR
(檔案指標的當前位置)、SEEK_END
(檔案末尾)三類常量。
-
(5)遍歷每一條地震道,讀、寫資料
dataInput=alloc2float(nt,ncdp); // 分配nt*ncdp(取樣點數×道數)所需的記憶體空間,用來存放二維地震道資料
//其中,alloc2float是解除安裝alloc.h中的函式,建立一個float型的二維陣列
//dataInput為二級指標,可簡記為dataInput指向每一行的開頭
memset(dataInput[0], 0, nt*ncdp * sizeof(float)); // 從第一行的開頭開始,將記憶體塊中nt*ncdp個字元賦值為0
// dataInput指向每行開頭,而dataInput[0]則為整個二維陣列的起始位置
// 在記憶體的動態儲存區中分配ncdp個長度為sizetraceheader的連續空間
traceheaderArray = (segy*)calloc(ncdp,sizetraceheader);
//逐道讀取道頭與地震道資料
for(int itrace = 0; itrace < ncdp; itrace++){
fread(&traceheaderArray[itrace],sizetraceheader,1,fpinput); // &traceheaderArray[itrace]為第itrace道的地址,讀取該道頭資訊
fread(dataInput[itrace],nt * sizeof(float),1,fpinput); // dataInput[itrace]指向列開頭,並讀取nt個取樣點的資訊
}//end for(int itrace = 0; itrace < ncdp; itrace++)
//逐道寫入道頭與地震道資料
for (int itrace = 0; itrace < ncdp; itrace++) {
fwrite(&traceheaderArray[itrace], sizetraceheader, 1, fpoutput); // 寫入該道頭資訊
fwrite(dataInput[itrace], nt * sizeof(float), 1, fpoutput); // 寫入nt個取樣點的資訊
}//end for(int itrace = 0; itrace < ncdp; itrace++)
// 在每個條件語句末尾的"}"新增備註,便於尋找和區分
//在寫操作完成後釋放記憶體
free(traceheaderArray); // free traceheader pointer
free2float(dataInput); // free data input pointer
-
alloc2float(nt,ncdp)
:建立nt*ncdp
個元素的二維陣列,並分配記憶體; -
memset()
,將某一塊記憶體中的內容全部設定為指定值, 通常為新申請的記憶體做初始化工作。void memset(void *ptr, int c, size_t n)
,複製字元c
(一個無符號字元)到引數ptr
所指向的字串的前n
個字元;- str -- 指向要填充的記憶體塊;
- c -- 要被設定的值;
- n -- 要被設定為該值的字元數。
完整程式碼
/*****************************************************************************
Function: copySeismicData2D
Description: copy segy file from input data to output data
Input:
const char *filenameInput [in] input filename (.segy)
Output:
const char *filenameOutput[out] output filename (.segy)
Return:
bool true program success
bool false program failed
Author: Fan XinRan
Date : 2022/2/9
Others:
*****************************************************************************/
#include "SegyDataIO2D.h"
bool copySeismicData2D(const char *filenameInput, const char *filenameOutput){
bhed fileheader; // file header
segy traceheader; // trace header
unsigned int nt=0; // number of sample
unsigned int sizetraceheader=sizeof(traceheader); // size of traceheader;
unsigned int sizefileheader=sizeof(fileheader); // size of fileheader;
unsigned int ncdp = 0; // number of cdp
long long size_file = 0; //size of input file
long long size_trace = 0; //size of per-trace
FILE *fpinput = NULL; // input file pointer
FILE *fpoutput = NULL; //output file pointer
float *dataInput = NULL; //input data pointer
segy *traceheaderArray = NULL; //traceheader pointer
fpinput = fopen(filenameInput, "rb"); //open input file pointer
fpoutput = fopen(filenameOutput,"wb"); //open output file pointer
if(fpinput==NULL){
printf("Cannot open %s file\n", filenameInput);
return false;
}
if(fpoutput==NULL){
printf("Cannot open %s file\n", filenameOutput);
return false;
}
fread(&fileheader,sizefileheader,1,fpinput);
nt = fileheader.hns;
_fseeki64(fpinput,0,SEEK_END);
size_file = _ftelli64(fpinput);
size_trace = nt*sizeof(float)+sizetraceheader;
ncdp = (size_file - (long long)sizefileheader)/size_trace;
_fseeki64(fpinput,sizefileheader,SEEK_SET);
fwrite(&fileheader, sizefileheader, 1, fpoutput);
dataInput = alloc2float(nt,ncdp);
memset(dataInput[0], 0, nt*ncdp * sizeof(float));
traceheaderArray = (segy*)calloc(ncdp,sizetraceheader);
for(int itrace = 0; itrace < ncdp; itrace++){
fread(&traceheaderArray[itrace],sizetraceheader,1,fpinput);
fread(dataInput[itrace],nt*sizeof(float),1,fpinput);
}//end for(int itrace = 0; itrace < ncdp; itrace++)
for (int itrace = 0; itrace < ncdp; itrace++) {
fwrite(&traceheaderArray[itrace], sizetraceheader, 1, fpoutput);
fwrite(dataInput[itrace], nt * sizeof(float), 1, fpoutput);
}//end for(int itrace = 0; itrace < ncdp; itrace++)
free(traceheaderArray);
free(dataInput); // free data input pointer
fclose(fpinput); //close input file pointer
fclose(fpoutput); //close output file pointer
return true;
}
3 主函式main.cpp及執行結果
#include"ReadSeismic.h"
void main(){
copySeismicData2D("Demo.segy","Output2.segy");
}
執行主函式後,程式會讀入Demo.segy
,再寫入到Output2.segy
,從而完成對SEGY檔案的複製。
附錄
I 二維陣列的動態記憶體分配方法
a.非連續分配
首先arr
是一個二級指標,為arr
分配xDim
空間,每一維都是一個指向陣列的指標,且每個陣列內部的地址空間是連續的,但是陣列之間的地址空間沒有連續性。
void malloc2D_1(int **&a)
{
a = new int*[xDim]; // new的作用與malloc類似,分配一個一維陣列的記憶體塊
for(int i=0;i<xDim;i++)
a[i] = new int[yDim]; // 對一維陣列的每一個元素繼續分配記憶體塊
assert(a!=NULL);
}
int main()
{
int **arr = NULL; // 宣告一個二級指標
malloc2D_1(arr); // 執行分配記憶體操作
}
釋放
需要逐個陣列釋放
void free2D_1(int **a)
{
for(int i=0;i<xDim;i++)
free(a[i]);
}
b.連續分配
void malloc2D_2(int **&a)
{
a = (int **)malloc( xDim * sizeof(int *) ); // 分配xDim字元的記憶體用來存放一級指標
a[0] = (int *)malloc( xDim * yDim * sizeof(int) ); // 分配xDim * yDim個元素所需的記憶體空間
for(int i=1;i<xDim;i++)
{
a[i] = a[i-1] + yDim; // 例如a[0]指向二維陣列起始位置,a[1]=a[0]+yDim,則指向下一行(第一行)的開頭,以此類推...
}
assert(a!=NULL);
}
int main()
{
int **arr = NULL;
malloc2D_2(arr);
}
釋放
只需要釋放兩個分配記憶體的指標即可
void free2D_2(int **a)
{
free(a[0]);
free(a);
}
II 兩種索引方法
假設建立一個100×100的二維陣列,並且每一行的數值等於其行號:
行號\列號 | 0 | 1 | 2 | ... | 97 | 98 | 99 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
... | ... | ... | ... | ||||
98 | 98 | 98 | 98 | 98 | 98 | 98 | 98 |
99 | 99 | 99 | 99 | 99 | 99 | 99 | 99 |
以此為例,分別介紹兩種索引方法。
a.以一維陣列方式索引
int nrow = 100;
int ncol = 100; // 宣告行號和列號
float *a1 = (float*)calloc(nrow*ncol,sizeof(float));// 分配nrow*ncol個元素所需的記憶體空間,將起始地址指向a1
for (int irow = 0; irow < nrow; irow++) // 遍歷行
{
for(int icol = 0; icol < ncol; icol++){ // 遍歷列
a1[irow*ncol + icol] = irow; // 在一維陣列上依據行、列計算索引,為指定地址賦值
}
}
b.以二維陣列方式索引
float **a2 = alloc2float(ncol, nrow); // 利用alloc2float函式分配nrow*ncol個元素所需的記憶體空間,a2指向每行的起始位置
memset(a2[0],0,ncol*nrow*sizeof(float)); // 陣列初始化為0
for (int irow = 0; irow < nrow; irow++)
{
for (int icol = 0; icol < ncol; icol++) {
a2[irow][icol] = irow; // 以二維陣列的方式定位行、列
printf("%f\n", a2[irow][icol]);
}
}