这个项目已经完成好几个月了,一直没有写总结。现在把这篇总结补上。

青岛供电段供电运行检测监控系统 概述

POSTGRES数据库。我们软件的任务是,将数据库中的数据以表格、图表、地图等形式展示出来。其中,我负责GIS地图(地理信息系统)的开发,包括地图的绘制(ArcGis, UDig)、地图发布(GeoServer)、地图展示(OpenLayers, JavaScript)和部分数据库查询操作(JPA)。该项目使用 Seam-2.3框架,以JBoss-7.1为web应用服务器。

系统登陆界面如下:

系统运行监控内容_java

主界面如下:

系统运行监控内容_数据库_02

GIS界面如下:

系统运行监控内容_数据库_03

学到的东西

虽然之前有Win32桌面应用开发基础和Java语言基础,但这次的GIS系统跟它们完全不搭边。这让我在老师分配下来任务时一度认为几乎不可能完成。其实老师的意思也是让我边学边做。。好吧,开干!

ArcGis

ArcGis是一套专业的,用于处理地理数据的软件。编辑地图用ArcMap就如同处理图片要使用PhotoShop一样自然。因此我的首要任务就是学习如何使用ArcMap。由于这套软件只能在Windows上运行,于是我不得不先在Windows下开发地图,然后再切换回Ubuntu编写Java代码。

ArcGis破解方法见: http://jingyan.baidu.com/article/1612d5001bc1c3e20f1eee7f.html

首先我从网上下载到了全国铁路规划图、全国省区和地级市规划图。地图矢量图文件以.shp结尾,而地图中的元素信息(如点元素的label)则保存在*.dbf文件中,用excel就能打开查看。 打开ArcMap,将所有shp文件导入,便得到全国省区,地级市和铁路规划图。这里我们只需要山东地图,所以要将无用的部分cut掉。在地图上右键选择“开始编辑”,用选择工具选中除山东省以外的部分,再按键盘上的Del键删除。这是一种很笨的方法,不过很有效。这时候老师发现,地图上少了一条铁路,或许是因为这条线路不太起眼所以没画上吧。于是我干脆自己给添上了。在编辑模式下,单击浮动工具栏上的线型元素,在模板中选中铁路样式,最后再在起点和终点拖出一条直线即可。完成编辑后要点击 "保存并完成编辑",否则修改不会生效。

这部分没有任何技术含量,完全是在拖鼠标,比较无聊。

GeoServer和PostGIS

GeoServer是一个免费、开源的地图服务器,采用Java编写而成。其实我们平时所看到的百度地图,Google Map都是类似于GeoServer这样的地图服务器软件所返回的图片数据。我们可以将shp文件直接发布到GeoServer中,也可以让GeoServer从数据库中获取地图数据。当用户请求到来时,GeoServer(大多数情况下)会以图片的形式返回地图数据。

GeoServer的使用方法见我另一篇文章: 使用GeoServer-2.3发布地图

在本项目中,我们需要让GeoServer从Postgre数据库中读取地图数据而不是直接发布shp文件。因此还需要安装PostGis-2.0,来为Postgre添加保存地理数据的功能,并将shp文件导入到数据表中。过程大致如下:

进入Postgre根目录的bin目录下,执行以下命令将shp文件转换为sql语句:

shp2pgsql -s "4326" -W "GBK" D:\demo\map\sd_city.shp sd_city > D:\sd_city.sql

执行以下命令执行包含地理数据的sql语句:

psql -d shandong -f D:\sd_city.sql -U postgres -W

注意,位于bin目录下的shp2pgsql.exe是PostGIS提供的工具,如果没有安装PostGis,shp2pgsql.exe就不存在。

SLD样式

SLD是一种类似于xml的标记语言,用于规定GeoServer如何渲染几何元素,在功能上类似于CSS。例如 shp中包含一些点元素,则SLD可以用来指定这些点的背景色,边框,大小,label的显示位置等。示例如下:

