接上文
seed文件二进制内容是经过steim压缩编码后进行数据交换的。steim压缩编码又分steim1和steim2两个版本。seed文件具体选用什么编码格式是根据头信息中1000子块里的编码格式来定的(在上一篇中有介绍)。本文只介绍steim的解码方法,其他编码格式有兴趣的可以自行研究。
steim解码格式在《地震波形数据交换格式》资料中有详细描述,下面就具体实现步骤和实现程序代码介绍如下:
seed文件二进制内容的存储结构是:大的数据块(一般为4096字节,其长度是在头信息010子块逻辑卷存储长度获悉的)嵌套多个小数据块(在测震文件数据中小数据块大小为512字节;强震数据文件中小数据块和大数据块大小一致也是4096字节。小数据块大小是在头信息052子块存储长度中获悉),小数据块中又嵌套多个64字节的数据帧组成。其中数据帧分为固定头段区(在每个小数据块开头,占一个64字节数据帧)和数据记录区(除固定头段区数据帧外其他数据帧);多个大的数据块组成通道数据;多个通道数据合成一个seed文件的二进制内容(通道数据是指采集传感器的(Z|E|N)不同方向的数据分量,其通道数量在头信息中050子块中通道个数获悉)。
steim1解码:64字节的数据帧读取过程中以4字节为基本单位,每4字节都代表32位二进制补码(有符号整数),每个数据帧共16组数据。跳过小数据块的第一帧(数据头段区),从第二帧开始,记为数据帧0,读取第一个4字节记为W0,将其转换为二进制形式如下所示:
数据帧0,每4字节转换为32int :[39164245 1983 2219 -2082504647 -98109678 1056806789 811082366 1576747393 -140542078 -1154529359 -930094279 129461759 -449930422 -36895742 1075988779 469149929]
W0: 39164245 前4个字节,包含此数据帧的16个2位编码C0到C15(00000010010101011001100101010101)
Ck
00:特殊值,Wk包含非数据信息,如头端或W0
01:Wk中包含4个1字节差值(4个8位样本)
10:Wk中包含2个2字节差值(2个16位样本)
11:Wk中包含1个4字节差值(1个32位样本)
W1:1983 接下来4个字节,为X0,前向积分常数 X0 = X-1 + d0, X1 = X0 + d1,..., Xn = Xn-1 + dn
W2:2219 接下来4个字节,为Xn,反向积分常数 Xn-1 = Xn - dn, Xi = Xi+1 - di+1, ..., X0 = X1 - d1
当一个系统冷启动或重新初始化后,卷上的第一个记录中d0与X0的关系:d0 = X0 - X-1 = X0
数据帧0中,W3到W15包含用2位编码C3到C15描述的样本差值di
1-63帧中,每个都在W0中含有码C0到C15,而W1到W15每个都含有用2位编码C1到C15描述的差值
W0转换为二进制为:00000010010101011001100101010101,以两位为一组记为Ck(k取值0~15)共计16组,其中Ck的内容就代表后续4字节的数据的解码方法。如上图所示,前3组即C0,C1,C2,内容都为00,代表后续读取的4字节数据不做任何处理;即C0代表W0,C1代表W1(W0后第二个4字节数据,内容为1983),C2代表W2(W0后第三个4字节数据,内容为2219)以此类推,C3代表W3,内容为10,代表数据帧0的第四个4字节数据将拆分为2个2字节数据(也就是这个地方要按2字节读取,获取两个数值,不再是4字节读取),下一个C4代表W4,内容为01,代表数据帧0的第五个4字节数据将拆分为4个1字节数据,获取四个数值……这样,W0转换的二进制数据就解释着数据帧0的16组4字节数据的解压算法(准确的说是除去前3组4字节数据,后面13组4字节数据的解码算法)。每个小数据块除去固定头段区后的第一个数据帧都是数据帧0这种形式,而后的数据帧则是另外一种形式。
取数据帧1(数据帧0后的下一帧)的第一个4字节记为W0转换为二进制:00100110010101011001100101010110,以两位为一组记为Ck(k取值0~15)共计16组,其中Ck的内容就代表后续4字节的数据的解码方法。其Ck内容所代表的解码算法同上,与上面不同的是这里解码的数据一共是15组。除数据帧0外,后面的数据帧都同数据帧1这种形式。
在数据帧0中的W1叫做前向积分常数记为X0,后面解码出来的数值记为dn(n为0,1,2……n),待一个小数据块中的所有数据全部解码完毕,进行如下操作:X0 = X-1 + d0, X1 = X0 + d1,..., Xn = Xn-1 + dn,最后一个数值应该等于数据帧0中W2的内容,即2219。如果不相等说明解码有错误。每个小数据块如此往复直到把一个通道里的所有内容全部解码。
steim2解码:在steim1解码的基础上,与其不同在于Ck代表的算法不同。
steim2:
Ck
00:同steim1,特殊值,Wk包含非数据信息
01: 同steim1,Wk中包含4个1字节差值(4个8位样本)
10:查看dnib,即Wk高端的2个位
dnib
01: Wk中含1个30位差值
10:Wk中含2个15位差值
11:Wk中含3个10位差值
11:查看dnib,即Wk高端的2个位
dnib
00:Wk中含5个6位差值
01:Wk中含6个5位差值
10:Wk中含7个4位差值
这里的算法需要在steim1的基础上,遇到Ck内容为10和11时再进行一步,即将对应的4字节数据转换为二进制后取出前两位记为dnib,再进行一轮dnib的选择,然后根据算法将Wk拆分。小数据块全部解码完毕,同steim1一样进行前向积分,而后验证最后一个数是否正确。如此就可以将seed文件正常解码了。
主要实现代码如下:
// 获取主数据内容
// file:读取的文件
// p_start: 读取文件的起始位置4096*(TimeHeadInfo.EndMark-1)
// p_end: 读取文件的偏移量(4096*(TimeHeadInfo.StartMark-1))
// save_length: 数据存储最小块大小,以字节为单位(每个最小块都有1对校验码)
// volume_length: 数据存储块大小,头文件占一个数据存储块
// 返回一个通道的数据有符号[]int32; 读取文件的位置int, error
func (s *HeadInfo) GetDatasSteim(file *os.File, p_start, p_end, save_length, volume_length int) ([]int32, string, error) {
steim,err := s.getEncodeType(file,p_start)
if err != nil {
return nil,"",err
}
var X0, Xn, Xl int32
var datas, rs_datas []int32
var m,n int
s_len := save_length/64
v_len := volume_length/64
for i := p_start - 1; i <= p_end-1; i++ {
m = i
flag_head := true
datas = []int32{}
for j := 1; j <= v_len; j++ {
n = j
if j % s_len == 0 {
Xl = 0
for k, v := range datas {
if k == 0 {
Xl = X0 - v
}
Xl = Xl + v
rs_datas = append(rs_datas, Xl)
}
if len(datas) > 0 && rs_datas[len(rs_datas)-1] != Xn {
return nil, fmt.Sprintf("%d+(%d/%d)",m,n*64,volume_length), errors.New("最后一位数据验证失败,数据存在错误!!!!")
}
flag_head = true
datas = []int32{}
continue
}
buffer := make([]byte, 64)
n, err := file.ReadAt(buffer, int64(volume_length*i+64*j))
if err != nil {
panic(err)
}
W0 := ""
if flag_head {
flag_head = false
W0 = fmt.Sprintf("%032b", binary.BigEndian.Uint32(buffer[0:4]))
//log.Println("W0: ", W0)
X0 = int32(binary.BigEndian.Uint32(buffer[4:8]))
//log.Println("Xl: ", X0)
Xn = int32(binary.BigEndian.Uint32(buffer[8:12]))
//log.Println("Xn: ", Xn)
Xl = 0
//h = 3
} else {
W0 = fmt.Sprintf("%032b", binary.BigEndian.Uint32(buffer[0:4]))
}
for a := 0; a < len(buffer[:n])/4; a++ {
data := binary.BigEndian.Uint32(buffer[a*4 : (a+1)*4])
Ck := W0[2*a : 2*(a+1)]
switch Ck {
case "00":
break
case "01":
//4个1字节
//log.Printf("8---Xk: %b \n", data>>24)
Xl = int32(int8(data >> 24))
//log.Println("Xl:", Xl)
datas = append(datas, Xl)
//log.Println("01---d0: ", int32(int8(data>>24)), "数值: ", Xl)
//log.Printf("8---Xk: %b \n", data&0xff0000>>16)
Xl = int32(int8(data & 0xff0000 >> 16))
//log.Println("Xl:", Xl)
datas = append(datas, Xl)
//log.Println("01---d1: ", int32(int8(data&0xff0000>>16)), "数值: ", Xl)
//log.Printf("8---Xk: %b \n", data&0xff00>>8)
Xl = int32(int8(data & 0xff00 >> 8))
//log.Println("Xl:", Xl)
datas = append(datas, Xl)
//log.Println("01---d2: ", int32(int8(data&0xff00>>8)), "数值: ", Xl)
//log.Printf("8---Xk: %b \n", data&0xff)
Xl = int32(int8(data & 0xff))
//log.Println("Xl:", Xl)
datas = append(datas, Xl)
//log.Println("01---d3: ", int32(int8(data&0xff)), "数值: ", Xl)
break
case "10":
var dd []int32
var X int32
if steim == "steim1" {
dd, X = s.NextSwitch1(Xl, data, "10")
}
if steim == "steim2" {
dd, X = s.NextSwitch2(Xl, data, "10")
}
Xl = X
for _, v := range dd {
datas = append(datas, v)
}
break
case "11":
var dd []int32
var X int32
if steim == "steim1" {
dd, X = s.NextSwitch1(Xl, data, "11")
}
if steim == "steim2" {
dd, X = s.NextSwitch2(Xl, data, "11")
}
Xl = X
for _, v := range dd {
datas = append(datas, v)
}
break
}
}
}
}
return rs_datas, fmt.Sprintf("%d+(%d/%d)",m,n*64,volume_length), nil
}
// steim1解压进一步选择
// previos_Xl:上一个差分数值,wk: Wk(4字节数组),mark:“10|11”
// 返回解压的数组和Xl
func (s *HeadInfo) NextSwitch1(previos_Xl int32, wk uint32, mark string) ([]int32, int32) {
data := wk
//log.Printf("Wk: %032b \n", data)
rdata := []int32{}
Xl := previos_Xl
//log.Println("switch1 mark: ", mark)
//log.Println("Xl: ", Xl)
if mark == "10" {
//2个16位差值
//log.Printf("16-0---Xk: %016b \n", data&0xffff0000>>16)
Xl = int32(int16(data & 0xffff0000 >> 16))
//log.Println("Xl:", Xl, "---int16 d0: ", int16(data&0xffff0000>>16), " ----int32 d0: ", int32(int16(data&0xffff0000>>16)))
rdata = append(rdata, Xl)
//log.Printf("16-1---Xk: %016b \n", data&0xffff)
Xl = int32(int16(data & 0xffff))
//log.Println("Xl:", Xl, " int16 d0: ", int16(data&0xffff), " int32: ", int32(int16(data&0xffff)))
rdata = append(rdata, Xl)
}
if mark == "11" {
Xl = int32(data)
log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
}
return rdata, Xl
}
// steim2解压进一步选择
// previos_Xl:上一个差分数值,wk: Wk(4字节数组),mark:“10|11”
// 返回解压的数组和Xl
func (s *HeadInfo) NextSwitch2(previos_Xl int32, wk uint32, mark string) ([]int32, int32) {
data := wk
Wk := fmt.Sprintf("%032b", data)
Ck := Wk[:2]
rdata := []int32{}
Xl := previos_Xl
if mark == "10" {
switch Ck {
case "01":
//30位差值
//log.Printf("30---Xk: %032b \n", int32(data)<<2>>2)
Xl = int32(data) << 2 >> 2
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
case "10":
//2个15位差值
//log.Printf("15-0---Xk: %032b \n", int32(data)<<2>>17)
Xl = int32(data) << 2 >> 17
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("15-1---Xk: %032b \n", int32(data)<<17>>17)
Xl = int32(data) << 17 >> 17
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
case "11":
//3个10位差值
//log.Printf("10-0---Xk: %032b \n", int32(data)<<2>>22)
Xl = int32(data) << 2 >> 22
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("10-1---Xk: %032b \n", int32(data)<<12>>22)
Xl = int32(data) << 12 >> 22
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("10-2---Xk: %032b \n", int32(data)<<22>>22)
Xl = int32(data) << 22 >> 22
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
}
}
if mark == "11" {
switch Ck {
//5个6位差值
case "00":
//log.Printf("6-0---Xk: %032b \n", int32(data)<<2>>26)
Xl = int32(data) << 2 >> 26
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("6-1---Xk: %032b \n", int32(data)<<8>>26)
Xl = int32(data) << 8 >> 26
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("6-2---Xk: %032b \n", int32(data)<<14>>26)
Xl = int32(data) << 14 >> 26
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("6-3---Xk: %032b \n", int32(data)<<20>>26)
Xl = int32(data) << 20 >> 26
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("6-4---Xk: %032b \n", int32(data)<<26>>26)
Xl = int32(data) << 26 >> 26
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
case "01":
//6个5位差值
//log.Printf("5-0---Xk: %032b \n", int32(data)<<2>>27)
Xl = int32(data) << 2 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("5-1---Xk: %032b \n", int32(data)<<7>>27)
Xl = int32(data) << 7 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("5-2---Xk: %032b \n", int32(data)<<12>>27)
Xl = int32(data) << 12 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("5-3---Xk: %032b \n", int32(data)<<17>>27)
Xl = int32(data) << 17 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("5-4---Xk: %032b \n", int32(data)<<22>>27)
Xl = int32(data) << 22 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("5-5---Xk: %032b \n", int32(data)<<27>>27)
Xl = int32(data) << 27 >> 27
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
case "10":
//7个4位差值
//log.Printf("4-0---Xk: %032b \n", int32(data)<<4>>28)
Xl = int32(data) << 4 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-1---Xk: %032b \n", int32(data)<<8>>28)
Xl = int32(data) << 8 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-2---Xk: %032b \n", int32(data)<<12>>28)
Xl = int32(data) << 12 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-3---Xk: %032b \n", int32(data)<<16>>28)
Xl = int32(data) << 16 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-4---Xk: %032b \n", int32(data)<<20>>28)
Xl = int32(data) << 20 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-5---Xk: %032b \n", int32(data)<<24>>28)
Xl = int32(data) << 24 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
//log.Printf("4-6---Xk: %032b \n", int32(data)<<28>>28)
Xl = int32(data) << 28 >> 28
//log.Println("Xl:", Xl)
rdata = append(rdata, Xl)
break
}
}
return rdata, Xl
}
在seed文件的解码过程中,我还遇到了两个小问题,到现在未能解决,如有那位高人知道,劳烦指教一二,不胜感激。第一个问题:按照采样率100Hz 24小时获取8640000个数据,测震数据正常解码后会发现比这个个数要多,多了五六百,七八百不等(在强震数据解码后没有类似问题),多了的这些数据是在数据头和数据尾部添加的测试数据,具体在数据头或者数据尾加了多少的数目我没找到在什么地方获取这个信息,研究了一下Jopens的源码也没找到,不过要是按照将多出的数据均分两头相减的方式,对于整体8640000个数据来说影响也不会太大,要是用于地震事件波形数据截取的程序,上述的方法基本没有影响;第二个问题:在头信息030子块数据字典中,我发现所有seed文件数据描述语言DDL都是USNSN的格式,而非steim2的格式,实际编解码是steim2格式,我不知道这里是有意这么标注还是程序写错了,不过这点对于编解码没有什么影响。
不得不说20年前Jopens软件就做到了这些,而且做的是那么全面,那么好,应用那么广泛,实在佩服,向老前辈们致敬!!!