用ArcGIS API for JavaScript制作三維可視化圖


前段時間接了一個項目,涉及到了空間信息三維可視化的工作。之前在網上查找無意中看到ArcGIS API for JavaScript(以下簡稱“ArcGIS API”或“該API”)可以在網頁上制作三維可視化圖。好在有友人在國外幫我把整個文檔和API下載下來了,於是就着手學習了一下這個API。

簡介

做GIS的肯定清楚ArcGIS是什么,包括一系列的ArcMap、ArcScence、ArcEngine等。ArcGIS推出了這套JavaScript API,現在有4.2版本,該版本可以創建二維、三維的網頁應用程序。

下面是官網給出的一些三維可視化的示例。

顯示衛星位置  使用三維符號可視化

使用三維實體符號可視化  根據房屋高度伸出房屋腳印

ArcGIS制作可視化圖的大體結構為:View 包含 Map 包含 Layer(s) 包含 Graphic(s)。帶(s)表示是一個數組。其中

  • View(視圖):對應HTML上的一個元素,在該元素中顯示底圖以及底圖上的所有圖層。
  • Map(底圖):可以為多種類型:街道圖、衛星圖、大洋圖、地表圖等。
  • Layer(圖層):有多種類型,如FeatureLayer、GraphicLayer、PointCloudLayer,甚至有CSVLayer等。對於圖層的解釋,ArcGIS官方文檔指出:
    圖層是 ArcMap、ArcGlobe 和 ArcScene 中地理數據集的顯示機制。一個圖層引用一個數據集,並指定如何利用符號和文本標注繪制該數據集。向地圖添加圖層時,要指定它的引用數據集並設定地圖符號和標注屬性。
  • Graphic(圖形):圖形在官方文檔中的解釋是:“圖形是一個矢量圖,代表了真實世界的地理事物或地理現象。它可以包含geometry(幾何)、symbol(符號)、attributes(特性)。”

    A Graphic is a vector representation of real world geographic phenomena. It can contain geometry, a symbol, and attributes.A  Graphic is displayed in the GraphicsLayer.

    當我們拿到帶有空間信息的數據,就可以對每個數據設計可視化的圖形,將同類的數據顯示的圖形添加到同一個圖層中,就可以顯示了。官方指出Graphic是顯示在GraphicLayer中的,但是FeatureLayer中有一個source屬性,可以將Grapchic自動轉換(autocast),從而在Layer中顯示。

制作可視化圖

使用ArcGIS API,需要引用各個組件(Map, SenceView, FeatureLayer ...),各個組件引用的路徑在官網文檔上會標注。引用時使用require()函數(這里使用TypeScript語法表示變量的類型)

require(modules: Array<String>, callback: function) => void

 

即可將各個組件引用到應用程序中。其中

  • modules:是一個字符串數組,每一項代表了一個組件的引用路徑。例如,組件Map的地址是 “esri/Map” 。
  • callback:是一個回調函數,表示各個組件加載完成后執行的函數操作。這個函數的的參數比較特別,參數個數與modules中引用的組件的個數相同,每個參數與modules中的組件路徑一一對應,表示引用的各個組件的一個對象。

於是,腳本整體上類似於:

require([
  "esri/Map",
  "esri/views/SceneView",
  "dojo/domReady!"
], function(Map, SceneView) {
  // Code to create the map and view will go here
});

 當然這些腳本要寫在一對script標簽里。

添加底圖

 在ArcGIS API中,所有地圖要素都是以對象的形式存在的。要在地圖上添加地理地圖,就需要創建地理地圖對象。Map類的對象就表示一個地理地圖。當引用了MA類組件之后,就可以創建其一個Map類的對象了。

var map = new Map({
  basemap: "streets",
  ground: "world-elevation"
});

Map類的屬性有這些:

  • basemap:地理底圖的類型。ArcGIS提供了多種類型的地理底圖,包括了OpenStreetMap(osm)。
  • layers:包含了地圖上展示的要素所在的圖層,這個圖層是“可操作的”,包括FeatureLayers、WebTileLayers和GraphicsLayers,其中不包含basemaps,也就是無法通過訪問layers屬性訪問basemap。
  • allLayers:與layers屬性不同的是,這個屬性包含了basemap圖層、ground圖層以及“可操作圖層”。
  • ground:這個屬性只在使用三維視角的時候有用。ground屬性是一個Ground類的對象,它將真實世界的地形或地勢渲染到底圖上。它包含了一組圖層來顯示地圖。在創建地理底圖的時候,ground屬性可以包含一組layer,也可以僅僅賦予一個字符串 world-elevation ,通過操作ground中的layer屬性來對底圖進行操作。示例見Toggle ground elevation

 通過調節這些屬性,就可以實現我們想要的地圖。

