浙江省自动气象站实况数据显示系统开发与实现
2022-11-16张驰吴书成魏爽滕舟鲁奕岑
张驰 吴书成 魏爽 滕舟 鲁奕岑
(浙江省气象信息网络中心 浙江省杭州市 310007)
随着气象现代化建设的不断推进,浙江省已建设4700多个区域自动气象站[1],为推动大数据资源共建共享,浙江省气象部门实现与水利部门约1万左右站点信息实时共享[2],如何方便快捷展示大量实时探测数据,是一个值得研究的重要课题[3],也是各部门对地面实况数据展示的迫切实际需求[4,5]。在实况网格业务不断推进中,其与站点实况资料的对比分析需求也十分必要[6]。
目前,地图服务常常应用在各类Web 系统上[7,8],使用地图时需要连接外网[9,10],而Web 系统的响应速度不足,对C/S 结构的PC 端气象离线地图开发较少有文献提及。GMap.NET 是一个开源免费的.NET 控件[11‐13],可应用于windows 系统下开发地图相关服务程序,其功能强大且跨平台,支持天地图、高德地图、百度地图、谷歌地图等主流的地图开发引擎,通过图层与标记点可方便的绘制点、线、面的空间要素,以及定位经纬度,地图缩放等操作。
本文针对站点实况数据展示应用在内网,必须使用离线地图,且考虑到系统交互响应速度的需求,设计开发了一套基于高德离线地图与GMap.NET 技术的浙江省自动气象站实况数据显示系统,使用该系统用户可方便的在地图上直观展示各常用地面要素,及相应的累计值、极值,时序数据曲线,并可叠加显示网格数据,给业务人员分析实况资料带来很多便利。
1 系统功能设计
将浙江省自动气象站实况数据方便直观的展示在地图上,要求地图本身范围需覆盖到浙江省及四周邻近省份,且可逐级缩放、拖拉操作,由于城区站点密集,地图最大放大级数要足够,显示的气象要素需包括气温、降水、相对湿度、气压、风向风速、地温、雪深、能见度等,以及相应的实时数据、累计值和极值等。为了对比实况网格产品资料,也需叠加显示格点数据。
为了增加操作友好性,加载数据的响应速度必须足够快,显示方式上要既能显示数值,也可显示站名、站号,按站号查找定位,计算两点间距离等辅助功能。自动站实况数据从浙江省省级实时数据库读取,由于在内网使用,需要使用离线地图。基于上述需求设计了如下数据结构来保存相关信息,其系统功能结构如图1 所示。
图1:系统功能结构
2 地图图层实现
2.1 GMap.NET地图数据
GMap.NET 支持在线与离线地图,为了内网使用方便,需要下载离线地图,本文选择的是高德地图。GMap.NET 默认不支持高德地图,但可以通过改写GMapProvider 地图数据源接口(添加AMapProvider 支持),增加支持下载与访问高德离线地图,本文根据需求选取的是浙江及四周邻近省份范围,地图级数选择1‐13 级,下载好的地图数据默认保存在SQLite 数据库里(路径TileDBv5enData.gmdb)。
地图服务程序开发中用到的GMap.NET 核心动态库主要为GMap.NET.Core.dll、GMap.NET.WindowsForms.dll,开发工具使用Visual Studio 2015/C#[14]。
2.2 地图加载
以如下方式加载离线高德地图,并初始化地图中心点与缩放等级。
2.3 行政区域边界图层
为了提高地图界面展示友好性,考虑给高德离线地图添加浙江省各地市(县)行政边界,为此可以加载浙江省各市县的bln 文件来实现。一个地市用一种颜色显示(各一个图层),先是读取bln 文件的各点经纬度,把这些经纬度添加到一个GMapPolygon(多边形)上,再定义一个GMapOverlay(图层)并添加该GMapPolygon(多边形),然后把该GMapOverlay(图层)添加到GMapControl 控件上即可。核心代码如下:
3 数据图层实现
3.1 气象数据图层
要显示一个站的某要素值,即给该要素值创建一个GMapMarker(数据标签),通过GMapMarker gMapMarker= new GMapMarkerImage(point,bitmap)可以创建一个默认标签或自定义标签,这里的bitmap 参数是一个Bitmap 类型的对象,可以自行设置,例如要显示一个气温值,可以如下方式创建一个Bitmap 类型对象:
objBitMap = new Bitmap(mywidth, myheight);
objGraphics = Graphics.FromImage(objBitMap);
然后往objGraphics 变量写入降水量值,同时可指定字体类型与颜色。为了增加显示区分度与友好性,国家站图标用”⊙”显示,浙江省内区域站图标用”.”显示,省外区域站图标用”o”显示,即站点图标后跟着要素值。
objGraphics.DrawString(icon+elementvalue, new Font("宋体", 9), Brushes.Red, new PointF(0, 0));
为了加快访问自定义数据标签速度,可将Bitmap 类型对象保存到MemoryStream(内存流)上,再以Memory Stream(内存流)方式创建GMapMarker(数据标签),其中注意须把图像格式选择为png 类型,以实现透明背景效果。
MemoryStream myStream = new MemoryStream();
objBitMap.Save(myStream, ImageFormat.Png);
GMapMarker gMapMarker = new GMapMarkerImage(point, Image.FromStream(myStream));
在创建一个GMapMarker(数据标签)后,可设置其Tag 属性,在光标移到其上时显示该站点需要的附属信息。
gMapMarker.Tag = " 站号:" + IIiii + " 站名:" +stationname + " " + "经度:" + lon + " " + "纬度:" + lat +" " + "海拔:" + height + " " + "要素:" + itemlist + " "+ "站类:" + stationtype + " " + "属地:" + province + "." +city + "." + country + "." + town + "." + village;
再将创建好的GMapMarker(数据标签)添加到一个GMapOverlay(图层),后者添加到GMapControl 控件上即可。要显示N 个站点数值,一一重复以上步骤即完成了气象数据图层的创建工作。需要指出的是,虽然一个GMapOverlay(图层)可以加载多个GMapMarker(数据标签),但当加载数量较大时会严重降低响应速度,故此处的具体数量不宜太多,可以动态调优后确定,当GMapMarker(数据标签)数量超出设定值时再动态创建一个新GMapOverlay(图层)即可。如图2 所示。
图2:气象数据图层显示效果(小时气温)
3.2 风杆风羽绘制
如表1 所示,气象上显示风向风速时习惯以风杆风羽表示,动态创建一个风杆风羽的GMapMarker(数据标签)即可实现此目的。
表1:风杆风羽含义
首先以站点经纬度为原点(origin_x,origin_y),根据风向角度计算出风杆末点的坐标值,设风向为WindD,风杆长度为L,其在x 轴、y 轴方向的投影长度分别为offset_x 和offset_y,则有:
offset_x =L * Math.Sin(WindD * Math.PI / 180)
offset_y =L * Math.Cos(WindD * Math.PI / 180)
通过语句objGraphics.DrawLine(mypen, origin_x, origin_y, origin_x + offset_x, origin_y + offset_y)画出风杆。风羽可根据风速来计算出短横、长横与三角形各自的数量,在风杆上画风羽时,一个技巧是先画三角形,次画长横,再画短横,从风杆的末点开始绘制风羽,风羽的末点坐标依赖于风羽长度,其各坐标点可据三角函数计算得出。为了增加显示友好性,不同等级风速可以不同颜色绘制,例如出现大风(≥17m/s)时以红色绘制风羽。如图3 所示。
图3:气象数据图层显示效果(小时极大风速)
3.3 网格文件加载
为了实况数据分析需要,可以把网格文件叠加显示在站点地图上,实现直观对比的功能,本文以micaps 第4 类格式文件为例进行说明。micaps 第4 类文件首部是数据格式说明,主体内容是一个R 行C 列的数组,给每个元素创建一个GMapMarker(数据标签)即可实现叠加显示网格文件的目的。但是相比站点数据而言,数组元素个数往往比站点个数大的多,若直接给每个元素创建一个GMapMarker(数据标签)显示是不适宜的。考虑到地图的缩放功能与其实际可见经纬度范围,提出如下两条创建GMapMarker(数据标签)的规则:
(1)当地图实际可见经纬度范围超过数据文件的经纬度范围时,无需把网格文件的全部数据点都显示出来,因为此时若全部显示则必然导致各点数值相互拥挤叠加以致无法分辨,故应该间隔若干数据点显示。
(2)当地图实际可见经纬度范围小于数据文件的经纬度范围时,也无需把网格文件的全部数据点都显示出来,在地图实际可见经纬度范围之外的那些数据点已然不可见,故只需显示地图实际可见经纬度范围内的数据点,并间隔显示。
地图实际可见经纬度范围可以如下代码获得:
PointLatLng pLeftTop = gmap.FromLocalToLatLng(gmap.Left, gmap.Top);
PointLatLng pLeftDown = gmap.FromLocalToLatLng(gmap.Left, gmap.Top + gmap.Height);
PointLatLng pRightTop = gmap.FromLocalToLatLng(gmap.Left + gmap.Width, gmap.Top);
PointLatLng pRightDown = gmap.FromLocalToLatLng(gmap.Left + gmap.Width, gmap.Top + gmap.Height);
数据点显示间隔数可绑定当前地图缩放级别,例如对应关系可如表2 所示设置。
表2:地图缩放级数与间隔数对应关系
例如一个501 行*601 列的网格文件,当地图放大到13级时,其实际可见经纬度范围里的数据点是18 行*38 列,实际创建GMapMarker(数据标签)的数量只占全部数据点的0.227%;当地图放大到10 级时,其实际可见经纬度范围里的数据点是28 行*60 列,实际创建GMapMarker(数据标签)的数量只占全部数据点的0.558%;当地图放大到8级时,其实际可见经纬度范围里的数据点是34 行*41 列,实际创建GMapMarker(数据标签)的数量只占全部数据点的0.463%。
采用上述两条规则以后,可以有效减少创建GMap Marker(数据标签)的数量,不仅改善了界面呈现友好性,同时极大提高交互响应速度。如图4 所示。
图4:网格文件显示效果(10 级与8 级)
3.4 两点间距计算
如图5 所示,计算两个站点之间的距离值是一项常规需求,这可以通过GMapControl 控件的MouseDown 事件来实现,按下鼠标左键时用语句PointLatLng p = new PointLatLng(currentPoint_lat, currentPoint_lon)记录下当前光标所在位置的经纬度,当连续点击两个位置后即可根据下式计算两点间的球面距:
图5:两点之间测距
上式中点1 的经纬度为(lat1,lon1),点2 的经纬度为(lat2,lon2),r 为地球半径,经纬度数值以弧度表示。
3.5 站点搜索定位
如图6 所示,由于自动站站点众多,有时想要查找定位出一个具体的站点往往不方便,此时可输入站号,通过匹配站号的方式找到某站点,取得其经纬度后创建一个GMapMarker(数据标签),再把地图重新定位到该站点即可,示例代码如下。
图6:站点搜索定位
4 小结
本文针对地面气象实况数据展示的实际需求,基于高德地图与GMap.NET 技术开发了浙江省自动气象站实况数据显示系统,其具有以下几个特点:
(1)GMap.NET 是开源免费的开发平台,功能强大,开发便捷。
(2)高德地图可达13 级,足够在站点密集的城区显示实况数据,地图数据没有安全隐患,便于内网安装部署。
(3)在叠加显示网格数据时提出两条有效的数据采样规则,系统界面友好、操作便捷且大幅度提升了交互响应速度。
(4)实况显示系统功能丰富,优化得到,运行稳定。