编程模式

引言

本章节讨论使用 GeoScene API for JavaScript 编写应用程序的编程模式和最佳实践。

加载类

获取 GeoScene API for JavaScript 后,使用 require() 将 GeoScene API for JavaScript 类异步加载到应用程序中。

该方法需要两个参数:

  • 一个有序字符串数组,作为每个导入 API 类的完整命名空间
  • 每个 API 类都将作为回调函数中的位置参数加载

例如,要加载 MapMapView 类,请先转至文档并查找每个类的完整命名空间。在本例中,Map 具有命名空间 "geoscene/Map",并且 MapView 的命名空间为 "geoscene/views/MapView"。然后将这些字符串作为数组传递给 require(),并使用局部变量名称 Map 和 MapView 作为回调函数的位置参数:

   
1
2
3
require(["geoscene/Map", "geoscene/views/MapView"], (Map, MapView) => {
  // 此应用程序逻辑使用 'Map' 和 'MapView' 
});

并非每个模块都需要使用 require() 进行加载,因为许多类可以使用自动转换从构造函数中初始化。

通过 AMD 模块 Dojo 工具包简介,了解有关 AMD 模块格式的更多信息。

构造函数

GeoScene API for JavaScript 中的所有类都有单个构造函数,并且所有属性都可通过将参数传递给构造函数来设置。

例如,下面是调用 MapMapView 类的构造函数的示例。

          
1
2
3
4
5
6
7
8
9
10
const map = new Map({
  basemap: "tianditu-vector"
});

const view = new MapView({
  map: map,
  container: "map-div",
  center: [ 116, 39 ],
  scale: 5
});

或者,可以使用 setters 直接指定类实例的属性。

             
1
2
3
4
5
6
7
8
9
10
11
12
13
const map = new Map();
const view = new MapView();

map.basemap = "tianditu-vector";           // 设置属性

const viewProps = {                    // 具有属性数据的对象
  container: "map-div",
  map: map,
  scale: 5000,
  center: [ 116, 39 ]
};

view.set(viewProps);                   // 使用 setter

属性

GeoScene API for JavaScript 支持以一种简单、一致的方式来获取、设置和查看类的所有属性。

许多 API 类都是 Accessor 类的子类,该类定义了以下方法。

方法名称返回类型说明
get(propertyName)不同使用名称 propertyName 获取属性的值
set(propertyFields)N/A对于 propertyFields 中的每个 key/value 对,此方法可将名称为 key 的属性值设置为 value
watch(propertyName, callback)WatchHandle当名为 propertyName 的属性值更改时,调用回调函数 callback

Getters

get 方法返回命名属性的值。

此方法是一种简便的方法,因为在不使用 get() 的情况下,要返回嵌套属性的值(例如,要返回 Map 对象的属性 basemaptitle),需要使用 if 语句来检查 basemap 是 undefined 还是 null

    
1
2
3
4
const basemapTitle = null;
if (map.basemap) {                     // 确保 `map.basemap` 存在
  basemapTitle = map.basemap.title;
}

get 方法不再需要 if 语句,并返回 map.basemap.title 的值,如果 map.basemap 存在,否则返回 null

 
1
const basemapTitle = map.get("basemap.title");

Setters

可直接设置属性的值。

   
1
2
3
view.center = [ 116, 39 ];
view.zoom = 6;
map.basemap = "tianditu-image";

当需要更改多个属性值时,set() 可以传递具有属性名称和新值的 JavaScript Object

      
1
2
3
4
5
6
const newViewProperties = {
  center: [ 116, 39 ],
  zoom: 6
};

view.set(newViewProperties);

在 4.x 之前,可以通过调用 getMethodname()setMethodname() 来获取或设置某些属性。不再支持这些 getter 和 setter 方法。

查看属性变化

使用 watch() 处理观察属性更改,这需要两个参数:

  • String 形式的属性名称,以及
  • 每当属性值更改时,调用的回调函数

WatchHandle 实例由 watch() 返回。

以下代码示例观察Map 对象的 basemap.title 属性,并在底图标题值更改时,调用 titleChangeCallback 函数。

            
1
2
3
4
5
6
7
8
9
10
11
12
const map = new Map({
  basemap: "tianditu-vector"
});

function titleChangeCallback (newValue, oldValue, property, object) {
  console.log("New value: ", newValue,
              "<br>Old value: ", oldValue,
              "<br>Watched property: ", property,
              "<br>Watched object: ", object);
};

const handle = map.watch('basemap.title', titleChangeCallback);

例如,如果 basemap 属性发生更改:

 
1
map.basemap = "geoscene-blue";

然后调用 titleChangeCallback 函数,并将以下内容输出到控制台:

    
1
2
3
4
New value: geoscene-blue
Old value: tianditu-vector
Watched property: basemap.title
Watched object: ...                    // 这将记录地图对象

可以在 WatchHandle 对象上调用 remove 方法以停止观察更改。

 
1
handle.remove();

并非所有属性都可观察,包括集合。可以注册一个事件处理程序去监听集合更改

注:FeatureLayer.sourceGraphicsLayer.graphics 属性都是集合, 请使用 on() 而非 watch() 来通知这些属性值的更改。

自动转换

自动转换可将 JavaScript 对象转换为 GeoScene API for JavaScript 类类型,而无需应用程序开发者显式导入这些类。

在以下代码示例中,为 FeatureLayer 创建 SimpleRenderer 需要五个 API 类。

                         
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
require([
  "geoscene/Color",
  "geoscene/symbols/SimpleLineSymbol",
  "geoscene/symbols/SimpleMarkerSymbol",
  "geoscene/renderers/SimpleRenderer",
  "geoscene/layers/FeatureLayer",
], (
  Color, SimpleLineSymbol, SimpleMarkerSymbol, SimpleRenderer, FeatureLayer
) => {
r
  const layer = new FeatureLayer({
    url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/WorldCities/FeatureServer/0",
    renderer: new SimpleRenderer({
      symbol: new SimpleMarkerSymbol({
        style: "diamond",
        color: new Color([255, 128, 45]),
        outline: new SimpleLineSymbol({
          style: "dash-dot",
          color: new Color([0, 0, 0])
        })
      })
    })
  });

});

通过自动转换,您不必导入渲染器和符号类;您需要导入的唯一模块是 geoscene/layers/FeatureLayer

                  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require([ "geoscene/layers/FeatureLayer" ], (FeatureLayer) => {

  const layer = new FeatureLayer({
    url: "https://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/WorldCities/FeatureServer/0",
    renderer: {                        // 自动转换为新 SimpleRenderer()
      symbol: {                        // 自动转换为新 SimpleMarkerSymbol()
        type: "simple-marker",
        style: "diamond",
        color: [ 255, 128, 45 ],       // 自动转换为新 Color()
        outline: {                     // 自动转换为新 SimpleLineSymbol()
          style: "dash-dot",
          color: [ 0, 0, 0 ]           // 自动转换为新 Color()
        }
      }
    }
  });

});

要了解类是否可以自动转换,请查看每个类的 GeoScene API for JavaScript 参考。如果属性可以自动转换,则将显示下图:

autocast label

例如,FeatureLayer 类的属性 renderer 的文档具有 autocast 标记。

请注意,使用自动转换的代码更简单,并且在功能上与上述代码片段相同,其中所有模块都显式导入。GeoScene API for JavaScript 将使用传递给构造函数中属性的值,并在内部实例化类型对象。

请记住,没有必要在模块类型已知或固定的属性上指定 type。例如,查看上面代码片段中 SimpleMarkerSymbol 类中的 outline 属性。它没有 type 属性,因为唯一具有 outline 属性的 Symbol 子类是 SimpleLineSymbol

        
1
2
3
4
5
6
7
8
const diamondSymbol = {
  type: "simple-marker",
  outline: {
    type: "simple-line", // 不需要,因类型 `simple-line` 是隐含的
    style: "dash-dot",
    color: [ 255, 128, 45 ]
  }
};

type 更通用的情况下,例如 FeatureLayer.renderer,则必须始终指定 type 以便自动转换正常工作。

所有代码示例均记录了是否正在自动转换类或属性。

异步数据

本部分介绍 GeoScene API for JavaScript 中的 JavaScript PromisesLoading 模式。

Promises

Promises 在 GeoScene API for JavaScript 中扮演着重要角色。使用 promises 允许在使用异步操作时编写更干净的代码。

什么是 Promise?

在最基本层面上,promise 是从异步任务返回的未来值的表示形式。当任务执行时,promise 允许其他进程同时运行,同时等待返回将来的值。这在发出多个网络请求时特别有用,因为时间和下载速度可能无法预测。

Promise 始终处于以下三种状态之一:

  • 等待中
  • 已完成
  • 已拒绝

解析 promise 时,它可以解析为 callback 函数中定义的值或另一 promise。当 promise 被拒绝时,应在 errCallback 函数中处理。

使用 Promises

Promises 通常与 then() 一起使用。这是一个功能强大的方法,它定义在实现 promise 时调用的回调函数,以及在 promise 被拒绝时调用的错误函数。第一个参数始终是成功回调,第二个可选参数是错误回调。

 
1
someAsyncFunction().then(callback, errorCallback);

一旦 promise 被解析,即会调用 callback;如果 promise 被拒绝,则会调用 errCallback

      
1
2
3
4
5
6
someAsyncFunction()
  .then((resolvedVal) => {
    console.log(resolvedVal);
  }, (error) => {
    console.error(error);
  });

catch() 方法可用于为 promise 或 promises 链指定错误回调函数。

      
1
2
3
4
5
6
someAsyncFunction()
  .then((resolvedVal) => {
    console.log(resolvedVal);
  }).catch((error) => {
    console.error(error);
  });

有关错误处理的详细信息,请参阅 GeoScene 错误

示例:GeometryService

在此示例中,geometryService 用于将多个点几何投影到新的空间参考。在 geometryService.project 文档中,请注意,project() 返回一个解析为投影几何数组的 promise。

                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require([
  "geoscene/rest/geometryService",
  "geoscene/rest/support/ProjectParameters",
  ], (geometryService, ProjectParameters) => {

    const geoService = "https://sampleserver6.geosceneonline.cn/arcgis/rest/services/Utilities/Geometry/GeometryServer";

    const projectParams = new ProjectParameters({
      geometries: [points],            // 假设这些是在别处定义的
      outSR: outSR,
      transformation = transformation
    });

    geometryService.project(geoService, projectParams)
      .then((projectedGeoms) => {
       console.log("projected points: ", projectedGeoms);
      }, (error) => {
        console.error(error);
      });
});

链式 promises

使用 promises 的优点之一是利用 then() 将多个 promises 链接在一起。

当图层或图形必须创建后才能显示时,这可能很有用。

当多个 promise 链接在一起时,请记住,promise 的解析值将传递给下一个 promise。这允许执行代码块序列,而不必在彼此之间嵌套回调。请务必注意,回调函数必须使用 return 关键字将解析值返回到下一个 promise。

有关链接 promise 的示例,请参阅链式 Promises 示例

使用以上 geoService 和 projectParams 代码,下面是将 promises 链接在一起的示例:

                                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
  const bufferLayer = new GraphicsLayer();

  function bufferPoints(points) {
    return geometryEngine.geodesicBuffer(points, 1000, "feet");
  }

  function addGraphicsToBufferLayer(buffers) {
    buffers.forEach((buffer) => {
      bufferLayer.add(new Graphic(buffer));
    });
    return buffers;
  }

  function calculateArea(buffer) {
    return geometryEngine.geodesicArea(buffer, "square-feet");
  }

  function calculateAreas(buffers) {
    return buffers.map(calculateArea);
  }

  function sumArea(areas) {
    for (let i = 0, total = 0; i < areas.length; i++) {
      total += areas[i];
    }
    return total;
  }

  geoService.project(projectParams)
    .then(bufferPoints)
    .then(addGraphicsToBufferLayer)
    .then(calculateAreas)
    .then(sumArea)
    .catch((error) => {
      console.error("One of the promises in the chain was rejected! Message:", error);
    });

附加资源

MDN Promise 文档中阅读有关 promises 的更多信息,以更深入地了解其结构和用法。

以下是指向博客的其他链接,这些博客通过其他有用示例解释了 promises:

可加载

图层、地图和门户项目等资源通常依赖于远程服务或磁盘上的数据集来初始化其状态。访问此类数据需要资源异步初始化其状态。可加载设计模式统一了此行为,采用此模式的资源称为“可加载”。

可加载资源处理并发和重复的请求,以允许在应用程序的各个部分之间共享相同的资源实例。此模式允许取消加载资源的情况,例如当服务响应缓慢时。最后,可加载资源通过可检查和观察的显式状态提供有关其初始化状态的信息。

加载状态

可加载类上的 loadStatus 属性返回可加载资源的状态。有四种状态可供使用。

状态说明
not-loaded未要求资源加载其元数据,并且其状态未正确初始化。
loading资源正在异步加载其元数据
failed资源无法加载其元数据,并且遇到的错误可从 loadError 属性获得。
loaded资源已成功加载其元数据,并且其状态已正确初始化。

以下状态转换表示可加载资源经历的阶段。

loadable-pattern

可加载接口包括侦听器,可以轻松监视可加载资源的状态、显示进度以及在状态更改时执行操作。

加载

调用 load() 时,资源开始异步加载其元数据。

此时,加载状态从 not-loaded 更改为 loading。异步操作完成后,将调用回调。如果操作遇到错误,将填充回调中的错误参数,并将 loadStatus 设置为 failed。如果操作成功完成,则错误参数为 null,且加载状态设置为 loaded,这意味着资源已完成加载其元数据,现在已正确初始化。

很多时候,同一资源实例由应用程序的不同部分共享。例如,图例组件和 LayerList 可能有权访问同一图层,并且它们可能都希望访问图层的属性以填充其 UI。或者,可以在应用程序之间共享相同的门户实例,以在应用程序的不同部分显示用户的项目和群组。load() 支持多个“监听器”以简化此类应用程序开发。它可以并发和重复调用,但仅尝试加载元数据一次。如果在调用 load() 时,加载操作已在进行中 (loading 状态) ,则它只需借助未完成的操作,并在该操作完成时排队调用回调。

如果调用 load() 时,操作已经完成 (loaded 或 failed 状态),则会立即调用回调,并传递操作的结果(无论是成功还是失败),且状态保持不变。这样就可以安全地对可加载的资源自由调用 load(),而不必检查资源是否已加载,也不用担心每次都会发出不必要的网络请求。

如果资源加载失败,调用 load() 不会更改其状态。将会立即使用过去的加载错误调用回调。

取消加载

调用 cancelLoad() 时,资源可取消任何未完成的异步操作以加载其元数据。这会将状态从 loading 转换为 failedloadError 属性将返回反映操作已取消的信息。

应谨慎使用此方法,因为将调用该资源实例的所有排队回调,并显示一个错误,指出操作已取消。因此,当共享同一资源实例时,应用程序中的一个组件可以取消由其他组件启动的负载。

如果资源未处于 loading 状态,则 cancelLoad() 方法不执行任何操作。

级联加载依赖项

可加载资源通常依赖于加载其他可加载资源来正确初始化其状态。例如,门户项目在其父门户完成加载之前无法完成加载。在首次加载要素服务的关联要素服务之前,无法加载该要素图层。这种情况称为负载依赖关系。

在任何资源上调用的可加载操作都透明地通过其依赖关系图进行级联。这有助于简化可加载资源的使用,并将责任放在资源上,以正确地建立和管理其加载依赖项。

以下代码示例演示了此级联行为如何使代码简洁。加载地图会导致门户项目开始加载,然后开始加载其门户。无需显式加载资源。

                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const view = new MapView({
  container: "viewDiv"
});

const portal = new Portal({
  url: "https://myportal/"
});

const webmap = new WebMap({
  portalItem: {
    portal: portal,
    id: "f2e9b762544945f390ca4ac3671cfa72"
  }
});

webmap.load()
  .then(() => { view.map = webmap; })
  .catch((error) => {
    console.error("The resource failed to load: ", error);
  });

依赖项可能无法加载。某些依赖项可能很关键,例如门户项目对其门户的依赖关系。如果加载此类依赖项时失败,则该错误将出现在启动加载循环的资源上,该资源也将无法加载。其他加载依赖项可能是偶发的,例如地图对其某个业务图层的依赖关系,即使其中一个依赖项无法加载,资源也可能能够成功加载。

使用 fromJSON

许多类,包括所有 symbolsgeometriesCameraViewpointColorFeatureSet,都包含一个名为 fromJSON() 的方法。

此函数可从 GeoScene 产品生成的 JSON 创建给定类的实例。此格式的 JSON 通常是通过 REST API 从 toJSON()  方法或查询创建的。有关如何查询几何、符号、web 地图等以及如何以 JSON 进行表示的信息和示例,请参阅 GeoScene REST API 文档

以下示例演示了如何使用以前使用 REST API 从查询中检索到的 JSON 创建 SimpleMarkerSymbol

                    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require(["geoscene/symbols/SimpleMarkerSymbol"], (SimpleMarkerSymbol) => {
  // 作为从 GeoScene REST API 查询生成的 JSON 响应的 SimpleMarkerSymbol 
  const smsJson = {
    "type": "esriSMS",
    "style": "esriSMSSquare",
    "color": [ 76,115,0,255 ],
    "size": 8,
    "angle": 0,
    "xoffset": 0,
    "yoffset": 0,
    "outline":
    {
      "color": [ 152,230,0,255 ],
      "width": 1
    }
  };

  // 从 JSON 表达式创建 SimpleMarkerSymbol 
  const sms = SimpleMarkerSymbol.fromJSON(smsJson);
});

作为输入参数传递给 fromJSON() 的 JSON 对象可能看起来类似于在同一类中作为构造函数参数传递的对象。但是,这两个对象在各个方面都不同,不应进行互换。这是因为 REST API 和 GeoScene API for JavaScript 之间的值和默认测量单位不同(例如,使用 REST API 以点为单位测量符号大小,而 GeoScene API for JavaScript 使用像素)。 

类构造函数中传递的参数是一个简单的 JSON 对象。此模式应始终用于创建类的新实例,除非处理之前由 toJSON() 或 REST API 的查询生成的 JSON 。在以前使用 REST API 或其他 GeoScene 产品(例如 GeoScene Server、GeoScene Online、GeoScene Portal 等)生成 JSON 的情况下,从 JSON 对象创建类实例时,始终使用 fromJSON(),而不是构造函数。

使用 jsonUtils

在使用 fromJSON() 实例化对象时,有几个 jsonUtils 类作为方便类提供,但对象的类型未知。

这些类可用于 JSON 对象表示 REST API 中的几何、渲染器或符号,但对象类型未知的情况。例如,如果图层的渲染器来自于 REST 请求,并且不确定渲染器是否为 UniqueValueRenderer,则 require() geoscene/renderers/support/jsonUtils 类来帮助确定渲染器的类型。

                                                                  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
require([ "geoscene/renderers/support/jsonUtils",
          "geoscene/layers/FeatureLayer"
], ( rendererJsonUtils, FeatureLayer ) => {

  const rendererJSON = {               // 通过 REST 请求获取的渲染器对象
     "authoringInfo":null,
     "type":"uniqueValue",
     "field1":"CLASS",
     "field2":null,
     "field3":null,
     "expression":null,
     "fieldDelimiter":null,
     "defaultSymbol":{
        "color":[
           235,
           235,
           235,
           255
        ],
        "type":"esriSLS",
        "width":3,
        "style":"esriSLSShortDot"
     },
     "defaultLabel":"Other major roads",
     "uniqueValueInfos":[
        {
           "value":"I",
           "symbol":{
              "color":[
                 255,
                 170,
                 0,
                 255
              ],
              "type":"esriSLS",
              "width":10,
              "style":"esriSLSSolid"
           },
           "label":"Interstate"
        },
        {
           "value":"U",
           "symbol":{
              "color":[
                 223,
                 115,
                 255,
                 255
              ],
              "type":"esriSLS",
              "width":7,
              "style":"esriSLSSolid"
           },
           "label":"US Highway"
        }
     ]
  };

  // 从其 JSON 表达式创建渲染器对象
  const flRenderer = rendererJsonUtils.fromJSON(rendererJSON);

  // 在图层上设置渲染器
  const layer = new FeatureLayer({
    renderer: flRenderer
  });
});

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.