ArcGIS API for JavaScript 4.2学习笔记[7] 鹰眼(缩略图的实现及异步处理、Promise、回调函数、监听的笔记)

文前说明:关于style就是页面的css暂时不作评论,由于官方给的例子的样式实在太简单了,照抄阅读便可。javascript

这篇文章有着大量AJS 4.x版本添加的内容,如监听watch、Promise对象、回调函数、异步处理等内容,原理性的东西我会在文末解释,各位看官不用担忧看不懂,我尽可能用通俗的语言解释这些。css

惯例,若是不习惯从头看到尾,能够直接跳到后面看总结html


你们应该看过商业地图的缩略图功能吧?以度娘地图为例,在使用街景地图的时候,左下角会出现一个地点同样的2D小地图:java

这个就是鹰眼功能的应用,在不少桌面软件中如Erdas、Envi,鹰眼是很常见的。es6

//若是如下超连接往后更新了4.3或更高版本,请自行寻找4.2的sample配合本文学习~编程

此次就解读2D overview map in SceneView这个例子。数组

源代码:点我promise

其实就是一个2D的MapView在3D的SceneView的显示而已,关键就在数据的同步,官方指出了watch()方法是关键。服务器

话很少说,先上最终效果图:dom

结构大概就是,大的DIV里放SceneView,小的DIV里放MapView。

小的DIV里又有一个黑色的区域来标识当前SceneView的区域。小的DIV的widgets被移除。

html代码为:

<body>
  <div id="viewDiv"></div>
  <div id="overviewDiv">
    <div id="extentDiv"></div>
  </div>
</body>

老样子,require给出引用(之前都叫第一个字符串数组参数,为了省事,之后直接叫引用了)

require(
    [
      "esri/Map",
      "esri/views/SceneView",
      "esri/views/MapView",
      "esri/core/watchUtils",
      "dojo/dom",
      "dojo/promise/all",
      "dojo/domReady!"
    ],
    function(Map, SceneView, MapView, watchUtils, dom, all){
        //你的代码
    }
);

重点应该是:

view的watch()方法、watchUtils的when方法、view的toScreen方法、view的extent属性、view的then方法。


 

既然有两个view(DIV),那么确定要有两份map(数据)。

因此第二参数(之前的文章叫函数参数,以后都叫第二参数)先将map和view定义以下:

      var mainMap = new Map({
        basemap: "hybrid",
        ground: "world-elevation"
      });
      var overviewMap = new Map({
        basemap: "osm"
      });
      var mainView = new SceneView({
        container: "viewDiv",
        map: mainMap
      });
      var mapView = new MapView({
        container: "overviewDiv",
        map: overviewMap
      });

mainMap、mainView是3D的,overviewMap、mapView是2D的。

固然,咱们看到的2D的小地图是没有放大缩小那些控件的,只需1行代码,就能够置空那些控件。

      mapView.ui.components = [];

查阅API,能够知道ui属性是DefaultUI类,DefaultUI继承自UI类。components是字符串数组,若赋值为空数组则清空。相应的,DefaultUI类有remove和empty方法能够清除控件,就不细说了。

为了便于操做,把当前区域的DIV“extendDiv”的DOM元素获取为变量:

var extentDiv = dom.byId("extentDiv");

 

以上就完成了准备部分。

接下来,数据加载完成后,就要对2D的地图和3D的地图进行“同步”了,须要用到两个view的then方法。

then()方法是Promise对象的特有方法,而Promise是什么暂时无需了解,只要知道在AJS 4.x中Promise是一个很重要的东西。

并且,MapView和SceneView类都继承了Promise类。不只如此,AJS 4.x中不少方法返回的都是Promise对象。

先看看mainView(3D视图)的then方法看看它作了什么:

mainView.then(function() {
  mainView.goTo({
    center: [7, 46],
    scale: 200000,
    heading: 35,
    tilt: 60
  },
 { animate:
true, duration: 100000 }) });

很好,它接受了一个参数,类型是方法。这个匿名方法干了什么呢?这不就是上一篇文章里说的缩放动画嘛!(goTo)跳过,看mapView(2D视图)的then方法看它作了什么:

mapView.then(function() {
  mainView.watch("extent", updateOverviewExtent);
  mapView.watch("extent", updateOverviewExtent);

  watchUtils.when(mainView, "stationary", updateOverview);

  function updateOverview() {
    mapView.goTo({
      center: mainView.center,
      scale: mainView.scale * 2 * Math.max(mainView.width /
        mapView.width,
        mainView.height / mapView.height)
    });
  }

  function updateOverviewExtent() {
    var extent = mainView.extent;

    var bottomLeft = mapView.toScreen(extent.xmin, extent.ymin);
    var topRight = mapView.toScreen(extent.xmax, extent.ymax);

    extentDiv.style.top = topRight.y + "px";
    extentDiv.style.left = bottomLeft.x + "px";

    extentDiv.style.height = (bottomLeft.y - topRight.y) + "px";
    extentDiv.style.width = (topRight.x - bottomLeft.x) + "px";
  }
});
mapView的then方法