添加視圖

我們需要一個視圖來對我們所制作的地圖進行觀察,包括對底圖的觀察以及其他所有要素的觀察。在ArcGIS軟件中,如果制作二維地圖或進行二維分析,就使用ArcMap軟件;如果制作三維地圖或進行三維分析,就使用ArcScene軟件。在ArcGIS API中也是一樣的。我們如果制作二維地圖,就使用MapView類的對象創建視圖;如果制作三維地圖,就使用SceneView類的對象創建視圖。

創建一個視圖可以的方法與創建底圖類似,如

var view = new SceneView({
    container: "viewDiv",
    map: map
});

這個SceneView的屬性非常多,日后有空一一列舉,與我們制作的三維地圖關系較大的有:

  • container:是一個DOM元素的id,地圖將渲染在該DOM元素內。ArcGIS API制作的地圖可以自適應。
  • map:顯示的底圖。制作好底圖后,在這里引用制作的底圖實例,即可在地圖上顯示。
  • camera:視圖攝像機。攝像機代表了三位觀察的位置和方向。該屬性是一個Camera類的實例,使用三個參數確定了攝像機的位置和方向:
    • position:攝像機的位置。是一個Point類的對象,可以使用經(longitude)緯(latitude)度作為平面位置的參數,也可以使用大地坐標(x, y)作為平面位置的參數。高程都是用z屬性來指定的。還有一個spatialReference的屬性,用於指定參考系。
    • heading:攝像機朝向的方位角。
    • tilt:攝像機的“垂直角”,這個定義與測量學中的垂直角(天頂距)定義不同,當攝像機豎直向下時,該角度為0°。

一旦創建了視圖對象,就會在選定的DOM元素中進行渲染。

添加圖層

ArcGIS API提供了豐富的圖層可以使用,但是不同的圖層代表了不同的含義。這里只分析一下FeatureLayer和GraphicLayer的區別:當圖層與某一地理實體相對應時,最好使用FeatureLayer,表示是地理要素的圖層,具有實體含義;否則使用GraphicLayer,表示僅僅是一些幾何元素,沒有地理含義。

我所拿到的項目要可視化的內容具有地理實體含義,所以使用FeatureLayer。一個FeatureLayer對象包含很多屬性,可以通過REST服務創建,也可以本地創建。使用REST服務創建需要發布REST服務,然后在url屬性上填入REST服務的地址。這里介紹從本地創建FeatureLayer中的要素,官網示例見Sample - Create a FeatureLayer with GeoJSON data

如果要從本地創建要素,需要同時設置FeatureLayer的五個屬性:fields、objectIdField、spatialReference、geometryType和source。

  • fields:是一個對象數組,相當於ArcMap中的屬性表各個字段的配置,每一個對象表示了屬性表中的一個字段。每個對象有以下幾個常用屬性
    • name:字段名。
    • type:字段的數據類型,與ArcMap中一樣,有small-integer、integer、single、double、string、date、oid、geometry、blob、raster、guid、global-id、xml等。
    • alias:字段替用名。
    • length:字段長度。
    • nullable:字段是否可空。
    • editable:字段時候可編輯。
  • objectIdField:指定fields中那個字段代表了要素的ObjectId。ObjectId是每個要素的唯一標識符。
  • spatialReference:指定地理參考系。
  • geometryType:表示要素的幾何類型,有point、mulitpoint、polyline、polygon等類型。
  • source:是一個Graphic對象的集合。每個Graphic對象包括三個部分:geometry、symbol和attribute。
    • geometry:是一個Geometry類的對象,定義了Graphic對象的地理位置。Geometry類的派生類有Point(點)、MultiPoint(多點)、Polyline(折線)、Polygon(折線)等。
    • symbol:該要素顯示時的符號,代表了可視化方式。在創建Graphic對象的時候直接指定其symbol屬性不是一個比較好的做法。比較好的做法是使用Renderer(渲染器)來對圖層中的所有要素進行統一渲染。
    • attribute:是一個對象數組,每個對象要包含fields中聲明的所有不可空字段。

除此之外,還有一些屬性是非常有用的:

  • renderer:渲染器。是一個Renderer類的對象,表示對要素的geometry和attribute如何進行渲染。
  • popupTemplate:彈出框的模板,是一個PopupTemplate對象,可以用來顯示要素的數據。

