地图应用分三种级别:示意地图(Map Chart),地图(Map),地理信息系统(GIS),第一种一般使用相对坐标系,后两种则为真实的地理坐标,其中第二种以谷歌地图为表明,平常生活中广泛使用,后一种则为专业的GIS,专业领域作拓扑分析、流域分析时用到,示意地图咱们已经有不少例子,好比美国大选示例、中国地图示例等,今天介绍第二种地图的应用,结合OpenLayers和谷歌地图实现地图的拓扑图应用:demo.qunee.com/map/map.html javascript
OpenLayers是开源地理基金会做(OSGeo.org)支持的项目之一,是一种通用的地理客户端平台,支持谷歌地图,Bing地图,WMS,GML等多种地图在线服务,这里用到的是谷歌地图,须要引入OpenLayers和google map的js类库和css文件 引入相关类库 css
<link rel="stylesheet" href="OpenLayers/theme/default/style.css" type="text/css"> <script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script> <script src="OpenLayers/OpenLayers.js"></script>
参照OpenLayers官方示例,完成地图初始化工做 html
function initMap(canvas, lon, lat){ map = new OpenLayers.Map(canvas, { projection: 'EPSG:3857', layers: [ new OpenLayers.Layer.Google( "Google Streets", // the default {numZoomLevels: 20} ), new OpenLayers.Layer.Google( "Google Physical", {type: google.maps.MapTypeId.TERRAIN} ), new OpenLayers.Layer.Google( "Google Hybrid", {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20} ), new OpenLayers.Layer.Google( "Google Satellite", {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22} ) ], center: new OpenLayers.LonLat(lon, lat).transform('EPSG:4326', 'EPSG:3857'), zoom: 5 }); map.addControl(new OpenLayers.Control.LayerSwitcher()); return map; }
OpenLayers与Qunee是两套不一样的组件库,有着各自的交互系统和坐标系,须要实现组件叠加,以及坐标和交互的同步 node
OpenLayers结构复杂,具备多个HTML图层,而Qunee相对简单,因此最终决定将Qunee插入到OpenLayers的viewportDiv中 canvas
var canvas = document.createElement('div'); canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.style.position = 'absolute'; canvas.style.top = '0px'; canvas.style.left = '0px'; canvas.style.zIndex = 999; map.viewPortDiv.insertBefore(canvas, map.viewPortDiv.firstChild); Q.doSuperConstructor(this, MapGraph, [canvas]);
Qunee使用的是屏幕坐标,与地图坐标系彻底不一样,须要作转换 api
须要两步,首先将经纬度转换成当前地图的投影坐标,使用的是OpenLayers提供的OpenLayers.LonLat#transform(原始投影, 目标投影)方法 ide
toLonLat: function(lon, lat){ var l = new OpenLayers.LonLat(lon, lat); l.transform('EPSG:4326', graph.map.getProjectionObject()); return l; }
而后将转换后的坐标转换成屏幕坐标 this
getPixelFromLonLat: function(lonLat){ return this.map.getPixelFromLonLat(lonLat); }
根据经纬度建立节点 google
createNodeByLonLat: function(name, lon, lat){ var l = this.toLonLat(lon, lat); var p = this.getPixelFromLonLat(l); var node = graph.createNode(name, p.x, p.y); node.lonLat = l; return node; }
同理,在节点移动后,须要将屏幕坐标转换成地理坐标 也须要两步,首先将qunee的逻辑坐标转换成屏幕坐标,而后再用OpenLayers的getLonLatFromPixel方法,转换成地理坐标
var pixel = this.toCanvas(data.location.x, data.location.y); data.lonLat = this.map.getLonLatFromPixel(new OpenLayers.Pixel(pixel.x, pixel.y));
在节点移动后都须要作这些转换,监听节点拖拽完成事件,进行坐标的同步
this.interactionDispatcher.addListener(function(evt){ if(evt.kind == Q.InteractionEvent.ELEMENT_MOVE_END){ var datas = evt.datas; Q.forEach(datas, function(data){ var pixel = this.toCanvas(data.location.x, data.location.y); data.lonLat = this.map.getLonLatFromPixel(new OpenLayers.Pixel(pixel.x, pixel.y)); }, this); } }, this)
OpenLayers和Qunee的交互是冲突的,好比拖拽操做,qunee响应了,OpenLayers就无法响应,这里咱们在Qunee交互的基础之上实现地图的漫游缩放操做
经过重写Q.Graph的translate方法,实现二者的同步,是否是挺简单
translate: function (tx, ty) { Q.doSuper(this, MapGraph, "translate", arguments); this.map.moveByPx(-tx, -ty); }
OpenLayers默认的经过双击、鼠标滚轮实现缩放,这些事件默认会被Qunee所拦截,因此须要本身添加和派发
this.html.ondblclick = createEventFunction(this, function(evt){ if(this.getElementByMouseEvent(evt)){ Q.stopEvent(evt); } }); this.onmousewheel = function(evt){ if (this._scaling) { return; } this._scaling = true; Q.callLater(function() { delete this._scaling; }, this, 200); this.map.zoomTo(this.map.zoom + (evt.delta > 0 ? 1 : -1), this.globalToLocal(evt)); }
缩放后的坐标同步
Qunee也有默认的缩放机制,但在地图应用中不太适用,因此须要屏蔽掉
this.enableWheelZoom = false; this.enableDoubleClickToOverview = false;
而后监听地图的缩放事件
this.map.events.register('zoomend', this, function(){this.updateNodes(true)});
实现对节点的坐标同步
updateNodes: function(updateLocation){ if(updateLocation === true){ this.forEach(function(d){ if(d instanceof Q.Node){ var l = d.lonLat; var p = this.getPixelFromLonLat(l); d.location = p; } }, this); this.translateTo(0, 0); return; } this.translateTo(this.map.layerContainerOriginPx.x, this.map.layerContainerOriginPx.y); }