很长的样子。

我慢慢解释。

仍然是接受一个方法做为参数(为何then接受的参数那么奇怪?文末会解释的)

//题外话:在javascript里头传函数/方法是很常见的,函数/方法是js的一种变量类型,在C/C++里头能够传递函数指针,在C#里头能够传递委托变量。

这个方法里有两个方法,命名为 updateOverview 和 updateOverviewExtent,咱们根据这两个方法把这个then方法的代码拆开看,发现watch和watchUtils.when是跟这两个方法配对的。

即:

//两个视图都与updateOverviewExtent方法绑定
mainView.watch("extent", updateOverviewExtent);
mapView.watch("extent", updateOverviewExtent);

function updateOverviewExtent() {
  var extent = mainView.extent;

  var bottomLeft = mapView.toScreen(extent.xmin, extent.ymin);
  var topRight = mapView.toScreen(extent.xmax, extent.ymax);

  extentDiv.style.top = topRight.y + "px";
  extentDiv.style.left = bottomLeft.x + "px";

  extentDiv.style.height = (bottomLeft.y - topRight.y) + "px";
  extentDiv.style.width = (topRight.x - bottomLeft.x) + "px";
}

查阅API,得知视图的父类Accessor就支持watch方法了。值得一提的是,为了实现监听变化,AJS4.x版本专门提供了watch方法代替了之前的旧方法。

watch的用法是:

对象.watch("该须要监听的属性名", 属性变化后须要执行的回调函数);

即某对象监听了它的某个属性后,这个属性一旦发生改变,就会去执行某些代码。

在本例中,须要监听的是两个view对象的extent(范围)属性,一旦extent发生变化,那么updateOverviewExtent()方法就会被执行。

updateOverviewExtent()方法的大概意思就是:获取3D视图的范围->获取2D视图的对角线两个角点->更改2D视图上方的区域框的DOM元素的尺寸属性(top、left、height、width)

光改变区域框是不行的,还要改变2D地图的范围。

watchUtils.when(mainView, "stationary", updateOverview);

function updateOverview() {
  mapView.goTo({
    center: mainView.center,
    scale: mainView.scale * 2 * Math.max(mainView.width /
      mapView.width,
      mainView.height / mapView.height)
  });
}

watchUtils这个对象,是位于esri/core/watchUtils模块下的一个类。

它表明的含义是:监听某个对象,当这个对象的某个属性是true时,执行给定的方法。

查阅API得知,这个类提供了when这个静态方法,when方法的意义是:

因此,在本例中,意思就是:

当mainView这个3D视图对象的"stationary"属性是true时,刷新mapView这个2D视图对象。

刷新2D视图对象主要用的是上一篇中说到的goTo()方法,本例只指定了center和scale这两个属性组成的Object匿名对象。

SceneView类的stationary属性是布尔类型的,意义是当前视图是否已经静止(通常视图会由鼠标拖拽或者goTo()方法产生动态效果,一旦中止下来,stationary就会变成true)

 

总结一下。

这个例子大概思路就是:

·先实例化两个map和两个view,对3D的mainView在建立完成后使用then()方法缩放到指定位置。

·其中,对2D的mapView建立完成后使用then()方法,分别监听两个view的extent属性,还监听3D视图的stationary属性。

·当extent属性发生变化时,2D视图上方范围框先进行变化,而后2D地图紧随变化。

·当3D视图静止下来后,刷新2D视图。

 

监听还算比较好理解,须要注意的很少,注意到watch和watchUtils.when这两个方法返回的都是WatchHandle对象。待之后研究多了监听后,再仔细看看别的监听方法。

难点就在于then方法。


 

难点。

then()方法怎么来的?这要从ES6(全名ECMAScript 2015)的新规范Promise对象提及。ECMAScript是JavaScript的标准,JS是ES的实现。

Promise是什么?这个东西说复杂也很复杂,它是:

为了处理异步操做多层回调函数的写法枯燥、难以阅读维护而产生的,由CommonJS社区发起的一个新规范的类。

最显著的特征是它实例化的对象都有then()和catch()方法(PromiseA+规范?好像是)

在AJS中,继承了Promise的类有:

所有的Layers

MapView、SceneView、LayerView

ViewAnimation

能返回Promise对象的类数不胜数。

因此说,为何要用Promise?

