zoukankan      html  css  js  c++  java
  • 百度Map与HT for Web结合的GIS网络拓扑应用

    在《HT for Web整合OpenLayers实现GIS地图应用》篇中介绍了HT for Web与OpenLayers的整合,不少朋友反应国内用得比较多的还是百度地图,虽然HT整合百度地图原理与OpenLayers一致,但不同GIS引擎客户端结合代码细节还是有不少差异,自定义地图风格更是完全不一样,为此我再开篇介绍下HT与百度地图整合的方案,这次我们将改进以前的例子,除了代表城市的拓扑节点外,再增加连线连接省会和城市,实现网络拓扑链路的流动效果。

    Screen Shot 2014-12-03 at 11.18.02 PM

    百度地图有多种客户端SDK,我们这里用的自然是JavaScript版的API,百度地图的2.0版增加了不少新功能,例如可以自定义地图样式模板,本例中我们特意设置成style:’midnight’的深色背景风格。插入map的位置与OpenLayers也不一样,通过mapDiv.firstChild.firstChild.appendChild(view);插入,zIndex这些属性都还好不需要设置。

    坐标转换方面从经纬度转换成平面坐标是map.pointToPixel函数,通过node.setPosition(map.pointToPixel(new BMap.Point(lon, lat)));可设置ht.Node对应的平面逻辑坐标,通过map.pixelToPoint(new BMap.Pixel(x,y))可将平面坐标转换成经纬度坐标,我们在监听节点图元被拖拽结束的endMove需要重新计算当前位置的经纬度时用到。

    地图数据方面每个省的省会城市都是第一个出现,因此我们构建了size大一点的带渐进色的图元代表省会城市,其他城市构建时同时构建ht.Edge的连线与省会节点连接,同时引入HTht-flow.js插件,只需要graphView.enableFlow(60);一句话就可以启动流动连线功能,每条edge连线还可以有很多flow.*相关的流动参考可控制流动效果,这里的简单例子我们不需要做定制,保持HT默认的流动参数就足够酷了。

    另外通过graphView.setLayers(['edgeLayer', 'nodeLayer']);我们为拓扑设置了两个层,node.setLayer(‘nodeLayer’);和edge.setLayer(‘edgeLayer’)使得图元节点全部呈现于连线之上,这个仅是简单例子如果需要可以随意定义更多的layers,layers的数组顺序决定了最终图元显示的先后顺序。

    传统使用GIS应用来说图层都需要操作GIS客户端API来进行,但从这个例子大家可以发现,以前需要在GIS的SDK扩展的功能也可以通过在HTGraphView实现,这样地图仅仅是作为背景,业务逻辑代码完全在更灵活强大的HT模型中进行,例如图元的隐藏和显示也都在HTGraphView中重载了graphView.isVisible函数实现的,通过HT设置可以定义到更细节的isNoteVisible这样的图元部件是否可见,这里代码和OpenLayers有的区别的仅仅是从map.zoom改为map.getZoom()。

    另外因为城市节点较多,每次移动界面时我们都需要调用node.setPosition(map.pointToPixel(new BMap.Point(lon, lat)));重新定位图元平面坐标,毕竟对于js来说密集型的大量计算不是其强项,因此我们监听了map的movestart、moveend、zoomstart、zoomend、dragstart和dragend等事件,做的都是同样的事情在开始变化前通过view.style.opacity = 0;因此GraphView拓扑图,当事件结束后通过view.style.opacity = 1;再次显示拓扑图,同时调用resetPosition();重新定位图元节点,这样保证用户能够流畅的操作地图,而密集型的运算仅在最后才进行一次。

    拓扑图方面还有些细节,例如graphView.setAutoScrollZone(-1)关闭默认拖拽图元到边缘时的自动滚动功能,graphView.handleScroll = function(){};关闭拓扑的滚轮缩放功能,graphView.handlePinch = function(){};关闭拓扑在touch触屏的缩放功能,因为这些功能都要传递给Map交给地图控制。

    graphView.mi监听了moveEnd事件,这个用于某些情况下,用户需要手工移动节点,意味着要改变节点的经纬度信息,因此在监听到moveEnd之后,我们通过data.lonLat = map.pixelToPoint(new BMap.Pixel(x,y));根据屏幕坐标重新得到移动后的经纬度信息。

    毕竟GraphView和Map的交互方式是完全不一样的,因此handleClick在用户点击界面时我们需要控制分流,也就是决定此次点击要交给GraphView处理,还是Map处理,我们通过var data = graphView.getDataAt(e);判断当前是否点击中了图元,如果选中了图元则GraphView拓扑图接管交互任务,因此通过e.stopPropagation();停止事件的继续传播,如果点击在背景上则无需特殊处理,Map自动会接管任务,毕竟我们是通过mapDiv.firstChild.firstChild.appendChild(view);的方式插入拓扑图,mapDiv作为底层父辈类组件依然会监听到事件,这点比OpenLayers的整合容易很多,没有太多需要特殊设置的地方。

    构建城市节点依然采用了http://llllll.li/randomColor/随机生成颜色的框架,仅对省会节点增加了渐进色的效果,省会节点和城市节点间构建了ht.Edge连线,通过'flow': true的style启动连线流动,flow.*还有很多控制参数,例如控制流动的方向,流动的效果等,这里就不再展开介绍了。

    以下为操作视频、抓图和源代码供大家参考,这样的结合可完美的将百度地图丰富的地图数据信息,与HT强大的逻辑拓扑功能相结合,否则光靠百度地图SDK的API的扩展方式,用户只能做些简单的效果,例如连线流动等效果都远不如HT这样一行代码就搞定来的容易。http://v.youku.com/v_show/id_XODQxMDcwNjQ0.html


    Screen Shot 2014-12-04 at 12.31.13 AM

    var dataModel = new ht.DataModel();
    var graphView = new ht.graph.GraphView(dataModel);              
    var map = null;   
    
    function init() {
        map = new BMap.Map("map");   
        var point = new BMap.Point(116.404, 39.915);                          
        map.addTileLayer(new BMap.TrafficLayer());                    
        map.centerAndZoom(point, 4);                    
        map.enableScrollWheelZoom();                       
        map.addControl(new BMap.NavigationControl());                               
    
        map.setMapStyle({
            features: ["road", "building","water","land"],
            style:'midnight'
        });
    
        var view = graphView.getView();
        view.className = 'graphView'; 
        var mapDiv = document.getElementById('map');
        mapDiv.firstChild.firstChild.appendChild(view);
    
        map.addEventListener('movestart', function(e){                   
           view.style.opacity = 0;
        });
        map.addEventListener('moveend', function(e){
           view.style.opacity = 1; 
           resetPosition(); 
        });
        map.addEventListener('dragstart', function(e){
           view.style.opacity = 0;                
        });
        map.addEventListener('dragend', function(e){
           view.style.opacity = 1; 
           resetPosition(); 
        });                                
        map.addEventListener('zoomstart', function(e){
            view.style.opacity = 0;
        });                
        map.addEventListener('zoomend', function(e){
            view.style.opacity = 1;    
            resetPosition(); 
        });                
    
        graphView.setLayers(['edgeLayer', 'nodeLayer']);
        graphView.enableFlow(60);
        graphView.enableToolTip();
        graphView.getToolTip = function(event){
            var data = this.getDataAt(event);
            if(data instanceof ht.Node){
                return '城市:' + data.s('note') + '
    经度:' + data.lonLat.lng + '
    维度:' + data.lonLat.lat;
            }
            return null;
        };
        graphView.isVisible = function(data){
            return map.getZoom() > 1 || this.isSelected(data);
        };
        graphView.isNoteVisible = function(data){
            return map.getZoom() > 8 || this.isSelected(data);
        }; 
        graphView.getLabel = function(data){
            if(data instanceof ht.Node){
                return '经度:' + data.lonLat.lng + '
    维度:' + data.lonLat.lat;
            }                    
        };
        graphView.isLabelVisible = function(data){
            return map.getZoom() > 9 || this.isSelected(data);
        }; 
    
        graphView.mi(function(e){
            if(e.kind === 'endMove'){
                graphView.sm().each(function(data){
                    if(data instanceof ht.Node){
                       var position = data.getPosition(),
                           x = position.x + graphView.tx(),
                           y = position.y + graphView.ty();                             
                       data.lonLat = map.pixelToPoint(new BMap.Pixel(x,y)); 
                    }                            
                });
            }
        });
    
        graphView.setAutoScrollZone(-1);
        graphView.handleScroll = function(){};
        graphView.handlePinch = function(){};
    
        var handleClick = function(e){
            var data = graphView.getDataAt(e);
            if(data){
                e.stopPropagation();
            }
            else if(e.metaKey || e.ctrlKey){
                e.stopPropagation();
            }
        };
        graphView.getView().addEventListener('click', handleClick, false);                
        graphView.getView().addEventListener('dblclick', handleClick, false);
        graphView.getView().addEventListener(ht.Default.isTouchable ? 'touchstart' : 'mousedown', handleClick, false);
    
        window.addEventListener("resize", function(e) {
            graphView.invalidate();
        }, false);                 
    
        var color = randomColor(),
            province = null;
        lines = china.split('
    ');
        for(var i=0; i<lines.length; i++) {
            line = lines[i].trim();
            if(line.indexOf('【') === 0){               
                color = randomColor();
                province = null;
            }else{
                var ss = line.split(' ');
                if(ss.length === 3){
                    var node = createNode(parseFloat(ss[1].substr(3)), parseFloat(ss[2].substr(3)), ss[0].substr(3), color); 
                    if(province){
                        var edge = new ht.Edge(province, node);
                        edge.s({
                            'edge.center': true,
                            'edge.width': 1,                                    
                            'flow': true
                        });
                        edge.setLayer('edgeLayer');
                        dataModel.add(edge);
                    }else{
                        province = node;
                        node.s({                                   
                            'shape.gradient': 'radial.center',                                    
                            'border.type': 'circle',
                            'border.width': 1,
                            'border.color': 'rgba(200, 200, 200, 0.5)'                                    
                        });
                        node.setSize(15, 15);
                    }
                }
            }
        }                
    
    }
    
    function createNode(lon, lat, name, color){
        var node = new ht.Node();
        node.s({
            'shape': 'circle',
            'shape.background': color,
            'note': name,                    
            'label.background': 'rgba(255, 255, 0, 0.5)',                    
            'select.type': 'circle'
        });
        node.setLayer('nodeLayer');
        node.setSize(10, 10);
        var lonLat = new BMap.Point(lon, lat); 
        node.setPosition(map.pointToPixel(lonLat));
        node.lonLat = lonLat;
        graphView.dm().add(node);
        return node;
    }            
    
    function resetPosition(e){
        graphView.tx(0);
        graphView.ty(0);
        dataModel.each(function(data){
            if(data instanceof ht.Node){
                var lonLat = data.lonLat,
                    position = map.pointToPixel(lonLat);   
                data.setPosition(position.x,position.y);                           
            }
        });            
    }
  • 相关阅读:
    LVS基于DR模式负载均衡的配置
    Linux源码安装mysql 5.6.12 (cmake编译)
    HOSt ip is not allowed to connect to this MySql server
    zoj 3229 Shoot the Bullet(无源汇上下界最大流)
    hdu 3987 Harry Potter and the Forbidden Forest 求割边最少的最小割
    poj 2391 Ombrophobic Bovines(最大流+floyd+二分)
    URAL 1430 Crime and Punishment
    hdu 2048 神、上帝以及老天爷(错排)
    hdu 3367 Pseudoforest(最大生成树)
    FOJ 1683 纪念SlingShot(矩阵快速幂)
  • 原文地址:https://www.cnblogs.com/xhload3d/p/4142635.html
Copyright © 2011-2022 走看看