<FeatureTypeStyle>
     <Rule>
       <PointSymbolizer>
         <Graphic>
           <Mark>
             <WellKnownName>circle</WellKnownName>
             <Fill>
               <CssParameter name="fill">#FF0000</CssParameter>
             </Fill>
           </Mark>
           <Size>6</Size>
         </Graphic>
       </PointSymbolizer>
     </Rule>
   </FeatureTypeStyle>

这会让点显示为红色:

系统运行监控内容_javascript_04

<FeatureTypeStyle>
     <Rule>
       <PointSymbolizer>
         <Graphic>
           <Mark>
             <WellKnownName>triangle</WellKnownName>
             <Fill>
               <CssParameter name="fill">#009900</CssParameter>
               <CssParameter name="fill-opacity">0.2</CssParameter>
             </Fill>
             <Stroke>
               <CssParameter name="stroke">#000000</CssParameter>
               <CssParameter name="stroke-width">2</CssParameter>
             </Stroke>
           </Mark>
           <Size>12</Size>
         </Graphic>
       </PointSymbolizer>
     </Rule>
   </FeatureTypeStyle>

能让点显示为带边框的绿色三角形:

系统运行监控内容_数据库_05

这就是SLD的作用。在不同的缩放级别下显示不同的元素,也可以通过SLD来完成。最终,这个项目所用的地图使用了较为复杂的SLD,比如把线(Line)元素渲染成

系统运行监控内容_系统运行监控内容_06

就需要100多行SLD。

OpenLayers

有了地图服务器,也有了地图,接下来如何才能让用户在浏览器中看到地图呢?这就是OpenLayers的功能了。OpenLayers是一个用于开发WebGIS客户端的JavaScript库。我们可以使用OpenLayers向提供WMS(Web Map Service)服务的GeoServer发送请求,将返回的地图图片显示在浏览器中,并添加导航,经纬度显示,缩放,popup等功能。

发送请求的代码示例如下:

// 向GeoServer请求地图
            function initMap() {
                map = new OpenLayers.Map(
                    "main_map",
                    { // 设置允许的缩放范围
                        'minScale': '2300000',
                        'maxScale': '80000' // 1km
                    }
                );

				

				
                wmsLayer = new OpenLayers.Layer.WMS(
                    "JiNan Railroad",
                   
                    "http://" + window.location.hostname + ":" + window.location.port + "/geoserver/Demo/wms",
                    {
                        'service': 'WMS',
                        'version': '1.1.1',
                        'request': 'GetMap',
                        'layers':  'jinan',
                        'styles': '',
                        'format': 'image/png',
                        'TILED': true // 告诉geoserver启用缓存
                    }
                );
            }

其中OpenLayers.Map的第一个参数"main_map"是一个div的id,OpenLayers会将地图显示到这个div中。

添加图层,添加控件,设置缩放中心的代码示例如下:

// 添加control
            map.addControl(new OpenLayers.Control.MousePosition());  // 鼠标位置
            map.addControl(new OpenLayers.Control.ScaleLine()); // 比例尺
            map.addControl(new OpenLayers.Control.PanZoomBar());    // 缩放条
            map.removeControl(map.getControlsByClass('OpenLayers.Control.Zoom')[0]); //删除默认控件

            // 添加layers
            map.addLayer(vector_layer);
            map.addLayer(markersLayer); 
            map.addLayer(wmsLayer);           
            

            // 设置默认缩放级别和缩放中心
            if(!map.getCenter()) {
                map.setCenter(new OpenLayers.LonLat(118.5, 36.5),0);
            }

地图页面的JS代码共有600多行,实现的功能如下:

1. 向地图中添加mark点,分三类,分别是受电弓、B值、温度。不同的点用不同的图片表示。

2. 每隔10分钟向服务器发送一个ajax请求,以更新mark点数据。

3. 鼠标点击mark点会显示popup框,可以查看该监测点的各项数据,和摄像头照片。

4. 地图右下角滚动显示mark点的更新信息,并提供 '在地图中标出' 功能。

5. 读取后台生成的JSON数据。

其中第2个功能的实现思路为,在地图页面中添加一个隐藏的iframe,在iframe所指向的子页面中通过RichFaces的a4j标签发送ajax请求,最后在子页面中调用父页面的JS函数将得到的数据传到父页面(地图页面)中。

