HT for Web作为逻辑拓扑图形组件自身没有GIS功能,但可以与各种GIS引擎即其客户端组件进行融合,各取所长实现逻辑拓扑和物理拓扑的无缝融合,本章将具体介绍HT for Web与开发免费的OpenLayers地图结合应用的关键技术点,该文介绍的结合的原理,其实还可推广到与ArcGIS、百度地图以及GoogleMap等众多GIS地图引擎融合的解决方案。
以上抓图为本文介绍的例子最终运行效果,接下来我们一步步来实现,首选显示地图信息需要有城市经纬度数据,搜索了下感谢此篇博客提供的数据。这么大量的数据我采用的是《HT图形组件设计之道(四)》中介绍的getRawText函数方式,有了数据之后剩下就是呈现的问题了,我们需要将HT的GraphView组件与OpenLayers的map地图组件叠加在一起,也就是OpenLayers的tile地图图片在下方,GraphView的组件在上方,由于GraphView默认是透明的,因此非图元部分用户可穿透看到地图内容。找到合适的组件插入位置是头疼的事情,ArcGIS、百度地图包括GoogleMap几乎每个不同的GIS组件都需要尝试一番才能找到合适的插入位置,其他GIS引擎组件的整合以后章节再介绍,本文我们关注的OpenLayers的插入方式为map.viewPortDiv.appendChild(graphView.getView())。
HT和OpenLayers组件叠加在一起之后,剩下就是拓扑里面图元的摆放位置与经纬度结合的问题,常规网络拓扑图中存储在ht.Node图元的position是逻辑位置,和经纬度没有任何关系,因此在GIS应用中我们需要根据图元的经纬度信息换算出position的屏幕逻辑坐标信息,如果你知道投影算法也可以自己提供函数处理,但所有GIS组件都提供了类似的API函数供调用,当然这部分也没有标准化,不同的GIS组件需要调用的API都有差异,但基本原理是一致的,对于OpenLayers我们通过map.getPixelFromLonLat(data.lonLat)可以将经纬度信息转换成屏幕像素逻辑坐标,也就是ht.Node需要的position坐标信息。
细心的同学会想到转换是双向的,有可能用户需要拖动图元节点改变其经纬度信息,这时候我们就需要另外一个方向函数,即根据屏幕逻辑坐标转换成当前坐标对应的经纬度,在OpenLayers中我们通过map.getLonLatFromPixel(new OpenLayers.Pixel(x, y));可以搞定。
显示搞定后剩下就是交互的问题了,HT自己有套交互体系,OpenLayers也需要地图漫游和缩放的交互,两者如何结合呢?如果能保留住两者的功能那就最好了,答案时肯定的,我们只需要添加mousedown或touchstart事件监听,如果graphView.getDataAt(e)选中了图元我们就通过e.stopPropagation();停止事件的传播,这样map地图就不会响应,这时候HT接管了交互,如果没有选中图元则map接管地图操作的交互。
以上交互设计似乎很完美了,结果运行时发现了几处折腾了我很久才找到解决方案的坑:
-
设置map.events.fallThrough = true;否则map不会将事件透传到HT的GraphView组件
-
graphView.getView().style.zIndex = 999; 需要指定一定的zIndex否则会被遮挡
-
graphView.getView().className = ‘olScrollable’; 否则滚轮不会响应地图缩放
-
设置ht.Default.baseZIndex: 1000 否则ToolTip会被遮挡
为了让这个例子用户体验更友好,我还用心折腾了些技术点供参考:
-
采用开源免费的http://llllll.li/randomColor/随机颜色类库,该类库还有很多非常棒的颜色获取函数,我只是简单的为每个省份显示不一样的颜色
-
重载了isVisible、isNoteVisible和isLabelVisible仅在缩放达到一定级别才显示更详细的内容,否则缩小时所有城市信息都显示完全无法查看,多少也能提高显示性能
以下为最终效果的抓图、视频和源代码:http://v.youku.com/v_show/id_XODM5Njk0NTU2.html
1function init(){ 2 graphView = new ht.graph.GraphView(); 3 var view = graphView.getView(); 4 5 map = new OpenLayers.Map("map"); 6 var ol_wms = new OpenLayers.Layer.WMS( 7 "OpenLayers WMS", 8 "http://vmap0.tiles.osgeo.org/wms/vmap0", 9 {layers: "basic"} 10 ); 11 map.addLayers([ol_wms]); 12 map.addControl(new OpenLayers.Control.LayerSwitcher()); 13 map.zoomToMaxExtent(); 14 map.events.fallThrough = true; 15 16 map.zoomToProxy = map.zoomTo; 17 map.zoomTo = function (zoom,xy){ 18 view.style.opacity = 0; 19 map.zoomToProxy(zoom, xy); 20 console.log(zoom); 21 }; 22 23 map.events.register("movestart", this, function() { 24 }); 25 map.events.register("move", this, function() { 26 }); 27 map.events.register("moveend", this, function() { 28 view.style.opacity = 1; 29 reset(); 30 }); 31 32 graphView.getView().className = 'olScrollable'; 33 graphView.setScrollBarVisible(false); 34 graphView.setAutoScrollZone(-1); 35 graphView.handleScroll = function(){}; 36 graphView.handlePinch = function(){}; 37 graphView.mi(function(e){ 38 if(e.kind === 'endMove'){ 39 graphView.sm().each(function(data){ 40 if(data instanceof ht.Node){ 41 var position = data.getPosition(), 42 x = position.x + graphView.tx(), 43 y = position.y + graphView.ty(); 44 45 data.lonLat = map.getLonLatFromPixel(new OpenLayers.Pixel(x, y)); 46 } 47 }); 48 } 49 }); 50 graphView.enableToolTip(); 51 graphView.getToolTip = function(event){ 52 var data = this.getDataAt(event); 53 if(data){ 54 return '城市:' + data.s('note') + ' 55经度:' + data.lonLat.lon + ' 56维度:' + data.lonLat.lat; 57 } 58 return null; 59 }; 60 graphView.isVisible = function(data){ 61 return map.zoom > 1 || this.isSelected(data); 62 }; 63 graphView.isNoteVisible = function(data){ 64 return map.zoom > 6 || this.isSelected(data); 65 }; 66 graphView.getLabel = function(data){ 67 return '经度:' + data.lonLat.lon + '\n维度:' + data.lonLat.lat; 68 }; 69 graphView.isLabelVisible = function(data){ 70 return map.zoom > 7 || this.isSelected(data); 71 }; 72 73 view.addEventListener("ontouchend" in document ? 'touchstart' : 'mousedown', function(e){ 74 var data = graphView.getDataAt(e); 75 if(data || e.metaKey || e.ctrlKey){ 76 e.stopPropagation(); 77 } 78 }, false); 79 view.style.position = 'absolute'; 80 view.style.top = '0'; 81 view.style.left = '0'; 82 view.style.right = '0'; 83 view.style.bottom = '0'; 84 view.style.zIndex = 999; 85 map.viewPortDiv.appendChild(view); 86 87 var color = randomColor(); 88 lines = china.split('\n'); 89 for(var i=0; i<lines.length; i++) { 90 line = lines[i].trim(); 91 if(line.indexOf('【') === 0){ 92 //province = line.substring(1, line.length-1); 93 color = randomColor(); 94 }else{ 95 var ss = line.split(' '); 96 if(ss.length === 3){ 97 createNode(parseFloat(ss[1].substr(3)), parseFloat(ss[2].substr(3)), ss[0].substr(3), color); 98 } 99 } 100 } 101} 102 103function reset(){ 104 graphView.tx(0); 105 graphView.ty(0); 106 graphView.dm().each(function(data){ 107 if(data.lonLat){ 108 data.setPosition(map.getPixelFromLonLat(data.lonLat)); 109 } 110 }); 111 graphView.validate(); 112} 113 114function createNode(lon, lat, name, color){ 115 var node = new ht.Node(); 116 node.s({ 117 'shape': 'circle', 118 'shape.background': color, 119 'note': name, 120 'label.background': 'rgba(255, 255, 0, 0.5)', 121 'select.type': 'circle' 122 }); 123 node.setSize(10, 10); 124 var lonLat = new OpenLayers.LonLat(lon, lat); 125 lonLat.transform('EPSG:4326', map.getProjectionObject()); 126 node.setPosition(map.getPixelFromLonLat(lonLat)); 127 node.lonLat = lonLat; 128 graphView.dm().add(node); 129 return node; 130}