这又要从异步操做提及了。

————

在AJS 4.x中,数据(Map类)和视图(View类)是分开的,3.x版本绘图渲染是Map本身完成的。

因为View视图类被分离开,绘图逻辑就成了它的主要功能。固然,绘图不会很快,每每有一个过程,尤为是超大数据量的绘图的时候会有一个比较长的等待过程。

因此,在JS里,较长的处理会丢给异步处理(就是同时进行好几个操做)

可是可是,咱们知道JS是单线程的,它是怎么处理异步处理的呢?简单说说,JS的异步处理实际上是个“伪异步”,是先完成同步代码才执行异步代码的。

一般,异步代码会作一些计算量比较大的事情,而同步代码则作一些不怎么耗时间的初始化工做。就是说

同步代码花少许的时间去初始化一些事情,其间有n个异步任务丢给异步队列。当同步代码完成初始化后(时间短),异步代码开始按顺序执行。

好比:界面的构建交给同步代码,而其间有n个后台数据交换、处理、计算的任务,就丢给异步队列去准备。当界面构造好(时间每每很短,几乎是秒速),异步代码就在后面开始执行。

这先看到的界面会让体验好不少,若是异步代码(就是耗时比较大的任务)放在同步代码里执行,那么因为同步的性质,必须等待这些耗时大的任务执行完成才能继续往下走(js的特色,单线程)

【在本例中】

初始化view,我不知道在云端是怎么运行的(由于我用的是CDN来运行AJS程序),可是我知道view的实例化确定是用了异步操做。

先完成网页的加载(出现3D地球和2D地图,同步),再进行视图的渲染(山体拔高等,异步)。

sometimes,异步操做固然会有一个结果,好比异步在后面花好长时间算出个矩阵,可是同步代码已经结束了,异步任务丢过去的时候结果还没出来,怎么获取它?

咱们能够用一个方法去获取它。这个方法,古时候叫回调函数

在没有Promise类的时候,一般用回调函数这种办法实现(也能用事件、监听)异步是很正常的一种。

可是当回调函数自己也是个异步操做的时候,就会显得晕头转向。

异步第一层,有结果要用回调函数返回给同步代码->回调函数是第二层,这个回调函数里头须要用二级回调函数返回结果给第一层->……

举个例子:

我是领导,我如今有两件事:有个事儿要作,和喝茶。

这两件事不冲突,虽然这个事儿很无聊,耗时大(如文字录入)。

因此我把这个事儿丢给经理(异步第一层),我继续喝茶(同步)

异步第一层就是经理要作这个事,可是这个事情绝大部分是无聊的,最后的整理比较简单。

因此经理就把这个无聊的部分丢给职员(异步第二层),等待职员把这部分作完的同时,也去喝茶(同步)。

因而,职员的结果就是二级回调函数,职员把结果完成后,“回调”给经理。

经理拿着职员的结果整理好,“回调”给领导。(第二层异步完成)

此时领导茶已经喝完了(同步完成),而任务也完成了(第一层异步完成)。

这里若是用老的写法将会很是的烦,若是用Promise的then写法就是

领导要作事儿.then(function(){让经理去作})

.then(function(){让职工作});

链式写法,简单,容易看,也容易维护。

then里面的function就是回调函数,告诉异步任务完成后,要怎么处理异步结果的一段代码。

最后看看then方法的语法:

then(function resolve, function rejected);

咱们通常只用前一个参数,即异步成功要怎么处理。然后一个参数是异步任务处理失败后要作什么。

甚至AJS官方还给出了处理中要作什么的第三个参数...这个就不说那么多了。

——

大概清楚是这么个过程后,咱们知道View对象是Promise对象(继承),并且有异步操做的过程。

因此,mainView.then(function(){...});的意义就是

当3D视图在服务器端异步操做成功后,使用goTo()缩放到指定的位置。

 

文末,我还想说说监听,监听在AJS 3.x版本里是经过事件完成的,而AJS 4.x全新使用了watch一派写法。有关这些能够参考AJS 4.2的Guide文档。

最后的最后,关于异步和回调函数部分我也是学了一天后才给出的模糊定义,但愿你们能看懂吧...我也不是很能理解,官方给的多层then()是这样的:

出处:点我

then里头固然是方法,无参的。只有子一层的结果完成的时候,父一层的then才能凭借子一层的结果的回调完成异步。

给一些我阅读中以为不错的对异步、回调函数讲解的文章:

大白话讲解Promise

ECMAScript 6入门

Javascript异步编程的4种方法

百度知道-JS中回调函数怎么理解

Javascript异步编程之—异步原理

JS中的回调函数,以及ES6中经过promise处理回调

相关文章
相关标签/搜索