當我們獲取到了可視化的數據,首先創建一個Graphic數組,在官網示例中,是這樣的

return arrayUtils.map(geoJson.features, function(feature, i) {
    return {
    geometry: new Point({
        x: feature.geometry.coordinates[0],
        y: feature.geometry.coordinates[1]
    }),
    // select only the attributes you care about
    attributes: {
        ObjectID: i,
        title: feature.properties.title,
        type: feature.properties.type,
        place: feature.properties.place,
        depth: feature.geometry.coordinates[2] + " km",
        time: feature.properties.time,
        mag: feature.properties.mag,
        mmi: feature.properties.mmi,
        felt: feature.properties.felt,
        sig: feature.properties.sig,
        url: feature.properties.url
    }
    };
});

這里使用了arrayUtils的方法將一個數組映射為另一個數組。也可以使用foreach循環來完成這件事。將Griphic數組用一個變量保存起來。

fields也需要我們進行創建,官網的示例中創建了如下的屬性表:

var fields = [
    {name: "ObjectID",alias: "ObjectID",type: "oid"},
    {name: "title",alias: "title",type: "string"},
    {name: "type",alias: "type",type: "string"},
    {name: "place",alias: "place",type: "string"},
    {name: "depth",alias: "depth",type: "string"},
    {name: "time",alias: "time",type: "date"},
    {name: "mag",alias: "Magnitude",type: "double"},
    {name: "url",alias: "url",type: "string"},
    {name: "mmi",alias: "intensity",type: "double"},
    {name: "felt",alias: "Number of felt reports",type: "double"},
    {name: "sig",alias: "significance",type: "double"}
];

我們現在就可以創建FeatureLayer了,示例代碼如下:

var lyr = new FeatureLayer({
    source: graphics, // autocast as an array of esri/Graphic
    // create an instance of esri/layers/support/Field for each field object
    fields: fields, // This is required when creating a layer from Graphics
    objectIdField: "ObjectID", // This must be defined when creating a layer from Graphics
    renderer: quakesRenderer, // set the visualization on the layer
    spatialReference: {
    wkid: 4326
    },
    geometryType: "point", // Must be set when creating a layer from Graphics
    popupTemplate: pTemplate
});

map.add(lyr);

最后一行通過map類對象的add()方法,將該要素圖層添加到地圖上。其中,quakesRenderer是創建的渲染器,下小節中會詳細講解。

設計渲染器

渲染器是地圖顯示符號的方法,相當於Echarts中的VisualMap配置項。ArcGIS API中有很多渲染器,我們這里對點符號的渲染可以使用SimpleRenderer,有幾個屬性

  • label:渲染器標簽。
  • symbol:渲染用的符號。是Symbol類的一個對象。Symbol的派生類包含了豐富的可視化類型,有二維的和三維的。如果使用三維的可視化符號,使用Symbol3D類,包括二維可視化符號的三維版本和一些三維可視化特有的符號,如MeshSymbol3D。
  • visualVariables:視覺變量,是一個對象數組。效果類似於Echarts中的VisualMap配置項,可以根據某一屬性值對顏色、尺寸、透明度、旋轉角度進行映射調整。每一個元素都是以下幾種類型:ColorVisualVariableSizeVisualVariableOpacityVisualVariableRotationVisualVariable

官網設計的渲染器如下所示:

var quakesRenderer = new SimpleRenderer({
symbol: new SimpleMarkerSymbol({
    style: "circle",
    size: 20,
    color: [211, 255, 0, 0],
    outline: {
    width: 1,
    color: "#FF0055",
    style: "solid"
    }
}),
visualVariables: [
{
    type: "size",
    field: "mag", // earthquake magnitude
    valueUnit: "unknown",
    minDataValue: 2,
    maxDataValue: 7,
    // Define size of mag 2 quakes based on scale
    minSize: {
        type: "size",
        expression: "view.scale",
        stops: [
        {value: 1128,size: 12},
        {value: 36111,size: 12},
        {value: 9244649,size: 6},
        {value: 73957191,size: 4},
        {value: 591657528,size: 2}]
    },
    // Define size of mag 7 quakes based on scale
    maxSize: {
        type: "size",
        expression: "view.scale",
        stops: [
        {value: 1128,size: 80},
        {value: 36111,size: 60},
        {value: 9244649,size: 50},
        {value: 73957191,size: 50},
        {value: 591657528,size: 25}]
    }
}]
});

 這個渲染器的效果如下所示

 

