文章目录
- 一、项目的大致流程
- 二、模拟生成气象观测数据
- 1、观测气象数据格式
- 2、气象站点参数
- 3.步骤
- 3.1、加载气象站点参数
- 3.2、用随机数生成气象观测数据
- 3.3、将气象观测数据写入文件
- 3.4、根据步骤设计主程序的参数
- 三、详解各个步骤
- 1.加载气象站点参数
- 1.1、设计加载气象站点参数函数
- 1.2 函数的实现
- 1.3 函数的代码
- 2.用随机数模拟生成气象观测数据
- 2.1 设计函数模拟生成观测数据的函数
- 2.2 函数的代码
- 2.3 注意事项
- 3.将 vsurfdata 容器中的数据写入文件
- 3.1 设计函数
- 3.2 函数的实现
- 四、主函数
- 4.1 运行截图
- 五、结合crontab 模拟数据源
- 六、总结
一、项目的大致流程
气象数据中心的大致流程如下。
二、模拟生成气象观测数据
本来气象观测数据是从气象观测站点获取的,现在我们只知道有哪些气象观测站点以及它的一些地理信息。但是现在无法从对应的气象观测站点获取气象观测数据,所以要模拟这些气象观测站点生成的气象观测数据。
模拟的方法就是在合理的范围内,生成随机数。
1、观测气象数据格式
气象观测数据无非就是气温、气压、降雨量等等。所以我们将模拟的气象观测数据的定格式为:站点代码(从哪个气象观测站点获取的,这个是真实的,不是模拟的),获取观测数据的时间,气温、气压、相对湿度、风向、风速、降雨量以及能见度。
2、气象站点参数
气象站点的参数,也就是气象站点的地理信息,省名称、站点编号、所在城市名、纬度、经度以及海拔高度。
3.步骤
3.1、加载气象站点参数
现在是生成对应的气象观测站点的观测数据,需要到站点的一些信息用来标识生成的气象观测数据是哪个站点的。
所以先加载站点的参数到程序中,因为站点数量是不确定的,加载到程序中用容器来暂时存放站点参数信息。
这里又涉及到一个小的技巧,当需要到程序外面的数据时,我们可以先定义一个容器来暂时存放这些数据,加载那些数据到容器中,当我们操作数据时,就不会影响到原数据。
3.2、用随机数生成气象观测数据
用生成随机数的方法来模拟生成气象观测数据,生成了之后先暂时存放在一个容器中,不着急将这些数据写入文件保存。放在程序中的容器方便调试,测试生成的数据是否符合要求,合格的数据再写到程序外的文件保存。
这里又涉及到一个技巧,就是在程序中生成的数据先在程序中定义一个容器来存放,不着急存到程序外的文件中。
3.3、将气象观测数据写入文件
3.4、根据步骤设计主程序的参数
(1)从上面的步骤可以知道,要使用的数据有站点的参数,要从存放站点参数的目录中导入,所以程序的一个参数是站点参数文件的路径及文件名。
(2)生成了观测数据要将数据保存在指定文件中,所以另一个参数是保存观测数据的指定文件路径
(3)另外一个参数就是程序的运行日志文件。
三、详解各个步骤
1.加载气象站点参数
因为生成气象站点的观测数据,要用到站点的参数来标识生成的观测数据是哪个站点的,所以要将站点参数加载到程序中。
1.1、设计加载气象站点参数函数
(1)函数名叫做 LoadSTCode
(2)函数的参数,加载站点参数,就是对气象站点参数文件进行读操作,所以函数的参数是气象站点的参数文件名。
(3)返回值,bool型,加载成功返回true,失败则false。
1.2 函数的实现
将气象站点参数加载到程序中的容器的步骤:
(1)打开存放气象站点参数的文件,这次是以读的方式打开
if ( File.Open(inifilename,"r") == false )
{
logfile.Write("File.Open(%s)failed.\n",inifilename);
return false;
}
(2)从文件中读取站点参数,因为有多个站点,所以用一个循环去读取
while(true)
{
memset(strinibuffer,0,sizeof(strinibuffer));
memset(&stcode,0,sizeof(struct st_stcode));
if ( File.Fgets(strinibuffer,100) == false )
{
logfile.Write(" File.Fgets(%s)完成.\n",inifilename);
break;
}
.....
}
(3)读取之后拆分气象站点参数到站点结构体中
CmdStr.SplitToCmd(strinibuffer,",",true); // 分隔符是逗号
CmdStr.GetValue(0, stcode.provname);
CmdStr.GetValue(1, stcode.obtid);
CmdStr.GetValue(2, stcode.cityname);
CmdStr.GetValue(3,&stcode.lat);
CmdStr.GetValue(4,&stcode.lon);
CmdStr.GetValue(5,&stcode.height);
(4)检验拆分是否成功,从这里就可以学到一个技巧,写程序不要着急,一步一步来,这里也说明了为什么还要定义一些结构体,容器去暂时存放站点信息,而不是直接导入保存的文件中。在程序中要操作数据,生成数据时,一般不建议直接操作源数据,而是复制一份(暂时放在一些变量中),这样保证了源数据不被更改,也方便调试程序。
printf("strinibuffer=%s",strinibuffer);
printf("provname=%s,obtid=%s,cityname=%s,lat=%.2lf,lon=%.2lf,height=%.2f\n",\
stcode.provname,stcode.obtid,stcode.cityname,stcode.lat,stcode.lon,stcode.height);
(5)拆分站点参数正确,导入容器。
vstcode.push_back(stcode);
1.3 函数的代码
// 二、将气象站点参数加载程序中的容器
bool LoadSTCode(const char *inifilename)
{
//(1)将气象站点参数加载到程序中,就是对气象站点参数文件进行操作,
//所以先定义一个文件操作类
CFile File;
//(2)打开站点参数文件,以读的方式
if ( File.Open(inifilename,"r") == false )
{
logfile.Write("File.Open(%s)failed.\n",inifilename);
return false;
}
//(3)将站点参数文件中的每个站点的参数读取出来了,还要进行拆分
//因为从文件中读取出来的每一个站点的参数是一个字符串,所以要定义一个拆分字符串类
CCmdStr CmdStr;
//(4)将站点参数拆分后,赋值到站点参数的结构体中,所以定义一个站点参数结构体
//因为我们定义的气象站点结构体的容器,所以拆分后不着急马上注入容器,先放在结构体中
struct st_stcode stcode;
//(5)因为站点是多个的,所以用一个while循环每次读取文件的一个站点的参数信息
//并且读取一个站点的,就拆分一个放入结构体中,然后注入容器中
//(6)定义一个字符串,用来暂时存放从参数文件读取出来的记录
char strinibuffer[101];
while(true)
{
memset(strinibuffer,0,sizeof(strinibuffer));
memset(&stcode,0,sizeof(struct st_stcode));
if ( File.Fgets(strinibuffer,100) == false )
{
logfile.Write(" File.Fgets(%s)完成.\n",inifilename);
break;
}
//(7)成功从参数文件中读取一条记录,也就是一个站点的参数信息
//进行拆分并且赋值到站点参数结构体对应的成员变量
CmdStr.SplitToCmd(strinibuffer,",",true); // 分隔符是逗号
CmdStr.GetValue(0, stcode.provname);
CmdStr.GetValue(1, stcode.obtid);
CmdStr.GetValue(2, stcode.cityname);
CmdStr.GetValue(3,&stcode.lat);
CmdStr.GetValue(4,&stcode.lon);
CmdStr.GetValue(5,&stcode.height);
printf("strinibuffer=%s",strinibuffer);
printf("provname=%s,obtid=%s,cityname=%s,lat=%.2lf,lon=%.2lf,height=%.2f\n",\
stcode.provname,stcode.obtid,stcode.cityname,stcode.lat,stcode.lon,stcode.height);
//(8)将拆分好的结构体注入容器中 vstcode
vstcode.push_back(stcode);
//sleep(5);
}
return true;
}
2.用随机数模拟生成气象观测数据
在这个步骤,将利用随机来生成气象观测数据,生成的数据先放在容器中,不着急写入文件中,写入文件可以放在另一函数中实现。
2.1 设计函数模拟生成观测数据的函数
(1)函数名 CrtSurfData
Crt——create
sur——survey
(2)函数的参数
先来分析这个函数的操作有什么,涉及到哪些变量。这个函数的目的就是生成对应的气象站点的观测数据,所以需要到气象站点的某些参数作为站点的标识。在前面的步骤中,气象站点的参数是已经导入程序中的容器(vstcode)中的了,所以要用到 vstcode 这个容器。
但是这个容器是全局变量,在CrtSurfData函数中可以直接使用,所以CrtSurfData函数不需要定义这个参数
还有一个操作就是,要将生成的气象观测数据导入容器,参照前面的 vstode 容器,在其他的程序中会使用到本程序导入容器中的数据,要操作本程序容器,比如说下一个将生成的观测数据写入文件。所以把容器定义成全局变量。
综上CrtSurfData函数可以不用参数
(3)函数的返回值
这个函数的是比较简单的,不容易出错,所以可以不需要返回值、
2.2 函数的代码
//三、模拟生成观测数据函数
void CrtSurfData()
{
/*struct st_surfdata
{
char obtid[11]; //站点代码
char ddatetime[21]; //生成数据的时间:格式yyyy-mm-dd hh:mi:ss
int t; //气温:单位 0.1摄氏度
int p; //气压:0.1百帕
int u; //相对湿度:0-100之间的值
int wd; //风向:0-360之间的值
int wf; //风速:单位 0.1m/s
int r; //降雨量: 0.1mm
int vis; //可见度:0.1米
}*/
//(1)要使用到观测数据结构体,定义一个结构体
struct st_surfdata stsurfdata;
//(2) 观测数据中有一个生成数据的时间,这个时间就是生成数据的那一刻系统时间
//所以要获取系统时间
char strLocalTime[21];
memset(strLocalTime,0,sizeof(strLocalTime));
LocalTime(strLocalTime,"yyyymmddhh24mi"); //获取系统当前时间函数
strcat(strLocalTime,":00");
//(3)要利用随机数模拟生成多个站点的观测数据,所以使用循环
srand( time(0) ); //随机数播种
for( int ii; ii<vstcode.size(); ii++ )
{
memset(&stsurfdata,0,sizeof(struct st_surfdata));
//stsurfdata.obtid=vstcode[ii].obtid;
//这里补充一个点,字符数组的赋值不要用=,这样会将空格也赋值进去
//尽量使用STRCPY()
STRCPY(stsurfdata.obtid,11,vstcode[ii].obtid); // 站点代码
STRCPY(stsurfdata.ddatetime,20,strLocalTime); // 系统当前时间
stsurfdata.t = rand()%351; //气温
stsurfdata.p = rand()%265+10000; //气压:0.1百帕
stsurfdata.u = rand()%100+1; //相对湿度:0-100之间的值
stsurfdata.wd = rand()%360; //风向
stsurfdata.wf = rand()%150; //风速
stsurfdata.r = rand()%16; //降雨量
stsurfdata.vis = rand()%5001+100000; //可见度
vsurfdata.push_back(stsurfdata);
}
}
2.3 注意事项
(1)始终要注意一个点就是,不要直接操作源数据,所以这里就定义了一些结构体和容器来暂时存放数据,操作的时候是操作这些变量里面的数据。
(2)字符数组的赋值不要直接用“=”,会将字符数组中的全部内容赋值过去,包括空格,如果没有初始化的话,会将一些垃圾值赋值过去。
所以涉及字符数组赋值时,用STRCPY或者STRNCPY函数。
3.将 vsurfdata 容器中的数据写入文件
这个步骤就是要将生成的观测数据写入指定的目录文件中,这里又有一个要学习的点,就是如果有多个生成观测数据的文件时,如何区别这些文件。可以在文件名上加上一些变量,以便区分。
还有一个重要的技巧,就是保证数据的完整性,之前的文章有介绍过,现在不详细说了。解决方法就是中间状态文件的标识,来保证文件的完整性。
3.1 设计函数
(1)函数名
CrtSurFile——生成观测数文件
(2)函数的参数
涉及的操作就是将观测数据写入文件,所以参数为输出的路径,就是要写到哪里
(3)函数的返回值
bool 类型。
3.2 函数的实现
// 四、将气象观测数据写入指定文件
bool CrtSurfFile(const char *outpath)
{
// (1)写入文件涉及文件操作,定义一个文件类
CFile File;
// (2)为了与其他的文件区别开来,要在文件名上加上其他的变量,所以要定义一个字符数组重命名
char stroutpath[301];
memset(stroutpath,0,sizeof(stroutpath));
// (3)在输出的文件名上加上的变量,其中一个是系统当前时间
char strLocalTime[21];
memset(strLocalTime,0,sizeof(strLocalTime));
LocalTime(strLocalTime,"yyyymmddhh24miss");
// (4)重命名文件名,并且为防止在写入文件还没有写完,文件就被读取,所以文件没有写完时,
// 加上中间状态的标识,在文件名后面加上 .tmp,这个步骤已经封装在OpenForRename函数中
SPRINTF(stroutpath,301,"%s/SURF_ZH_%s_%d.txt",outpath,strLocalTime,getpid());
//(5) 以写的方式打开文件,OpenForRename已经封装有中间状态文件的操作
if ( File.OpenForRename(stroutpath,"w") == false )
{
printf("File.OpenForRename(%s)failed!\n",stroutpath);
return false;
}
//(6)向文件写入数据,多个观测站点的观测数据,所以用for循环
for( int ii; ii<vsurfdata.size(); ii++ )
{
//站点代码,数据时间,气温,气压,相对湿度,风向,风速,降雨量,能见度
File.Fprintf("%s,%s,%.1f,%.1f,%d,%d,%.1f,%.1f,%.1f\n",vsurfdata[ii].obtid,\
vsurfdata[ii].ddatetime,vsurfdata[ii].t/10.0,vsurfdata[ii].p/10.0,vsurfdata[ii].u,\
vsurfdata[ii].wd,vsurfdata[ii].wf/10.0,vsurfdata[ii].r/10.0,vsurfdata[ii].vis/10.0);
}
logfile.Write("观测数据写入数据文件(%s)成功!生成数据时间=%s,记录数=%d\n",\
stroutpath,vsurfdata[0].ddatetime,vsurfdata.size());
// (7) 数据完全写入,关闭文件,并且重命名文件名,就是将中间状态去掉
File.CloseAndRename();
return true;
}
四、主函数
int main(int argc, char *argv[])
{
if(argc != 4)
{
printf("本程序用于模拟生成气象站点的气象观测数据。\n");
printf("Using:/oracle/qxidc/bin/crtsurfdata 站点参数 存放气象观测数据目录 程序运>
行日志名\n");
printf("Example: /oracle/qxidc/bin/crtsurfdata /oracle/qxidc/ini/stcode.ini /oracle/qxidc/data/ftp/surfdata /oracle/qxidc/log/crtsurfdata.log\n");
return -1;
}
// 一、关闭全部的输入和输出
void CloseIOAndSignal();
// 1.1 处理程序退出的信号
// 1.2 打开日志文件
if ( logfile.Open(argv[3],"a+") == false )
{
printf("打开日志文件(%s)failed.\n",argv[3]);
return -1;
}
// 二、将气象站点参数加载程序中的容器
if ( LoadSTCode(argv[1]) == false )
{
logfile.Write("LoadSTCode(%s)failed.\n",argv[1]);
return -1;
}
logfile.Write("气象站点参数加载成功(%s)\n",argv[1]);
// 三、模拟生成观测数据
CrtSurfData();
// 四、将观测数据写入指定的文件
if ( CrtSurfFile(argv[2]) == false )
{
logfile.Write("CrtSurfFile(%s)failed.\n",argv[2]);
return -1;
}
return 0;
}
4.1 运行截图
(1)我利用sleep(),在下图位置停下,模拟没有写完的情况,效果生效。
(2)30秒后,再次查看文件,.tmp已经被去掉
(3)查看文件内容,符合预期效果。
五、结合crontab 模拟数据源
现实中气象站点生成的观测数据,能每分钟或者每小时就会生成一批观测数据,监测天气的变化。然后给其他的部门使用这些数据,一般用 ftp从气象站点的服务器获取气象数据。
模拟实现这个功能,生成每分钟的观测数据,要结合crontab,写一个crontab任务,让系统每分钟执行一次这个模拟生成观测数据的程序。如下图
*/1 * * * * /oracle/qxidc/bin/crtsurfdata /oracle/qxidc/ini/stcode.ini /oracle/qxidc/data/ftp/surfdata /oracle/qxidc/log/crtsurfdata.log
~
六、总结
(1)在程序中尽量不要直接操作源数据,先复制一份再操作,这样利于调试程序,程序中生成的数据也是一样,尽量不要直接导出。先用中间变量来存放数据,调试完,确认数据正确再导出或者操作方法正确再操作数据。
(2)区分文件的方法,可以在重命名时,加上其他的变量。
(3)保证文件的完整性,加上中间状态的标识。
(4)善于封装工具。
(5)在处理数据的时候,整数比浮点数好处理,浮点数可以用整数来表示,比如 12个 0.1,可以表示1.2.
(6)结合crontab,实现每分钟生成模拟数据。