ArcGIS API for JavaScript 4.2学习笔记[30] 点和线高程查询(第八章完结)

终于到最后一篇了,可喜可贺。

本例先说明了如何进行单点的高程差分析,然后说明了道路的起伏分析。前者很直观地比较了两个年份的高程数据之间的差值,体现山区的高程变化(有啥用啊?)后者,一条路上的起点终点起伏多少,可以给驾驶导航提供更多样化的数据。

本例使用了高程图层和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,请注明来意。

posted @ 2017-04-09 09:47 秋意正寒 阅读(...) 评论(...) 编辑 收藏