文章目录

  • 一、项目的大致流程
  • 二、模拟生成气象观测数据
  • 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 模拟数据源
  • 六、总结


一、项目的大致流程

  气象数据中心的大致流程如下。

6月1ri气象预测数据分析报告的研究目的_数据

二、模拟生成气象观测数据

  本来气象观测数据是从气象观测站点获取的,现在我们只知道有哪些气象观测站点以及它的一些地理信息。但是现在无法从对应的气象观测站点获取气象观测数据,所以要模拟这些气象观测站点生成的气象观测数据。

  模拟的方法就是在合理的范围内,生成随机数。

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(),在下图位置停下,模拟没有写完的情况,效果生效。

6月1ri气象预测数据分析报告的研究目的_写入文件_02

6月1ri气象预测数据分析报告的研究目的_数据_03

(2)30秒后,再次查看文件,.tmp已经被去掉

6月1ri气象预测数据分析报告的研究目的_数据_04

(3)查看文件内容,符合预期效果。

6月1ri气象预测数据分析报告的研究目的_加载_05

五、结合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,实现每分钟生成模拟数据。