電信資源管理系統:基於 H5 疊加 OpenLayers3 GIS


前言

通過結合 HTML5 和 OpenLayers 可以組合成非常棒的一個電信地圖網絡拓撲圖的應用,形成的效果可以用來作為電信資源管理系統,美食定位分享軟件,片區找房,繪制鐵軌線路等等,各個領域都能夠涉及的一款應用。雖然這個 Demo 是結合 OpenLayers3 的,其實還可推廣到與 ArcGIS、百度地圖以及 GoogleMap 等眾多 GIS 地圖引擎融合。

http://www.hightopo.com/demo/openlayers/

代碼生成

創建地圖

OpenLayers 是一個用於開發 WebGIS 客戶端的 JavaScript 包。OpenLayers 支持的地圖來源包括 Google Maps、Yahoo、 Map、微軟 Virtual Earth 等多種離線在線地圖,這里用到的是比較大眾化的谷歌地圖 Google Map 的在線地圖,使用 OpenLayers 前只需要引入相關的類庫以及 css 文件:

<link rel="stylesheet" href="css/ol.css" type="text/css">
<script src="lib/ol.js"></script>

初始化地圖的操作則是將 Map 放進一個 div 元素中,初始化一個 ol.Map 地圖類,這在整個電信資源管理系統中必不可少,然后設置這個類中的各個參數:

復制代碼
var mapDiv = document.getElementById('mapDiv');
map = new ol.Map({
    target: 'mapDiv',// 地圖容器
    controls: ol.control.defaults().extend([
        graphViewControl,// 自定義拓撲控件
        new ol.control.OverviewMap(),// 地圖全局視圖控件
        new ol.control.ScaleLine(),// 比例尺控件
        new ol.control.ZoomSlider(),// 縮放刻度控件
        new ol.control.ZoomToExtent()// 縮放到全局控件
    ]),
    layers: [// 圖層
        new ol.layer.Tile({
            source: new ol.source.XYZ({// 谷歌地圖 
                url:'http://www.google.cn/maps/vt/pb=!1m4!1m3!1i{z}!2i{x}!3i{y}!2m3!1e0!2sm!3i345013117!3m8!2szh-CN!3scn!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e0'  
            })  
        })
    ],
    view: new ol.View({// 地圖視圖
        projection: 'EPSG:3857',// 投影
        center: ol.proj.fromLonLat([106, 35]),// 視圖的初始中心 中心的坐標系由projection選項指定    
        zoom: 4// 縮放級別 用於計算視圖的初始分辨率
    })
});
復制代碼

上面的代碼根據每行的代碼注釋加上官方 API 解釋應該沒有什么難度。細心的朋友可能注意到了一個非官方的控件:graphViewControl 控件,這個控件是我自定義出來,用來在這個控件上繪制拓撲圖形的,聲明和定義部分在 GraphViewControl.js 文件中。

自定義控件

自定義 OpenLayers 的控件,無非就是將某個類繼承於 ol.control.Control 類,然后針對不同的需求重寫父類方法或者增加方法。

我在聲明類的時候傳了一個 options 參數,通過在定義類的時候設置控件的容器元素並且將控件渲染到 GIS 地圖的 viewport 之外:

var view = graphView.getView();// 獲取拓撲組件的 div
ol.control.Control.call(this, {
element: view,// 控件的容器元素
target: options.target// 將控件渲染到地圖的視口之外
});

上面的 graphView 是通過 GraphViewControl 在父類方法上新添加的一個方法並且初始化值為 ht.graph.GraphView,HT 的拓撲圖形組件:

// 獲取GraphView對象
GraphViewControl.prototype.getGraphView = function() { return this._graphView; };
var graphView = this._graphView = new ht.graph.GraphView();// 拓撲圖組件

我在控件中還給 graphView 拓撲組件添加了一些事件的監聽,由於 OpenLayers 和 HT 是兩款不同的 js 庫,有着各自的交互系統和坐標系,首先我們將某些我們需要獲取在 HT 上做的交互事件並停止事件傳播到 OpenLayers 上:

復制代碼
// 拖拽 node 時不移動地圖
var stopGraphPropagation = function(e) {
    var data = graphView.getDataAt(e);// 獲取 graphView 事件下的節點
    var interaction = graphView.getEditInteractor();// 獲取編輯交互器
    if (data || e.metaKey || e.ctrlKey || interaction && interaction.gvEditing) {
        e.stopPropagation();// 不再派發事件 該方法將停止事件的傳播,阻止它被分派到其他 Document 節點
    }
}

