1. 程式人生 > >利用Arcgis for javascript API繪製GeoJSON並同時彈出多個Popup

利用Arcgis for javascript API繪製GeoJSON並同時彈出多個Popup

1.引言

  由於Arcgis for javascript API不可以繪製Geojson,並且提供的Popup一般只可以彈出一個,在很多專題圖製作中,會遇到不少的麻煩。因此本文結合了兩個現有的Arcgis for javascript API擴充庫,對其進行改造達到繪製Geojson並同時彈出多個Popup的目的。

  本文實現的效果圖:

 

                  圖1 上海5個地點的部分預報屬性                                 圖2 上海某三條航線的部分預報屬性

2. 各類依賴庫引入及前端HTML

  首先需要先載入需要用的常用js、Arcgis及兩個擴充庫的js及部分css(下載地址見其github):

複製程式碼
<!DOCTYPE html>
<html>
<head>
    <title>Add GeoJSON and Display Multiple Popup</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=7,IE=9">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"
> <!-- ArcGIS API for JavaScript CSS--> <link rel="stylesheet" href="http://js.arcgis.com/3.13/esri/css/esri.css"> <link rel="stylesheet" href="http://js.arcgis.com/3.13/dijit/themes/claro/claro.css"> <!-- Web Framework CSS - Bootstrap (getbootstrap.com) and Bootstrap-map-js (github.com/esri/bootstrap-map-js)
--> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="./css/bootstrap.min.css"> <!-- PopExtendCss --> <link href="./vendor/ncam/PopupExtended.css" rel="stylesheet" /> <!-- PopupExtended references --> <script> var dojoConfig = { parseOnLoad: false, async: true, tlmSiblingOfDojo: false, packages: [{ name: "ncam", location: location.pathname.replace(/\/[^/]+$/, '') + "ncam" }] }; </script> <!-- ArcGIS API for JavaScript library references --> <script src="//js.arcgis.com/3.10"></script> <!-- Terraformer reference --> <script src="./vendor/terraformer/terraformer.min.js"></script> <script src="./vendor/terraformer-arcgis-parser/terraformer-arcgis-parser.min.js"></script> <!-- other reference --> <script src="./vendor/jquery.js"></script> </head> <body> </body> </html>
複製程式碼

  加入底圖所需要的div與圖層切換的Button:

<body>
     <div id="mapDiv"></div>
     <button type="line" id="shanghaiPoint" class="btn btn-default buttonRight" style="top:20px;right:20px">上海區各點</button>
     <button type="point" id="threeLine" class="btn btn-default buttonRight" style="top:70px;right:20px">三條航線</button>
</body>

3.置入Popupextended並擴充geojsonlayer.js  

  然後從geojsonlayer.js原始碼入手,開始將PopupExtended擴充套件其中,讓我們新構建的geojsonlayer直接可擁有多個Popup。在geojsonlayer.js的constructor中很容易可以找出infotemplate的set方法:

            // Default popup
            if (options.infoTemplate !== false) {
                this.setInfoTemplate(options.infoTemplate || new InfoTemplate("GeoJSON Data", "${*}"));
            }

  很明顯,geojsonlayer初始化時通過options傳入引數進行判斷並構造,所以實現本文目的的大致思路是將這裡的setInfoTemplate替換成可以擴充套件的PopupExtended:

複製程式碼
if (options.infoTemplate !== false) {
                //① create a PopupTemplate
                var template = new PopupTemplate({
                        title: "{name}",
                        fieldInfos: [
                            { fieldName: "Id", label: "Id", visible: true },
                            { fieldName: "publishdate", label: "觀測日期", visible: true },
                            { fieldName: "waveheight", label: "浪高", visible: true },
                            { fieldName: "wavedirection", label: "浪向", visible: true },
                            { fieldName: "windspeed", label: "風速", visible: true },
                            { fieldName: "winddirection", label: "風向", visible: true },
                            { fieldName: "comfort", label: "等級", visible: true }
                        ],
                        extended: { 
                            actions: [
                                { text: " IconText", className: "iconText", title: "Custom action with an icon and Text", click: function (feature) { alert("Icon Text clicked on " + "id: " + feature.attributes.id + " " + feature.attributes.name); } },
                                { text: "", className: "iconOnly", title: "Custom action only using an icon", click: function (feature) { alert("Icon action clicked on " + "id: " + feature.attributes.id + " " + feature.attributes.name); } }                          
                            ],
                         //uses a pretty bad custom theme defined in PopupExtended.css.
                             scaleSelected: 1.6
                        }
                    });
                //② create a extend for basemap
                var extendedPopup = new PopupExtended({
                        extended: {
                            themeClass: "light",
                            draggable: true,
                            defaultWidth: 250,
                            actions: [{
                                text: "其他", className: "defaultAction", title: "Default action added in extendedPopup properties.",
                                click: function (feature) { alert("clicked feature - " + feature.attributes); }
                            }],
                            hideOnOffClick: false,
                            multiple: true,
                        },
                        highlight: false,
                        //titleInBody: false,
                    }, dojo.create("div"));
                    
                //③set the map to use the exteneded popup
                extendedPopup.setMap(options.baseMap);
                options.baseMap.infoWindow = extendedPopup;

                this.setInfoTemplate(options.infoTemplate || template);
            }
