格点数据应用之一 数据存储篇(PG) 您所在的位置:网站首页 气象ec和gfs代表什么 格点数据应用之一 数据存储篇(PG)

格点数据应用之一 数据存储篇(PG)

2024-01-18 13:37| 来源: 网络整理| 查看: 265

本文使用 Zhihu On VSCode 创作并发布

格点数据是一种常见的数据类型,典型如气象领域的NetCDF与Grib/Grib2文件,但是由于格点数据比较冷门,专业搞数据库设计的技术人员搞不懂也不知道如何设计存储,而另一方面搞气象应用开发的技术人员大部分情况下不懂数据库还在使用格点文件进行业务应用开发,因此特意写下本篇文章,能对专业搞数据库的和专业搞气象应用的技术人员有所启发。

本文基于PostgreSQL数据库进行设计,PG数据库是一个TP与AP都比较强势的混合型数据库,特性多功能强黑科技丰富。本文主要技术思想参考自《基于云上分布式NoSQL的海量气象数据存储和查询方案》,并结合PG实际情况进行稍微改良。

一 格点数据简介1.1 数据特征

格点数据是典型的多维数据集,打开任何格点数据文件,如下图示意:

Image

netcdf(nc)文件文件都会显示列出(要素),时间,空间维度信息,不同维度组合能得到不同的格网平面数据,因此一个格点文件是由多个不同格网平面数据组成的。概括来说,格点文件维度信息主要由“四维”构成,说明如下:

物理量:或称“要素”,如风、温、湿、压等等。高度:比如地面风,高空1000米风,高空2000米风等,通常需要高度维度记录不同高度的气象信息。时间:必须有起报时间、预报时刻的时间维度,表达数据的时间特性。平面地理位置:描述格网的起始地理经纬度,终止地理经纬度代表的地理范围,也就是和GIS里常用的BBOX概念是一个意思。

结论:格点文件是多维数据集,文件内部根据维度组合由多个格网平面数据组成,通过“四维”能唯一描述某一个格网平面。

1.2 格点元数据

首先贴个格网示意图:

格网数据示意图

格网本身是有两部分组成:元数据+格点数组,元数据会定义格网的维度特征,例如包括格网的起点经纬度Start,终点经纬度End,除此之外包括一些其他重要信息,如每个格点(小方格就是格点)的宽高(一般都相等)res_x,res_y,宽就是水平方向的分辨率,高就是垂直方向的分辨率,一个格点文件内的格网平面会共享部分重要元数据,列表信息如下:

起始经度起始纬度终止经度终止纬度水平分辨率垂直分辨率格网宽度格网高度

同时,不同格网平面自身会根据“四维”参数不同,有自己的独享元数据,列表信息如下:

物理量名称起报时间预报时刻格网高度

元数据与格点值数组之间存在很多计算关系。

观察上图可知,格网的宽高是由地理范围和格点分辨率决定的,计算公式如下:

格网宽=(End.x-Start.x)/res_x; 格网高=(End.y-Start.y)/res_y; 格点总数 = 格网宽*格网高;

格网的行列号与格点数组索引序号存在如下计算关系:

index = grid_row*格网宽+grid_column;

一个格网平面是由很多格点单元(小正方形)组成的,这种小单元拥有地理位置属性,即输入任意经纬度,都能通过元数据,计算出其对应格网行列号,再通过上一个公式获取格点值,具体计算关系如下:

grid_row = floor((lat-Start.y)/res_y); grid_column = floor((lon-Start_x)/res_x);二 业务需求

格点数据在业务中应用场景主要分三部分:

1 格点穿透查询

很多生活类网站诸如“未来7天天气”这种功能,如下图:

未来7天天气

前文可知,格点文件是由多个格网平面组成,格网平面由多个格点单元组成,格点单元存在地理属性,所谓穿透查询,就是通过输入任意经纬度,根据元数据转换成格网行列号,然后获取相关格网平面在该行列号处的格点值。细分有:1 根据预报时刻的穿透查询,输入的是当前所在城市的经纬度,输出连续若干天的格点值对应的天气情况;2 根据起报时间的历史天气查询,如“历史上的今天的天气怎么样?”; 3 根据高度不同,查询从高空到低空的对应地理位置天气情况等。

2 WebGIS等值面可视化

属于可视化范畴,具体参考《格点数据应用之二 WebGIS等值面可视化篇》。

3 行业空间分析

如国家电网需要根据暴雨等级等值面分析受影响的高压杆塔等,属于GIS空间分析范畴,这一部分是行业中最有用的气象+行业的一种应用场景,具体可参考本公众号Spatial Data后续文章《格点数据应用之三 空间分析性能优化篇》。

本篇主要阐述格点穿透查询,由上文阐述可知,穿透查询由于查询的条件不同,可能涉及多个气象格点文件,通常每个格点文件都比较大,如果使用格点文件进行业务应用时,通常需要先打开若干业务相关格点文件,把较大数据加载到内存取数,IO与CPU开销都很大,这种做法是很不合理的,因此本文结合PG做了存储设计与性能测试。

三 存储设计

前文所述,一个格点文件,通常包括很多个不同时间不同高度的格网,而一个格网,是由元数据+格点数组组成的。采用连续数组是一种空间连续数据的一种压缩形式,避免反复描述数据位置时间等增大文件体积,所以在设计上我们仍将保留这种格点文件设计形式。

存储设计上将一份格网设计成三张表:数据集表(dataset),格网表(grid),数据表(data),设想一个格点文件是一个数据集,数据集表主要描述通用元数据,格网表主要描述“四维”独享元数据,数据表采用PG数组类型存储格点数组值。各个表之间关系如下:一个数据集对应多个格网,每个格网都会标记所属的数据集id,一个格网对应数据表里多个格点数组,这里说明下,格网文件是用一个数组存的数据,而数据库将这个大的数组拆分成连续的小数组存储,避免单列太到影响性能。

数据设计拓扑图

这里详细说明下为何拆分数据数组,由于穿透查询只针对局部某个格点,从小数组中获取数值比从大数组中获取数值能极大降低IO开销。本文实际测试通常拆分小数组以2000到2500长度为宜,该列大小小于8kb,PG库Page默认8kb,如果数据超过8kb也会增加扫描开销,那么,假设100*100的格网,共计10000个格点值,这里如果按照2500一组拆分,实际数据表就会存四行数据,每个记录所属格点id,分组序号,分组数组。

详细表结构设计如下:

create table dataSet( id uuid default uuid_generate_v4() primary key, start_x numeric, start_y numeric, end_x numeric, end_y numeric, increment_x numeric, increment_y numeric, count_x int, count_y int, chuck_number int default 2500, insert_time timestamp without time zone default now() ); COMMENT ON TABLE dataSet IS '格点数据集'; COMMENT ON column dataSet.id is '数据集id'; COMMENT ON column dataSet.start_x is '格网起点经度'; COMMENT ON column dataSet.start_y is '格网起点纬度'; COMMENT ON column dataSet.end_x is '格网终点经度'; COMMENT ON column dataSet.end_y is '格网终点纬度'; COMMENT ON column dataSet.increment_x is '格网经度增量'; COMMENT ON column dataSet.increment_y is '格网纬度增量'; COMMENT ON column dataSet.count_x is '格网宽度数量'; COMMENT ON column dataSet.count_y is '格网高度数量'; COMMENT ON column dataSet.chuck_number is '格网值分块数量'; COMMENT ON column qxcp.Grid_DataSet.insert_time is '入库时间'; create table grid_meta( id uuid default uuid_generate_v4() primary key, dataset_id uuid not null, feature text, z numeric default 0, qbsj timestamp without time zone, ybsk int ); COMMENT ON TABLE grid_meta IS '格网平面元数据'; COMMENT ON column grid_meta.id is '格网平面id'; COMMENT ON column grid_meta.dataset_id is '格网所属的数据集id'; COMMENT ON column grid_meta.feature is '要素名称'; COMMENT ON column grid_meta.z is '格网高度'; COMMENT ON column grid_meta.qbsj is '格网起报时间'; COMMENT ON column grid_meta.ybsk is '格网预报时刻'; create index gr id_meta_idx on grid_meta using btree(feature,qbsj,ybsk,z); create table grid_data{ grid_id bigint not null, chuck_idx int, datas double precision[] ); COMMENT ON TABLE grid_data IS '格网平面数据'; COMMENT ON column grid_data.grid_id is '数据所属格网平面id'; COMMENT ON column grid_data.chuck_idx is '格网数据分块序号'; COMMENT ON column grid_data.datas is '格网分块数据'; create index grid_data_idx on grid_data using btree(grid_id,chuck_idx);

那么穿透查询,输入任意经纬度,需要先转换行列号,再根据行列号和格网分块数量,计算对应的chuck_idx值和datas数组的值即可,计算公式如下:

// 根据经纬度计算grid_row,grid_column,略,前文 // 根据行列号得到格点数组序号 grid_idx = grid_row*格网宽度+grid_column; chuck_idx = ceil(grid_idx/chuck_number); datas_idx = grid_idx%chuck_number;

示例:查询某一数据集id为xxx,物理量为x,qbsj为n,ybsk为[xx1,xx2,...xxn],高度默认0的数据天气情况,伪代码分两步:第一步:根据数据集id获取元数据,根据输入经纬度计算chuck_idx与datas_idx

BEGIN --先根据格网描述,返回格网id select a.* from dataSet a where a.id=xxx into dataset_rec; --根据经纬度计算其chuck_num与relative_idx grid_row := floor((lat-dataset_rec.start_y)/dataset_rec.increment_y); grid_column := floor((lon-dataset_rec.start_x)/dataset_rec.increment_x); --pg中数组序号从1开始计数 grid_idx := ((grid_row*dataset_rec.count_x)+grid_column)+1; chuck_idx := ceil(grid_idx/dataset_rec.chuck_number); datas_idx := grid_idx%dataset_rec.chuck_number; END;

第二部分,根据chuck_idx与datas_idx去数据库捞数据:

with temp_grid_meta as ( select b.id,b.qbsj,b.ybsk from grid_meta b where b.feature=xxx and b.qbsj=_qbsj and b.ybsk=any(ybsks) ) select a.qbsj,a.ybsk,b.datas[datas_idx] as grid_value from temp_grid_meta a,grid_data b where b.grid_id=a.id and b.chuck_idx=chuck_idx;

通常将第一部分第二部分合并业务逻辑写成PG的一个function,对外提供业务服务。笔者实际测试情况是,格点穿透查询基本在1-2ms之间,速度非常快。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有