/** pointerdown 當指針變為活動事件
*    對於鼠標,當設備從按下的按鈕轉換到至少一個按鈕被按下時,它會被觸發。
*    對於觸摸,當與數字化儀進行物理接觸時會被觸發。
*    對於筆,當觸筆與數字化儀進行物理接觸時會被觸發。
**/
view.addEventListener('pointerdown', stopGraphPropagation, false);
view.addEventListener('touchstart', stopGraphPropagation, false);// 當觸摸點被放置在觸控面板上事件
view.addEventListener('mousedown', stopGraphPropagation, false);// 鼠標點下事件
復制代碼

GraphViewControl 類定義部分還添加了一些關於移動和編輯節點的交互事件,主要是將節點的像素坐標轉為 OpenLayers 的 ol.Cordinate 地圖視圖投影中的坐標並存儲到節點的業務屬性(HT 的一個可以存儲任意值的對象)中,這樣我們只需要通過獲取或設置節點的業務屬性 coord 就可以自由獲取和設置節點在 map 上的像素坐標。

var position = data.getPosition(),// 獲取選中節點的坐標
    x = position.x + graphView.tx(),// 節點橫坐標+graphView水平平移值
    y = position.y + graphView.ty();// 節點縱坐標+graphView垂直平移值

var coord = map.getCoordinateFromPixel([x, y]);// 根據坐標的像素獲取地圖視圖投影中的坐標
data.a('coord', coord);

這里我就提一些基礎的功能,其他的就不作解釋了,只是一些擴展。

值得注意的一點是,我們在上面對節點在電信 GIS 地圖視圖投影中的坐標進行了數據存儲,但是這個方法對於 Shape 類型的節點來說不太合適,因為地圖上一般都是用點圍成區域面,勾勒出某個國家或者某個城市的輪廓,縮放的時候並不實時保持大小,而是根據地圖的縮放來縮放,實時保持在電信 GIS 地圖的某個位置,所以我對 Shape 類型的節點中所有的點遍歷了一遍,都設置了業務屬性 pointCoord,獲取地圖視圖投影中的坐標:

復制代碼
// 給 shape 類型的節點的每個點位置都設置為經緯度
if (e.kind === 'endEditPoint' || e.kind === 'endEditPoints' || e.kind === 'endEditResize' || e.kind === 'endMove') {
    if (data instanceof ht.Shape) {// Shape 類型的節點
        data.getPoints().forEach(function(point, index) {
            var pointCoord = map.getCoordinateFromPixel([point.x, point.y]);// 獲取給定像素的坐標
            data.a('pointCoord['+index+']', pointCoord);
        });
    }
}
復制代碼

圖層疊加

OpenLayers 的結構比較復雜,而 HT 相對來說簡單很多,所以我將 HT 疊加到 OpenLayers Map 的 viewport 中。這里我在子類 GraphViewControl 中重載了父類 ol.control.Control 的 setMap 方法,在此方法中將 HT 的拓撲組件 graphView 添加到 OpenLayers 的視圖 viewport 中,我們知道,HT 的組件一般都是絕對定位的,所以我們要設置 css 中的位置和寬高屬性:

復制代碼
var graphView = self._graphView;// = GraphViewControl.getGraphView()
var view = graphView.getView();// 獲取 graphView 組件的 div
var dataModel = graphView.getDataModel();// 獲取 graphView 的數據容器
view.style.top = '0';
view.style.left = '0';
view.style.width = '100%';
view.style.height = '100%';

map.getViewport().insertBefore(view, map.getViewport().firstChild);// getViewPort 獲取用作地圖視口的元素 insertBefore 在指定的已有子節點(參數二)之前插入新的子節點(參數一)
復制代碼

並對數據容器增刪變化事件進行監聽,通過監聽當前加入數據容器的節點類型,將當前節點的像素坐標轉為地圖視圖投影中的坐標存儲在節點的業務屬性 coord 上:

復制代碼
dataModel.addDataModelChangeListener(function(e) {// 數據容器增刪改查變化監聽
    if (e.kind === 'add' && !(e.data instanceof ht.Edge)) {// 添加事件&&事件對象不是 ht.Edge 類型
        if (e.data instanceof ht.Node) {
            var position = e.data.getPosition();
            var coordPosition = map.getCoordinateFromPixel([position.x, position.y]);// 獲取給定像素的坐標
            e.data.a('coord', coordPosition);
        }

        if (e.data instanceof ht.Shape) {// 給 shape 類型的節點上的每個點都設置經緯度
            e.data.getPoints().forEach(function(point, index) {// 對 shape 類型的節點則將所有點的坐標都轉為經緯度
                var pointCoord = map.getCoordinateFromPixel([point.x, point.y]);// 獲取給定像素的坐標
                e.data.a('pointCoord['+index+']', pointCoord);
            });
        }
    }
});
復制代碼

最后監聽地圖更新事件,重設拓撲:

map.on('postrender', function() { self.resetGraphView(); });

坐標轉換

重設拓撲在這邊的意思就是將拓撲圖中節點坐標從我們一開始設置在 HT 中的像素坐標重新通過地圖的縮放或者移動將地圖視圖投影中的坐標轉為像素坐標設置到節點上,這時候前面存儲的業務屬性 coord 就派上用場了,記住,Shape 類型的節點是例外的,還是要對其中的每個點都重新設置坐標:

復制代碼
GraphViewControl.prototype.resetGraphView = function() {// 重置 graphView 組件的狀態
    var graphView = this._graphView;
    graphView.tx(0);// grpahView 水平平移值
    graphView.ty(0);// graphView 垂直平移值

    graphView.dm().each(function(data) {// 遍歷 graphView 中的數據容器 
        var coord = data.a('coord');// 獲取節點的業務屬性 coord
        if (coord) {
            var position = map.getPixelFromCoordinate(coord);// 獲取給定坐標的像素
            data.setPosition(position[0], position[1]);// 重新給節點設置像素坐標
        }
        if (data instanceof ht.Shape) {
            var points = data.toPoints();// 構建一個新的Shape點集合並返回
            data.getPoints().clear();// 清空點集合
            data._points = new ht.List();

            points.forEach(function(point, index) {// 給 shape 重新設置每一個點的像素坐標
                point.x = map.getPixelFromCoordinate(data.a('pointCoord['+ index +']'))[0];
                point.y = map.getPixelFromCoordinate(data.a('pointCoord['+ index +']'))[1];
                data._points.add(point);
            });

            data.setPoints(data._points);
        }
    });

    graphView.validate();//刷新拓撲組件
}
復制代碼

場景搭建

OpenLayers 的 Map 部分做好了,接下來就是將它放進場景中了~但是從上面的截圖中能看到,除了地圖,頂部有工具條(但是我是用 formPane 表單組件做的),左側有一個可供拖拽的 Palette 面板組件,通過 HT 的 borderPane 邊框面板組件將整個場景布局好:

復制代碼
raphViewControl = new GraphViewControl();// 自定義控件,作為 openlayers 地圖上自定義控件
graphView = graphViewControl.getGraphView();// 獲取拓撲圖組件
dm = graphView.getDataModel();// 獲取拓撲圖中的數據容器

palette = new ht.widget.Palette();// 創建一個組件面板
formPane = createFormPane();// 工具條的 form 表單

borderPane = new ht.widget.BorderPane();// 邊框面板組件
borderPane.setTopView(formPane);// 設置頂部組件為 formPane
borderPane.setLeftView(palette, 260);// 設置左邊組件為 palette 參數二為設置 該view的寬度
borderPane.setCenterView(mapDiv);// 設置中間組件為 mapDiv

borderPane.addToDOM();// 將面板組件添加到 body 中
復制代碼

這樣整個場景的布局和顯示就完成了,非常輕松~

工具條

本身 HT 有自帶的工具條,但是因為 form 表單在排布以及樣式上面可以更靈活,所以采用這個。

復制代碼
var fp = new ht.widget.FormPane();
fp.setVGap(0);// 設置表單組件水平間距 默認值為6
fp.setHGap(0);// 設置表單的行垂直間距 默認值為6
fp.setHPadding(4);// 設置表單左邊和右邊與組件內容的間距,默認值為8
fp.setVPadding(4);// 設置表單頂部和頂部與組件內容的間距,默認值為8
fp.setHeight(40);// 設置表單高度

var btBgColor = '#fff',
    btnIconColor = 'rgb(159, 159, 159)',
    btnSelectColor = 'rgb(231, 231, 231)';

