終於到最后一篇了,可喜可賀。
本例先說明了如何進行單點的高程差分析,然后說明了道路的起伏分析。前者很直觀地比較了兩個年份的高程數據之間的差值,體現山區的高程變化(有啥用啊?)后者,一條路上的起點終點起伏多少,可以給駕駛導航提供更多樣化的數據。
本例使用了高程圖層和RouteTask。
本例對應的官方例子是:Query Elevation (Points)和Query Elevation (Lines)
1. 點高程差查詢
1.1 結果顯示
選了一個明顯的點,綠色的是地形變化前的高程點,紅色的球是當前的高程點,代表地表起伏變化的是紅色的線(即高程差)。
圖中隨便點擊一個點,稍等幾秒鍾后就會出現高程查詢結果。顯示的高程圖層是當前的高程圖層。
且不管view上方的白色提示區html構成如何(這個比較簡單,html代碼沒什么邏輯性,一看就懂),來看看其引用:
require([ "esri/Map", "esri/views/SceneView", "esri/Graphic", "esri/geometry/Polyline", "esri/layers/ElevationLayer", "esri/symbols/PointSymbol3D", "esri/symbols/ObjectSymbol3DLayer", "esri/symbols/LineSymbol3D", "esri/symbols/LineSymbol3DLayer", "dojo/promise/all", "dojo/domReady!" ], function( Map, SceneView, Graphic, Polyline, ElevationLayer, PointSymbol3D, ObjectSymbol3DLayer, LineSymbol3D, LineSymbol3DLayer, all){ ... } )
用於填充用的符號和符號圖層不說,重點是ElevationLayer。
1.2 骨架
function(...){ var beforeLandslideUrl = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/OsoLandslide/OsoLandslide_Before_3DTerrain/ImageServer/"; var afterLandslideUrl = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/OsoLandslide/OsoLandslide_After_3DTerrain/ImageServer/"; var beforeLandslideLayer = new ElevationLayer({url: beforeLandslideUrl}); var afterLandslideLayer = new ElevationLayer({url: afterLandslideUrl}); var map = new Map({... , ground:{layers:[beforeLandslideLayer, afterLandslideLayer]}}); var view = new View({...}); var afterPointSymbol = new PointSymbol3D({...}); var beforePointSymbol = new PointSymbol3d({...}); var lineSymbol = new LineSymbol3D({...}); var resultsContainer = document.getElementById("resultsDiv"); view.on("click", function(event){...}); document.getElementById("elevAfter").addEventListener("change", function(evt){...}); }
一開頭就是兩個使用ImageServer的兩個高程圖層;
然后,將map的ground屬性設置為這兩個高程圖層;
設置前后點符號和連接他們的線符號樣式;
主要的是view的click事件,以及是否顯示新高程圖層(afterLandslideLayer)復選框的change監聽事件。
1.3 所以重點就在兩個事件上了
1.3.1 click事件
由於click事件比較大,縮寫成骨架形式:
view.on("click",function(event){ resultsContainer.innerHTML = "Query elevation ..."; var position = event.mapPoint; var queryBeforeLandslide = beforeLandslideLayer.queryElevation(position); var queryAfterLandslide = afterLandslideLayer.queryElevation(position); all([queryBeforeLandslide,queryAfterLandslide]) .then(function(results){...}) otherwise(function(error){...}); } );
首先,把DOM面板上的提示信息寫為Query elevation ...,然后獲取當前點擊的點位置信息position(Point類)。
根據這個Point,使用高程圖層的空間查詢方法queryElevation(Point),返回一個簡單幾何體信息。
利用這兩個返回的的“東西”,使用dojo提供的all方法進行異步操作,如果成功則執行回調函數1,否則執行回調函數2。
otherwise的回調函數比較短,僅僅為DOM元素寫入“查詢失敗”的提示信息。所以重點就在回調函數1:
.then(function(results) { var posBeforeLandslide = results[0].geometry; var posAfterLandslide = results[1].geometry; view.graphics.removeAll(); view.graphics.add(new Graphic({ geometry: posBeforeLandslide, symbol: beforePointSymbol })); view.graphics.add(new Graphic({ geometry: posAfterLandslide, symbol: afterPointSymbol })); var lineGeometry = new Polyline({ spatialReference: posBeforeLandslide.spatialReference }); lineGeometry.addPath([posBeforeLandslide, posAfterLandslide ]); view.graphics.add(new Graphic({ geometry: lineGeometry, symbol: lineSymbol })); var elevationDifference = Math.abs(posBeforeLandslide.z - posAfterLandslide.z); resultsContainer.innerHTML = "Elevation difference: " + elevationDifference.toFixed(2) + " m"; })
從兩個返回的“東西”中獲得geometry屬性,官方還是沒寫明白results是什么東西...
清除view的圖形信息。
先添加兩個點幾何體,然后根據這倆點實例化一條Polyline(使用addPath()方法),把Polyline添加到視圖的圖形屬性中。
最后刷新DOM面板上的高程查詢信息即可。
1.3.2 復選框的change監聽事件
這個就比較簡單了,也挺有趣。它打勾就代表新的高程圖層顯示。
document.getElementById("elevAfter").addEventListener("change", function(evt) { afterLandslideLayer.visible = evt.target.checked; beforeOrAfter = evt.target.checked ? "after" : "before"; });
僅僅是改動了afterLandslideLayer的visible屬性而已。
最后給出這例的完整HTML代碼:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no"> <title>Query Elevation (Points) - 4.2</title> <style> html, body, #viewDiv { padding: 0; margin: 0; height: 100%; width: 100%; } #paneDiv { position: absolute; top: 12px; left: 62px; width: 80%; padding: 0 12px 0 12px; background-color: rgba(255, 255, 255, 0.85); border: 1px solid white; color: black; } #resultsDiv { font-size: 1.2em; text-align: center; border-bottom: 1px solid gray; padding: 10px 0px; } #activeElevationLayerDiv { margin: 10px 0; } ul #red { color: rgb(150, 26, 15); } ul #green { color: rgb(21, 150, 15); } ul span { color: black; } ul { margin: 0 0 10px 0; } </style> <link rel="stylesheet" href="https://js.arcgis.com/4.2/esri/css/main.css"> <script src="https://js.arcgis.com/4.2/"></script> <script> require([ "esri/Map", "esri/views/SceneView", "esri/Graphic", "esri/geometry/Polyline", "esri/layers/ElevationLayer", "esri/symbols/PointSymbol3D", "esri/symbols/ObjectSymbol3DLayer", "esri/symbols/LineSymbol3D", "esri/symbols/LineSymbol3DLayer", "dojo/promise/all", "dojo/domReady!" ], function( Map, SceneView, Graphic, Polyline, ElevationLayer, PointSymbol3D, ObjectSymbol3DLayer, LineSymbol3D, LineSymbol3DLayer, all ) { // Create elevation layers var beforeLandslideUrl = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/OsoLandslide/OsoLandslide_Before_3DTerrain/ImageServer/"; var afterLandslideUrl = "http://sampleserver6.arcgisonline.com/arcgis/rest/services/OsoLandslide/OsoLandslide_After_3DTerrain/ImageServer/"; var beforeLandslideLayer = new ElevationLayer({ url: beforeLandslideUrl }); var afterLandslideLayer = new ElevationLayer({ url: afterLandslideUrl }); // Create Map and View var map = new Map({ basemap: "satellite", ground: { layers: [beforeLandslideLayer, afterLandslideLayer] } }); var view = new SceneView({ container: "viewDiv", map: map, camera: { // initial view: heading: 332.8, tilt: 65.5, position: { x: -13563643, y: 6153016, z: 577, spatialReference: { wkid: 3857 } } } }); // Initialize symbols var afterPointSymbol = new PointSymbol3D({ symbolLayers: [new ObjectSymbol3DLayer({ material: { color: [150, 26, 15] }, resources: { primitive: "sphere" }, width: 8 })] }); var beforePointSymbol = new PointSymbol3D({ symbolLayers: [new ObjectSymbol3DLayer({ material: { color: [21, 150, 15] }, resources: { primitive: "sphere" }, width: 8 })] }); var lineSymbol = new LineSymbol3D({ symbolLayers: [new LineSymbol3DLayer({ material: { color: [150, 26, 15] }, size: 1.5 })] }); var resultsContainer = document.getElementById("resultsDiv"); view.on("click", function(event) { resultsContainer.innerHTML = "Querying elevation..."; // Query both elevation layers for the elevation at the clicked map position var position = event.mapPoint; var queryBeforeLandslide = beforeLandslideLayer.queryElevation( position); var queryAfterLandslide = afterLandslideLayer.queryElevation( position); // When both query promises resolve execute the following code all([queryBeforeLandslide, queryAfterLandslide]) .then(function(results) { var posBeforeLandslide = results[0].geometry; var posAfterLandslide = results[1].geometry; // Clear graphics from previous result (if applicable) view.graphics.removeAll(); // Draw a point graphic for position before landslide view.graphics.add(new Graphic({ geometry: posBeforeLandslide, symbol: beforePointSymbol })); // Draw a point graphic for position after landslide view.graphics.add(new Graphic({ geometry: posAfterLandslide, symbol: afterPointSymbol })); // Draw a vertical line that illustrates the elevation difference var lineGeometry = new Polyline({ spatialReference: posBeforeLandslide.spatialReference }); lineGeometry.addPath([posBeforeLandslide, posAfterLandslide ]); view.graphics.add(new Graphic({ geometry: lineGeometry, symbol: lineSymbol })); // Compute and display the difference in elevation var elevationDifference = Math.abs(posBeforeLandslide.z - posAfterLandslide.z); resultsContainer.innerHTML = "Elevation difference: " + elevationDifference.toFixed(2) + " m"; }) .otherwise(function(error) { resultsContainer.innerHTML = "Elevation query failed (" + error.message + ")"; }); }); // When both elevation layers are set "visible", the surface is defined by the latter layer (afterLandslideLayer). // Thus we can toggle between "before" and "after" by toggling the visibility of afterLandslideLayer. document.getElementById("elevAfter").addEventListener("change", function(evt) { afterLandslideLayer.visible = evt.target.checked; beforeOrAfter = evt.target.checked ? "after" : "before"; }); }); </script> </head> <body> <div id="viewDiv"></div> <div id="paneDiv"> <div id="resultsDiv">Click on the map to see the difference in elevation before and after the landslide.</div> <div id="activeElevationLayerDiv"> Legend: <ul> <li id="green"><span>Surface point before landslide</span></li> <li id="red"><span>Surface point after landslide</span></li> </ul> <input type="checkbox" id="elevAfter" checked><label for="elevAfter">Show surface after landslide</label> </div> </div> </body> </html>
2. 線路高程查詢
2.1 先看看結果
點擊2個以上的點,生成一條最短路徑,途徑的路線的總長度、總上升和總下降高程均有顯示。total ascent和total descent的差值即為起始點終點的高程差。
所以這一例是基於最短路徑分析的(RouteTask)。
2.2 重點代碼(與RouteTask有別的部分)
在線和點符號的設置上,和地圖和場景的設置上和RouteTask那篇文章有點改動外,幾乎是照搬了預設,僅僅對view的click事件進行了修改。重點就放在了:
on(view, "click", addStop); function addStop(event){...} function onRouteUpdate(data){...}
這段代碼上,前一個方法體是view的click事件,后一個方法體是對查詢結果的繪制和路線的更新。
先看簡單的addStop()方法:
function addStop(event) { if (!event.mapPoint) { return; } var stop = new Graphic({ geometry: event.mapPoint, symbol: markerSymbol }); routeLayer.add(stop); routeParams.stops.features.push(stop); if (routeParams.stops.features.length >= 2) { routeTask.solve(routeParams) .then(onRouteUpdated) .otherwise(function(err) { routeLayer.remove(stop); routeParams.stops.features.pop(); console.error(err); }); } }
先檢測點擊是否產生了mapPoint,是則繼續,實例化一個Graphic對象,添加到routeLayer中。
然后設置RouteTask必須的RouteParameters參數。如果點擊的點數>=2個,執行RouteTask.solve()方法。
緊接着異步操作鏈,成功則繼續執行onRouteUpdated(),失敗則移除剛剛生成的點(從RouteParameters和GraphicsLayer中刪除)。
來看看onRouteUpdate()是如何獲取高程信息的:

function onRouteUpdated(data) { var route = data.routeResults[0].route var geometry = route.geometry; var elevationPromise = map.ground.queryElevation(geometry); elevationPromise.then(function(result) { var path = result.geometry.paths[0]; var ascent = 0; var descent = 0; for (var i = 1; i < path.length; i++) { var d = path[i][2] - path[i - 1][2]; if (d > 0) { ascent += d; } else { descent -= d; } } document.getElementById("distanceDiv").innerHTML = "<p>total distance: " + Math.round(route.attributes.Total_Kilometers * 1000) / 1000 + " km</p>"; document.getElementById("ascDiv").innerHTML = "<p>total ascent: " + Math.round(ascent * 100) / 100 + " m</p>"; document.getElementById("descDiv").innerHTML = "<p>total descent: " + Math.round(descent * 100) / 100 + " m</p>"; routeLayer.add(new Graphic({ geometry: result.geometry, symbol: pathSymbol })); }, function(error) { console.error(error); }) }
從RouteTask.solve()中獲取返回值中的route信息,再從route中獲取geometry信息。
然后利用這個geometry,使用Map對象的ground屬性的queryElevation(高程圖層的高程查詢方法)進行高程查詢。
對高程查詢的結果進行異步操作then(),如果異步操作成功則執行如下的回調函數:
elevationPromise.then(function(result) { var path = result.geometry.paths[0]; var ascent = 0; var descent = 0; for (var i = 1; i < path.length; i++) {...} document.getElementById("distanceDiv").innerHTML = ... ; document.getElementById("ascDiv").innerHTML = ... ; document.getElementById("descDiv").innerHTML = ... ; routeLayer.add(new Graphic({ geometry: result.geometry, symbol: pathSymbol })); }, function(error) { console.error(error); } )
獲取查詢結果中的geometry中的path信息,使用一個for循環統計高程的上下變化,輸出到DOM元素上顯示,最后在GraphicsLayer中添加這個線要素查詢結果。
3. 總結
兩個高程查詢的例子都是基於高程圖層的queryElevation()方法的,而最關鍵的就是獲取需要查詢的Geometry。前者是通過點擊事件,后者則是通過RouteTask的分析結果。
這一例可以放到第七章的,只不過包裝得看起來像空間分析了~
AJS4.2 基礎部分學習結語
有點拖沓啊。本來一個月能完成的事情非得拖兩個月。老師指定要看的章節我都看了,在我實際學習中發現需要加強學習的Layer章節和Graphic章節會在以后慢慢更新的。
總之感謝一路看過來我的博客的人,國內第一個對AJS完整解讀的博文系列終於寫完了——第一部分。對於初學者來說,前面30篇博客算是能成功入門了,接下來的學習任務,
就是對AJS4.2剩余重要章節的補充和學習中遇到的零碎知識總結,以及對AJS4.3及以后更高版本的新特性的學習了。
博客還會繼續更新,歡迎大家繼續交流學習。
本人郵箱:onsummer@foxmail.com,請注明來意。