iframe:

<iframe src="JsonUpdateData.seam" width="0" height="0"></iframe>

JsonUpdateData.xhtml:

<h:form>
			<a:poll id="poll" interval="600000" enabled="true" render="update-frame" />
		</h:form>
		<a:outputPanel id="update-frame">		 
			<h:outputText id="jsonDataUpdate" value='#{alarmAction.getRecentEquipData("empty") }' />			
			<script type="text/javascript">			
				console.log('iframe: update data');
				
				var jsonStr = document.getElementById("jsonDataUpdate").innerHTML;
				var jsonObj = eval('(' + jsonStr + ')');			

				function reset() {
					// 1秒后再更新测量数据
					if(parent.onUpdateData(jsonObj) == false) {
						console.log(new Date().toLocaleTimeString() + ':更新测量数据失败,1秒后重试');
						setTimeout(reset, 1000);
					}
				}

				reset();
				
			</script>
		</a:outputPanel>

子页面调用父页面的方法,只需用parent.fun()即可。

在页面中通过EL表达式生成Json将后台数据传到前台

后台通过JPA查询数据库,将设备数据封装成AlarmInfo对象,然后在页面中通过EL表达式调用AlarmAction的getEquipJson()方法来生成JSON数据。示例如下:

JavaScript代码:

var equipments = #{alarmAction.getEquipJson()};

Java代码:

public String getEquipJson() {
		List<Equipment> list = entityManager.createQuery("select e from Equipment e order by e.code")
				.getResultList();
		
		StringBuilder str = new StringBuilder("{");
		for(Equipment e : list) {
			str.append("'" + e.getCode() + "':" +
					"{ 'id':'" + e.getId().toString() + "'," +
					"'name':'" + e.getName() + "'," +
					"'low':'" + e.getAlarmLow() + "'," +
					"'high':'" + e.getAlarmHigh() + "'," +
					"'code':'" + e.getCode() + "'," +
					"'type':'" + e.getType() + "'," +
					"'yPos':'" + e.getGisX().toString() + "'," +
					"'xPos':'" + e.getGisY().toString() + "'," +
					"'status':'" + e.getStatus() + "'},"
					);
		}
		int lastIndex = str.lastIndexOf(",");
		str.replace(lastIndex, lastIndex + 1, "}");	
		
		
		return str.toString();
	}

学到的非技术内容

1. 要随时重构你的代码,想方设法提高程序可读性。否则,你可能改Bug的时候连自己的代码是啥意思都记不着了。(非常非常重要

2. 尽量多写注释。

3. 在localhost上运行正常,永远不能确定在服务器上也能正常运行。

4. 遇到问题时用Google而不是Baidu。

5. 提高JavaScript水平。

6. 一定要把需求搞明白再开始工作。

7. 有人催你的时候一定不要急,否则你更搞不定。

8. 有问题尽量自己解决,不到万不得以不要打扰别人。(别人或许比你更忙)

9.熬夜是得不偿失的。

10. 只阅读英文资料。

小事件

有一次,铁路局的领导要来视察,这个项目是重点视察内容。这时程序已经部署到服务器上了,而我的地图偏偏出了些问题。于是我开始debug。如果单单如此倒也没什么,要命的是,客户那边还一遍遍给我们打电话催,问bug解决没。这时老师就帮我挡着,说快了,一会就好了。而我呢,满头大汗在找错误,心想,完了,我死定了。。然后我悲剧地发现这个错误不是改一行两行就能解决的,而是一个逻辑错误。。在催促的压力下,30分钟后,bug解决了。。。好险!如果是在公司的话,估计第二天我就不用来上班了。。

这是自己参与的第一个项目,回想起来,感觉又可笑又感慨。可笑的是自己当时经验不足,犯了不少低级错误,现在想想真是非常丢人;感慨的是,如果没有第一次的历练,自己永远也具备不了独立做项目的能力。当时我才大一,还有的是时间。如果是在公司上班犯这些错误,那。。后果不堪设想。。。。。