fp.addRow([// 添加行 首尾各加了一個'',並且占的寬度均為相對值0.1,就會將中間部分居中
    '', {
        id: 'select',// id 唯一標示屬性,可通過 formPane.getItemById(id) 獲取添加到對應的 item 對象
        button: {// ht.widget.Button 為按鈕類
            background: btBgColor,// 設置背景顏色
            icon: './symbols/icon/select.json',// 設置圖標
            iconColor: btnIconColor,// 設置圖標顏色
            selectBackground: btnSelectColor,// 設置選中背景顏色
            togglable: true,// 設置按鈕是否處於開關狀態
            groupId: 't',// 設置組編號,屬於同組的togglable按鈕具有互斥功能
            toolTip: '編輯',// 設置文字提示,可通過 enableToolTip() 和 disableToolTip() 啟動和關閉文字提示
            onClicked: function() {// 按鈕點擊觸發函數
                editableFunc();
            }
        }
    }, {
        id: 'pointLine',
        button: {
            background: btBgColor,
            icon: './symbols/icon/line.json',
            iconColor: btnIconColor,
            selectBackground: btnSelectColor,
            togglable: true,
            groupId: 't',
            toolTip: '連線',
            onClicked: function () {
                /** 通過 setInteractors 組合交互器
                * DefaultInteractor實現Group、Edge和SubGraph圖元的默認雙擊響應,手抓圖平移,滾輪縮放,鍵盤響應等功能
                * TouchInteractor實現移動設備上的Touch交互功能
                * CreateEdgeInteractor 為 CreateEdgeInteractor.js 文件中自定義的連線交互器
                * CreateShapeInteractor 為 CreateShapeInteractor.js 文件中自定義的多邊形交互器
                **/
                graphView.setInteractors([new ht.graph.DefaultInteractor(graphView), new ht.graph.TouchInteractor(graphView, {
                    selectable: false
                }), new CreateEdgeInteractor(graphView)]);
            }
        }
    },''
], [0.1, 36, 36, 0.1]);
復制代碼

上面的 form 表單中添加行我只列出了兩個功能,一個編輯的功能,另一個繪制連線的功能。formPane.addRow 為添加一行元素,參數一為元素數組,元素可為字符串、json 格式描述的組件參數信息、html 元素或者為 null 的空,參數二為為每個元素寬度信息數組,寬度值大於1代表固定絕對值,小於等於1代表相對值,也可為 80+0.3 的組合。

為了讓我想顯示的部分顯示在工具欄的正中央,所以我在第一項和最后一項都設置了一個空,占 0.1 的相對寬度,並且比例相同,所以中間的部分才會顯示在正中央。

上面代碼通過 setInteractors 組合我們所需要的交互器。DefaultInteractor 實現 Group、Edge 和 SubGraph 圖元的默認雙擊響應,手抓圖平移,滾輪縮放,鍵盤響應等功能;TouchInteractor 實現移動設備上的 Touch 交互功能。至於最后面的 CreateEdgeInteractor 則是繼承於 ht.graph.Interactor 交互器的創建連線的交互器。這里細細地分析一下這個部分,以后就可以修改或者自定義新的交互器。

自定義交互器

 我們通過 ht.Default.def(className, superClass, methods) 定義類,並在 methods 對象中對方法和變量進行聲明。

setUp 方法在對象被創建的時候被調用,根據需求在這里設置一些功能,我設置的是清除所有的選中的節點:

setUp: function () {// CreateEdgeInteractor 對象被創建的時候調用的函數
    CreateEdgeInteractor.superClass.setUp.call(this);this._graphView.sm().cs();// 清除所有選中
}

 tearDown 方法在對象結束調用的時候被調用,繪制連線的時候,如果未結束繪制怎么辦?下一次繪制不可能連着上一次繼續繪制,所以我們得在結束調用這個類的時候將之前的繪制的點都清除:

復制代碼
tearDown: function () {// CreateEdgeInteractor 對象結束調用的時候調用的函數
    CreateEdgeInteractor.superClass.tearDown.call(this);

    // 清除連線起點、終點以及連線中間的各個點  
    this._source = null;
    this._target = null;
    this._logicalPoint = null;
}
復制代碼

關於鼠標事件以及 touch 事件,我希望這兩者在操作上相同,所以直接在鼠標事件中調用的 touch 事件的方法。

繪制連線需要鼠標左鍵先選中一個節點,然后拖動鼠標左鍵不放,移動鼠標到連線的終點節點上,此時一條連線創建完畢。

