功能需求如下:
在一张地图上,我们希望点击相应的点之后,能够弹框显示该点的具体信息。
首先,要完成该图的效果,要首先保证你了解了下面的相关知识:
1.如果你的openlayers工程和Geoserver部署在不同的服务器上,则,你需要解决跨域的问题。
无论你是通过WMS的GetFeatureInfo或者是通过WFS的GetFeature来实现,最终都是以Ajax的方式来发起请求。
下图是用firebug来捕捉点击事件,可以看出,X-Requested-With:XMLHttpRequest
所以该请求通过Ajax方式发起。
讲到这个地方,有必要温习一下几个知识点:同源策略,跨域访问。
注:参考 袭烽-《同源策略和跨域访问 》 该文章写的很好很全,建议读者抽空细读。
(1)何谓同源策略:(Same origin policy)
URL由协议、域名、端口和路径组成,如果两个URL的协议、域名和端口相同,则表示他们同源。
浏览器的同源策略,限制了来自不同源的"document"或脚本,对当前"document"读取或设置某些属性。 (白帽子讲web安全[1])
从一个域上加载的脚本不允许访问另外一个域的文档属性。
(2)跨域访问:
顾名思义,就是一个站点中的资源去访问另外一个不同域名站点上的资源。
这种情况很常见,比如说通过 style 标签加载外部样式表文件、通过 img 标签加载外部图片、
通过 script 标签加载外部脚本文件、通过 Webfont 加载字体文件等等。
默认情况下,脚本访问文档属性等数据采用的是同源策略。
2.具体如何实现跨域访问
注:参考 袭烽-《同源策略和跨域访问 》
(1) JSONP
JSONP技术实际和Ajax没有关系。我们知道<script>标签可以加载跨域的javascript脚本,并且被加载的脚本和当前文档属于同一个域。因此在文档中可以调用/访问脚本中的数据和函数。如果javascript脚本中的数据是动态生成的,那么只要在文档中动态创建<script>标签就可以实现和服务端的数据交互。
JSONP就是利用<script>标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。其中callback函数本地文档的JavaScript函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用callback函数。当这段脚本加载到本地文档时,callback函数就被调用。
(2) Proxy
使用代理方式跨域更加直接,因为SOP的限制是浏览器实现的。如果请求不是从浏览器发起的,就不存在跨域问题了。
使用本方法跨域步骤如下:
1. 把访问其它域的请求替换为本域的请求
2. 本域的请求是服务器端的动态脚本负责转发实际的请求
各种服务器的Reverse Proxy功能都可以非常方便的实现请求的转发。
(3) CORS
通过在HTTP Header中加入扩展字段,服务器在相应网页头部加入字段表示允许访问的domain和HTTP method,客户端检查自己的域是否在允许列表中,决定是否处理响应。
实现的基础是JavaScript不能够操作HTTP Header。某些浏览器插件实际上是具有这个能力的。
服务器端在HTTP的响应头中加入(页面层次的控制模式):
Access-Control-Allow-Origin: example.com
Access-Control-Request-Method: GET,POST
Access-Control-Allow-Headers: Content-Type,Authorization,Accept,Range,Origin
Access-Control-Expose-Headers: Content-Range
Access-Control-Max-Age: 3600
多个域名之间用逗号分隔,表示对所示域名提供跨域访问权限。"*"表示允许所有域名的跨域访问。
客户端可以有两种行为:
1. 发送OPTIONS请求,请求Access-Control信息。如果自己的域名在允许的访问列表中,则发送真正的请求,否则放弃请求发送。
2. 直接发送请求,然后检查response的Access-Control信息,如果自己的域名在允许的访问列表中,则读取response body,否则放弃。
本质上服务端的response内容已经到达本地,JavaScript决定是否要去读取。
我是通过设置Proxy的方式来实现跨域访问的,具体做法,请看我上一篇的文章:Tomcat6 配置cgi openlayers跨域访问
下面贴上我程序中重要部分的代码:
<!DOCTYPE html> <html> <head> <title>ChinaMap1</title> <Meta name="keywords" content="keyword1,keyword2,keyword3"> <Meta name="description" content="this is my page"> <Meta name="content-type" content="text/html; charset=GBK"> <Meta name="language" content="java"> <Meta name="import" content="java.util.*,java.io.*"> <style type="text/css"> #map { width: 1200px; height: 900px; border: 0.5px solid black; } </style> <script src="lib/OpenLayers.js"></script> <script src="lib/proj4js/lib/proj4js-combined.js"></script> <script src="lib/transform.js"></script> <script type="text/javascript"> OpenLayers.ProxyHost = "cgi/proxy.cgi?url="; var map; var untiled; var tiled; var layer_teleport; var tpoint; var xmlHttp; var popup;//全局的变量,popup //var result; function init() { // if this is just a coverage or a group of them,disable a few items,// and default to jpeg format format = 'image/png'; var bounds = new OpenLayers.Bounds(-2578821.947218156,2367106.345797384,2092054.120327893,6385320.290080428); var options = { controls : [],maxExtent : bounds,maxResolution : 22933.484092629773,projection : "EPSG:111111",units : 'm' }; map = new OpenLayers.Map('map',options); // setup tiled layer tiled = new OpenLayers.Layer.WMS("Geoserver layers - Tiled","http://localhost:8090/geoserver/ChinaMap/wms",{ LAYERS : 'MysqLChinaMap',STYLES : '',format : format,tiled : true,tilesOrigin : map.maxExtent.left + ',' + map.maxExtent.bottom },{ buffer : 0,displayOutsideMaxExtent : true,isBaseLayer : true,yx : { 'EPSG:111111' : false } }); // setup single tiled layer untiled = new OpenLayers.Layer.WMS("Geoserver layers - Untiled",format : format },{ singleTile : true,ratio : 1,yx : { 'EPSG:111111' : false } }); map.addLayers([ untiled,tiled ]); layer_teleport = new OpenLayers.Layer.WMS("Teleport",{ layers : 'ChinaMap:viewteleportgeo',INFO_FORMAT : 'application/vnd.ogc.gml',transparent : true },{ isBaseLayer : false }); map.addLayer(layer_teleport); //添加弹出框,popup popup = new OpenLayers.Control.WMSGetFeatureInfo( { url : 'http://localhost:8090/geoserver/ChinaMap/wms',title : 'Identify features by clicking',queryVisible : true,layers : [ layer_teleport ],eventListeners : { getfeatureinfo : function(event) { var attributes; var teleportName = null; var lat = 0; var long = 0; var latDegrees = 0; var latMinutes = 0; var latSeconds = 0; var longDegrees = 0; var longMinutes = 0; var longSeconds = 0; var picture; var result = null; var feature = event.features[0]; if (feature) { attributes = feature.attributes; teleportName = attributes.TeleportName; latDegrees = attributes.LatDegrees; latMinutes = attributes.LatMinutes; latSeconds = attributes.LatSeconds; lat = parseFloat(latDegrees) + parseFloat(latMinutes / 60) + parseFloat(latSeconds / 3600); lat = Math.round(lat * 100) / 100;//保留小数点后两位 longDegrees = attributes.LongDegrees; longMinutes = attributes.longMinutes; longSeconds = attributes.longSeconds; long = parseFloat(longDegrees) + parseFloat(longMinutes / 60) + parseFloat(longSeconds / 3600); long = Math.round(long * 100) / 100;//保留小数点后两位 picture = attributes.picture; console.log(picture); result = "<div> <table> <tr> <td> <strong> 名称:</strong></td> <td>" + teleportName + "</td></tr> <tr> <td> <strong> 纬度:</strong></td><td>" + lat + "</td></tr> <tr><td><strong>经度:</strong></td><td>" + long + "</td></tr><tr><td colspan=\"2\"> <img src=\""+picture+"\" alt=\""+teleportName +"\"/></td></tr></table></div>"; } if (result) { map.addPopup(new OpenLayers.Popup.FramedCloud( "chicken",map.getLonLatFromViewPortPx(event.xy),null,result,true),true); } } } }); map.addControl(popup); popup.activate(); //页面增加切换,勾选图层的按钮 map.addControl(new OpenLayers.Control.LayerSwitcher()); // build up all controls map.addControl(new OpenLayers.Control.PanZoomBar({ position : new OpenLayers.Pixel(2,15) })); map.addControl(new OpenLayers.Control.Navigation()); map.addControl(new OpenLayers.Control.Scale($('scale'))); map.addControl(new OpenLayers.Control.MousePosition({ element : $('location') })); console.log("Hello"); map.zoomToExtent(bounds); //注册map的鼠标单击监听事件,关键字“click”代表单击鼠标 map.events.register("click",map,onMapClick); } function onMapClick(e) { var lonlat = map.getLonLatFromViewPortPx(e.xy); // Lamber 转为 经纬度 var p = ChinaMap.transformToLonLat(lonlat.lon,lonlat.lat); document.getElementById("lon").value = p.x; document.getElementById("lat").value = p.y; } </script> </head> <body onload="init()"> <div id="map"></div> 经度: <input id="lon" type="text" disabled="disabled"> 纬度: <input id="lat" type="text" disabled="disabled"> </body> </html>
注:代码中的地图坐标EPSG:111111是自定义的,因为Geoserver默认无此坐标系。
题外话:前三周刚刚接触GIS开发,发现由于地球的不规则椭球形,导致各个地方将椭球形表面的地形转化成平面地图绘制 的时候各家的标准都不一样。
中国早年采用的是Beijing-1954大地坐标系,貌似采用的是苏联“克拉索夫斯基”测绘标准,而后采用1980-Xian大地坐标系。但是由于早年测绘的数据迁移转化
的工作量大等一系列原因,某些地图仍旧采用的是1954-Beijing的大地坐标。普及一下,谷歌地图用的是ESPG:900913,而默认的国际标准是ESPG:4326。
而本文作者恰好工作需要,用到的是Beijing-1954标准,需要自定义ESPG:111111,这样才好把坐标转化为国际的经纬度来方便查看。
另,附上ESPG:111111的定义:
该定义可以在Geoserver 2.4.8版本的D:\Program Files\Coding Software\GeoServer 2.4.8\data_dir\user_projections下的epsg.properties文件中添加下面一行:
111111=PROJCS["China_Lambert_Conformal_Conic",GEOGCS["GCS_Beijing_1954",DATUM["D_Beijing_1954",SPHEROID["Krasovsky_1940",6378245.0,298.3]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic"],PARAMETER["False_Easting",PARAMETER["False_Northing",PARAMETER["Central_Meridian",105.0],PARAMETER["Standard_Parallel_1",30.0],PARAMETER["Standard_Parallel_2",62.0],PARAMETER["Latitude_Of_Origin",UNIT["Meter",1.0],AUTHORITY["EPSG","111111"]]
则在Geoserver中添加数据图层的使用,使用该标准,就能把Beijing-1954大地坐标较好地转化成ESPG:4326经纬坐标
至此完成。
由于刚刚接触javascript,所以代码写的特别丑,目前程序还在改动,有新功能还会更新推进。
GIS开发的新手,有误之处请各位不吝赐教。