複製程式碼

  由上段程式碼可見,引入Popup給GeoJSON共分為三步:①例項化一個你需要的PopupTemplate(這裡起名為template),可指定你需要展示的主題、資料項及擴充套件的一些互動action;②例項化一個PopupExtended並設定一些預設的Popup屬性;③將例項化的PopupExtended——extendedPopup的Map設定為baseMap,並將baseMap的infowindow設定為extendedPopup,最後將geojsonlayer的infoTemplate設定為新構建的template。這樣便可以實現對置入底圖的geojsonlayer進行多個infoTemplate展示需求了。原始碼見github:展示效果如圖3:

圖3 對geojsonlayer擴充Popupextended後的顯示效果

  如若只需增加多個Popup至geojsonlayer的話,以上部分足以實現了。

4.增加新的Attributes及調整Popup的樣式  

  由於設計上的需求,筆者需要從其他地址獲取觀測點的部分觀測值,並且筆者的老師覺得應該加一些icon給屬性,美化展示效果,所以需要重新構造兩部分:①獲取併為graphics增加新的attributes;②重構geojsonlayer的infoTemplate的content。

4.1 獲取併為graphics增加新的attributes:

  通過在button上利用fetch及Promise.all來同時獲取6個點或3條航線的資料,並傳入至初始化geojsonlayer的函式內;

複製程式碼
$("#shanghaiPoint").click(function(){
            // if first init geojsonlayer
            if(firstPointInit){
                var requestBZ = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/80';
                var requestWGQ = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/81';
                var requestHS = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/82';
                var requestLCG = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/83';
                var requestJHG = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/84';
                var requestJSW = 'http://wx.dhybzx.org:18080/forecast/shanghai_sea_env/85';
                var urls = [requestBZ, requestWGQ,requestHS,requestLCG,requestJHG,requestJSW]

                Promise.all(urls.map(url =>
                    fetch(url).then(resp => resp.json())
                )).then(results => {
                    
                var tempJson = {
                     "堡鎮":results[0][0],
                     "外高橋":results[1][0],
                     "橫沙":results[2][0],
                     "蘆潮港":results[3][0],
                     "金匯港":results[4][0],
                     "金山衛":results[5][0]
                }
                
                addGeoJsonToMap("./data/six_point.json",tempJson)

                });
            }else{
                //geojsonlayer has been initial
                 addGeoJsonToMap("./data/six_point.json")
            }
        
        })
複製程式碼

  這裡的Promise.all採用了ES2015的箭頭函式,相容性問題需要自己考慮,也可以手動改成ES5支援的。將額外的attributes組裝成tempJson後傳入至初始化方法addGeoJsonToMap內。

4.2 重構geojsonlayer的infoTemplate的content:

  在geojsonlayer.js內繼續做一部分修改,註釋掉例項化template中的fieldInfos屬性及值,並且為geojsonlayer的infoTemplate設定新的content,程式碼如下:

複製程式碼
               var template = new PopupTemplate({
                        title: "{name}",
                        // fieldInfos: [
                        //     { fieldName: "Id", label: "Id", visible: true },
                        //     { fieldName: "publishdate", label: "觀測日期", visible: true },
                        //     { fieldName: "waveheight", label: "浪高", visible: true },
                        //     { fieldName: "wavedirection", label: "浪向", visible: true },
                        //     { fieldName: "windspeed", label: "風速", visible: true },
                        //     { fieldName: "winddirection", label: "風向", visible: true },
                        //     { fieldName: "comfort", label: "等級", visible: true }
                        // ],
                        extended: { 
                            actions: [
                                { text: " IconText", className: "iconText", title: "Custom action with an icon and Text", click: function (feature) { alert("Icon Text clicked on " + "id: " + feature.attributes.id + " " + feature.attributes.name); } },
                                { text: "", className: "iconOnly", title: "Custom action only using an icon", click: function (feature) { alert("Icon action clicked on " + "id: " + feature.attributes.id + " " + feature.attributes.name); } }                          
                            ],
                         //uses a pretty bad custom theme defined in PopupExtended.css.
                             scaleSelected: 1.6
                        }
                    });
                //create a extend for basemap
                var extendedPopup = new PopupExtended({
                        extended: {
                            themeClass: "light",
                            draggable: true,
                            defaultWidth: 250,
                            actions: [{
                                text: "其他", className: "defaultAction", title: "Default action added in extendedPopup properties.",
                                click: function (feature) { alert("clicked feature - " + feature.attributes); }
                            }],
                            hideOnOffClick: false,
                            multiple: true,
                        },
                        highlight: false,
                        //titleInBody: false,
                    }, dojo.create("div"));
                    
                //set the map to use the exteneded popup
                extendedPopup.setMap(options.baseMap);
                options.baseMap.infoWindow = extendedPopup;

                this.setInfoTemplate(options.infoTemplate || template);
                this.infoTemplate.setContent("<b class='popupTitle'>${name}</b>" +
                                "<div class='hzLine'></div>"+
                                "<div class='popupContent'>"+
                                "<i class='glyphicon glyphicon-calendar'></i><b>日期: </b> ${publishdate}<br/>"+
                                "<i class='glyphicon glyphicon-resize-vertical'></i><b>浪高: </b> ${waveheight}<br/>" +
                                "<i class='glyphicon glyphicon-random'></i><b>浪向: </b> ${wavedirection}<br/>"+
                                "<i class='glyphicon glyphicon-share'></i><b>風速: </b> ${windspeed}<br/>" +
                                "<i class='glyphicon glyphicon-transfer'></i><b>風向: </b> ${winddirection}<br/>"+
                                "<i class='glyphicon glyphicon-export'></i><b>等級: </b> ${comfort}<br/>"+
                                "</div>"
                    );
複製程式碼

  額外的屬性和新的infoTemplate樣式構造完成,但存在一個問題,即額外的attributes必須要在geojsonlayer繪製好後再進行設定並展示,arcgis提供了layer的layer-add及layer-add-result事件,但是無法監控到graphics是否已經增入至geojsonlayer內,所以必須要再做一些改進,使額外的屬效能夠在graphics繪製完畢後再新增進去。具體方法分為兩步:1)初始化geojsonlayer時,將showAllPopup方法傳入其建構函式內;2)在grahics新增至layer後,呼叫showAllPopup方法,顯示所有的Popup。前端程式碼如下:

複製程式碼
//add GeoJSON to baseMap , constuct show&hide popup method and add other attribute to graphics
        function addGeoJsonToMap(url,otherJson){
            
             require(["esri/map",
            "./src/geojsonlayer.js",
            "esri/geometry/Point", "esri/SpatialReference",
            "dojo/on",
            "dojo/dom",
            "dojo/domReady!"],
              function (Map, GeoJsonLayer, Point, SpatialReference,on, dom) {

                 var hasThisLayer=false;
                 otherJson=otherJson?otherJson:"";
                 hideAllPopup()
           //judge layer has been init
                   map.getLayersVisibleAtScale().forEach(function(item){
                       if(item._url==url&&item.dataType=="geojson"){
                           console.log(item)
                           item.show();
                           console.log("dd")
                           showAllPopup(item);
                           hasThisLayer=true;
                           // map.setExtent(item.extent)
                       }else if(item._url!=url&&item.dataType=="geojson"){
                           item.hide();
                       }
                   })

                   if(!hasThisLayer){
                       addGeoJsonLayer(url);                                  
                   }

                   //show all popups
                   function showAllPopup(layer){
                      ......
                   }

                   //hide all popups
                   function hideAllPopup(){
                     .......
                   }

                   //add other attribute to grpahics for popup
                   function addAttrToGrpahics(item,type){
                     .......
                   }
                

                // Add the layer
                function addGeoJsonLayer(url) {
                    // Create the layer
                    var geoJsonLayer = new GeoJsonLayer({
                        baseMap:map,
                        url: url,
                        onLayerLoaded:function(layer){
                            showAllPopup(layer);
                        }              
                    });

                    // Add to map
                    geoJsonLayer.dataType="geojson"; 
                    map.addLayer(geoJsonLayer);
                  
                   
                }
            });
        }
複製程式碼

並且在geojsonlayer.js的constructor內加入:

this._onLayerLoaded = options.onLayerLoaded;

在最後的_addGraphics方法中onLoad方法後,加入:

if (this._onLayerLoaded) this._onLayerLoaded(this);

利用show/hide方法,控制popup顯示及隱藏。

複製程式碼
//open all popup
layer.graphics.forEach(function(item){
  if(firstPointInit&&otherJson[item.attributes.name]){
      addAttrToGrpahics(item,layer.graphics[0].geometry.type)
  }
  var loc = map.toScreen(item.geometry);
  map.infoWindow.setFeatures([item]);
  map.infoWindow.show(loc);
})
//hide all popup
var tempLength = map.infoWindow.openPopups.length;
for(var i=0;i<tempLength;i++){
  map.infoWindow.openPopups[0].hide()
}
複製程式碼

5. 結論  

  至此,本文已經完成了在Arcgis for javascript API中實現Geojson的繪製,並同時展示其多個Popup的需求。最終的展示效果如圖1、2。原始碼見筆者的