首先是 touchstart 選中一個節點:

復制代碼
handle_mousedown: function (e) {// 鼠標點下事件
    this.handle_touchstart(e);
},
handle_touchstart: function (e) {// 開始 touch
    this._sourceNode = this.getNodeAt(e);// 獲取事件下的節點
    if (this._sourceNode) {
        this._targetNode = null;// 初始化 targetNode
        this.startDragging(e);
        this._graphView.addTopPainter(this);// 增加頂層Painter 使用Canvas的畫筆對象自由繪制任意形狀,頂層Painter繪制在拓撲最上面
        this._graphView.sm().ss(this._sourceNode);// 設置選中
    }
},
getNodeAt: function(e){// 獲取事件下的節點
    if (ht.Default.isLeftButton(e) && ht.Default.getTouchCount(e) === 1) {// 鼠標左鍵被按下 && 當前Touch手指個數為1
        var data = this._graphView.getDataAt(e);// 獲取事件下的節點

        if(data instanceof ht.Node) return data;// 為 ht.Node 類型的節點
    } 
    return null;
}
復制代碼

 

然后手指滑動 touchmove :

復制代碼
handleWindowMouseMove: function (e) {
    this.handleWindowTouchMove(e);
},
handleWindowTouchMove: function (e) {// 手指滑動
    var graphView = this._graphView;// 拓撲組件
    this.redraw();// 如果不重新繪制矩形區域,那么容易造成臟矩形
    this._logicalPoint = graphView.getLogicalPoint(e);// 獲取事件下的邏輯坐標
    this._targetNode = this.getNodeAt(e);// 獲取事件下的 edge 的終點

    if (this._targetNode) graphView.sm().ss([this._sourceNode, this._targetNode]);// 設置起始和終止節點都被選中
    else graphView.sm().ss([this._sourceNode]);// 只選中起始節點
},
redraw: function () {
    var p1 = this._sourceNode.getPosition(),// 獲取連線起始端的節點的坐標
        p2 = this._logicalPoint;

    if (p1 && p2) {
        var rect = ht.Default.unionPoint(p1, p2);// 將點組合成矩形
        ht.Default.grow(rect, 1);// 改變rect大小,上下左右分別擴展 extend 的大小
        this._graphView.redraw(rect);// 重繪拓撲,rect參數為空時重繪拓撲中的所有圖元,否則重繪矩形范圍內的圖元
    }
}
復制代碼

最后 touchend 創建連線:

復制代碼
handleWindowMouseUp: function (e) {
    this.handleWindowTouchEnd(e);
},      
handleWindowTouchEnd: function (e) { 
    if (this._targetNode) {
        var edge = new ht.Edge(this._sourceNode, this._targetNode);// 創建新的連線節點
        if (this._edgeType) edge.s('edge.type', this._edgeType);// 設置連線的類型

        this._graphView.dm().add(edge);// 將節點添加進數據容器
        this._graphView.sm().ss(edge);// 設置選中您當前連線
    }
    editableFunc();// 繪制結束后 工具條選中“編輯”項
    this._graphView.removeTopPainter(this);// 移除頂層畫筆
}
復制代碼

至於還未創建連線之前(也就是說為選中終止節點),鼠標在拖動的過程中會創建一條連線,這里是直接用 canvas 繪制的:

復制代碼
draw: function (g) {// 繪制起點與鼠標移動位置的連線
    var p1 = this._sourceNode.getPosition(),
        p2 = this._logicalPoint;    

    if(p1 && p2){
        g.lineWidth = 1;
        g.strokeStyle = '#1ABC9C';
        g.beginPath();
        g.moveTo(p1.x, p1.y);
        g.lineTo(p2.x, p2.y);
        g.stroke();              
    }
}    
復制代碼

這樣,自定義連線類結束!

面板組件

左側面板組件 ht.widget.Palette 支持自定義樣式及單選、拖拽操作,由 ht.DataModel 驅動,用 ht.Group 展示分組,ht.Node 展示按鈕元素。

展示分組,首先得創建分組和組中的按鈕元素:

復制代碼
function initPalette(palette) {// 加載palette面板組件中的圖元
    var nodeArray = ['city', 'equipment'];
    var nameArray = ['城市', '大型'];// arrNode中的index與nameArr中的一一對應

    for (var i = 0; i < nodeArray.length; i++) {
        var name = nameArray[i];

        nodeArray[i] = new ht.Group();// palette面板是將圖元都分在“組”里面,然后向“組”中添加圖元即可
        palette.dm().add(nodeArray[i]);// 向palette面板組件中添加group圖元
        nodeArray[i].setExpanded(true);// 設置分組為打開的狀態
        nodeArray[i].setName(name);// 設置組的名字

        var imageArray = [];
        switch(i){
            case 0:
                imageArray = ['symbols/5.json', 'symbols/6.json', 'symbols/叉車.json', 'symbols/公交車.json', 'symbols/人1.json', 'symbols/人2.json', 'symbols/人3.json', 'symbols/樹.json', 'symbols/樹2.json'];
                break;
            case 1: 
                imageArray = ['symbols/飛機.json', 'symbols/吊機.json', 'symbols/卡車.json', 'symbols/貨輪.json', 'symbols/龍門吊.json', 'symbols/公園.json'];
                break;
            default: 
                break;
        }
        setPaletteNode(imageArray, nodeArray[i], palette);
    }
}

function setPaletteNode(imageArray, array, palette) {// 創建 palette 上 節點及設置名稱、顯示圖片、父子關系
    for (var i = 0; i < imageArray.length; i++) {
        var imageName = imageArray[i],
            name = imageName.slice(imageName.lastIndexOf('/')+1, imageName.lastIndexOf('.'));// 獲取最后一個 / 和最后一個.中間的文本,作為節點的 name
        
        createNode(imageName, name, array, palette);// 創建節點,顯示在 palette 面板上
    }
}

function createNode(image, name, parent, palette) {// 創建palette面板組件上的節點
    var node = new ht.Node();
    palette.dm().add(node);// 將節點添加進 palette 的數據容器中
    node.setImage(image);// 設置節點的圖片
    node.setName(name);// 設置節點名稱
    node.setParent(parent);// 設置節點的父親
    node.s({// 設置節點的屬性
        'draggable': true,// 如果Node的draggable設為true,Palette可以自動處理dragstart,但是dragover和drop事件需要我們處理
        'image.stretch': 'centerUniform',// 圖片的繪制方式為非失真方式
    });
    return node;
}
復制代碼

創建完后,我們就要啟用模擬的拖拽事件 handleDragAndDrop(e, state): 

復制代碼
palette = new ht.widget.Palette();// 創建一個組件面板

var data;
palette.handleDragAndDrop = function(e, state) {// 左側面板組件拖拽功能
    if ( state === 'prepare' ) data = palette.getDataAt(e);
    else if( state === 'begin' || state === 'between' ) {}
    else {
        if (!ht.Default.containedInView(e, graphView)) return; // 判斷交互事件所處位置是否在graphView組件之上

        var node = new ht.Node();// 拖拽到graphView中就創建一個新的節點顯示在graphView上
        node.setImage(data.getImage());// 設置節點上貼圖
        node.setName(data.getName());// 設置名稱(為了顯示在屬性欄中)
        node.s('label', '');// 在graphView中節點下方不會出現setName中的值,label優先級高於name
        node.p(graphView.lp(e));// 將節點的位置設置為graphView事件下的拓撲圖中的邏輯坐標,即設置鼠標點下的位置為節點坐標

        graphView.dm().add(node);// 將節點添加進graphView中
        graphView.sm().ss(node);// 默認選中節點
        graphView.setFocus(node);// 設置將焦點聚集在該節點上
        
        editableFunc();// 設置節點為可編輯狀態並且選中導航欄中的“編輯”
    }
}
復制代碼

好了,先在你就可以直接從左側的 palette 面板組件上直接拖拽節點到右側的地圖上的 graphView 拓撲圖。

我們可以在 graphView 上進行繪制節點的編輯、繪制連線、繪制直角連線以及繪制多邊形。

最后

在上面基於 GIS 的電信資源管理系統的基礎上我嘗試了增加切換地圖的功能,同時還在導航欄上添加了“地鐵線路圖”,這個地鐵線路圖實現起來也是非常厲害的,下次我會再針對這個地鐵線路圖進行一次詳解,這里就不多做解釋,來看看我添加后的最終結果:



http://www.hightopo.com/demo/openlayers/

如果有什么建議或者意見,歡迎留言或者私信我,也可以直接去 HT for Web(https://hightopo.com/) 官網查閱相關資料。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM