终于到最后一篇了,可喜可贺。
本例先说明了如何进行单点的高程差分析,然后说明了道路的起伏分析。前者很直观地比较了两个年份的高程数据之间的差值,体现山区的高程变化(有啥用啊?)后者,一条路上的起点终点起伏多少,可以给驾驶导航提供更多样化的数据。
本例使用了高程图层和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>
Query Elevation (Points)
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);
})
}
onRouteUpdate()
从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,请注明来意。