整體代碼

  1 var arcgis_groupLayer;
  2 
  3 function ArcGIS_Map_Init() {
  4     require([
  5         "esri/layers/GroupLayer",
  6         "esri/layers/FeatureLayer",
  7         "esri/Map", 
  8         "esri/views/SceneView",
  9         "esri/widgets/LayerList",
 10         "esri/layers/support/Field",
 11         "esri/geometry/Point",
 12         "esri/renderers/SimpleRenderer",
 13         "esri/symbols/PointSymbol3D",
 14         "esri/symbols/ObjectSymbol3DLayer",
 15         "esri/request",
 16         "dojo/_base/array",
 17         "dojo/dom",
 18         "dojo/on",
 19         "dojo/domReady!"
 20     ], function (GroupLayer, FeatureLayer, Map, SceneView, LayerList, Field, Point, SimpleRenderer, PointSymbol3D, ObjectSymbol3DLayer, esriRequest, 
 21     arrayUtils, dom, on) {
 22         
 23         var arcgis_fields = [
 24             {name: "ObjectID", alias: "ObjectID", type: "oid"},
 25             {name: "title", alias: "title", type: "string"},
 26             {name: "num", alias: "num", type: "integer"}
 27         ];
 28 
 29         arcgis_groupLayer = new GroupLayer({
 30             title: "消防數據",
 31             visibility: true,
 32             visibilityMode: "exclusive"
 33         })
 34 
 35         var arcgis_arcgismap = new Map({
 36             basemap: "osm",
 37             layers: [arcgis_groupLayer]
 38         });
 39 
 40         var arcgis_initCam = {
 41             position: {
 42                 x: 120.61,
 43                 y: 30.50,
 44                 z: 100000,
 45                 spatialReference: {
 46                     wkid: 4326
 47                 }
 48             },
 49             heading: 15,
 50             tilt: 60
 51         };
 52 
 53         var arcgis_view = new SceneView({
 54             map: arcgis_arcgismap,
 55             container: "arcgismap",
 56             camera: arcgis_initCam
 57         });
 58 
 59         arcgis_view.then(function () {
 60             arcgis_layerList = new LayerList({
 61                 view: arcgis_view
 62             })
 63 
 64             arcgis_view.ui.add(arcgis_layerList, "top-right");
 65         })
 66 
 67         $.getJSON("js/json/fire.json", function (data) {
 68             var graphics = [];
 69 
 70             data.forEach(function(iSender, i) {
 71                 graphics.push({
 72                     geometry: new Point({
 73                         longitude: iSender.longitude,
 74                         latitude: iSender.latitude
 75                     }),
 76                     attributes: {
 77                         ObjectID: iSender.senderId,
 78                         title: iSender.senderName,
 79                         num: iSender.num
 80                     }
 81                 })
 82             }, this);
 83 
 84             var arcgis_fireRenderer = new SimpleRenderer({
 85                 symbol: new PointSymbol3D({
 86                     symbolLayers: [new ObjectSymbol3DLayer({
 87                         resource: {
 88                             primitive: "cube"
 89                         },
 90                         width: 500,
 91                         depth: 500,
 92                         material: {color: "#e6b600"}
 93                     })]
 94                 }),
 95                 label: "火警數",
 96                 visualVariables: [{
 97                     type: "size",
 98                     field: "num",
 99                     axis: "height"
100                 },{
101                     type: "size",
102                     axis: "width-and-depth",
103                     useSymbolValue: true,
104                 }]
105             });
106 
107             var arcgis_fireLayer = new FeatureLayer({
108                 source: graphics,
109                 fields: arcgis_fields,
110                 objectIdField: "ObjectID",
111                 renderer: arcgis_fireRenderer,
112                 spatialReference: {
113                     wkid: 4326
114                 },
115                 geometryType: "point",
116                 popupTemplate: {
117                     title: "{title}",
118                     content: [{
119                         type: "fields",
120                         fieldInfos: [{
121                             fieldName: "num",
122                             label: "火警數",
123                             visible: true
124                         }]
125                     }]
126                 },
127                 title: "火警數",
128                 id: "fireLayer"
129             })
130 
131             arcgis_groupLayer.add(arcgis_fireLayer, 0);
132         })
133 
134         $.getJSON("js/json/fault.json", function (data) {
135             var graphics = [];
136 
137             data.forEach(function(iSender, i) {
138                 graphics.push({
139                     geometry: new Point({
140                         longitude: iSender.longitude,
141                         latitude: iSender.latitude
142                     }),
143                     attributes: {
144                         ObjectID: iSender.senderId,
145                         title: iSender.senderName,
146                         num: iSender.num
147                     }
148                 })
149             }, this);
150 
151             var arcgis_faultRenderer = new SimpleRenderer({
152                 symbol: new PointSymbol3D({
153                     symbolLayers: [new ObjectSymbol3DLayer({
154                         resource: {
155                             primitive: "cube"
156                         },
157                         width: 500,
158                         depth: 500,
159                         material: {color: "#0098d9"}
160                     })]
161                 }),
162                 label: "故障數",
163                 visualVariables: [{
164                     type: "size",
165                     field: "num",
166                     axis: "height"
167                 },{
168                     type: "size",
169                     axis: "width-and-depth",
170                     useSymbolValue: true,
171                 }]
172             });
173 
174             var arcgis_faultLayer = new FeatureLayer({
175                 source: graphics,
176                 fields: arcgis_fields,
177                 objectIdField: "ObjectID",
178                 renderer: arcgis_faultRenderer,
179                 spatialReference: {
180                     wkid: 4326
181                 },
182                 geometryType: "point",
183                 popupTemplate: {
184                     title: "{title}",
185                     content: [{
186                         type: "fields",
187                         fieldInfos: [{
188                             fieldName: "num",
189                             label: "故障數",
190                             visible: true
191                         }]
192                     }]
193                 },
194                 title: "故障數",
195                 id: "faultLayer",
196                 visible: false                
197             })
198 
199             arcgis_groupLayer.add(arcgis_faultLayer, 1);
200         })
201 
202         $.getJSON("js/json/linkage.json", function (data) {
203             var graphics = [];
204 
205             data.forEach(function(iSender, i) {
206                 graphics.push({
207                     geometry: new Point({
208                         longitude: iSender.longitude,
209                         latitude: iSender.latitude
210                     }),
211                     attributes: {
212                         ObjectID: iSender.senderId,
213                         title: iSender.senderName,
214                         num: iSender.num
215                     }
216                 })
217             }, this);
218 
219             var arcgis_linkageRenderer = new SimpleRenderer({
220                 symbol: new PointSymbol3D({
221                     symbolLayers: [new ObjectSymbol3DLayer({
222                         resource: {
223                             primitive: "cube"
224                         },
225                         width: 500,
226                         depth: 500,
227                         material: {color: "#2b821d"}
228                     })]
229                 }),
230                 label: "聯動數",
231                 visualVariables: [{
232                     type: "size",
233                     field: "num",
234                     axis: "height"
235                 },{
236                     type: "size",
237                     axis: "width-and-depth",
238                     useSymbolValue: true,
239                 }]
240             });
241 
242             var arcgis_linkageLayer = new FeatureLayer({
243                 source: graphics,
244                 fields: arcgis_fields,
245                 objectIdField: "ObjectID",
246                 renderer: arcgis_linkageRenderer,
247                 spatialReference: {
248                     wkid: 4326
249                 },
250                 geometryType: "point",
251                 popupTemplate: {
252                     title: "{title}",
253                     content: [{
254                         type: "fields",
255                         fieldInfos: [{
256                             fieldName: "num",
257                             label: "聯動數",
258                             visible: true
259                         }]
260                     }]
261                 },
262                 title: "聯動數",
263                 id: "linkageLayer",
264                 visible: false
265             })
266 
267             arcgis_groupLayer.add(arcgis_linkageLayer, 2);
268         })
269     })
270 }
271 
272 spatial_echarts_instant.arcgismap = {
273     init: function () {
274         ArcGIS_Map_Init();
275     },
276     clear: function (params) {
277         arcgis_view = {};
278     },
279     resize: function (params) {
280         
281     },
282     setOption: function (params) {
283         require([
284             "esri/views/SceneView"
285         ], function (SceneView) {
286             arcgis_view = new SceneView({
287                 map: arcgis_arcgismap,
288                 container: "arcgismap",
289                 camera: arcgis_initCam
290             });
291         })
292     },
293     changeLayer: function (layerIndex) {
294         var layerId = null;
295         switch (layerIndex) {
296             case 1:
297                 layerId = "fireLayer";
298                 break;
299             case 2:
300                 layerId = "faultLayer";
301                 break;
302             case 3:
303                 layerId = "linkageLayer";
304                 break;
305             default:
306                 break;
307         }
308         if (layerId) {
309             var layer = arcgis_groupLayer.findLayerById(layerId);
310             layer.visible = true;
311         }
312     }
313 }
314 
315 $(function () {
316     ArcGIS_Map_Init();
317 })

 


免責聲明!

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



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