ArcGIS-JavaScript-开发示例-全-

ArcGIS JavaScript 开发示例(全)

原文:zh.annas-archive.org/md5/C5B34B58FB342061E6400E7ECE284E58

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Web 技术正在迅速变化,ArcGIS JavaScript API 也是如此。无论您的开发经验如何,ArcGIS 都提供了一种简单的方式来创建和管理地理空间应用程序。它为您提供了地图和可视化、分析、3D、数据管理以及对实时数据的支持。

本书涵盖的内容

第一章,“API 基础”,旨在为整本书涉及的主题奠定坚实的基础。本章设置了跟随进一步解释的主题所需的基本环境,以及开发专业外观代码的基础。提供了对 dojo 和 JavaScript 编码的模块化模式的介绍,以及对基本 ArcGIS 概念的解释。用户将在需要时看到有关基本概念的简要解释,包括代码片段或图表。

第二章,“图层和小部件”,涉及 API 中使用的不同类型的图层以及每种类型的理想上下文。我们还将介绍 Esri 提供的一些最常用的内置小部件,供我们在应用程序中使用。

第三章,“编写查询”,将深入研究编写不同类型的查询、检索结果并显示它。我们将开发一个野火应用程序,以了解诸如识别、查找和查询任务等查询操作类型。我们还将学习如何使用 FeatureTable 小部件显示表格信息,并使用 Infotemplates 格式化弹出内容。

第四章,“构建自定义小部件”,将解释如何将所有代码组织成模块化小部件,并在我们的应用程序中使用它。我们将讨论如何全局配置 dojo 以及如何提供国际化支持。我们将通过构建涉及使用绘图工具栏的空间查询来扩展我们在上一章中开发的野火应用程序。

第五章,“使用渲染器”,深入探讨了颜色、符号、渲染器以及每种情况下如何有效使用它们的主题。本章还将处理数据可视化技术的微妙之处,以及创建符号和图片标记符号的技巧和窍门。我们将通过开发一个流量计应用程序来演示三种基本渲染器的效用:简单渲染器、唯一值渲染器和类别断点渲染器。

第六章,“处理实时数据”,将详细介绍什么构成实时数据,还将介绍如何可视化数据并获取最新更新的数据。我们将构建一个飓风追踪应用程序来演示这一点,并将利用 API 提供的几何引擎功能和现代浏览器提供的地理位置功能添加全球风数据仪表和天气小部件。

第七章,“地图分析和可视化技术”,将使您更接近成为地图数据科学家。本章将涵盖很多内容,从一些基础统计概念的复习开始。我们将看到代码的实际运行,并了解统计定义和要素图层统计模块如何为我们提供宝贵的统计量,这些统计量可以用于有意义地呈现地图数据。然后,我们将评估如何在渲染器中有效使用视觉变量,如 colorInfo、opacityInfo、rotationInfo 和 sizeInfo。我们将利用所学知识开始构建一个人口统计分析门户。

第八章,“高级地图可视化和图表库”,将使用三种不同的图表库,如 dojo、D3.js 和 Cedar,来扩展我们在上一章开始构建的人口统计门户,并为用户提供更多的视觉分析信息。

第九章,“使用时间感知图层进行可视化”,将解释如何使用 TimeSlider dijit 来可视化时空数据,以及如何通过将其纳入时间感知的美国干旱数据来使用自定义的 D3.js timeslider 和自定义的时间序列直方图。

本书所需的内容

对于本书,我们需要 NotePad++/Brackets 编辑器,Google Chrome/Mozilla Firefox 或任何现代浏览器,Visual Studio Community Edition 2015 和 Node.js for Windows。

本书适合谁

本书是为希望使用 ArcGIS JavaScript API 开发出色的地图应用程序的 JavaScript 开发人员而编写的,但更重要的是,空间思维将帮助用户走得更远。

约定

在本书中,您会发现许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄显示如下:“安装 IIS 后,您可以在Program Files文件夹内的IIS Express文件夹中找到可执行文件”

代码块设置如下:

<link rel="stylesheet" href="http://js.arcgis.com/3.15/esri/css/esri.css">

<script src="http://js.arcgis.com/3.15/"></script>

当我们希望引起您对代码块的特定部分的注意时,相关行或项会以粗体显示:

**on(map, "layers-add-result", function (evt) {**
console.log("1.", earthQuakeLayer.id);
...
console.log("5.", worldCities.layerInfos);
});

任何命令行输入或输出都是这样写的:

**1\. Earthquake Layer**
**2\. [Object,**
**Object,**
**. . .**
**Object]**
 **3\. esriGeometryPoint**
**4\. 1000**
**5\. [Object, Object, Object]**

新术语重要单词以粗体显示。例如,在屏幕上看到的单词,比如菜单或对话框中的单词,会出现在文本中,就像这样:“单击 IIS Express 应用程序名称旁边的添加按钮,然后单击安装按钮。”

注意

警告或重要说明会以这样的方式出现在一个框中。

提示

提示和技巧会以这种方式出现。

第一章:API 的基础

您可能正在阅读这本书,因为您想要使用 ArcGIS JavaScript API 将空间能力集成到您的 Web 应用程序中,并使其变得更加令人惊叹,或者您希望很快成为一名 Web 地图数据科学家。无论是什么,我们都与您同在。但是在着手实际项目之前,我们不认为需要一些基础工作。本章就是关于这个的——为本书后面使用的概念奠定坚实的基础。本章在内容上设计多样,涵盖了以下主题的许多内容:

  • 使用 API 编写您的第一个地图应用程序

  • 复习坐标几何、范围和空间参考系统。

  • 介绍 dojo 和 AMD 编码模式

  • 了解 ArcGIS Server 和 REST API

  • 搭建开发环境

搭建开发环境

这本书是一本示例书,我们将通过开发的应用程序来解释概念。因此,在本章开始时,确保您的开发环境已经运行起来是至关重要的。以下部分提到的大多数环境只是我们的偏好,可能不是必须的,以实现本书提供的代码示例。所有的代码示例都针对运行在基于 Windows 的操作系统和名为Brackets集成开发环境IDE)。如果您有不同的操作系统和 IDE 选择,我们欢迎您在您最舒适的环境中开发。

浏览器、Web 服务器和 IDE

为了开发、部署和执行任何 Web 应用程序,我们需要以下组件:

  • Web 浏览器

  • Web 服务器

  • 集成开发环境(IDE)

Web 浏览器

我们在整本书中都使用了 Google Chrome,因为它提供了一些很棒的开发者工具和 HTML 检查工具。我们认为 Mozilla 也是一个很好的用于开发的浏览器。

Web 服务器

本书中开发的应用程序是使用 IIS Express 进行托管的。IIS Express 是一个轻量级的 Web 服务器,主要用于托管.NET Web 应用程序。尽管本书中的所有项目都是使用纯 HTML、CSS 和 JavaScript 开发的,但我们将使用 Esri .NET 资源代理来访问 ArcGIS 在线安全内容,并避免跨域问题。

读者可以通过安装 Web 平台安装程序或直接从 Microsoft 下载页面安装 IIS Express,如下步骤所示:

  1. 要安装 IIS Express,请访问www.microsoft.com/web/downloads/platform.aspx使用 Web 平台安装程序进行安装。

  2. 下载后,在搜索文本中搜索IIS Express。搜索结果将显示 IIS Express 应用程序。点击 IIS Express 应用程序名称后面的添加按钮,然后点击页面底部的安装按钮,如下面的屏幕截图所示:Web server

  3. 从 Web 平台安装程序安装 IIS Express 可以确保我们可以获得 IIS Express 的最新版本,而直接下载链接可能无法提供最新版本的链接。在撰写本书时,最新的 IIS Express 直接下载链接可以在www.microsoft.com/en-us/download/details.aspx?id=34679找到。

  4. 安装 IIS 后,您可以在Program Files文件夹内的IIS Express文件夹中找到可执行文件。默认位置通常是C:\Program Files\IIS Express

  5. 我们将在每个项目中提供一个可执行的批处理(.bat)文件,帮助启动 Web 服务器并将项目托管在指定的端口上。

  6. 您可以在我们为本书开发的每个项目的可执行文件中找到以下代码行:

"C:\Program Files\IIS Express\iisexpress.exe" /path:<app location>  /port:9098
  1. 前面的行将在端口9098上托管应用程序。因此,要访问该应用程序,您只需要使用 URL-http://localhost:9098/

IDE

开发 JavaScript 代码的 IDE 选择很多,有经验的开发人员已经知道他们需要使用什么。我们在整本书中都使用了 Brackets 作为我们首选的 IDE。

设置 ArcGIS 开发者帐户

在本书的一些练习中,您将需要一个 ArcGIS 开发者帐户。这也是一个让您探索 ESRI 为开发人员提供的各种功能的机会。要设置开发者帐户,只需免费注册developers.arcgis.com/en/sign-up/

你好,地图-快速启动代码

如果你和我们一样,你可能想立刻用编码的方式制作你的第一张地图。所以在这里。尝试将这些代码添加到 Brackets IDE 中的新 HTML 文件中。您还可以从代码存储库下载名为B04959_01_CODE01的 HTML 源代码,并双击 HTML 文件来运行它。

Hello, Map – the jump-start code

观察前面的代码行时,您可能已经注意到了这两件事:

  • 我们不需要任何许可证、身份验证或密钥来运行此代码。换句话说,API 是免费的。您只需要使用 CDN 链接。

  • 我们将在浏览器中看到这张美丽的制图地图,如下面的截图所示:Hello, Map – the jump-start code

  • 我们鼓励您缩放或平移到您想要查看地图的位置。如果您还没有弄清楚如何缩放/平移地图,我们将立即处理:

单击并拖动或按任何箭头键会导致平移,但不会改变详细级别。

Shift +左键拖动、鼠标滚动、双击或单击地图上的+或*- *按钮会导致缩放和显示的详细级别发生变化。

注意

还有其他方法可以实现缩放/平移功能。这里提到的方法只是为了获得初步的理解。

理解快速启动代码

让我们试着理解刚才看到的代码。这段代码中有三个概念我们想解释。第一个涉及 API 的引用链接或我们用来下载 ArcGIS JavaScript API(v 3.15)及其相关样式表的内容交付网络CDN)。第二个概念试图向您介绍所采用的编码模式,即异步模块定义AMD)模式。这是最新版本的 dojo(v1.10)所使用的。下一个概念是关于您在浏览器中运行代码时看到的内容-地图和我们提供给它的参数。

API 参考链接

首先要做的是引用 API 来开发基于 ArcGIS JavaScript API 的应用程序。Esri 是拥有该 API 的组织,但该 API 是免费的,可供公众使用。截至 2016 年 3 月,API 的最新版本是 3.15,相应的 dojo 工具包版本是 1.10。

以下库可能是您可能需要引用的唯一库,以使用 ArcGIS JavaScript API 的功能以及许多 dojo 工具包,如core dojodijitdgrid等:

<link rel="stylesheet" href="http://js.arcgis.com/3.15/esri/css/esri.css">

<script src="http://js.arcgis.com/3.15/"></script>

请参阅此链接,以获取 ArcGIS JavaScript API 的完整文档-developers.arcgis.com/javascript/jsapi/

当您访问上述 URL 时,您将看到一个网页,提供了 API 的完整文档,包括API 参考指南示例代码论坛主页等多个选项卡。

提示

下载示例代码

您可以从www.packtpub.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,以便直接通过电子邮件接收文件。

你可以按照以下步骤下载代码文件:

  • 使用你的电子邮件地址和密码登录或注册到我们的网站。

  • 将鼠标指针悬停在顶部的支持选项卡上。

  • 点击代码下载和勘误

  • 搜索框中输入书名。

  • 选择你要下载代码文件的书籍。

  • 从下拉菜单中选择你购买这本书的地方。

  • 点击代码下载

你也可以通过点击 Packt Publishing 网站上书籍页面上的代码文件按钮来下载代码文件。可以通过在搜索框中输入书名来访问该页面。请注意,你需要登录到你的 Packt 账户。

文件下载后,请确保使用最新版本的解压缩软件解压文件夹:

  • Windows 上的 WinRAR / 7-Zip

  • Mac 上的 Zipeg / iZip / UnRarX

  • Linux 上的 7-Zip / PeaZip

API 参考列出了 API 下所有模块的详细信息、属性、方法和可用的事件。左侧窗格将大多数模块分组以便参考。例如,名为esri/layers的分组有多个从中继承的模块。以下截图显示了从esri/layers继承的不同模块的分组情况:

API 参考链接

指南部分提供了重要主题的详细说明,比如使用查询任务使用 ArcGIS 在线小部件,以及使用符号和渲染器。以下截图显示了设置地图范围的详细指南:

API 参考链接

示例代码选项卡是另一个有用的部分,其中包含数百个示例应用程序,用于演示 API 中的不同概念。这些示例代码最好的部分是它们带有一个沙盒设施,你可以用来通过修改代码来玩耍。

论坛选项卡会将你重定向到以下网址—geonet.esri.com/community/developers/web-developers/arcgis-api-for-javascript

GeoNet 社区论坛是一个很好的地方,可以在那里提出问题并分享解决方案,与像你一样的开发者交流。

由于它与 dojo 框架的紧密集成,需要对 dojo 工具包有一定的了解,其参考文档可以在dojotoolkit.org/reference-guide/1.10/上访问。

编码的 AMD 模式

如果你观察了代码结构,它可能如下所示:

编码的 AMD 模式

如果你不熟悉 JavaScript 编码的这种模式,它被称为 AMD 编码模式,ArcGIS JavaScript API 强调使用这种编码模式。在最初的章节中,我们将介绍很多关于这个的内容,以便熟悉 dojo 和 AMD。从代码结构中,你可能已经了解到代码需要某些模块,加载这些模块的函数要求它们按照相同的顺序。在我们的情况下,一些模块是 Esri 模块(esri/..)和 dojo 模块(dojo/..)。如果你想知道是否可以需要自定义定义的模块,答案绝对是肯定的,这将是我们在本书中的主要部分。

esri/map 模块

代码中的高亮行构成了我们快速启动代码的核心:

 **var map = new Map("mapDiv", {**
 **basemap: "national-geographic"**
 **});**

map模块接受两个参数。第一个参数是包含map对象的div容器。第二个参数是一个可选对象,它接受许多属性,可以用来设置地图的属性。

在我们的快速入门代码中,可选对象中的basemap属性设置了 Esri 提供的基础地图代码之一,名为national-geographic,用作背景地图显示。我们建议您尝试使用其他 Esri 提供的基础地图,例如以下内容:

  • 卫星

  • 深灰色

  • 浅灰色

  • 混合

  • 拓扑

设置初始地图范围

有时,当应用程序打开时,您可能希望将其缩放到特定感兴趣的区域,而不是首先以世界范围显示地图,然后再缩放到您想要查看的区域。为了实现这一点,地图模块提供了一个属性来设置其初始范围,并在任何时候以编程方式更改其范围。

在此之前,让我们看看在地图的上下文中范围是什么。

注意

范围是包围地图上感兴趣区域的最小边界矩形。

刷新一些坐标几何

要了解范围,了解坐标几何将有所帮助。我们将把黄色的线段称为折线。蓝色线代表多边形(在我们的例子中是矩形):

刷新一些坐标几何

现在,试着观察前述图表和以下图表之间的差异。

刷新一些坐标几何

以下是关于前述图表的一些说明:

  • 点由一对坐标表示;图 1 中为(2,2),图 2 中为(-1,-1)

  • 折线由一系列坐标表示

  • 多边形也由一系列坐标表示,类似于折线

除了坐标和坐标轴之外,你可能已经发现两个图形的形状是相同的。这可能意味着两件事:

  • 图表的x位置向左移动了 3 个单位,y位置向下移动了 3 个单位

  • 或者可能意味着原点将其xy位置移动了-3 个单位

第二种可能对我们来说更重要,因为它意味着图表的实际位置没有改变,只是原点或坐标轴改变了位置。因此,相对于坐标轴,图表形状(矩形、点和线)的坐标也发生了变化。

注意

相同的形状可以根据参考坐标系统的不同具有不同的坐标。在 GIS 的上下文中,这种坐标系统称为空间参考

测验时间!

让我们测试一下我们的知识。尝试解决以下测验:

Q1. 如果原点(矩形的左下角)是(100000,100000),点(三角形符号)的坐标将是什么?

Q2. 由于多边形和折线都由一系列坐标表示,我们如何得出结论,给定一系列坐标,形状是多边形还是折线?

Q3. 需要多少个坐标来表示一个矩形?

思考一下,我们很快就会给出答案。

空间参考系统

在数字屏幕上显示世界或世界的一部分作为地图时,我们需要使用空间参考系统来识别地图上位置的坐标,这是一个与我们的图表一样的二维表面。有许多标准的空间参考系统在使用中。我们需要知道的最基本的是,每个参考系统都有一个唯一的识别号,被 API 所识别。用于定义特定空间参考系统的完整参数(如使用的基准、原点坐标、使用的测量单位等)也可以用来识别特定的空间参考系统。

注意

用于识别 SRS 的唯一 ID 称为Well-known IDwkid)。

列出用于定义空间参考系统的参数的字符串称为Well-known Textwkt)。

正如你可能预料的那样,每个空间参考系统都与不同的测量系统相关联,如英尺、米或十进制度。

例如,4326是全球坐标系统WGS 84的 wkid。该参考系统的测量单位是十进制度。

102100是另一个全局坐标系统的 wkid,其测量单位为米。

以下 URL 提供了一系列 wkid 和对应的 wkt,网址为developers.arcgis.com/javascript/jshelp/pcs.htmldevelopers.arcgis.com/javascript/jshelp/gcs.html

测验结果

A 1. (100002,100002)—相对于原点,该点在正 x 方向上离开 2 个单位,在正 y 方向上离开 2 个单位。

A 2. 除非在几何对象中明确提到,否则坐标序列可以是折线或多边形。但是多边形具有一个使它与折线不同的属性——第一个和最后一个坐标必须相同。折线可以具有相同的第一个和最后一个坐标,但并非所有折线都满足这一标准。

A 3. 如果你的答案是 4,那太棒了!但如果你的答案是 2,那你太棒了。

是的。只需要两个坐标就足以定义矩形,这要归功于它的垂直性质。这两个坐标可以是任意一对对角坐标,但为了 API 的方便起见,我们将采用左下角坐标和右上角坐标。左下角坐标在 4 个坐标值对中具有最小的xy坐标值,而右上角坐标具有最大的xy坐标值:

测验结果

获取当前地图范围

将地图缩放到您想要设置为地图初始范围的范围。在快速启动代码中,地图变量是一个全局对象,因为它是在require函数之外声明的。

<script>
    var map; //Global variable
    require([
      "esri/map"
    ],
      function (
        Map
      ) {
        map = new Map("myMap", {
          basemap: "national-geographic"
        });
});
});
</script>

这意味着我们可以在浏览器控制台中访问地图的属性。在缩放地图和所需的范围作为地图初始范围之后,使用Ctrl + Shift + I命令(在 Chrome 中)打开开发者工具。在 JavaScript 浏览器控制台中,尝试访问地图属性,getMaxScale()getMinZoom()getMinScale()getMaxZoom()getScale()extent

获取当前地图范围

比例实际上是地图测量从现实世界测量中缩小的因素。最大比例显示地图上的最大细节,而地图的最小比例显示最少的细节。map.getMaxScale()的值小于map.getMinScale()的值,因为比例值代表倒数。因此1/591657527 < 1/9027(在我们的实例中分别为 1/9027.977411 和 1/591657527.59…)。

另一方面,缩放级别是地图显示的离散缩放级别。大多数涉及 Basemaps 或 Tiledmaps(将在后面的章节中讨论)的地图只能在特定的缩放级别(称为缩放级别)上显示。最小缩放级别通常为0,并与地图的最大比例相关联。

map.getScale()给出了当前的比例,map.extent给出了地图的当前范围。我们可以使用这个extent对象来使用地图的setExtent()方法设置地图的范围。参考map模块的 API 文档,并导航到地图的setExtent方法。setExtent()方法接受两个参数——Extent对象和一个可选的 fit 对象。当我们点击文档中提供的超链接Extent对象时,它会将我们重定向到Extent模块的 API 文档页面:

获取当前地图范围

Extent的构造函数接受一个 JSON 对象并将其转换为范围对象。我们可以从地图范围的 JSON 字符串中获取这个 JSON 对象:

获取当前地图范围

前面的图像向我们展示了地图的范围的 JSON 字符串,我们已经放大到了地图的范围。以下的截图显示了坐标与我们打算放大的地图区域的关系(用矩形标出):

获取当前地图范围

现在,我们可以复制 JSON 对象,创建一个Extent对象,并将其分配给地图的setExtent方法。但在此之前,我们需要导入Extent模块(esri/geometry/Extent)。以下截图解释了如何实现这一点。

获取当前地图范围

现在当我们刷新地图时,地图将自动放大到我们设置的范围。

加载模块的模板生成器

在之前的代码中,我们成功设置了地图的初始范围,我们需要使用两个模块:esri/mapesri/geometry/Extent。随着应用程序的增长,我们可能需要添加更多的模块来为应用程序添加额外的功能。对于新手用户来说,从 API 中找到模块名称并将其整合到应用程序中可能会很麻烦。可以通过一个网页应用程序模板生成器来简化这个过程,该生成器可以在swingley.github.io/arg/找到。

以下是应用程序的截图:

加载模块的模板生成器

我们require函数所需的模块可以在应用程序顶部提供的文本框中输入。有两个多选列表框:一个列出 Esri 模块,另一个列出 dojo 模块。一旦我们开始输入我们应用程序所需的模块名称,列表就会显示与我们输入的名称匹配的建议模块。一旦我们从任一列表框中选择所需的模块,它就会被添加到require函数的模块列表中,并且适当的别名会作为参数添加到回调函数中。一旦选择了所有所需的模块,我们就可以使用右侧生成的基本模板。要设置地图的初始范围,可以通过搜索以下名称来加载所需的模块:

  • 地图(esri/map

  • 范围(esri/geometry/Extent

理解 dojo 和 AMD

顾名思义,AMD 编码模式依赖于将 JavaScript 代码模块化。有很多原因你可能需要开始编写模块化代码或模块:

  • 模块是为了单一目的而编写的,而且专注于此

  • 因此模块是可重用的

  • 模块具有更清晰的全局范围

虽然有许多编写模块化 JavaScript 的格式,比如 CommonJS 和 ES Harmony,但我们只会处理 AMD,因为最新版本的 ArcGIS JavaScript API 和基于其上的 dojo 工具包使用 AMD 风格的编码。Dojo 加载器解析依赖项并在运行应用程序时异步加载模块。

AMD 的关键组件

在本节中,我们将看一下definerequire方法,这是 AMD 的关键组件。

定义方法

define方法定义了一个模块。一个模块可以有自己的私有变量和函数,只有被define函数返回的变量和函数才会被导入该模块的其他函数所暴露。define方法的一个示例如下:

定义方法

请注意我们代码示例中的以下内容:

  • define方法中的第一个参数是模块名称或 ID。这是可选的。dojoGreeting是我们模块的名称。

  • 第二个参数是我们模块的依赖项数组。对于这个模块,我们不需要任何依赖项,所以我们只传递一个空数组。

  • 第三个参数是一个回调函数,接受我们可能已加载的依赖项的任何别名。请注意,用作函数参数的别名应该与在依赖数组中定义的顺序相同。由于我们没有使用任何依赖项,所以我们不会将任何东西传递给这个回调函数。

  • 在回调函数中,我们可以有许多私有作用域的变量和函数。我们想要从该模块中公开的任何变量或函数都应该包含在定义函数内的return语句中。

  • 在我们的示例中,_dojoGreeting是一个由define方法返回的私有作用域变量。

require 方法

require方法使用自定义定义的模块或在外部库中定义的模块。让我们使用刚刚定义的模块和require方法:

require 方法

就是这样。请密切关注require方法的参数:

  • 第一个参数是模块依赖项的数组。第一个模块依赖项是我们刚刚定义的自定义模块dojoGreeting

  • dojo/dom模块让我们可以与 HTML 中的dom元素交互。

  • dojo/domReady!是一个 AMD 插件,它会等待 DOM 加载完成后再返回。请注意,该插件在末尾使用了特殊字符“!”。在回调函数中,我们不需要分配别名,因为它的返回值是无意义的。因此,这应该是依赖数组中使用的最后一个模块之一。

  • 回调函数使用dojoGreetingdom作为dojoGreetingdojo/dom模块的别名。如前所述,我们不需要为dojo/domReady!使用别名。

  • dom模块的byId()方法通过其 ID 返回一个dom节点的引用。它与document.getElementById()非常相似,只是dom.byId()可以在所有浏览器中使用。

  • 在我们的 register 方法中,我们假设有一个 ID 为greetingdiv元素。

一些很棒的 dojo 模块

你已经了解了两个 dojo 模块,即dojo/domdojo/domReady。现在,是时候熟悉一些其他很棒的dojo模块了,你应该尽可能在编写 ArcGIS JS API 应用程序时使用它们。坚持使用纯 dojo 和 Esri JS 模块将在代码完整性和跨浏览器统一性方面产生巨大的回报。更重要的是,Dojo 在常用的 JavaScript 功能方面为你带来了一些惊喜,其中一些我们将很快介绍。

Dojo dom 模块

你已经使用了dojo/dom模块。但是还有其他的 dojo dom模块,它们可以让你操作和处理dom节点:

  • dojo/dom-attr:这是与dom属性相关的首选模块:

  • 模块中的has()方法检查给定节点中是否存在属性

  • get()方法返回请求属性的值,如果该属性没有指定或默认值,则返回 null

  • 正如你可能已经猜到的,有一个set()方法,你可以用它来设置属性的值

  • dojo/dom-class:该模块提供了与dom节点关联的 CSS 类的大部分操作

  • dojo/dom-constructdojo/dom-construct模块让你可以轻松构建dom元素

Dojo 事件处理程序模块

dojo/on模块是一个由大多数浏览器支持的事件处理程序模块。dojo/on模块可以处理大多数类型对象的事件。

Dojo 数组模块

你应该优先使用 dojo 的数组模块,而不是原生 JavaScript 数组函数,原因有很多。Dojo 的数组模块名为dojo/_base/array

dojo/_base/array

正如您所期望的那样,作为数组模块的一部分,有一个称为forEach()的迭代器方法,以及indexOf()lastIndexOf()方法。现在来看最好的部分。有一个filter()方法,它返回一个根据特定条件过滤的数组。我们认为map()方法是一个宝石,因为它不仅遍历数组,还允许我们修改回调函数中的项目并返回修改后的数组。您是否曾想过检查数组的每个元素或至少一个元素是否满足特定条件?请查看此模块中的every()some()方法。

这个示例代码解释了 dojo 数组模块的两个主要方法:

dojo/_base/array

上述代码将以下内容打印到浏览器的控制台窗口中:

Day #1 is Monday
Day #2 is Tuesday
Day #3 is Wednesday
Day #4 is Thursday
Day #5 is Friday
Day #6 is Saturday
Day #7 is Sunday

了解 ArcGIS 服务器和 REST API

ArcGIS 服务器是 Esri 公司的产品,通过在网上共享地理空间数据来实现 WebGIS。我们的 JavaScript API 能够通过 REST API 消耗这个服务器提供的许多服务。这意味着 ArcGIS 服务器提供的所有这些服务都可以通过 URL 访问。现在,让我们看看 REST API 接口对开发人员有多么有帮助。

服务类型

当您运行本书中提供的第一个代码时,在网页上看到了一个地图。您在浏览器中看到的地图实际上是一组拼接在一起的图像。如果您在加载地图时观察了开发者工具中的网络选项卡,您会意识到这一点。这些单独的图像被称为瓦片。这些瓦片也是由 ArcGIS MAP 服务器提供的。以下是一个这样的瓦片的 URL:server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/2/1/2

这意味着通过 ArcGIS 服务器发布并可通过 API 访问的任何资源都是通过 URL,如下面的屏幕截图所示:

服务类型

注意

ArcGIS 服务端点的格式将是:<ArcGIS 服务器名称>/ArcGIS/rest/services/<文件夹名称>/<服务类型>

ArcGIS 服务器提供了一个用户界面来查看这些 REST 端点。这个界面通常被称为服务目录

在计划使用特定的 GIS 服务之前,开发人员需要查看服务目录。服务目录支持多种格式,如 JSON 和 HTML,HTML 是默认格式。如果您无法查看服务目录,您需要联系您的 GIS 管理员,以启用您感兴趣的服务的服务浏览功能。

使用服务目录

让我们探索 Esri 提供的一个名为sampleserver3.arcgisonline.com的示例 GIS 服务器。

要查看任何 GIS 服务器的服务目录,语法是<GIS 服务器名称>/ArcGIS/rest/services

因此,我们需要导航到的 URL 是:sampleserver3.arcgisonline.com/ArcGIS/rest/services

您将在浏览器中看到这个屏幕:

使用服务目录

服务目录中感兴趣的项目是文件夹标题标签下的链接列表和服务标题标签下的链接列表。我们鼓励您导航到这些链接中的每一个,并查看它们公开的服务类型。您将找到以下类型的服务:

  • MapServer:这个服务地理空间数据

  • FeatureServer:这使得编辑功能成为可能

  • ImageServer:这个服务图像瓦片

我们有没有提到服务目录支持多种格式,比如 JSON?我们鼓励您在 URL 的末尾添加一个查询字符串参数,比如?f=json。要将服务目录视为 HTML,只需从 URL 中删除查询字符串参数。

地图服务器

地图服务器将 GIS 数据公开为 REST 端点。

让我们更多地了解名为Parcels的特定地图服务器,位于BloomfieldHillsMichigan文件夹中。导航到此 URL:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer

以下标题标签对我们特别感兴趣:图层、表和描述。现在,让我们更深入地研究地图服务器中的一个图层。这三个图层都值得浏览。为了解释起见,让我们选择第一个图层(图层 ID:0),可以直接导航到此 URL:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0

此 URL 中列出的所有标题标签都值得思考。我们将讨论其中一些:

  • Geometry Type描述了特定图层的几何类型。在我们调查的 URL 中,它被命名为'esriGeometryPoint',这意味着它是一个点要素。

  • 元数据,如'Description''Copyright Text'

  • 关于数据的地理范围的信息在标签'Extent''Spatial Reference'下。

  • Drawing Info标签定义了数据在地图上的呈现方式。

  • 'Fields'显示了我们图层的表模式。实际字段名与字段类型和字段的别名一起提到。别名和字段类型信息对于对数据执行查询是必要的。'esriFieldTypeString''esriFieldTypeSmallInteger'的字段类型表示该字段应分别被视为字符串和数字。'esriFieldTypeOID'是一种特殊类型的字段,它保存了图层中要素的唯一对象 ID。

查询端点

页面底部将有一个名为支持的操作的标题标签,列出了此层公开的各个端点的链接。可能会有一个名为查询的链接。这个链接是我们深入研究 ArcGIS Server 和 REST 端点的原因。点击链接或使用此直接 URL 导航到它:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0/query

查询端点

UI 提供了我们可以使用该特定图层(建筑足迹)进行查询的所有可能方式。查询操作似乎支持空间查询和平面表 SQL 查询。目前,让我们只讨论平面表查询。Where字段和Return Fields (Comma Separated)是处理平面表查询的字段。Where字段接受标准的 SQL where子句作为输入,Return Fields接受一个以逗号分隔的字段名称值,需要作为输出。但在开发的这个阶段,我们只是探索者,我们只需要看到这个接口返回的数据类型。将以下值输入到相应的文本框中:

  • Where: 1 = 1

  • Return Fields: *

点击查询(GET)按钮并滚动到屏幕底部。

查询实际上返回了来自数据库的所有字段的所有图层数据,但 ArcGIS Server 将结果限制为 1000 个要素。请注意浏览器的 URL 已更改。以下 URL 是用于触发此查询的 REST GET 请求 URL:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0/query?text=&geometry=&geometryType=esriGeometryPoint&inSR=&spatialRel=esriSpatialRelIntersects&relationParam=&objectIds=&where=1%3D1&time=&returnIdsOnly=false&returnGeometry=true&maxAllowableOffset=&outSR=&outFields=*&f=html

以下 URL 从前述 URL 中删除所有可选和未定义的查询参数,也将产生相同的结果:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0/query?where=1%3D1&outFields=*&f=html

现在让我们通过缩小Where子句来更详细地分析结果数据。注意结果中第一个要素的OBJECTID字段:

  1. 删除Where子句文本框中的值。

  2. 在对象 ID 文本框中输入所注意的OBJECTID。我们注意到的对象 ID 是5991(但您也可以选择任何一个)。

  3. 有一个名为格式的下拉菜单。选择名为'json'的下拉值

  4. 点击查询(GET)按钮。

或者,这是实现相同操作的直接 URL:sampleserver3.arcgisonline.com/ArcGIS/rest/services/BloomfieldHillsMichigan/Parcels/MapServer/0/query?objectIds=5991outFields=*&f=pjson

现在,结果看起来非常详细。我们正在查看的是单个要素的数据。JSON 返回了几个特征键值对,其中包括displayFieldNamefieldAliasesgeometryTypespatialReferencefieldsfeatures等键。

让我们看看feature键值对。features键的值是对象数组。每个对象都有名为attributesgeometry的键。属性保存对象的值,列出字段名称和其值的键值。在我们的案例中,PARCELID是字段名,"1916101009"是它的值:

查询端点

几何对象表示具有环对象数组的多边形要素。每个环都是浮点数数组。我们之前处理多边形时只是一个坐标数组。但是 ArcGIS Server 将多边形视为环的数组。要理解环的概念,请查看以下插图:

查询端点

在前面的插图中,我们处理了两个不相交的多边形,但在现实世界中被视为单个单位,比如房屋和车库。ArcGIS 用两个环表示多边形要素。第一个环由坐标组成,称为[[x1, y1], [x2, y2],…[x6,y6]],第二个环由坐标组成,称为[[x7,y7],..[x10, y10]]。

总结

我们使用了 ArcGIS JS API 的 CDN 来访问 API,并尝试理解地图和 Esri 几何模块。我们试图通过复习坐标几何知识来更好地理解范围和空间参考。我们现在知道,范围只是一个可以使用两个坐标定义的最小边界矩形,空间参考系统类似于图表上的坐标轴。我们试图查看道具工具包提供的一些令人惊叹的模块,我们必须考虑在我们的代码中使用它们。ArcGIS Server 将其 GIS 数据和其他资源公开为 REST API,也就是说,它可以作为 URL 使用。您还了解到开发人员在开始通过 API 消耗任何服务之前,必须始终参考服务目录。我们在这本书中为通过项目工作制定了开发环境的偏好。下一章将涉及 API 中使用的不同类型的图层以及每种类型使用的理想上下文。我们还将介绍 Esri 提供的一些最常用的内置小部件,并在我们的应用程序中使用它们。

第二章:图层和小部件

构成我们网络地图应用程序的两个基本组件是图层和小部件。地图对象类似于一个容纳所有图层的画布,用户可以与之交互,例如平移和缩放地图。图层主要与特定数据源相关联。小部件由 JavaScript 逻辑和 HTML 模板(如果需要用户交互)组成。小部件可以与地图交互,也可以独立运行。Esri 开发了许多通用小部件,并将其捆绑到 API 中。我们将在本书中讨论如何使用这些小部件。我们还将在下一章中看到如何开发自定义小部件。本章为开发显示历史地震数据的完整网络地图应用程序奠定了起点。随着我们在本章中的进展,我们将在以下主题中获得牢固的立足点:

  • API 支持的数据源

  • 在 API 的上下文中图层的概念

  • 图层的功能分类

  • 不同类型的图层及其属性

  • 要素图层与 DynamicMapService 与图形图层

  • 使用 Esri 的内置小部件

API 支持的数据源

ArcGIS JavaScript API 是一种功能强大且灵活的客户端地图软件,它提供了对各种空间数据源的集成支持,目前正在生产中。它还支持可视化平面文件格式,如 CSV,其中包含一些纬度和经度信息。

为了充分利用 ArcGIS JavaScript API 提供的全部功能,了解它支持的数据源列表以及其公开的属性和方法是很重要的。

截至版本 3.14,ArcGIS JavaScript API 支持的数据源可以大致分为以下几类:

  • ArcGIS Server 服务

  • 符合 OGC 标准的 GIS 服务

  • 平面文件格式

  • 自定义网络服务(最好是 REST 服务)

让我们回顾不同的数据源格式,并了解如何获取有关数据的必要信息,以在 ArcGIS JavaScript API 中使用。

平面文件格式

API 提供原生支持以渲染 KML 和 CSV 等平面文件格式。

KML

Keyhole 标记语言KML)是一种空间文件格式,最初由 Google 开发,目前由 OGC 维护。它支持点、线和多边形几何图形,甚至图像叠加。KML 是一种广为人知的 XML,以其多功能性而闻名,但它非常冗长,并且在 Google 地图中使用。KML 文件可以在任何文本编辑器中打开,如 Notepad++。

CSV 文件

CSV 文件是一种存储以逗号分隔的字段值的表格数据的纯文本文件格式。CSV 文件包含有关纬度和经度或坐标值(如XY坐标)的信息。API 可以读取 CSV 文件,并将位置信息转换为 API 上的位置。

ArcGIS Server

ArcGIS Server 可用于在 Web 上共享空间数据。在我们的情况下,如果我们有形状文件、个人地理数据库、文件地理数据库或企业地理数据库,我们可以使用 ArcGIS Server 将数据作为 REST 服务在 Web 上提供。ArcGIS JavaScript 能够消费这些服务并将其显示在地图上。在其他空间格式的情况下,例如 DWG,我们可以使用 ArcGIS 桌面或特征操作引擎FME),这是一个用于转换为 Esri 文件格式并通过 ArcGIS Server 发布的空间 ETL 工具。

图层的概念

如果你曾经学过 GIS 的入门课程,你一定熟悉 GIS 图层相互叠加的经典形象。在 API 的上下文中,图层是作为 REST 端点或 JSON 对象可用的数据资源。(没错,你可以使用 JSON 字符串构建 Web 地图图层。)我们很快就会讨论这些地图图层的来源和类型,但在此之前,让我们列出任何地图图层的最重要考虑因素:

  • 图层是任何数据源的容器对象

  • 可以使用图层对象将数据添加到地图对象中

  • 图层形成堆栈架构——添加的第一层位于底部

通常将底图图层放在底部

  • 地图对象具有一个特殊的内置图层,用于包含所有地图图形

这被称为图形图层,并且始终位于顶层

  • 所有其他功能图层都是在中间添加的

  • 图层的可见性可以随时打开或关闭

向地图添加图层

在处理不同类型的图层之前,我们将讨论如何将任何图层添加到地图对象中,因为这个过程对于任何图层类型都是相同的,而且非常简单。在下图中,我们可以看到所有类型的图层:

向地图添加图层

有两种方法可以将任何图层添加到地图对象中。假设prjMap是定义的地图对象的名称,我们需要添加一个图层;你可以采用以下两种方法之一:

  • 方法 1
//Method 1
prjMap.addLayer(layer1);
/*layer1 is the layer object we would like to add to the map. */
  • 方法 2
//Method 2
prjMap.addLayers([layer1]);

就是这么简单!第二种方法是首选方法,因为某些小部件或功能必须等到地图中的所有图层加载完毕才能使用。使用第二种方法将使我们能够使用在所有图层加载完毕后触发的事件处理程序。我们将在本章末讨论这些事件处理程序。

图层的功能分类

从功能上讲,可以将添加到地图的不同类型的图层分类如下:

  • 底图或瓦片地图图层

  • 功能图层

  • 图形图层

让我们分别讨论每一个。

底图图层

底图图层是可以用作参考背景地图的图层。通常,卫星图像、地形图(显示海拔高度的地图)或街道地图可以起到这个作用。底图通常是缓存的图像瓦片。这意味着底图是一个静态资源。由于它们是静态的,并且作为图像瓦片提供,我们无法与底图上看到的要素进行交互(例如查询或选择)。而且由于这是底图,这也是最底层的图层,也是首先添加到地图中的图层。

现在,API 提供了不同的方法来向地图添加basemap属性:

  • basemap属性添加到地图对象中:
var map = new Map("mapDiv", {basemap: "streets"});
  • 使用 API 提供的内置basemap库。

这使我们能够在多个底图之间切换,例如卫星图像、街道地图、地形图、国家地理地图、OpenStreetMaps 等。

  • 通过向地图对象添加瓦片地图图层来创建自己的底图(我们很快将讨论有关瓦片地图图层的内容)。

下载名为B04959_02_CODE_01的项目文件夹,并打开index.html以了解底图库小部件的使用方法:

底图图层

功能图层

功能图层显示所有最新更改,因此在性质上是动态的,而不同于底图或缓存瓦片图层的相对静态性质。功能图层是可以与之交互的图层。API 提供了对大多数这些图层执行不同操作的选项,例如:

  • 选择要素

  • 检索要素的属性和几何信息

  • 对数据执行查询

  • 渲染要素(使用不同的符号、颜色、宽度和其他图形属性对要素应用样式)

  • 允许对要素进行创建、更新和删除(CRUD)操作

功能图层将动态重投影,基于 Basemap 的空间参考。这意味着功能图层的空间参考系统可能与 Basemap 不同,它们仍然会与 Basemap 对齐,因为 API 将从服务器请求功能图层的重投影数据。有不同类型的功能图层,例如动态图层和要素图层,我们将很快处理。

图形图层

图形图层在操作方面具有最大的灵活性。在这里,您可以向属性对象添加尽可能多的数据。您可以分配或修改其几何(使用绘图工具栏或甚至以编程方式),添加符号,查询它(对于功能图层,查询或更新操作可能被禁用),删除它,用它来选择功能图层中的要素,或者仅将其用作标注工具。但是图形图层的寿命也是最短的,因为它在会话结束后不会持久存在-这些只是存储在客户端上。由于这些属性,将图形图层作为最顶层是有意义的,不是吗?

注意

处理图形图层时,开发人员需要注意输入数据源的空间参考。esri/geometry/webMercatorUtils是一个方便的模块,可以让我们将 Web 墨卡托坐标转换为地理坐标,反之亦然。

图层类型

我们对图层的功能分类有了一瞥。API 提供了一系列模块,用于从不同数据源加载图层,这些数据源通常属于我们研究过的功能分类之一。我们将回顾 API 提供的一些最重要的图层类型以及它公开的方法和属性。

ArcGIS Tiledmap 服务图层

这是由 ArcGIS Server 提供的缓存 Tiledmap 图层:

名称
模块名称 esri/layers/ArcGISTiledMapServiceLayer
数据源类型 ArcGIS REST Service
图层类型 BaseMap /Tiled Cache Layer
响应类型 Cached image tiles
构造函数 new ArcGISTiledMapServiceLayer(url, options?)
首选别名 ArcGISTiledMapServiceLayer

提示

首选别名

API 提供的首选别名作为代码约定的一部分,并可以在developers.arcgis.com/javascript/jsapi/argument_aliases.html上访问。

为什么我们需要使用不同的 Basemap,当我们已经有 Esri 提供的很多选项呢?嗯,我们发现了来自 NOAA 的美学和视觉信息丰富的瓦片地图服务,显示了世界地形和海底地形(海底高程差异)的彩色阴影浮雕:

ArcGIS Tiledmap 服务图层

您可以考虑将其用作 Basemap,用于显示任何全球现象,如灾害或地震。我们该如何做呢?如果您查看此模块的构造函数,它会查找一个必需的URL参数和一个可选的options参数。

我们谈论的 NOAA 服务的 URL 是maps.ngdc.noaa.gov/arcgis/rest/services/etopo1/MapServer

现在,让我们尝试将其作为ArcGISTiledMapLayer来使用(代码参考:B04959_02_CODE1.html):

require([
"esri/map",
"esri/layers/ArcGISTiledMapServiceLayer",
"dojo/domReady!"
    ], function (
            Map, ArcGISTiledMapServiceLayer
        ) {
var map = new Map("mapDiv");
vartileMap = new ArcGISTiledMapServiceLayer("http://maps.ngdc.noaa.gov/arcgis/rest/services/etopo1/MapServer");
map.addLayer(tileMap);
        });

这就是您需要编写的所有代码,以在屏幕上看到美丽的地图。

Tiledmap 服务的服务目录为我们提供了许多有用的信息,开发人员在使用应用程序中的 Tiledmap 服务之前应该考虑。让我们查看先前提到的ArcGISTiledMapServiceLayer的服务目录。在下一节提供的服务目录截图中,开发人员可以了解有关数据源的许多信息:

  • 空间参考

  • TileInfo

  • 初始范围和 FullExtent

  • 最小比例和最大比例

  • 贡献到瓦片的图层

空间参考

瓦片地图服务或底图的空间参考是开发人员在编码的初始阶段经常忽视的重要属性之一。瓦片地图服务的空间参考设置为整个地图的空间参考。添加到地图中的操作图层,如动态地图服务和要素图层,符合此空间参考,无论它们各自的空间参考是什么。

空间参考

TileInfo

TileInfo提供了有关TiledMapService遵循的平铺方案的信息。详细级别可用于设置地图的缩放范围。

范围和比例信息

范围和比例信息为我们提供了有关可见瓦片范围的信息。

从项目文件夹B04959_02_CODE_02下载完整的代码,并查看您美丽的瓦片地图的效果。

ArcGIS DynamicMapService 图层

这个模块,顾名思义,是来自 ArcGIS Server REST API 的动态托管资源:

名称
模块名称 esri/layers/ArcGISDynamicMapServiceLayer
数据源类型 ArcGIS REST 服务
图层类型 功能图层
响应类型 动态生成的图像
构造函数 new ArcGISDynamicMapServiceLayer(url, options?)

动态地图图层实际上代表了非缓存地图服务公开的所有数据。出于同样的原因,动态地图图层是一种复合图层,因为地图服务通常具有多个图层。

我们将在一会儿看到这意味着什么。我们将参考服务目录(是的,这是一个花哨的术语,用于指导我们导航到地图服务 URL 时出现的界面)。

在浏览器中打开此地图服务的 URL - maps.ngdc.noaa.gov/arcgis/rest/services/SampleWorldCities/MapServer

您将能够看到地图服务公开的所有数据图层。因此,当您使用此地图服务时,所有数据将作为单个 DynamicMapService 图层的一部分显示在地图上:

The ArcGIS DynamicMapService layer

注意

如果您无法看到先前显示的任何服务的服务目录,则并不意味着该服务已离线;可能是在生产机器上关闭了服务浏览。

确保尝试通过附加名为f值为json的查询参数来尝试 URL,例如,{{url}}?f=json

之前,我们讨论了如何将ArcGISTiledMapServiceLayer添加到地图中。以下代码在现有的瓦片图层上添加了ArcGISDynamicMapService图层:

require(["esri/map",
"esri/layers/ArcGISTiledMapServiceLayer",
"esri/layers/ArcGISDynamicMapServiceLayer",
"dojo/domReady!"
],
function (
Map,
ArcGISTiledMapServiceLayer,
ArcGISDynamicMapServiceLayer
) {
var map = new Map("mapDiv");
varshadedTiledLayer = new ArcGISTiledMapServiceLayer('http://maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/etopo1_hillshade/MapServer');

**varworldCities = new ArcGISDynamicMapServiceLayer("http://maps.ngdc.noaa.gov/arcgis/rest/services/SampleWorldCities/MapServer");**

map.addLayers([shadedTiledLayer, worldCities]);
    });

现在,如果您已经注意到,ArcGISDynamicMapServiceLayerArcGISTiledMapServiceLayer都使用地图服务。那么,我们如何知道哪个地图服务应该用作瓦片地图服务,哪个可以用作 DynamicMapService 呢?您可能已经猜到了。服务目录就是答案。服务目录中有一个特定的标题,您必须在其中查找区分缓存瓦片地图服务和非缓存地图服务的地图服务。这就是TileInfo

注意

区分缓存瓦片地图服务和非缓存地图服务的属性称为 Tile Info。

TileInfo 包含有关详细信息的信息。详细级别确定地图将显示的离散比例级别。这些详细级别也称为缩放级别,地图的缩放控件中的标记与这些缩放级别对应。

现在,瓦片地图服务和动态地图服务响应的提供方式是相似的。两者都是作为图像提供的。瓦片地图服务为每个范围提供多个图像瓦片,而动态地图服务仅为给定范围提供一个图像。

如果您注意到您的网络选项卡,将会有一个名为exportGET请求方法附加到我们声明的 DynamicMapService。这是从服务器获取动态地图图像的GET请求:

ArcGIS DynamicMapService 图层

观察前面GET请求的查询字符串中的名称-值对。您会注意到以下字段名:

  • dpi字段名定义了每英寸点数的图像分辨率

  • transparent字段名定义了响应图像是透明的,因此可以查看背景 Basemap

  • format字段名的值为png,这是响应图像的格式

  • bbox字段名的值请求图像所请求的范围(由四个坐标—XminYminXmaxYmax组成)。

  • bboxSR字段名的值定义了bbox坐标的空间参考,imageSR定义了请求响应图像的空间参考

  • 最后一个字段名为f定义了响应的格式;当然是一个image

注意

练习

在前面的GET请求中,将f字段名的值从image更改为html,然后查看您会得到什么。

如果您查看 API 页面,您会发现此模块提供了许多属性和方法。以下表格显示了一些最重要的方法:

方法名 描述
exportMapImage(imageParameters?, callback_function?) 这使用imageParameters对象指定的值导出地图。回调函数事件返回地图图像。
refresh() 这将通过向服务器发出新请求来刷新地图。
setDPI(dotsPerInch) 这使得可以设置导出地图的每英寸点数的图像分辨率。
setLayerDefinitions(stringArray of Layerdefintions) 这使我们能够过滤动态地图服务显示的数据。
setVisibleLayers(Array_of_LayerIds) 这只显示传递参数作为 ID 的图层。

现在,请确保您具备以下要求以显示 DynamicMapService:

  • 仅显示Cities图层

  • 为动态地图图像提供 0.5 的透明度

  • 仅显示人口超过 100 万的城市

以下代码片段指导您如何完成此操作(代码参考:B04959_02_CODE2.html):

varworldCities = new ArcGISDynamicMapServiceLayer("http://maps.ngdc.noaa.gov/arcgis/rest/services/SampleWorldCities/MapServer", {
"id": "worldCities",
**"opacity": 0.5,**
"showAttribution": false
});
**worldCities.setVisibleLayers([0]);**
**worldCities.setLayerDefinitions(["POP > 1000000"]);**

注意

当任何地图对象的选项对象中的showAttribution属性设置为true时,数据源的所有归因都显示在地图的右下角。

setLayerDefinitions(stringArray of Layerdefintions)方法接受where子句的字符串数组。在传递动态地图服务的图层定义时,请记住以下几点。

定义表达式(where子句)的索引应与应用表达式的图层的索引相匹配。例如,如果Cities图层在前面的地图服务中的索引为5,则图层定义将如下所示:

VarlayerDefintion = [];
**layerDefinition[5] = "POP > 1000000";**
worldCities.setLayerDefinitions(layerDefinition);

一旦满足这些条件,生成的地图将如下所示。半透明的蓝色点是人口超过一百万的世界城市:

ArcGIS DynamicMapService 图层

要素图层

要素图层是地图服务的一个单独图层,具有几何类型。地图服务中的单独图层可以是要素图层,甚至是栅格图层;例如,sampleserver4.arcgisonline.com/ArcGIS/rest/services/Elevation/ESRI_Elevation_World/MapServer/1maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/hazards/MapServer/0都是地图服务的单独图层,但前一个 URL 是栅格图层资源,后一个是要素图层资源。栅格图层在服务目录中没有几何类型属性,而要素图层具有点、多点、折线或多边形几何类型之一。

要素图层是非常灵活的实体,因为它支持高级查询、选择、渲染,有时甚至支持编辑功能。要素图层(或栅格图层)是通过它所属的地图服务中的索引来识别的:

名称
模块名称 esri/layers/FeatureLayer
数据源类型 ArcGIS REST 服务
图层类型 功能图层
响应类型 要素集合(要素具有几何、属性和符号)
构造函数 new FeatureLayer(url, options?)

将要素图层/图层添加到地图上与添加 DynamicMapService 图层或 Tiledmap 服务图层相同:

define([
"esri/map",
"esri/layers/FeatureLayer"
],
function(
Map,
FeatureLayer
){
var map = new Map("mapDiv");
var featureLayer1 = new FeatureLayer(featureLayer1URL);
var featureLayer2 = new FeatureLayer(featureLayer2URL);
**map.addLayers([featureLayer1, featureLayer2]);**
});

要素图层构造函数

FeatureLayer构造函数有两个参数——FeatureLayer URL 和一个可选的options对象。options对象提供了一堆选项来配置FeatureLayer构造函数。其中最重要的options属性之一被命名为mode

mode属性定义了要素图层在地图上的渲染方式。由于要素图层流式传输要素的实际几何,不像地图服务(提供动态生成的图像)或 Tiledmap 服务(只提供预先渲染的缓存瓦片),要素图层在地图上的渲染有一些性能考虑。要素图层可以通过四种模式进行渲染。这四种模式是 API 提供的常量的数值值。如果要素图层模块的回调函数别名是要素图层,那么可以使用以下装饰来访问这四种模式:

  • FeatureLayer.MODE_SNAPSHOT

  • 这一次性地从服务器获取所有要素并驻留在客户端上

  • 这在应用额外的过滤器时进行更新

  • FeatureLayer.MODE_ONDEMAND

  • 根据需要获取要素

  • 连续的小额开销

  • 默认MODE

  • FeatureLayer.MODE_SELECTION

  • 只有使用selectFeatures()方法选择的要素才会显示

  • FeatureLayer.MODE_AUTO

  • MODE_SNAPSHOTMODE_ONDEMAND之间切换(此选择由 API 进行)

  • 两全其美

我们将尝试为历史地震添加一个FeatureLayer构造函数到地图上。提供这些要素图层的地图服务可以在maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/hazards/MapServer找到。

地震图层是地图服务中的第五个图层。但你也可以尝试其他要素图层。以下是一个代码片段,让你将要素图层添加到地图对象中(代码参考:B04959_02_CODE3.html):

define(["esri/map",
"esri/layers/FeatureLayer",
"dojo/domReady!"],
function (Map, FeatureLayer
) {
varearthQuakeLayerURL = 'http://maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/hazards/MapServer/5';
earthQuakeLayer = new FeatureLayer(earthQuakeLayerURL, {
id: "Earthquake Layer",
outFields : ["EQ_MAGNITUDE", "INTENSITY", "COUNTRY", "LOCATION_NAME", "DAMAGE_DESCRIPTION", "DATE_STRING" ],
opacity: 0.5,
mode: FeatureLayer.MODE_ONDEMAND,
definitionExpression: "EQ_MAGNITUDE > 6",
});

map.addLayers([earthQuakeLayer]);
});

前面的代码可以解释如下:

  • id属性为要素图层分配一个 ID

  • opacity属性让我们为地图定义不透明度

  • definitionExpression属性是一个where子句,让我们过滤在地图上显示的要素

  • outFields属性让我们定义要素图层提供的字段

这是FeatureLayer叠加在 DynamicMapService 图层和 Tiledmap 服务图层上的屏幕截图。半透明的彩色圆圈代表曾经发生过任何地震的地点,其震级超过 6 里氏标度:

The FeatureLayer constructor

当您在地图上平移或缩放时,将获取要素并触发相应的GET请求,该请求将按需获取要素。如果在加载要素图层后立即打开开发者控制台中的网络选项卡,您将能够了解很多事情:

  • API 使用要素图层的query方法来获取要素。

  • 在查询字符串中,将包含查询参数,例如geometryspatialRelgeometryTypeinSR,这些参数定义了需要获取要素的范围。查询字符串中还可以找到其他FeatureLayer构造函数选项,例如outFieldswhere子句(对应于definitionExpression)。

  • 如果单击预览响应选项卡,您将注意到GET请求获取了一系列要素。每个要素都有一个属性对象和一个几何对象。属性对象将包含outFields数组中提到的字段名称和特定要素的相应字段值:The FeatureLayer constructor

我们将在下一章中讨论如何查询和选择要素图层中的要素。目前,我们最好知道以下方法对要素图层对象做了什么:

方法 描述
clear() 清除所有图形
clearSelection() 清除当前选择
getSelectedFeatures() 获取当前选择的要素
hide() 将图层的可见性设置为false
isEditable() 如果FeatureLayer可编辑,则返回true
setInfoTemplate(infoTemplate) 指定或更改图层的info模板
setOpacity(opacity) 图层的初始不透明度(其中1为不透明,0为透明)
show() 将图层的可见性设置为true

Infotemplates

Infotemplates 提供了一种简单的方式来提供 HTML 弹出窗口,显示有关要素的信息,当我们点击时。我们将在下一章中详细讨论 Infotemplates。

Infotemplates

图形图层

我们已经讨论了一些关于图形图层的内容。我们知道地图对象默认包含一个图形图层,并且可以使用地图对象的graphics属性引用它。我们还可以创建自己的图形图层并将其添加到地图中。但是,地图提供的默认图形图层始终位于顶部。

让我们更多地了解图形图层和添加到图形图层的Graphic对象。图形图层是Graphic对象的容器。

Graphic对象具有以下值:

  • 几何

  • 符号

  • 属性

  • Infotemplate

几何

几何将具有类型(点、多点、折线、多边形和范围)、空间参考和构成几何图形的坐标。

符号

符号是一个更复杂的对象,因为它与其所代表的几何图形相关联。此外,符号的样式是由用于填充符号的颜色或图片以及符号的大小来定义的。

让我们回顾一小段代码,以更好地理解这一点。这是一个为多边形构造符号的简单代码段:

Symbol

属性

图形的属性是一个键值对对象,用于存储有关图形的信息。

InfoTemplate

InfoTemplate是一个 HTML 模板,可以用来在我们点击时显示有关图形的相关信息。

地图和图层属性

图层之间有许多共同的属性,这些属性为我们提供了有关图层的相关信息。例如,fullExtentidinfoTemplatesinitialExtentlayerInfosmaxRecordCountmaxScaleminScaleopacityspatialReferenceunitsurlvisibleLayers对于动态地图图层和瓦片地图图层来说是相同的,而dynamicLayerInfoslayerDefinitions等属性则特定于 DynamicMapService 图层。那么,tileInfo属性是特定于瓦片地图图层的吗?

尝试通过将属性记录到控制台来探索这些属性。例如,如果您需要打印要素图层中的字段列表,请使用要素图层的fields属性。

以下是将有关要素图层和 DynamicMapService 图层的某些信息记录到控制台的代码片段(代码参考:B04959_02_CODE5.html):

on(map, "layers-add-result", function (evt) {
console.log("1.", earthQuakeLayer.id);
console.log("2.", earthQuakeLayer.fields);
console.log("3.", earthQuakeLayer.geometryType);
console.log("4.", earthQuakeLayer.maxRecordCount);

console.log("5.", worldCities.layerInfos);
});

以下是您将在控制台中获得的屏幕输出:

**1\. Earthquake Layer**
**2\. [Object,**
**Object,**
**. . .**
**Object]**
 **3\. esriGeometryPoint**
**4\. 1000**
**5\. [Object, Object, Object]**

Featurelayer.fields返回一个字段对象数组。每个对象包含aliaslengthnamenullabletype等属性。DynamicLayer.layerInfos返回一个layerInfo对象数组。layerInfo对象提供有关图层的信息:

地图和图层属性

地图和图层事件

改变地图的范围,向地图添加图层,向地图添加一组图层,甚至点击地图或鼠标 - API 对所有这些都有事件处理程序。在使用事件时,让我们坚持使用 dojo 的 on 模块来处理事件。找到使用 dojo 的"dojo/on"模块处理事件的原型:

地图和图层事件

目标 事件 描述
地图 extent-change 地图范围发生变化时触发
地图 layers-add-result 在使用map.addLayers()方法后,所有添加到地图的图层加载完成时触发
地图 load 这个很明显
地图 basemap-change
要素图层 selection-complete 从要素图层中选择要素后

在前面的代码片段中,记录了某些图层属性,您可能已经注意到整个代码片段都包含在一个on语句中:

**on(map, "layers-add-result", function (evt) {**
console.log("1.", earthQuakeLayer.id);
...
console.log("5.", worldCities.layerInfos);
});

我们需要在on事件中打印出所有与图层相关的属性,因为我们需要等到所有图层加载完成,否则大多数属性都将返回未定义。这个名为layers-add-result的特定事件仅在所有添加到地图的图层数组加载完成后触发。

使用 Esri 小部件 - 神灯

小部件是 dojo 的基石。小部件是可以在 dojo 中构建、配置和扩展的 UI 组件,以执行特定任务。因此,当有人为我们提供了一个完成我们需要做的任务的小部件时,我们只需稍微配置一下,然后将其提供给小部件应该驻留的容器节点引用即可实例化它。

所以,好消息是 Esri 为我们提供了内置小部件,可以完成很多事情,比如查询要素、地理编码地址(将文本地址转换为地图上的位置)、添加小部件以显示地图图例、添加小部件以搜索属性,甚至添加小部件以在多个基础地图之间切换。所有 Esri 构建的小部件都可以在 API 参考页面的目录部分中找到,位于esri/dijits下。

BaseMapGallery 小部件

嗯,你一定不会惊讶这个小部件确实存在吧?在本章开头处理TiledMapLayers时,我们已经提醒过你了。Basemap 图层小部件为我们提供了一个小部件,我们可以从基础地图库中切换基础地图。查看以下集成基础地图到我们应用程序的原型代码(代码参考:B04959_02_CODE6):

require(["esri/map",
"esri/dijit/BasemapGallery"], function (Map, BasemapGallery){
varbasemapGallery = new BasemapGallery({
showArcGISBasemaps: true,
map: map
        }, "basemapGalleryDiv");
});

图例小部件

地图图例列出了地图中的图层和所有图层使用的符号。自己构建图例涉及获取layerinfosdrawinginfos,并将它们列在div中——这个过程听起来很麻烦。幸运的是,Esri 为我们提供了dijit(可能是 dojo 和 widget 的混成词)来构建图例:

图例小部件

我们使用以下代码来启动图例小部件(代码参考:B04959_02_CODE6

require(["esri/map",
"esri/dijit/Legend"],  function (Map, Legend){
    on(map, "layers-add-result", function (evt) {
varlegendDijit = new Legend({
map: map,
            }, "legendDiv");
legendDijit.startup();
        });
});

摘要

本章我们涵盖了很多内容。我们试图确定数据添加到地图的过程。我们确定了数据源,如 ArcGIS 服务器服务、OGC 数据、CSV、KML 等。然后,我们介绍了支持显示和进一步操作三种主要 ArcGIS REST 服务数据源的 API 提供的模块,即 ArcGIS Tiledmap 服务图层、ArcGIS DynamicMapService 图层和要素图层。您还学会了如何实例化图层以及如何浏览它们的属性和事件。我们还处理了一种特殊类型的图层,即图形图层,它是地图中最顶层的图层,并且用作地图中所有图形的容器对象。我们品尝了 Esri 提供的大量内置小部件。在下一章中,我们将深入研究编写空间查询和检索结果。您还将学习如何使用几何服务和几何引擎来处理几何操作。

第三章:编写查询

"提问的艺术和科学是所有知识的源泉。"
--托马斯·伯格

查询是通过 API 向地图提出问题的门户。它们在 API 术语中被视为任务,因为形成查询并获取答案的过程是一系列必须正确执行的操作。在本章中,我们将开发一个野火位置应用程序,以了解以下概念:

  • 构建和执行查询任务

  • 构建和执行识别任务

  • 构建和执行查找任务

  • 查询、查找和识别任务的承诺、延迟和结果对象

  • 使用FeatureTable dijit

  • 使用Infotemplates

开发野火应用程序

在本章中,我们将开发一个应用程序,该应用程序将显示美国的活跃野火位置,并显示任何位置的野火潜在风险的背景地图。我们还将尝试通过利用 API 提供的组件来提供搜索/查询功能。以下屏幕截图提供了我们在本章结束时将开发的最终应用程序的大致呈现:

开发野火应用程序

该应用程序将具有以下组件:

  • 深灰色底图

  • 两个操作地图服务,一个显示美国的野火潜在风险(栅格数据),另一个显示活跃的野火位置(点数据)

  • 一个图例 dijit(dojo 小部件),显示添加到地图上的图层的符号

  • 一个报告小部件,显示所有活跃野火位置的记录

  • 一个查询小部件,您可以根据数据中的区域范围查询活跃的野火位置(此信息在数据的一个字段中可用)

  • 一个查找小部件,您可以在其中输入任何文本,所有与搜索文本匹配的州或火灾名称都将被获取

  • 一个地图点击事件,将在地图点击位置标识并显着显示野火潜在风险

  • 有两个操作数据源;一个是位于maps7.arcgisonline.com/arcgis/rest/services/USDA_USFS_2014_Wildfire_Hazard_Potential/MapServer的野火潜在风险地图服务,另一个是位于livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer的活跃野火数据

  • 后一个地图服务是一个安全地图服务,这意味着我们需要一个 ArcGIS Online 帐户或 ArcGIS 开发者帐户来使用它。除了前述数据源,要访问 ArcGIS Online 数据的大量数据以及发布在世界生活地图集(doc.arcgis.com/en/living-atlas/)中的数据,我们需要执行以下操作:

  • 在 ArcGIS 开发者门户中注册应用程序并获取应用程序的令牌

  • 在我们的应用程序中加入 ArcGIS 代理代码

在开发者门户中注册应用程序

使用我们的 ArcGIS 开发者凭据(我们在第一章的设置开发环境部分中创建的)登录到 ArcGIS 开发者门户(developers.arcgis.com/)。

接下来,通过单击以下屏幕截图中突出显示的适当图标,导航到开发者门户的应用程序页面。您甚至可以通过访问developers.arcgis.com/applications/来实现。

在开发者门户中注册应用程序

当我们点击注册新应用程序按钮时,我们将被提示输入关于我们的应用程序的详细信息,如下图所示。提供所需的详细信息后,如果我们再次点击注册新应用程序按钮,我们将被带到另一个屏幕,显示应用程序的令牌。这个短暂的令牌可以用于访问任何受 ArcGIS Online 地图服务保护的服务。例如,尝试在浏览器中访问这个—livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer

您将被重定向到一个需要您输入令牌的页面。当您提供在上一个屏幕中获得的令牌时,您可以看到我们打算查看的地图服务的服务目录。以下屏幕截图解释了这个过程:

在开发者门户中注册应用程序

在应用程序中使用代理

在这个项目中,我们需要使用 Esri 资源代理来访问安全的 ArcGIS Online 数据源。资源代理是处理来自客户端到 ArcGIS Server 的请求并将来自 ArcGIS Server 的响应转发回客户端的服务器端代码。Esri 提供了一个专门适用于 ArcGIS Server 和 ArcGIS Online 的代理实现。Github 代码可以在github.com/Esri/resource-proxy找到。

我们将只使用包含以下重要文件的资源代理的 ASP.NET 变体:

  • proxy.ashx

  • proxy.config

  • Web.config

proxy.ashx文件包含用于发出请求并将响应转发回客户端的服务器端代码逻辑。我们需要配置proxy.config并在其中包含我们的 ArcGIS 开发者凭据。以下是proxy.config页面的示例截图:

在应用程序中使用代理

要配置proxy.config文件,请执行以下步骤:

  1. proxy.config文件中,修改serverUrl标签中urlusernamepassword的属性值。对于tokenServiceUri属性,值应始终为https://www.arcgis.com/sharing/generateToken

  2. 对于url属性,值将是 ArcGIS Server 服务的位置。指定特定的 URL(在这种情况下,您将设置matchAll="false")或只是根 URL(如前面的屏幕截图所示;在这种情况下,matchAll值将是"true")。

注意

有关配置proxy.config文件的更多详细信息,请参阅github.com/Esri/resource-proxy/blob/master/README.md#proxy-configuration-settings

  1. 配置代理页面后,我们需要在应用程序中添加几行代码。我们需要加载esri/config模块,并在我们的应用程序代码中使用以下行:
esriConfig.defaults.io.proxyUrl = "/proxy/proxy.ashx";
esriConfig.defaults.io.alwaysUseProxy = true;

在我们的应用程序中,proxy.ashx页面位于应用程序根目录下的proxy文件夹中。如果代理页面位于不同的应用程序中,我们需要更改esriConfig.defaults.io.proxyUrl变量的值。当我们将esriConfig.defaults.io.alwaysUseProxy值设置为true时,所有请求都将由代理处理。如果我们只需要特定的 URL 由代理处理,我们可能需要添加几行代码,如下所示:

urlUtils.addProxyRule({
urlPrefix: "route.arcgis.com",
proxyUrl: "/proxy/proxy.ashx"
    });

urlUtils函数由esri/urlUtils模块提供。

以下图表显示了从客户端到安全 ArcGIS Server 服务的 HTTP REST 请求的流程:

在应用程序中使用代理

引导应用程序

本书中的所有应用程序都使用 Bootstrap 地图库进行样式设置和引导。这些库的源代码可以在github.com/Esri/bootstrap-map-js找到。

下载所需的库后,我们需要将以下 CSS 和 JavaScript 库添加到我们的应用程序中:

<head>
<!-- Bootstrap-map-js& custom styles -->
<link href="css/lib/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="css/lib/bootstrapmap.css">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css">
</head>
<body>

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="js/lib/bootstrap.min.js"></script>
</body>

添加这些库后,我们需要添加另一个 JavaScript 文件作为 dojo 模块,而不是作为脚本引用。在我们的应用程序中,讨论中的 JavaScript 库位于/js/lib/bootstrapmap.js

将此库作为 require 函数中的模块添加时,我们需要省略文件扩展名。以下屏幕截图说明了这一说法:

引导应用程序

因此,我们将使用bootstrapmap模块而不是esri/map模块来创建地图。bootstrapmap模块接受esri/map提供的所有属性和方法,因为bootstrapmap模块只是esri/map模块的包装。

查询操作类型

在 ArcGIS Server 提供的数据上可以进行各种类型的查询操作。在本章中,我们将处理 API 提供的三种最重要的查询操作:

  • 查询任务

  • 查找任务

  • 识别任务

查询任务

查询任务让我们只操作一个图层,因此查询任务的构造函数要求我们提供要素图层的 URL。查询任务让我们使用属性(字段值;例如,查询人口超过 200 万的城市)或使用位置(例如,查找所有位于地图当前范围或自定义绘制范围内的加油站)。当满足查询条件的要素数量大于服务器设置的限制(ArcGIS Server 中的maxRecordCount设置)时,我们可以使用名为分页的功能以批处理模式检索所有要素。

查找任务

查找任务可以在地图服务和多个字段上操作。查找任务基本上在给定地图服务中的所有图层的所有字段中搜索给定的文本。当我们不知道要搜索的字段,因此无法构造适当的 SQLwhere子句来查询数据时,这是一个理想的操作依赖。

识别任务

识别任务主要是基于位置的搜索操作,返回与给定几何图形(例如地图点击点)相交的给定地图服务中所有图层的所有数据。

在所有前面的任务中,我们可以限制进行搜索操作的字段或图层。以下矩阵总结了三种不同类型的查询操作可用的所有选项:

识别任务

构建和执行查询任务

查询任务旨在查询featureLayer。因此,要实例化querytask,我们需要提供featurelayer的 URL。在 API 的 3.15 版本中,该模块被命名为esri/tasks/QueryTask

QueryTask 构造函数

QueryTask构造函数的语法如下:

newQueryTask(url, options?)

QueryTask构造函数的示例如下:

require([
"esri/tasks/QueryTask", ...
], function(QueryTask, ... ) {
varqueryTask = new QueryTask("<Feature Layer URL>")
});

构造函数参数

启用查询功能的要素图层的 URL,以验证要素图层上的查询功能是否已启用,我们必须访问地图服务的服务目录,并检查“查询”是否是我们想要查询的要素图层支持的操作之一。

例如,在我们处理的 Active Wildfire 地图服务中,我们想要查询包含有关 Active Wildfire 图层的数据的图层。地图服务中只有一个图层,因此要素图层的图层索引为0

要素图层的 URL 是livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer/0

当我们访问此链接并滚动到页面底部找到支持的操作部分时,我们将看到查询操作被列在那里:

构造函数参数

使用 Query 任务执行查询涉及以下步骤:

构造函数参数

实例化 QueryTask 对象

查询任务基于 Active Wildfire 要素图层。因此,我们将使用要素图层的 URL 来实例化QueryTask对象。以下代码行解释了如何实例化QueryTask

var wildFireActivityURL = "http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer/0";
var queryTask = new QueryTask(wildFireActivityURL);

构建查询对象

QueryTask对象只是定义要查询的图层或数据,但我们需要使用Query对象来定义实际的查询是什么。Query对象由esri/tasks/Query模块提供。

查询对象执行以下操作:

  • 它形成一个 SQLwhere子句来按属性查询。

  • 它使用空间几何体执行查询。

  • 它指示查询执行时的空间关系。

  • 它请求从服务器获取要素字段的数组。

  • 它指示查询结果是否需要返回几何信息。

查询对象有一个名为where的属性。该属性接受 SQL 的where子句,并获取满足where子句的数据。

where子句的格式如下:

query.where = "<Query Expression 1><AND/OR><Query Expression 2> …<AND/OR><QueryExpression n>"

其中Query Expression"<FieldName><operator><value>";<FieldName>是要查询的要素中字段的名称。

<operator>是一种 SQL 运算符,例如LIKE=,>,<

以下代码片段演示了where子句的使用:

query.where = "STATE = 'OK' OR STATE = 'WY'";

当我们想要从feature类中检索所有要素时,where子句需要设置为表达式,例如1=1。真表达式是在所有情况下都评估为true的表达式。

您可以使用真表达式检索所有要素:

query.where = "1=1";

注意

在实践中,使用此表达式返回的要素数量由服务器设置MaxRecordCount确定,如下截图所示。默认值为1000。可以在 ArcGIS 服务器设置中更改此限制。

在评估字符串时,请记住将字符串值括在单引号内:

locQuery.where = "STATE_NAME = 'OK'";

输出要素集中所需的字段可以作为字段名称数组传递给query对象参数outFields

query.outFields = ["FIRE_NAME", "STATE ", "LATITUDE", "LONGITUDE"];

我们可以通过将值truefalse传递给query对象参数returnGeometry来指示是否需要要素的几何信息:

query.returnGeometry = true;

以下截图显示了如何构造一个完整的Query对象,该对象可以从Query任务对象中设置的要素图层中检索所有要素:

构建查询对象

通过空间几何体查询

我们可以从具有与另一个输入几何体的空间关系的要素图层中获取要素。我们需要定义输入几何体和要检索的要素之间的空间关系。当未定义时,默认空间关系变为交集。这意味着我们正在尝试获取与输入几何体相交的要素。

通过空间几何体查询

查询对象还提供了其他类型的空间关系作为常量:

  • Query.SPATIAL_REL_CONTAINS:这将检索完全包含在输入几何体中的所有要素

  • Query.SPATIAL_REL_INTERSECTS:这是默认的空间关系,获取与输入要素相交的所有要素

  • Query.SPATIAL_REL_TOUCHES:在这里,获取所有与输入几何体相接触的要素

通常,输入几何体可能是来自另一个要素、类或draw对象的几何体,或者在我们的情况下,当前地图的范围,如下面的代码片段所示:

var query = new Query();
query.outFields = ["FIRE_NAME", "STATE", "LATITUDE", "LONGITUDE"];
query.returnGeometry = false;
query.where = "1=1";
query.geometry = map.extent;
query.returnGeometry = false;

执行查询

当我们需要执行查询并检索结果时,我们需要在查询任务对象中调用查询执行方法。可以执行查询任务以获取满足查询条件的实际特征。在某些情况下,我们可能只需要满足查询条件的特征数量或查询结果的空间范围。可以在查询任务对象上执行五种类型的查询操作:

  • 查询特征

  • 查询数量

  • 查询范围

  • 查询对象 ID

  • 关系查询

所有操作都接受查询对象作为第一个参数,并返回一个延迟对象。让我们了解三个最重要的查询任务操作的用途:查询数量,查询范围和查询特征。

查询数量

当我们只想要满足查询条件的特征数量时,可以使用这个操作。以下屏幕截图显示了在一组查询特征上进行数量查询操作,给定一个查询对象(带有查询范围)。结果将是满足查询对象的特征数量:

查询数量

通过数量查询特征的图示

当我们使用查询任务的executeForCount()方法时,仍然使用查询对象作为方法参数。这可以是属性查询、空间查询或两者的组合。这个方法的主要目的是快速评估查询操作返回的特征数量。有时,这可能是您需要向用户显示的唯一信息。

让我们继续创建一个用户界面来获取满足我们查询条件的特征数量。以下屏幕截图显示了一个带有文本框输入查询文本和获取数量按钮的引导面板。我们还提供了另一个隐藏的divdiv包含一个显示特征数量的标签。

查询数量

点击获取数量按钮时应该执行查询操作。当在查询文本框中没有提供输入时,查询将评估为真值表达式;也就是说,将返回地图范围内所有特征的数量。以下代码就是实现这一点的:

on(dom.byId("queryBtn"), "click", function () {
query.outFields = ["FIRE_NAME", "STATE", "LATITUDE", "LONGITUDE"];
query.returnGeometry = false;
query.where = dom.byId("queryTxt").value || "1=1";
query.geometry = map.extent;
varqueryCountDeferred = queryTask.executeForCount(query);
queryCountDeferred.then(function (count) {
dom.byId("FeatCountDiv").style.display = "block";
dom.byId("featCountLbl").innerHTML = "Result: " + count + " Features";
}, function (err) {
console.log(err);
});
});

在上述代码片段中,on是由 dojo 提供的事件处理程序模块(dojo/on)。dom模块的byId()方法用于获取具有 ID—queryBtndom元素的引用。我们在queryBtnclick事件上执行了上述代码片段。请注意,在突出显示的代码中,我们处理了当查询文本框没有输入时的情况。executeForCount()方法返回一个延迟对象。当Deferred对象被解析时,使用.then()方法触发回调。在.then方法中,我们定义了两个函数;第一个函数在操作成功时触发,第二个函数在操作抛出错误时触发。我们还可以在queryTask对象上使用execute-for-count-complete事件来检索结果。

result对象只返回数量。

请参考以下 API 文档,以获取有关此方法返回的结果对象的更多信息—developers.arcgis.com/javascript/jsapi/querytask-amd.html#event-execute-for-count-complete

我们在地图上的操作结果将如下屏幕截图所示:

查询数量

我们还在用户界面中引入了获取特征按钮,以检索满足查询条件的实际特征记录,并在 HTML 表格中显示它们。我们将在queryTask对象上执行execute()方法来实现这一点。

查询特征

这种方法提供了有关正在查询的特征的最大信息。

查询特征操作的图示如下:

查询要素

QueryTask对象中的execute()方法用于查询要素。此方法返回一个Deferred对象。此成功事件处理程序返回一个Featureset对象。Featureset对象返回一个包含要素数组的数组,以及有关要素的几何类型和空间参考的其他辅助信息。

要素集包含以下内容:

  • features:图形数组。图形数组中的每个项目都具有以下属性:

  • attributes:与图形关联的字段和字段值的名称值对

  • geometry:定义图形的几何

  • geometryType:要素的几何类型。

  • spatialReference:要素的空间参考。

在我们的应用程序中,我们将尝试在单击按钮时调用execute方法,并构造一个 HTML 字符串,该字符串将使用称为FeatureSet的结果将其显示为 HTML 表。以下屏幕截图演示了如何遍历结果要素集并创建 HTML 表字符串:

查询要素

单击获取要素按钮时,用于获取要素计数的查询对象也用于执行此查询操作。因此,理想情况下,每次更改查询文本或地图范围时,获取要素按钮和 HTML 查询结果将被隐藏,我们需要在单击获取要素按钮之前单击获取计数按钮。我们编写了一个函数,隐藏显示要素计数的 div,并清除 HTML 表。代码如下所示:

function clearQueryTbl() {
dom.byId("FeatCountDiv").style.display = "none";
dom.byId("QueryTbl").innerHTML = '';
}

以下屏幕截图展示了我们在地图上的代码运行情况:

查询要素

查询范围

当我们想要知道满足查询的要素的范围时,我们可以使用这种方法。这将在许多方面帮助我们:

  • 我们可以了解现象的空间范围

  • 我们可以将地图缩放到要素的范围,而无需实际接收要素

以下图表说明了“范围”操作的查询:

查询范围

构建和执行 IdentifyTask

IdentifyTask可以在地图服务中操作多个图层,并从与给定几何图形相交的所有要素中获取信息。我们将使用 IdentifyTask 在地图上单击并获取单击位置处的潜在野火价值。要执行IdentifyTask,我们需要遵循三个步骤:

  1. 实例化 IdentifyTask。

  2. 构造 Identify 参数。

  3. 执行 IdentifyTask。

实例化 IdentifyTask

实例化 IdentifyTask 涉及加载所需的模块并使用地图服务 URL 进行实例化。执行IdentifyTask所需的模块如下:

  • esri/tasks/IdentifyTask

  • esri/tasks/IdentifyParameters

我们将在野火潜力地图服务上操作 IdentifyTask。地图服务包含单个栅格图层和表示野火潜力级别的像素值。以下代码片段显示了如何实例化 IdentifyTask:

varwildfirePotentialURL = "http://maps7.arcgisonline.com/arcgis/rest/services/USDA_USFS_2014_Wildfire_Hazard_Potential/MapServer";
varidentifyTask = new IdentifyTask(wildfirePotentialURL);

构造识别参数对象

Identify 参数提供了许多属性来定义正在执行的识别操作。在处理多个图层时,我们可以使用layerIds属性来限制可以执行识别的图层。geometry属性让我们设置用于选择地图服务中的要素的几何图形。在我们的应用程序中,我们使用地图click点作为 IdentifyParameter 的输入几何图形。当使用点几何时,我们还需要为 IdentifyParameters 中的容差属性定义值。容差值是指可以被视为输入几何的一部分的输入点几何周围的像素数。

在下面的截图中,我们构造了一个识别参数对象,该对象被地图click事件处理程序包裹。地图click事件处理程序的mapPoint属性为识别操作提供了输入几何对象:

构造识别参数对象

执行 IdentifyTask

execute()方法可以用于执行 IdentifyTask。execute()方法返回Deferred对象,Deferred对象的成功回调返回IdentifyResult数组对象。

识别结果表示地图服务中一个图层中的单个已识别要素。该对象具有以下属性:

  • displayFieldName:这是图层的主要显示字段的名称

  • featurefeature对象包含一个数组对象和一个几何对象

  • layerId:这是包含要素的图层的唯一 ID

  • layerName:这是图层的名称

由于识别结果是一个数组对象,我们只显示一个值,我们将从识别结果对象中取出第一个值(result[0]),如下截图所示。我们需要显示的值在一个名为CLASS_DESC的属性字段中。由于这个值是由一个以冒号(:)分隔的类代码前缀和类描述组成的(例如,5:非常高),我们将根据冒号分隔字符串并仅使用描述部分。

以下截图显示了用于执行识别操作以及将识别结果显示为map点击位置的标签的代码,该位置由指针光标表示:

执行 IdentifyTask

构建和执行查找任务

查找任务基本上是对地图服务中所有字段进行基于属性的搜索。查找任务的结果与 IdentifyTask 的结果相同,只是多了一个foundFieldName的值,表示搜索文本所在的字段名称。与 Query 任务和 IdentifyTask 类似,执行查找任务的三个步骤如下:

  1. 实例化一个查找任务。

  2. 构建查找参数。

  3. 执行查找任务。

让我们逐一讨论这三个步骤。

实例化查找任务

要执行查找任务,需要加载以下模块:

  • esri/tasks/FindTask

  • esri/tasks/FindParameters

我们需要提供地图服务的 URL 来实例化查找任务。以下代码段显示了我们将如何在应用程序中执行此操作:

var find = new FindTask("http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Wildfire_Activity/MapServer");

构建查找参数

要构建查找参数,我们需要使用esri/task/FindParameters模块。查找参数模块具有诸如searchTextlayerIdsseachFields之类的属性,让我们定义查找任务。searchText属性是需要搜索的文本,这需要来自 UI 文本框。layerIds让我们定义查找任务应该操作的layerIds。我们还可以限制进行搜索的字段。以下截图显示了我们如何构建查找任务的 UI 并构造查找参数对象:

构建查找参数

执行查找任务

Find任务的execute()方法可用于执行它。调用此方法将返回一个Deferred对象,在其成功回调函数中返回一个查找结果对象。我们将尝试构建一个 HTML 表格,就像我们为 Query 任务结果所做的那样,并在FindTbl div中显示它。以下代码行用于完成此操作:

var findTaskDeferred = find.execute(findParams);
findTaskDeferred.then(function (result) {
  vartblString = '<table class="table table-striped table-hover">';
  tblString += '<thead><tr><th>FIRE NAME</th>';
  tblString += '<th>STATE</th>';
  tblString += '<th>LOCATION</th>';
  array.forEach(result, function (searchitem) {
    tblString += '<tr><td>' + searchitem.feature.attributes["Fire Name"] + '</td>';
    tblString += '<td>' + searchitem.feature.attributes["State"] + '</td>';
    tblString += '<td> (' + searchitem.feature.attributes["Longitude"] + ',' + searchitem.feature.attributes["Latitude"] + ')</td></tr>';
  });
  tblString += '</tbody></table >';
  dom.byId("FindTbl").innerHTML = tblString;
}, function (err) {
  console.log(err);
});

在下面的截图中,我们可以看到当我们插入搜索文本W时,搜索文本已从两个不同的字段Fire NameState中获取:

执行查找任务

构建要素表

要素表构建一个表,显示给定要素图层的所有信息,并将其放置在给定的dom元素中。要素表是 Esri 小部件,可以通过加载esri/dijit/FeatureTable模块来使用。该模块允许我们选择要显示的字段。下面的截图显示了如何构建要素表以及它在应用程序中的显示方式:

构建要素表

构建弹出窗口

当您的 Web 应用程序的用户点击感兴趣的要素时,他们应该看到有关他们点击的要素的一系列有用信息。弹出窗口是向用户显示特定上下文属性信息的媒介。弹出窗口补充了地图的空间信息。

最简单的弹出窗口只显示所有或选定的属性值。更高级和直观的弹出窗口在弹出窗口中使用图表和图像。

帮助创建弹出窗口的模块包括esri/InfoTemplateesri/dijit/PopupTemplateesri/dijit/InfoWindowesri/dijit/Popup

esri/dijit/PopupTemplate扩展了esri/InfoTemplate,而esri/dijit/Popup扩展了esri/dijit/InfoWindow。因此,让我们简要介绍一下InfoTemplate,然后转向Popup模板。

构建 Infotemplates

可以使用占位符创建InfoTemplate对象。占位符通常是属性字段名,以美元符号($)开头,用大括号({})括起来,例如${Fieldname}

当我们需要检索感兴趣要素提供的所有字段时,字段名可以被*替换,例如${*}

要素图层和图形对象都有InfoTemplate属性。创建的infotemplate可以设置为这些图层。InfoTemplate构造函数接受两个参数,标题和内容:

模块
模块名称 esri/InfoTemplate
父对象 要素图层、图形对象、动态图层和地图的信息窗口
构造函数 new InfoTemplate (title, content)

下面的截图为Active wildfire要素图层创建了infotemplate,并在弹出窗口中显示了州名、火灾名称和被点击的野火要素的面积范围等字段。Infotemplate的标题也是由占位符创建的:

构建 Infotemplates

本章的代码清单可以在名为B04959_03_CODE的代码文件夹中找到。

摘要

本章解释了搜索和查询数据的不同方法。我们构建了一个应用程序,可以执行查询任务、查找任务,以及识别任务。我们还发现了名为dijit的要素表以及Infotemplates的实用性。在下一章中,我们将看到如何将所有代码组织成模块化小部件,并在应用程序中使用它。我们还将讨论涉及使用绘图工具栏的空间查询的构造,以及创建由应用程序用户定义的输入几何体。

第四章:构建自定义小部件

本章的主要目标是开发一个自定义小部件,可以执行空间查询并在简单的 HTML 表格中显示结果。在构建自定义小部件的过程中,您将学习以下主题:

  • 如何使用道场创建一个简单的类

  • 如何全局配置道场

  • 道场小部件的生命周期是什么

  • 如何创建模板小部件

  • 如何为国际化提供支持

  • 如何组织道场代码

  • 绘图工具栏的工作原理

  • 如何使用本章讨论的所有功能构建自定义小部件

创建一个简单的类

Dojo 类提供了一种继承和扩展其他模块以使用模板并创建小部件的方法。道场中的类位于一个模块中,并且模块返回类声明。要在模块中声明类,我们需要加载一个名为dojo/_base/declare的模块,该模块提供了声明类的支持。

以下屏幕截图显示了一个简单的道场类声明:

创建一个简单的类

在这个屏幕截图中,declaredojo/_base/declare模块的回调函数装饰。类声明接受三个参数:classnamesuperclassproperties

classname 参数是可选的。当提供了一个 classname 字符串时,声明被称为命名类。当它被省略时,就像我们的情况一样,它被称为匿名类。我们将继续使用匿名类,因为命名类必须在特定条件下使用。

超类是我们想要扩展的模块或模块数组。如果超类参数为 null(如我们的片段中),这意味着我们的类声明本身就是一个超类。

类声明中的第三个参数是类属性。我们可以在这里定义类构造函数、其他类属性和类方法。

配置道场

Dojo 有一个名为dojoConfig的全局对象,其中包含所有的配置参数。我们可以修改dojoConfig对象来配置选项和默认行为,以适应道场工具包的各个方面。

dojoConfig对象让我们定义在我们的 Web 应用程序中定义的自定义模块的位置,并使用包名标记它。因此,当我们需要加载这些自定义模块时,我们可以使用包名来引用文件夹位置。

注意

在引用 Esri JS API 之前,必须声明dojoConfig对象。

配置道场

还有其他配置选项,如asyncparseOnLoadwaitSecondscacheBust。有关dojoConfig主题的详细信息,请参阅道场工具包文档dojotoolkit.org/documentation/tutorials/1.10/dojo_config/

  • async选项定义了道场核心是否应异步加载。推荐的值是true

  • locale选项允许我们覆盖浏览器提供给道场的默认语言。这将帮助我们为不同的目标语言环境开发应用程序,并使用道场的i18n模块测试我们的小部件是否支持国际化。

  • cacheBust选项是一个非常有用的选项,当配置为true时,将时间字符串附加到模块的每个 URL,从而避免模块缓存。

让我们看看这些选项对我们有什么作用:

<script>
        var dojoConfig = {
            has: {
                "dojo-debug-messages": true
            },
            parseOnLoad: false,
            locale: 'en-us',
            async: true,  
            cacheBust: true,
            packages: [
                {
                    name: "widgets",
                    location: "/js/widgets"
            },
                {
                    name: "utils", 
                    location: "/js/utils"
                }
         ]
        };
    </script>
    <script src="//js.arcgis.com/3.14/"></script>
    <script src="js/app.js"></script>

配置道场

dojoConfig对象中将 cacheBust 配置为 True 的效果

开发独立的小部件

在道场中编写类的主要目的是开发独立的小部件。道场专门为我们提供了一个支持小部件开发的模块:dijit/_WidgetBase。我们还需要其他辅助模块,如dijit模板模块、道场解析和道场国际化模块,以在 Web 应用程序中开发一个完整的小部件。

WidgetBase模块关联的关键方面是小部件的生命周期概念。小部件的生命周期为我们提供了在小部件的不同阶段使用的方法,即从小部件的初始化,到其dom节点完全加载并可被应用程序利用,直到小部件的销毁。

此模块应作为类声明中的超类数组传递。以下是一个基本小部件的片段:

define([
    //class
    "dojo/_base/declare",

    //widgit class
    "dijit/_WidgetBase",

    "dojo/domReady!"
], function (
    declare,
    _WidgetBase
) {
 **return declare([_WidgetBase], {**
/*Class declaration inherits "dijit/_WidgetBase" module*/

        constructor: function () {}
    });
});

dijit 生命周期

_WidgetBase选项提供了程序流将按特定顺序执行的几种方法。按顺序执行的一些最重要的方法如下信息图所示:

dijit 生命周期

小部件生命周期信息图

上述图表可以描述如下:

  • constructor:这是小部件实例化时调用的第一个方法。constructor函数可以用作一个名为domNode的特殊属性。这可以包含对domNode的引用,小部件将放置在其中。constructor函数的第一个参数将是一个options对象,我们可以向其中发送任何我们想发送给小部件的对象值:
constructor: function (options, srcRefNode) {
            this.domNode = srcRefNode;
        }
  • postCreate:此方法在小部件的所有属性执行后立即执行。所有小部件的事件处理程序将在此处定义。应在postCreate()方法中添加一行特定的代码,以便所有在WidgetBase中定义的定义都能正确继承。在下面的代码片段中,已经突出显示了特定的代码行:
postCreate: function(){
            **this.inherited(arguments);**
        }
  • postCreate():这个方法也是托管特殊this.own()方法的正确位置。在此方法中定义的事件处理程序将在小部件实例被销毁时释放事件处理程序。

  • Startup:此方法在构造完 dom 节点后触发。因此,任何对 dom 节点的修改都将在此处完成。这是通过该方法外部调用小部件执行的方法。

创建模板化小部件

模板化小部件允许开发人员在运行时将 HTML 文件作为模板字符串加载。所有特定于小部件的 dom 节点都应在此 HTML 模板中定义。Dojo 提供了另外两个模块,以使我们使用模板更轻松、更高效。这些模块名为dijit/_TemplatedMixindijit/_WidgetsInTemplateMixin。除了这两个模块,我们还需要加载一个名为dojo/text!的 dojo 插件,它实际上将 HTML 页面加载为模板字符串。插件的工作方式是在dojo/text!中的感叹号(!)后附加 HTML 文件路径:

    "dojo/text!app_widgets/widgettemplate/template/_widget.html"

类属性应包括一个名为templateString的特定属性。此属性的值将是用于表示dojo/text!<filename.html>插件的回调函数装饰。

让我们看一个基本的代码片段,涵盖了之前讨论的所有主题,并尝试开发一个模板小部件:

创建模板化小部件

我们的模板文件非常无害,只包含一个简单的h1标题标签。正是templateString属性保存了这个 HTML 字符串。

app_widgets/widgettemplate/template/_widget.html文件的内容如下:

<h1>This is Templated widget</h1>

现在,让我们看看如何实例化这个小部件。如前所述,我们需要在小部件中调用startup方法来执行此小部件。我们将从另一个 JavaScript 文件中调用这个方法,该文件将传递一个对 dom 节点的引用,其中我们的小部件将被放置:

创建模板化小部件

/js/widgets/app.js 的内容

这个文件将从index.html文件中调用,该文件具有名为templatedWidgetDivdom元素:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width">
    <link rel="stylesheet" type="text/css" href="css/style.css">

</head>

<body>
    <h1>Using Dojo Classes</h1>
    <div id="templatedWidgetDiv"/>
    <script>
        /* dojo config */
        var dojoConfig = {
            has: {
                "dojo-debug-messages": true
            },
            parseOnLoad: false,
            locale: 'en-us',
            async: true,  
            cacheBust: true,
            packages: [
                {
                    name: "app_widgets",
                    location: "/js/widgets"
            },
                {
                    name: "utils", 
                    location: "/js/utils"
                }
         ]
        };
    </script>
    <!--Call the esri JS API library-->
    <script src="//js.arcgis.com/3.14/"></script>
    <!--Call the /js/utils/app.js file-->
    <script src="js/app.js"></script> 
</body>

</html>

小部件文件夹结构

现在是讨论小部件文件夹结构的时候了。在开发大型项目时,文件夹结构是项目构建过程的重要部分,我们需要在项目的初始阶段定义。我们将提供关于如何决定文件夹结构的一般指导方针。这可以根据您的偏好和项目需求进行修改。

创建项目文件夹的指南

创建项目文件夹的指南如下图所示:

创建项目文件夹的指南

让我们详细讨论每一个。

创建单一入口点

我们不需要在索引页面上拥挤所有小部件的实例化。最好是我们可以定义一个模块,可以作为实例化我们需要的所有小部件的单一入口点。

我们的 HTML 页面将只包含对 JS API 和这个单一入口 JavaScript 模块的引用:

    <!--Call the esri JS API library-->
    <script src="//js.arcgis.com/3.15/"></script>
    <!--Call the javaScript file which serves as the single point of entry-->
 **<script src="js/app.js"></script>**

作为单一入口点的文件的内容如下:

创建单一入口点

定义 dojoConfig

我们之前讨论过这一点。dojoConfig 对象将在索引页面中声明。这是一个全局对象,其值可以通过加载名为 dojo/_base/config 的模块在程序的任何地方访问。

模块化代码

这是 AMD 编码模式的核心原则。模块化的概念意味着我们应该解耦任何功能上不同的代码。正如我们所知,dojo 模块返回一个可公开访问的对象。这个对象可以是一个类声明,就像我们之前看到的那样。模块的另一个用途是可以用作应用程序的配置文件。

已经创建了一个基于 dojo 模块的示例 config 文件供您参考:

define(function () {
    /* Private variables*/
    var baseMapUrl = "http://maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/etopo1_hillshade/MapServer";
    var NOAAMapService = "http://maps.ngdc.noaa.gov/arcgis/rest/services/web_mercator/hazards/MapServer";
    var earthquakeLayerId = 5;
    var volcanoLayerId = 6;
    /*publicly accessible object returned by the COnfig module */
    return {
        app: {
            currentVersion: "1.0.0"
        },

        // valid themes: "claro", "nihilo", "soria", "tundra", "bootstrap", "metro"
        theme: "bootstrap",

        // url to your proxy page, must be on same machine hosting you app. See proxy folder for readme.
        proxy: {
            url: "proxy/proxy.ashx",
            alwaysUseProxy: false,
            proxyRuleUrls: [NOAAMapService]
        },

        map: {

            // basemap: valid options: "streets", "satellite", "hybrid", "topo", "gray", "oceans", "national-geographic", "osm"
            defaultBasemap: "streets",
            visibleLayerId: [this.earthquakeLayerId, this.volcanoLayerId];

            earthQuakeLayerURL: this.NOAAMapService + "/" + this.earthquakeLayerId,
            volcanoLayerURL: this.NOAAMapService + "/" + this.volcanoLayerId
        }
    }
});

提供国际化支持

根据用户的区域设置自定义应用程序中显示的文本称为国际化。Dojo 提供了一个名为 dojo/i18n! 的插件来提供这种支持。当我们提到插件时,这意味着它在感叹号 (!) 之后期望一个文件路径作为参数。文件路径指的是一个 JavaScript 模块,其中提到了一个名为 root 的对象,并列出了所有支持的区域设置。

例如,dojo/i18n!app_widgets/widget_i18n/nls/strings 指的是在 app_widgets/widget_i18n/nls 文件夹中定义的 strings 模块(请记住 app_widgets 是指向 /js/widgets 位置的包名称)。

当前的区域设置由用户的浏览器确定。在 dojo 中,locale 通常是一个五个字母的字符串;前两个字符代表语言,第三个字符是连字符,最后两个字符代表国家。

例如,看一下以下内容:

  • en-us 值代表英语作为语言和美国作为国家

  • ja-jp 值代表日语作为语言和日本作为国家

  • zh-cn 值代表简体中文作为语言和中国作为国家

  • zh-tw 值代表简体中文作为语言和台湾作为国家提供国际化支持

提供国际化支持的步骤

提供国际化支持的步骤如下:

  1. 在小部件所在的文件夹中创建一个名为 nls 的文件夹。

  2. 定义一个具有名为 root 的对象并在 root 对象下列出所有支持的区域设置的模块。例如,看一下以下内容:

"zh-cn" : true,
"de-at" : true
  1. root 对象将包含所有支持语言的字符串变量,例如 widgetTitledescription

  2. 为每个定义的区域设置创建一个文件夹,例如 zh-cnde-at

  3. 在每个 language 文件夹中创建与 root 模块同名的模块。

  4. 新模块将包含root对象的所有属性。属性的值将包含相应值的特定语言翻译。

  5. 加载名为dojo/i18n!的模块,后面跟上根模块的路径。

  6. declare构造函数中,将i18n模块的callback函数声明分配给名为this.nls的属性:

define([
    //class
    "dojo/_base/declare",
    "dojo/_base/lang",

    //widgit class
    "dijit/_WidgetBase",

    //templated widgit
    "dijit/_TemplatedMixin",

    // localization
 **"dojo/i18n!app_widgets/widget_i18n/nls/strings",**

    //loading template file
    "dojo/text!app_widgets/widget_i18n/template/_widget.html",

    "dojo/domReady!"
], function (
    declare, lang,
    _WidgetBase,
    _TemplatedMixin,
    nls,
    dijitTemplate
) {
    return declare([_WidgetBase, _TemplatedMixin], {
        //assigning html template to template string
        templateString: dijitTemplate,
        constructor: function (options, srcRefNode) {
            console.log('constructor called');
            // widget node
            this.domNode = srcRefNode;
 **this.nls = nls;**
        },
        // start widget. called by user
        startup: function () {
            console.log('startup called');
        }
    });
});

小部件文件夹结构概述

让我们再次审查widget文件夹结构,以便在开始任何项目之前将其用作模板:

  1. 我们需要一个主文件(比如index.html)。主文件应该有dojoConfig对象,引用应用程序中使用的所有 CSS,以及 Esri CSS。它还应该有对 API 的引用和对作为入口点的模块的引用(app.js)。

  2. 所有小部件都放在js文件夹中。

  3. 所有站点范围的 CSS 和图像分别放入应用程序root目录中的CSSimage文件夹中。

  4. 所有小部件将放置在js文件夹内的widgets文件夹中。每个小部件也可以放置在widgets文件夹内的单独文件夹中。

  5. 模板将放置在widget文件夹内的template文件夹中。

  6. 将国际化所需的资源放在名为nls的文件夹中:小部件文件夹结构概述

构建自定义小部件

我们将扩展上一章中开发的应用程序,添加高级功能和模块化的代码重构。让我们在应用程序中创建一个自定义小部件,该小部件可以执行以下操作:

  • 允许用户在地图上绘制多边形。多边形将以半透明红色填充和虚线黄色轮廓进行符号化。

  • 多边形应该获取多边形边界内的所有重大森林火灾事件。

  • 这将显示为图形,数据应该在网格中。

  • 必须提供国际化支持。

小部件所需的模块

让我们列出定义类及其相应预期回调函数装饰所需的模块。

用于类声明和 OOPS 的模块

模块
dojo/_base/declare declare
dijit/_WidgetBase _WidgetBase
dojo/_base/lang lang

使用 HTML 模板的模块

模块
dijit/_TemplatedMixin _TemplatedMixin
dojo/text! dijitTemplate

用于使用事件的模块

模块
dojo/on on
dijit/a11yclick a11yclick

用于操作 dom 元素及其样式的模块

模块
dojo/dom-style domStyle
dojo/dom-class domClass
dojo/domReady!

用于使用绘制工具栏和显示图形的模块

模块
esri/toolbars/draw Draw
esri/symbols/SimpleFillSymbol SimpleFillSymbol
esri/symbols/SimpleLineSymbol SimpleLineSymbol
esri/graphic Graphic
dojo/_base/Color Color

用于查询数据的模块

模块
esri/tasks/query Query
esri/tasks/QueryTask QueryTask

国际化支持的模块

模块
dojo/i18n! nls

使用绘制工具栏

绘制工具栏使我们能够在地图上绘制图形。此工具栏与事件相关联。完成绘制操作后,它将返回在地图上绘制的对象作为几何图形。按照以下步骤使用绘制工具栏创建图形:

使用绘制工具栏

初始化绘制工具栏

绘图工具栏由名为esri/toolbars/draw的模块提供。绘图工具栏接受地图对象作为参数。在postCreate函数中实例化绘图工具栏。绘图工具栏还接受一个名为options的额外可选参数。options对象中的一个属性名为showTooltips。这可以设置为true,以便在绘制时看到相关的工具提示。工具提示中的文本可以自定义。否则,将显示与绘制几何图形相关的默认工具提示:

return declare([_WidgetBase, _TemplatedMixin], {
    //assigning html template to template string
    templateString: dijitTemplate,
    isDrawActive: false,
    map: null,
 **tbDraw: null,**
    constructor: function (options, srcRefNode) {
      this.map = options.map;
    },
    startup: function () {},
    postCreate: function () {
      this.inherited(arguments);
 **this.tbDraw = new Draw(this.map, {showTooltips : true});**
    }
...

绘图工具栏可以在按钮的“单击”或“触摸”事件(在智能手机或平板电脑上)上激活,该按钮旨在指示“绘制”事件的开始。Dojo 提供了一个模块,可以处理“触摸”和“单击”事件。该模块名为dijit/a11yclick

要激活绘图工具栏,我们需要提供要绘制的符号类型。绘图工具栏提供了一组常量,对应于绘制符号的类型。这些常量是POINTPOLYGONLINEPOLYLINEFREEHAND_POLYGONFREEHAND_POLYLINEMULTI_POINTRECTANGLETRIANGLECIRCLEELLIPSEARROWUP_ARROWDOWN_ARROWLEFT_ARROWRIGHT_ARROW

激活绘图工具栏时,必须使用这些常量来定义所需的绘图操作类型。我们的目标是在单击绘图按钮时绘制多边形。代码如下截图所示:

初始化绘图工具栏

绘图操作

一旦激活绘图工具栏,绘图操作就开始了。对于点几何,绘图操作只需单击一次。对于折线和多边形,单击会向折线添加一个顶点,双击结束草图。对于自由手折线或多边形,“单击”和“拖动”操作绘制几何图形,“松开鼠标”操作结束绘制。

绘制结束事件处理程序

绘图操作完成后,我们需要一个事件处理程序来处理绘图工具栏绘制的形状。API 提供了一个draw-end事件,该事件在绘图操作完成后触发。必须将此事件处理程序连接到绘图工具栏。此事件处理程序将在小部件的postCreate()方法中的this.own()函数内定义。事件结果可以传递给命名或匿名函数:

postCreate: function () {
...
 **this.tbDraw.on("draw-end", lang.hitch(this, this.querybyGeometry));**
    },
...
querybyGeometry: function (evt) {
      this.isBusy(true);
      //Get the Drawn geometry
      var geometryInput = evt.geometry;
...
}

符号化绘制的形状

draw-end事件回调函数中,我们将以结果对象的形式获得绘制形状的几何图形。要将此几何图形添加回地图,我们需要对其进行符号化。符号与其所代表的几何图形相关联。此外,符号的样式由用于填充符号和其大小的颜色或图片定义。仅需对多边形进行符号化,我们需要使用SimpleFillSymbolSimpleLineSymbol模块。我们可能还需要esri/color模块来定义填充颜色。

让我们回顾一小段代码,以更好地理解这一点。这是一个简单的代码片段,用于构造一个具有半透明实心红色填充和黄色虚线的多边形符号:

符号化绘制的形状

在前面的截图中,SimpleFillSymbol.STYLE_SOLIDSimpleLineSymbol.STYLE_DASHDOTSimpleFIllSymbolSimpleLineSymbol模块提供的常量,用于为多边形和线条设置样式。

在符号的构造中定义了两种颜色:一种用于填充多边形,另一种用于着色轮廓。颜色可以由四个组件定义。它们如下:

  • 红色

  • 绿色

  • 蓝色

  • 不透明度

红色、绿色和蓝色分量的取值范围为0255,不透明度的取值范围为01。根据 RGB 颜色理论,可以使用红色、绿色和蓝色分量的组合来产生任何颜色。因此,要创建黄色,我们使用红色分量的最大值(255)和绿色分量的最大值(255);我们不希望蓝色分量对我们的颜色产生影响,所以使用0。不透明度值为0表示 100%透明,不透明度值为1表示 100%不透明。我们使用0.2作为填充颜色。这意味着我们的多边形需要 20%的不透明度,或者 80%的透明度。该组件的默认值为1

符号只是一个通用对象。这意味着任何多边形几何体都可以使用该符号来渲染自己。现在,我们需要一个容器对象在地图上显示以前定义的符号绘制的几何体。由esri/Graphic模块提供的图形对象充当容器对象,可以接受几何体和符号。图形对象可以添加到地图的图形图层中。

注意

地图对象中始终存在一个图形图层,可以通过使用地图的graphics属性(this.map.graphics)来访问。

对绘制的形状进行符号化

执行查询

小部件的主要功能是根据用户的绘制输入定义和执行查询。以下图像将为我们提供构造querytask和处理执行的一般方法:

执行查询

初始化 QueryTask 和 Query 对象

我们将使用在上一章中使用的 Active Wildfire 要素图层。在提供输入几何体时,我们将使用从draw-end事件中获取的几何体,而不是使用地图的当前范围几何体,就像我们在上一章中所做的那样。我们将获取绘制几何体内的所有要素,因此我们将使用真值表达式(1=1)作为where子句。以下代码解释了如何构造query对象以及如何执行和存储queryTask作为延迟变量:

var queryTask = new QueryTask(this.wildFireActivityURL);
var query = new Query();
query.where = "1=1";
query.geometry = geometryInput;
query.returnGeometry = true;
query.outFields = ["FIRE_NAME", "AREA_", "AREA_MEAS"];
var queryDeferred = queryTask.execute(query);

查询事件处理程序

QueryTask对象上的execute方法返回一个延迟变量。这意味着我们应该使用.then()操作来引出任务执行结果。成功处理程序返回一个featuresetfeatureset是一组要素。要素包含图形以及一些属性。

现在,有两个操作需要执行以显示查询结果:

  1. 通过适当地对查询结果进行符号化并将其添加为地图上的适当图形来突出显示查询结果。

  2. 在简单的 HTML 表格中显示满足查询条件的 Active Wildfires 的详细信息。HTML 表格应该来自 HTML 模板文件。

定义 HTML 模板

我们需要一个 HTML 模板来渲染小部件。该小部件将具有以下组件:

  • 一个按钮的click事件将切换绘制事件

  • 一个按钮用于清除绘制的图形,以及结果图形和 HTML 表格

  • 一个dom元素来保存正在构建的 HTML 表格。

以下屏幕截图解释了 HTML 模板的构造方式:

定义 HTML 模板

应该使用dojo/text!插件将此 HTML 文件作为插件加载。完成后,可以使用此符号访问代码中由dojo-attach-point引用的所有dom元素。还应该实现处理toggleDraw按钮和clear按钮的点击事件的函数。以下屏幕截图显示了这个的基本实现:

定义 HTML 模板

对查询结果进行符号化

查询返回的要素是野火位置,所有位置都具有点几何。我们可以使用SimpleMarkerSymbolPictureMarkerSymbol来对查询返回的要素进行符号化。PictureMarker符号接受以下属性:

  • angle

  • xoffset

  • yoffset

  • type

  • url

  • contentType

  • width

  • height

我们将使用应用程序的 PNG 资源来定义PictureMarkerSymbol

  var symbolSelected = new PictureMarkerSymbol({
  "angle": 0,
  "xoffset": 0,
  "yoffset": 0,
  "type": "esriPMS",
  "url": "images/fire_sel.png",
  "contentType": "image/png",
  "width": 24,
  "height": 24
});

将图形添加到地图

将所有查询结果特征转换为具有我们刚刚定义的PictureMarkerSymbol的图形。此外,我们还将为每个图形添加一个infotemplateinfotemplate的内容将从查询结果属性中获取。可以通过迭代查询结果对象返回的要素来构建 HTML 表。以下屏幕截图清楚地说明了整个过程:

将图形添加到地图

完整的代码清单可以在名为B049549_04_CODE02的文件夹中找到。

摘要

在本章中,您学习了如何在 dojo 中创建类和自定义小部件,还学习了一个 dojo 小部件的生命周期。然后,我们遵循了为任何与 dojo 相关的项目创建文件夹结构的指南。我们还看了如何使用 dojo 模块提供的国际化功能来支持不同的语言。最后,我们创建了一个自定义小部件,该小部件使用绘图工具来接受用户绘制的多边形,并将其用于查询要素图层。我们还在 HTML 表和地图上显示了结果。在接下来的章节中,我们将学习如何更好地直观地对图形进行符号化,使用一种称为渲染的技术。渲染是一种很好的可视化技术,它让我们能够根据要素中特定属性的值以不同方式对要素进行符号化。在后续章节中,我们将扩展可视化技术,以涵盖数据的非空间表示,如图表和图形。

第五章:使用渲染器

渲染器为我们提供了一种直观地使用不同符号和颜色来可视化数据的媒介。渲染器不仅是一种数据可视化技术,而且越来越被认为是一种数据分析工具。正确使用渲染器将帮助我们看到数据中的空间模式,并显示各种现象的地理分布。对基本制图学、色彩理论甚至统计学的理解将帮助我们创建更好的渲染器,最终更好地洞察可用数据。本章将涵盖以下主题:

  • 学习 API 提供的不同符号和颜色

  • 学习如何创建SimpleRenderer方法

  • 学习如何高效创建UniqueValueRenderer方法

  • 学习何时使用ClassBreakRendererHeatmapRenderers

  • 讨论ScaleDependantRenderers可以有用的情景

  • 智能制图简介

使用颜色

处理颜色的 Esri 模块称为esri/Color。在处理颜色模块之前,让我们对颜色有一个基本的了解。

RGB 颜色模型

可见光谱中的任何颜色(紫罗兰到红色之间的颜色范围)都可以用红(R)、绿(G)或蓝(B)颜色的组合来表示。这就是RGB 颜色模型。还有其他颜色模型,但让我们现在先使用 RGB 颜色模型。每种颜色 R、G 或 B 都可以用 0 到 255 的比例来表示。

以下图片显示了三种原色(R、G 和 B)及其叠加效果之间的关系:

RGB 颜色模型

当三种颜色(R、G 和 B)以相等比例混合时,产生的颜色总是位于灰度范围内的某个位置。以下几点值得注意:

  • 例如,如果R=0G=0B=0,混合产生黑色。

  • 如果R=255G=255B=255,混合产生白色。

  • 任何其他数字值,当等量混合时,产生灰色的阴影。

例如,如果R=125G=125B=125,它将是灰色。

  • 颜色模型还显示,当红色和绿色混合在一起时(R=255G=255B=0),我们得到黄色。

  • 当仅混合红色和蓝色时(R=255G=0B=255),我们得到品红色。

  • 当绿色和蓝色混合时,我们得到青色(R=0G=255B=255)。

Esri 颜色模块

要使用 RGB 颜色模型定义颜色,可以使用以下格式:

var r = g = b = 125;
var color = new Color([r, g, b]);

在上面的片段中,coloresri/Color模块的一个实例,rgb分别是红色、绿色和蓝色的值。颜色应始终按照(rgb)的顺序添加为数组对象。如预期的那样,color变量存储了灰色。如果我们需要向颜色添加透明度,我们可以定义透明度值,称为alpha,它是一个介于01.0之间的整数,其中0表示完全透明,1.0表示不透明。透明度值将作为数组中的第四个值添加:

define(["esri/Color"], function(Color){
var r = g = b = 100;
var alpha = 0.5; // 50 % transparency
var color2 = new Color ([r, g, b, alpha]);
})

RGB 值可以表示为十六进制数。例如,[255, 0, 0]可以表示为#FF0000。API 还允许我们通过其英文命名字符串来表示颜色,例如blue

define(["esri/Color"], function(Color){
var colorString = "red";
var colorHex = "#FF0000"; 
var color1 = new Color(colorString);
var color2 = new Color(colorHex);

使用符号

符号是基于它们试图符号化的几何图形。因此,用于表示点、线和多边形的符号彼此不同。除了几何图形之外,定义符号所需的三个重要参数是以下:

  • 风格

  • 颜色

  • 维度(或大小)

通常提供风格作为模块常量。例如,SimpleLineSymbol.STYLE_DASHDOTSimpleFillSymbol.STYLE_SOLIDSimpleMarkerSymbol.STYLE_CIRCLE,其中SimpleLineSymbolSimpleFillSymbolSimpleMarkerSymbol分别用于符号化线、多边形和点要素:

  • 这些符号的颜色可以由我们在前面章节中讨论的颜色模块定义。

  • 基于几何类型,尺寸意味着不同的东西。例如,对于线符号,我们使用称为 width 的参数来指代线的厚度,而对于点,我们使用名为 size 的参数来定义其尺寸。

让我们先讨论基于三角形的几何符号,然后再处理非基于几何的和特殊符号。

基于几何的符号如下:

  • SimpleLineSymbol: 用于表示线几何

  • SimpleMarkerSymbol: 用于表示点几何

  • SimpelFillSymbol: 用于表示多边形几何

SimpleLineSymbol

线符号构造函数是最简单的,因为它只需用样式、颜色和宽度三个参数来定义。

名称
模块名称 esri/symbols/SimpleLineSymbol
构造函数 new SimpleLineSymbol(style, color, and width)

style 是一个模块常量。该模块提供以下样式:

  • STYLE_DASH (创建由短划线组成的线)

  • STYLE_DASHDOT (创建由短划线点组成的线)

  • STYLE_DOT (创建由点组成的线)

该模块提供了其他样式常量,如 STYLE_LONGDASH, STYLE_LONGDASHDOT, STYLE_NULL, STYLE_SHORTDASH, STYLE_SHORTDASHDOT, STYLE_SHORTDASHDOTDOT, STYLE_SHORTDOT, 和 STYLE_SOLID

STYLE_SOLID 是默认样式,提供了一个连续的实线。

我们可以使用 simpleLineSymbol.setColor(color) 方法设置线的颜色;这里,color 是 Esri Color 对象,simpleLineSymbolSimpleLineSymbol 对象的一个实例。style 常量可以使用 setStyle(style) 方法设置。SimpleLineSymbol.toJson() 是一个重要的方法,它将 SimpleLineSymbol 转换为 ArcGIS 服务器 JSON 表示。

以下代码片段将创建一条红色实线:

var simpleLineSymbol = new SimpleLineSymbol();
var color = new Color("red");
simpleLineSymbol.setColor(color);
simpleLineSymbol.setWidth(2);

SimpleMarkerSymbol

SimpleMarkerSymbol 方法用于表示一个点。与表示线的复杂性相比,表示点几何有一个额外的复杂性,即它接受一个轮廓参数,该参数本身是一个 SimpleLineSymbol 对象。

SimpleMarkerSymbol

名称
模块名称 esri/symbols/SimpleMarkerSymbol
构造函数 new SimpleMarkerSymbol(style, size, outline, color)

该模块提供了以下样式常量:

  • STYLE_CIRCLE

  • STYLE_DIAMOND

  • STYLE_SQUARE

setAngle(angle) 方法按指定角度顺时针旋转符号。setColor(color) 方法设置符号的颜色。setOffset (xy) 设置屏幕单位中标记的 xy 偏移量。setOutline(outline) 设置标记符号的轮廓。setSize(size) 允许我们以像素为单位设置标记的大小。setStyle(style) 设置标记符号样式。toJson() 将对象转换为其 ArcGIS 服务器 JSON 表示。

ArcGIS 符号沙盘

如果选择合适的颜色和样式以及其他属性来表示一个符号似乎是一个困难的选择,下面的网页试图通过提供一个沙盒来生成任何类型的符号和定义类似符号所需的代码来帮助你。该网页位于 developers.arcgis.com/javascript/samples/playground/index.html

导航到此 URL 将使您进入类似以下截图的页面。我们可以选择几乎任何类型的符号:

ArcGIS symbol playground

选择其中一个将导航到另一个页面,您可以在该页面上选择属性并生成符号代码。

ArcGIS symbol playground

嗯,我们很容易生成了生成半透明、红色、菱形 SimpleMarkerSymbol(无轮廓)所需的代码:

// Modules required: 
// esri/symbols/SimpleMarkerSymbol
// esri/symbols/SimpleLineSymbol

var marker = new SimpleMarkerSymbol();
marker.setStyle(SimpleMarkerSymbol.STYLE_DIAMOND);
marker.setColor(new Color([255, 0, 0, 0.55]));
marker.setSize(25);

SimpleFillSymbol

SimpleFillSymbol模块帮助我们为多边形生成符号。

  • 模块名称:esri/symbols/SimpleFillSymbol

  • new SimpleFillSymbol(style, outline, color)

STYLE参数的一些模块常量如下:

  • STYLE_BACKWARD_DIAGONAL

  • STYLE_CROSS

  • STYLE_NULL

SimpleFillSymbol.STYLE_SOLID是默认样式。

PictureMarkerSymbol

当我们需要描绘一个图标来象征一个点几何体时,我们可以使用这个模块。我们不需要将颜色信息作为参数提供,而是需要一个图像 URL 来显示一个图片作为标记符号。

名称
模块 esri/symbols/PictureMarkerSymbol
构造函数 new PictureMarkerSymbol(url, width, height)

developers.arcgis.com/javascript/samples/portal_symbols/index.html找到的网页上可以帮助我们搜索适当的PictureMarkerSymbol

导航到此 URL 将打开下面显示的页面。当选择图片图标时,下方会生成代码。可以重用此代码来重新创建在网页中选择的PictureMarkerSymbology

生成的代码是PictureMarkerSymbol的 JSON 表示。JSON 对象提供以下属性:

  • angle

  • xoffset

  • yoffset

  • type

  • url

  • contentType

  • width

  • height

  • imageData

在这些中,imageDataurl是多余的,所以如果我们可以使用 URL 属性,我们可以避免imageData属性。imageData属性只是图像的Base64表示。为了避免这种情况,我们可以取消网页右上角的一个框,上面写着启用 Base64 编码之类的字样。

此外,如果anglexoffsetyoffset的值为 0,我们也可以省略这些。

PictureMarkerSymbol

使用此网页提供的图标的 URL 以及 ArcGIS Symbol Playground 将使我们能够进一步自定义PictureMarkerSymbol

PictureMarkerSymbol

要自定义PictureMakerSymbol,请使用以下内容:

// Modules required: 
// esri/symbols/PictureMarkerSymbol

var marker = new PictureMarkerSymbol();
marker.setHeight(64);
marker.setWidth(64);
marker.setUrl("http://static.arcgis.com/images/Symbols/Basic/RedStickpin.png");

PictureFillSymbol

PictureFillSymbol进一步让我们用图像填充多边形几何体。

PictureFillSymbol

TextSymbol

文本符号可以用来代替标签。文本符号缺乏几何信息,因此需要附加到几何体上。

TextSymbol

从 ArcGIS Symbol Playground 生成的以下片段演示了生成TextSymbol的组件:

// Modules required: 
// esri/symbols/TextSymbol
// esri/symbols/Font

var font = new Font();
font.setWeight(Font.WEIGHT_BOLD);
font.setSize(65);
var textSym = new TextSymbol();
textSym.setFont(font);
textSym.setColor(new Color([255, 0, 0, 1]));
textSym.setText("Sample Text");

使用渲染器

当应用程序使用从 Web 地图或 GIS 服务引用的图层时,Web 地图或服务本身提供了默认的绘图属性,确定图层的绘制方式。开发人员可以选择通过使用颜色、符号和渲染器来改变和增强要素的显示方式来覆盖这种行为。

您可以使用setSymbol()方法将符号应用于单个图形。当您想要将符号应用于动态、要素或图形图层中的所有图形时,可以使用渲染器。

渲染器使得可以快速地对许多要素进行符号化,可以使用单个符号或基于属性值使用多个符号。

ArcGIS API for JavaScript 中提供的一些渲染器如下:

  • SimpleRenderer:将相同的符号应用于图层中的所有图形

  • UniqueValueRenderer:根据每个图形的唯一属性值应用特定的符号

  • ClassBreaksRenderer:根据属性值的范围应用不同大小或颜色的符号

  • DotDensityRenderer:显示离散空间现象的空间密度变化

  • HeatmapRenderer:将点数据转换为显示高密度或加权区域集中度的光栅显示,使用模糊半径和强度值

  • TemporalRenderer:这可视化地图当前范围内的实时或历史观测,考虑相对要素老化和观测事件发生的轨迹,如飓风。

  • ScaleDependentRenderer:这根据地图的当前比例尺对同一图层应用不同的渲染器。

为场景选择渲染器

API 文档中的符号和渲染器指南提供了一个很好的指南,介绍了如何使用符号和渲染器。文档可以在developers.arcgis.com/javascript/jshelp/inside_renderers.html上访问。

UniqueValueRendererClassBreaksRenderer是基于属性的渲染器。这意味着属性值决定了要素的符号化方式。要确定在特定情况下使用UniqueValueRenderer还是ClassBreaksRenderer,需要考虑需要进行分类的字段值的性质。

注意

如果要渲染的字段上的唯一值集合较小且离散,请考虑使用UniqueValueRenderer

如果要渲染的字段上的唯一值集合具有广泛范围和/或是连续的,请考虑使用ClassBreaksRenderer

UniqueValueRendererClassBreaksRenderer具有defaultSymbol属性,当值或断点无法匹配时会使用。在开发过程中,您可以使用具有高对比度颜色的默认符号,快速验证是否有任何要素未能匹配渲染器的标准。

开发流量计应用程序

我们将开发一个流量计应用程序,以演示如何使用以下渲染器:

  • 简单渲染器

  • 独特值渲染器

  • 类别分隔渲染器

  • 热力图渲染器

数据源

流量计数据由 Esri 提供,作为其世界地图集的一部分。这意味着我们需要有 ArcGIS 开发者登录才能访问内容。流量计数据的地图服务的 URL 是livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/StreamGauge/MapServer/

地图服务提供了美国各地的流量计读数,显示了测量区域的当前水位。我们试图开发的应用程序旨在演示不同的渲染技术在流量计数据上的应用。下一节中即将呈现的快照提供了我们在本章结束时将开发的最终应用程序的初步呈现。

如果您没有 ArcGIS 开发者帐户,请参考第三章编写查询,了解如何注册帐户并在应用程序代理中使用凭据的说明。

简单渲染器

SimpleRendereresri/renderers/SimpleRenderer模块提供,其构造函数接受任何适当的符号或 JSON。由于所有的流量计位置都是点位置,我们将使用SimpleMarkerSymbol来对其进行符号化。

由于我们已经讨论了如何从相应的模块构建PictureMarkerSymbol,我们将看到如何使用符号的 JSON 形式。使用符号的 JSON 表示意味着我们不再需要为每个符号和颜色单独加载模块。以下快照显示了 JSON 是如何形成并在SimpleRenderer构造函数中使用的:

简单渲染器

在前面的代码中,在渲染器分配为SimpleRenderer之后,必须使用setRenderer()方法将渲染器对象设置为要素图层。此外,一旦渲染应用到要素图层上,图例应该被刷新:

streamLyr.setRenderer(renderer);
streamLyr.redraw();
legend.refresh();

应用独特值渲染器

唯一值渲染器由esri/renderers/UniqueValueRenderer模块提供。唯一值渲染器允许我们为数据中一组唯一值定义不同的符号。最多可以提供三个属性字段来确定数据的唯一性。唯一值渲染器期望uniqueValueInfos对象。该对象基本上是唯一值和用于表示该值的符号之间的映射。因此,所有具有特定值的要素将由相应的映射符号渲染。我们可以为渲染器提供defaultSymbol对象,该对象将用于表示在uniqueValueInfos对象中未定义的任何值。以下是 JSON 表示唯一值渲染器对象,用于表示洪水阶段的唯一值。我们要表示的洪水阶段的唯一值如下:

  • 主要

  • 适度

  • 次要

  • 行动

var rendererJson = {
  "type": "uniqueValue",
  "field1": "STAGE",
  "defaultSymbol": {},
  "uniqueValueInfos": [{
    "value": "major",
    "symbol": {
      "color": [163, 193, 163],
      "size": 6,
      "type": "esriSMS",
      "style": "esriSMSCircle"
    }
        }, {
    "value": "moderate",
    "symbol": {
      "color": [253, 237, 178],
      "size": 6,
      "type": "esriSMS",
      "style": "esriSMSCircle"
    }
        }, {
    "value": "minor",
    "symbol": {
      "color": [242, 226, 206],
      "size": 6,
      "type": "esriSMS",
      "style": "esriSMSCircle"
    }
        }, {
    "value": "action",
    "symbol": {
      "color": [210, 105, 30],
      "size": 6,
      "type": "esriSMS",
      "style": "esriSMSCircle"
    }
  }]
};
var renderer = new UniqueValueRenderer(rendererJson);

上述代码在应用程序中呈现如下:

应用唯一值渲染器

以下属性可用于特征图层,根据多个视觉属性(如颜色旋转大小不透明度)进行渲染:

渲染器方法 目的
setColorInfo() 这显示使用颜色渐变的连续值数组
setRotationInfo() 这会旋转一个符号以指示方向的变化(例如,行驶的车辆或飓风事件)
setSizeInfo() 这会根据一系列数据值的范围更改符号大小或宽度
setOpacityInfo 这会更改用于显示图层的 alpha 值

类别分隔渲染器

当字段被分类和视觉区分时,它会分布在一系列值上,我们可以使用ClassBreaksRenderer。可以通过加载esri/renderers/ClassBreaksRenderer模块来使用ClassBreaksRenderer

类别分隔渲染器与唯一值渲染器非常相似,因为类别分隔渲染器的构造函数期望一个classBreakInfos对象,这与uniqueValueInfos对象类似。

classBreakInfos是一个classBreakInfo对象数组,它将类范围和符号进行了映射。类范围由类的最小值(classMinValue)和类的最大值(classMaxValue)定义。

类别分隔渲染器

以下快照显示了如何使用classBreakInfo数组构建ClassBreakRenderer JSON 对象,并在地图上呈现:

类别分隔渲染器

热力图渲染器

HeatmapRenderer将点数据渲染成突出显示更高密度或加权值的光栅可视化。该渲染器使用正态分布曲线在垂直和水平方向上分布值。

这个平均函数被水平和垂直应用,以产生一个模糊的影响区域,而不是一个特定的单一点。

HeatmapRenderer模块构造函数接受一个颜色数组。第一种颜色用于表示最小影响的区域,数组中的最后一种颜色用于表示像素的最高影响。我们还可以为HeatmapRenderer构造函数定义其他参数,如blurRadius、最大像素强度和最小像素强度。以下代码快照用于生成HeatmapRenderer

热力图渲染器

点密度渲染器

DotDensityRenderer提供了创建数据的点密度可视化的能力。点密度地图可以用来可视化离散空间现象的空间密度变化。我们可以使用多个字段以不同颜色在同一地图上可视化多个变量。例如,我们可以使用不同的颜色来显示各种族群的分布。地图上的密度随着用户的放大或缩小而变化。使用ScaleDependentRenderer为每个比例或缩放范围设置唯一的点密度渲染器,以便dotValuedotSize可以在多个比例范围内变化。

BlendRenderer

ClassBreakRendererUniqueValueRenderer的问题在于,您必须为任何给定值分配特定的颜色。当基于明确的边界值分配离散颜色不可取时,我们可以使用BlendRenderer

BlendRenderer让您对数据进行模糊分类。它允许您为不同字段的值分配不同的颜色,并使用一些不透明度来表示值的大小。由于我们对每个字段使用了不透明度,最终的渲染将是这些颜色的混合。这张图显示了如何混合颜色和不透明度变量以提供渲染:

BlendRenderer

以下地图显示了美国各地主要少数民族群体的地图。这样的插图可以给出主要特征的感觉,同时不完全压制其他细节:

BlendRenderer

智能映射

SmartMapping模块提供了许多辅助方法,帮助我们选择最佳的渲染方法。以下插图显示了SmartMapping模块提供的方法列表:

SmartMapping

注意

智能映射模块:esri/renderers/smartMapping

类别渲染器的分类方法

类别渲染器辅助方法,如createClassedColorRenderer()createClassedSizeRenderer(),需要classificationMethod作为参数。如果我们需要理解每个值的重要性,选择这个值是非常重要的。

以下分类方法可用:

  • 等间隔

  • 自然断点

  • 四分位数

  • 标准差

默认方法是等间隔的。

等间隔分类将数据平均分成预定义数量的类别。这样的分类可能不一定反映数据的偏斜。例如,如果数据范围是 0-100 万,而大部分数据集中在 30 万-50 万之间,那么与其将数据分类为 0-25 万、25 万-50 万、50 万-75 万和 75 万-100 万,更好的分类方案是在 30 万-50 万之间有更多的分类范围。

自然断点、四分位数和标准差等分类方法有助于更好地分隔数据;因此,我们的数据可视化技术将在统计上更加准确。这个主题将在第七章中进行更详细的讨论,地图分析和可视化技术

总结

本章深入探讨了颜色、符号、渲染器以及每种方法可以有效使用的情况。本章还涉及了数据可视化技术的细微差别,以及创建符号和图片标记符号的技巧和窍门。我们通过开发一个流量计应用程序来展示了三种基本渲染器的实用性:简单渲染器、唯一值渲染器和分级渲染器。在接下来的章节中,我们将探讨高级可视化技术,以在空间和时间尺度上对数据进行视觉分类。

第六章:处理实时数据

不断更新的数据给我们在检索和渲染它们方面带来了重大挑战。在本章中,我们将通过开发一个旨在跟踪飓风的应用程序来处理实时数据的两种基本方法。在本章中,您将学习以下主题:

  • 了解实时数据的性质,如飓风数据

  • 使用 ArcGIS 提供的内置选项来可视化数据

  • 获取最新数据的方法

  • 设置图层的刷新间隔的方法

应用程序背景

我们将处理由国家飓风中心(NHC)提供的飓风数据。NHC 提供了描述热带飓风活动路径和预测的地图服务。NHC 提供的实时数据可以在livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Hurricane_Active/MapServer找到。

地图服务提供以下数据:

  • 预测位置

  • 观测位置

  • 预测路径

  • 观测路径

  • 不确定性锥

  • 警报和警告

  • 热带风暴力

预测和观测位置代表飓风的中心,而路径代表连接的预测和观测位置,以便了解飓风的移动方向。

应用程序背景

服务目录标题下,单击ArcGIS.com 地图以全面了解地图服务中的数据。

可视化地图数据

ArcGIS Online 是可视化和使用 ArcGIS Server 上托管的数据的有效媒介。在 ArcGIS Online 中打开地图服务时,会显示默认的符号,并且我们可以了解我们在应用程序中将要使用的数据的范围。

在以下截图中,我们可以看到预测位置要素图层及其默认符号。使用的符号是 PictureMarkerSymbol,它可以让我们了解过去三天(72 小时)飓风的强度。

可视化地图数据

以下截图全面展示了地图服务中的所有数据,包括预测位置和路径,以及观测位置:

可视化地图数据

关闭目录中的所有图层,只打开观测位置图层。观测位置图层只是由简单的渲染器渲染的。符号不会根据任何字段值的大小而变化。它只显示过去 72 小时内测得的风暴活动的位置。

可视化地图数据

现在 ArcGIS Online 为我们提供了各种设置其符号的选项。当我们在目录中点击图层的名称时,会打开以下屏幕。它显示了基于哪些样式可以更改符号。在以下截图中,强度的风暴被选择为显示的字段,并且符号的大小基于强度值的数量:

可视化地图数据

数据可以根据各种分类技术进行分类,例如等间隔分位数自然间隔等。

可视化地图数据

最后,观测路径实际上显示了过去 72 小时飓风所经过的路径,并使用唯一值渲染器来渲染数据。

可视化地图数据

构建飓风追踪应用程序

现在我们已经通过 ArcGIS Online 服务了解了我们的数据,我们可以使用地图服务 URL 构建自己的网络地图应用程序。在我们的应用程序中,我们打算包括以下内容:

  • 向地图添加显示过去和现在飓风位置的图层

  • 添加全球风数据

  • 添加一个仪表小部件来显示风速

  • 添加一个当前天气小部件,显示用户浏览器位置的当前天气信息

  • 添加一个当前飓风列表小部件,显示当前飓风的更新列表以及选择时每个飓风的详细信息

符号化活跃的飓风层

我们有多个要处理的要素层。让我们尝试构建一个图层字典。在以下代码片段中,我们将尝试创建一个对象数组,其中每个对象都具有诸如 URL 和标题之类的属性。URL 是指要素层的 URL,标题属性是指我们想要引用要素层的标题:

var windDataURL = "http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/NOAA_METAR_current_wind_speed_direction/MapServer";

var activeHurricaneURL = "http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Hurricane_Active/MapServer";

var layerDict = [
          {
            title: "Forecast Error Cone",
            URL: activeHurricaneURL + "/4"
          },
          {
            title: "Forecast Tracks",
            URL: activeHurricaneURL + "/2"
          },
          {
            title: "Observed Track",
            URL: activeHurricaneURL + "/3"
          },
          {
            title: "Watches and Warnings",
            URL: activeHurricaneURL + "/5"
          },
          {
            title: "Forecast Positions",
            URL: activeHurricaneURL + "/0"
          },
          {
            title: "Past Positions",
            URL: activeHurricaneURL + "/1"
          },
          {
            title: "Wind Data",
            URL: windDataURL + "/0"
          }
        ];

这有助于我们使用图层名称或标题属性检索要素层。让我们使用dojo/_base/array模块提供的array.map()方法将每个对象的相应要素层添加到layerDict数组中。array.map()方法,如果你还记得第一章, API 的基础,实际上是遍历数组中的元素并返回一个数组。然后,可以修改正在迭代的每个项目。在我们的情况下,我们正在尝试对每个项目执行以下操作:

  1. 从每个项目的 URL 创建一个要素层。

  2. 将要素层添加到地图中。

  3. layerDict数组中的每个项目对象中添加一个额外的图层属性。

以下代码片段解释了这个过程:

var layerDict = array.map(layerDict, function (item) {
          var featLayer = new FeatureLayer(item.URL, {
            mode: FeatureLayer.MODE_ONDEMAND,
            outFields: ["*"]
              //infoTemplate: infoTemplate
          });
          map.addLayer(featLayer);
          item.layer = featLayer;
          return item;
        });

现在layerDict数组中的每个对象都将具有一个额外的图层属性,该属性保存了由 URL 引用的要素层。

要检索要素层,我们可以使用dojo/_base/array模块提供的array.filter()方法中的图层名称。filter方法()遍历每个对象项,并根据我们的谓词条件返回一个过滤后的数组。

以下代码行返回标题为“预测误差锥”的要素层,并将其保存在名为foreCastErrorConeFeatureLayer的变量中:

var foreCastErrorConeFeatureLayer = array.filter(layerDict, function (item) 
{
  return item.title == "Forecast Error Cone";
})[0].layer;

我们正在尝试对一些要素层中的要素进行符号化。我们将从过去的位置开始。过去的位置特征层默认情况下由一个带有中心点的圆表示。我们将尝试使用红旗来表示它。将采取以下方法来对其进行符号化:

  1. 导入esri/symbols/PictureMarkerSymbol模块。

  2. 查找代表红旗的 PNG 的 URL,并使用它创建一个PictureMarkerSymbol

  3. 导入esri/renderers/SimpleRenderer模块,并创建一个SimpleRenderer,为渲染器分配我们刚刚创建的PictureMarkerSymbol的符号。

  4. 为要素层设置我们刚刚创建的简单渲染器的渲染器。

以下代码行清楚地解释了这个过程:

var pastPositionLayer = array.filter(layerDict, function (item) {
    return item.title == "Past Positions";
})[0].layer;

var pastPositionSymbol = new PictureMarkerSymbol({
  "angle": 0,
  "type": "esriPMS",
  "url": http://static.arcgis.com/images/Symbols/Basic/RedFlag.png",
  "contentType": "image/png",
  "width": 18,
  "height": 18
});

var pastPositionRenderer = new SimpleRenderer(pastPositionSymbol);
pastPositionLayer.setRenderer(pastPositionRenderer);

现在,我们可以尝试渲染预测误差锥层。预测误差锥是代表预测预测中的不确定性的多边形要素层。每种飓风类型都有两个多边形要素。一个多边形代表 72 小时的预测误差多边形,另一个代表 120 小时的预测误差多边形。这些信息在要素层的FCSTPRD字段中可用。

让我们创建一个唯一值渲染器,并根据FCSTPRD字段名称的值以不同的方式对每种类型的多边形进行符号化。要创建唯一值渲染器,我们需要采取以下方法:

  1. 导入esri/renderers/UniqueValueRendereresri/symbols/SimpleLineSymbolesri/symbols/SimpleFillSymbol模块。

  2. 为渲染器创建一个默认符号。由于我们知道对于所有我们的预测误差多边形,FCSTPRD字段值将是72120,我们将创建一个具有空符号的SimpleFillSymbol,并将其轮廓设置为 null 线符号。

  3. esri/renderers/UniqueValueRenderer模块创建一个UniqueValueRenderer对象。将其分配为我们刚刚创建的默认符号以及FCSTPRD作为渲染基础的字段名。

  4. 使用addValue()方法向渲染器添加值。addValue()方法接受每个唯一值(72 / 120)及其对应的符号。

  5. 将渲染器设置为预测误差锥体要素图层

**//Get the Forecast Error Cone feature layer**
var foreCastErrorConeFeatureLayer = array.filter(layerDict, function (item) {
  return item.title == "Forecast Error Cone";
})[0].layer;

**//Create a Null SimpleFillSymbol**
var defaultSymbol = new SimpleFillSymbol().setStyle(SimpleFillSymbol.STYLE_NULL);

**//With a null Line Symbol as its outline**
defaultSymbol.outline.setStyle(SimpleLineSymbol.STYLE_NULL);

var renderer = new UniqueValueRenderer(defaultSymbol, "FCSTPRD");

**//add symbol for each possible value**
renderer.addValue('72', new SimpleFillSymbol().setColor(new Color([255, 0, 0, 0.5])));
renderer.addValue('120', new SimpleFillSymbol().setColor(new Color([255, 255, 0, 0.5])));

**//Set Renderer**
foreCastErrorConeFeatureLayer.setRenderer(renderer);

我们已经尝试使用PictureMarkerSymbol标志化要素图层,并使用SimpleRenderer进行渲染。对于另一个要素图层,我们使用了唯一值渲染器,以不同的方式渲染具有特定字段不同值的要素。现在让我们尝试一种称为CartographicLineSymbol的特殊符号。

CartographicLineSymbol提供了额外的属性,如端点和连接,定义了线的端点和边缘连接的呈现方式。要了解有关这两个属性的更多信息,请访问 API 页面developers.arcgis.com/javascript/jsapi/cartographiclinesymbol-amd.html

我们想要使用CartographicLineSymbol来标志预测轨迹要素图层。以下显示了如何使用该符号并渲染特定要素图层:

  1. 导入esri/symbols/CartographicLineSymbol模块。

  2. 对于样式参数,使用STYLE_DASHDOT,颜色参数为黄色,像素宽度为5,端点类型为CAP_ROUND,连接类型为JOIN_MITER

  3. 使用SimpleRenderer的符号。

  4. 将渲染器设置为预测轨迹要素图层。

以下代码片段对先前的方法进行了编码:

var lineSymbol = new CartographicLineSymbol(
  CartographicLineSymbol.STYLE_DASHDOT,
  new Color([255, 255, 0]), 5,
  CartographicLineSymbol.CAP_ROUND,
  CartographicLineSymbol.JOIN_MITER, 5
);
var CartoLineRenderer = new SimpleRenderer(lineSymbol);

forecastTrackLayer.setRenderer(CartoLineRenderer);

当先前的渲染器应用于过去位置图层、预测轨迹预测误差锥体图层时,我们的地图如下所示:

标志化活动飓风图层

添加全球风数据仪表

全球风数据也是 ArcGIS 实时数据提供的地图服务,提供各个位置的全球级风数据。我们的目标是合并一个仪表部件,根据悬停的风位置改变其仪表读数。风数据已经被适当地默认标志化。

以下屏幕截图显示了基于我们的全球风数据的仪表部件。地图中的箭头是风特征位置,箭头的方向表示风的方向,箭头的颜色和大小表示风的速度。两个示例中的仪表读数表示悬停在其上的特征(由一个粗黄色圆圈突出显示)。

添加全球风数据仪表

风数据的 URL 已在我们先前的代码片段中提供,并已添加到layerDict数组中:

var activeHurricaneURL = "http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Hurricane_Active/MapServer";

由于此 URL 已添加到layerDict数组中,我们可以继续创建一个表示来自其标题"Wind Data"的风数据的要素图层:

var windFeatureLayer = array.filter(layerDict, function (item) {
          return item.title == "Wind Data";
        })[0].layer;

现在让我们添加一个仪表部件,可以利用来自该图层的数据。该仪表由 Esri 的dijit(dojo 部件)esri/dijit/Gauge提供。仪表构造函数非常简单。它接受一个GaugeParameter对象和容器 dom ID。

GaugeParameter对象需要我们构建。在创建GaugeParameter对象之前,请记住以下几点:

  1. layer属性接受表示要素图层的引用。

  2. dataField属性指示应使用哪个字段来获取仪表读数。

  3. dataFormat属性接受两个值——valuepercent。当选择百分比时,仪表的最大值会自动计算,并且仪表读数显示为最大值的百分比。当dataFormat值选择为value时,悬停的要素的实际值将显示为仪表读数。

  4. dataLabelField属性可用于表示站点名称或关于所悬停特征的任何其他辅助属性,这些属性可以标识特征。这应该与title属性结合使用,它表示dataLabelField属性表示的内容。

  5. color属性让我们设置仪表读数的颜色。

  6. 如果value被选择为dataFormat的值,我们还需要为maxDataValue属性提供一个值。

以下代码是我们用来创建你在之前截图中看到的风速计小部件的代码:

var windGaugeParams = {
          caption: "Wind Speed Meter",
          dataFormat: "value",
          dataField: 'WIND_SPEED',
          dataLabelField: "STATION_NAME",
          layer: windFeatureLayer,
          color: "#F00",
          maxDataValue: 80,
          title: 'Station Name',
          unitLabel: " mph"
        };
var windGauge = new Gauge(windGaugeParams, "gauge");
windGauge.startup();

跟踪最新的活跃飓风

让我们创建一个小部件来跟踪最新的活跃飓风。我们已经有了代表活跃飓风位置的所有图层。我们的目标是获取所有活跃飓风的最新位置,并在小部件中显示出来。

以下截图显示了我们的小部件在开发后的样子:

跟踪最新的活跃飓风

小部件中的下拉框列出了所有流行的活跃飓风的名称。以下网格显示了所选飓风的详情。

以下思路已经纳入到了这个小部件的开发中:

  1. 使用缓存破坏查询来获取风暴名称的唯一列表,并用这个列表填充下拉框。

  2. 在下拉框的选择更改时,获取所选风暴的最新要素。

  3. 在小部件中填充所选风暴的详情。

  4. 每 30 秒获取更新的详情。

获取风暴的唯一列表

为了获取我们数据中的唯一值,查询对象有一个名为returnDistinctValues的属性,其值应为布尔值true。以下代码片段解释了该属性的用法:

query.returnDistinctValues = true;

此外,查询对象的 outfield 属性应该只列出那些需要唯一值的字段。在我们的情况下,字段名是STORMNAME。请参考以下代码片段以了解这一点:

query.outFields = ["STORMNAME"];

为了每次都能获得更新的结果,我们需要避免缓存的查询结果。所以我们可能需要使用一个类似于1=1的模式,而不是使用一个真值表达式。

"random_number = random_number".

这将帮助我们获得非缓存的查询结果。非缓存的查询结果确保我们在一定时间内查看到的是最新数据。让我们编写一个可以创建这样的查询字符串的函数:

var _bust_cache_query_string: function () {
  var num = Math.random();
  return num + "=" + num;
}

现在我们可以在每次需要为查询对象的where属性分配一个值时使用这个函数:

query.where = this._bust_cache_query_string();

在查询对象中使用returnDistinctValues属性时,我们需要将returnGeometry属性设置为布尔值false。以下代码解释了如何形成查询任务和查询对象,以及如何使用查询结果来填充下拉框。在代码的结尾,我们将调用一个_update_hutticane_details()方法。这个方法获取所选StormName的最新详情:

events: function () {
  //initialize query task
  var queryTask = new QueryTask("http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Hurricane_Active/MapServer/1");

  //initialize query
  var query = new Query();
  query.returnGeometry = false;
  query.where = "1=1 AND " + this._bust_cache_query_string();
  query.outFields = ["STORMNAME"];
  query.returnDistinctValues = true;
  var that = this;

  queryTask.execute(query, function (result) {
    console.log(result);

    var i;
    //Remove all existing items

    for (i = that.cbxactiveHurricane.options.length - 1; i >= 0; i--) {
      that.cbxactiveHurricane.remove(i);
    }
    //Fill n the new values
    array.forEach(result.features, function (feature) {
      console.debug(feature.attributes.STORMNAME);
      that.cbxactiveHurricane.options[that.cbxactiveHurricane.options.length] = new Option(feature.attributes.STORMNAME, feature.attributes.STORMNAME);
    });
    that._update_hutticane_details();
  });

this.updateTimmer = setInterval(lang.hitch(this, this._update_hutticane_details), 30000);
}

在前面的代码行中,观察最后三行。我们使用一个timer函数,每 30 秒调用一次_update_hutticane_details()。这是一个获取飓风最新详情的函数。

获取最新数据并在网格上显示

在前面的代码片段中,当我们尝试构建查询对象时,我们使用了returnDistinctValues属性来根据字段名获取不同的值。现在我们将使用查询对象的orderByFields属性来根据字段名对要素进行排序。为了首先获取最新的要素,字段名应该代表一个时间字段。在我们的情况下,字段名是DTG。为了确保我们获取查询结果的最新时间作为第一个要素,我们可以在构建查询对象时使用以下代码行。orderByField接受一个字符串数组,每个项目都提到了要根据哪个字段名进行排序,以及排序是升序(ASC)还是降序(DESC)。默认顺序是升序:

query.orderByFields = ["DTG DESC"];

以下代码行演示了如何构建所需的查询对象以及如何使用结果来填充小部件中关于最新风暴的信息:

_update_hutticane_details: function () {
  var selected_hurricane = this.cbxactiveHurricane.value;

  var queryTask = new QueryTask("http://livefeeds.arcgis.com/arcgis/rest/services/LiveFeeds/Hurricane_Active/MapServer/1");
  var query = new Query();
  query.returnGeometry = true;
  query.where = "STORMNAME='"+ selected_hurricane +"' AND " + this._bust_cache_query_string();
  query.outFields = ["*"];
  **query.orderByFields = ["DTG DESC"];**
  var that = this;
  queryTask.execute(query, function (result) {
    console.log(result);
    if (result.features.length>0){
      that._mslp.innerHTML = result.features[0].attributes.MSLP;
      that._basin.innerHTML = result.features[0].attributes.BASIN;
      that._stormnum.innerHTML = result.features[0].attributes.STORMNUM;
      that._stormtype.innerHTML = result.features[0].attributes.STORMTYPE;
      that._intensity.innerHTML = result.features[0].attributes.INTENSITY;
      that._ss.innerHTML = result.features[0].attributes.SS;
    }
  });
}

请注意上一段代码中的where子句。我们仅选择了从下拉框中选择的StormName的详细信息,并使用缓存破坏函数获取最新数据:

query.where = "STORMNAME='"+ selected_hurricane +"' AND " + this._bust_cache_query_string();

刷新要素图层

显示时间数据的要素图层可能需要在各种间隔时间刷新。我们可以使用要素图层来刷新间隔属性以设置此功能:

featureLayer. refreshInterval = 5; // in minutes

这是我们之前处理的缓存破坏技术的补充。

创建天气小部件

我们将尝试在我们的应用程序中创建一个天气小部件,该小部件显示用户所在位置的当前天气状况。用户的位置实际上是指现代浏览器中地理位置 API 识别的浏览器位置。当浏览器无法找到用户的位置时,我们将尝试找到地图中心的天气数据。创建天气小部件为我们提供了以下机会和挑战:

  • 天气数据在实时不断更新,并且是一个时空现象,意味着随着地点和时间的变化而变化

  • 它为我们提供了使用外部天气 API 的机会,这是一个非 ArcGIS 基础的数据

  • 它为我们提供了一个探索客户端几何操作的机会,例如缓冲区和地理和 Web 墨卡托坐标之间的转换

开放天气 API

我们需要找到一个数据源来获取最新的天气数据。幸运的是,开放天气 API 是获取不同格式的天气数据的简单免费选项。付费计划提供更大的使用级别。对于我们的目的,免费版本效果很好。

该 API 提供 REST 端点,可提供以下类型的数据:

  • 当前天气数据

  • 5 天/3 小时预报

  • 5 天/3 小时预报

  • 历史数据

  • 紫外线指数

  • 天气地图图层

  • 气象站

我们将使用当前天气数据端点来获取给定位置的天气详情。

要访问 API,您需要注册 API 密钥。以下 URL 解释了如何获取appid并在 REST 查询中使用它:openweathermap.org/appid#get

我们将使用的基本 URL 是这个:

var url = "http://api.openweathermap.org/data/2.5/weathers";

我们将提供纬度和经度值以发出请求到开放天气 API。我们尝试使用esriRequest对象进行 HTTP GET请求,需要导入esri/request模块。以下片段解释了如何构建esriRequest对象:

var request = esriRequest({
  // Location of the data
  url: this.url + '?lat=' + this.lat + '&lon=' + this.lon + '&appid=' + this.apikey,

  handleAs: "json"
});

如果观察正在构建的 URL,它需要三个参数,即latlonappid

appid参数接受我们之前生成的应用程序密钥。我们将遵循两种方法来获取纬度和经度值:

  1. 如果浏览器支持地理位置 API,则从浏览器位置获取纬度和经度值。

  2. 如果浏览器不支持地理位置 API,则将地图范围的中心点投影到地理坐标,并用于获取该位置的天气数据。

使用地理位置 API

使用地理位置 API 就像调用导航器对象的geolocation.getCurrentPosition()方法一样简单。该方法返回一个回调对象,其中包含浏览器的位置。以下代码行显示了如何调用geolocationAPI 以获取浏览器的当前位置:

getLocation: function () {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(lang.hitch(this, this.showPosition));
  } else {
    console.log("Geolocation is not supported by this browser.");
  }
}

在上述代码中,调用对象是一个名为showPosition()的函数。showPosition()函数将位置作为回调对象。可以使用coords属性访问位置的坐标。

在输入数据上使用几何引擎

coords对象具有三个属性,即:

  • 纬度

  • 经度

  • 准确性

我们清楚地了解了纬度和经度,但精度是什么?精度是表示由 API 提供的坐标可能存在的误差的数值数量,单位为米。换句话说,位置在一个误差圆内是准确的。当我们提到它是一个误差圆时,能否在地图上将其可视化,这样我们就可以知道浏览器的大致位置,也许可以证实结果。我们尝试了一下;看起来相当准确。为了创建一个误差圆,我们采取了以下方法:

  1. 使用纬度和经度值创建一个点几何体。

  2. 使用 API 提供的webMercatorUtils将点从地理坐标转换为 Web 墨卡托坐标。

  3. 使用 API 提供的geometryEngine模块,围绕投影点创建一个缓冲区,缓冲区半径等于位置的精度。

  4. 使用SimpleFillSymbol对缓冲区几何进行符号化。

以下代码行清楚地解释了前面的过程:

showPosition: function (position) {
  console.log(position);
  this.accuracy = position.coords.accuracy;
  this.lat = position.coords.latitude;
  this.lon = position.coords.longitude;

  //error circle
  var location_geom = new Point(this.lon, this.lat, new SpatialReference({ wkid: 4326 }));
  var loc_geom_proj = webMercatorUtils.geographicToWebMercator(location_geom);
  var location_buffer = geometryEngine.geodesicBuffer(loc_geom_proj, this.accuracy, "meters", false);

  console.log(location_buffer);
  var symbol = new SimpleFillSymbol().setColor(new Color([255, 0, 0, 0.5]));
  this.map.graphics.add(new Graphic(location_buffer, symbol));
  //this.map.setExtent(location_buffer.getExtent());
  this.getWeatherData();
}

我们将使用从showPosition()方法获取的纬度和经度来获取该位置的天气数据。

在小部件中显示天气数据

我们之前讨论了如何使用esriRequest模块向天气 API 发出 HTTP GET 请求,并请求获取浏览器提供的纬度和经度的当前天气数据。该请求是一个 promise,我们将使用then方法来解析它。

下面的代码块演示了esriRequest promise 是如何被解析以及如何用来显示当前天气数据的:

request.then(function (data) {
  console.log("Data: ", data);
  that.weather.innerHTML = Math.round(data.main.temp - 270) + " deg C " +
  data.weather[0].main + ' (' + data.weather[0].description + ')';
  var imagePath = "http://openweathermap.org/img/w/" + data.weather[0].icon + ".png";
  // Set the image 'src' attribute
  domAttr.set(that.weatherIcon, "src", imagePath);
  that.windSpeed.innerHTML = data.wind.speed + ' kmph';
  that.cloudiness.innerHTML = data.clouds.all + ' %';
  that.pressure.innerHTML = data.main.pressure;
  that.humidity.innerHTML = data.main.humidity + ' %';
  that.pressure.innerHTML = data.main.pressure + ' Pa'
  that.sunrise.innerHTML = that._processDate(data.sys.sunrise);
  that.sunset.innerHTML = that._processDate(data.sys.sunset);
  that.coords.innerHTML = data.coord.lon + ', ' + data.coord.lat;
}

在之前的代码中,温度总是以开尔文返回。因此,为了将其转换为摄氏度,我们需要减去270。时间转换是使用名为_processDate()的函数应用的。由 open weather API 发布的时间是基于 UTC 的 Unix 时间。

我们编写的_processDate()函数如下所示:

_processDate: function (dateStr) {
  if (dateStr == null) {
    return "";
  }
  var a = new Date(dateStr * 1000);
  return dateLocale.format(a, {
    selector: "date",
    datePattern: "yyyy-MM-dd HH.mm v"
  });
}

在前面的函数中使用的dateLocale对象是一个 dojo 模块(dojo/date/locale),它提供了处理的date对象的本地化时间版本。小部件如下所示。红色圆圈就是我们谈论的误差圆。我们还能够创建一个小的天气图标,总结天气状况。

在小部件中显示天气数据

如果你好奇之前小部件的 HTML 模板会是什么样子,我们有一件事要说——我们让你失望了吗?在这里:

<div>
  <form role="form">
    <div class="form-group">
      <label dojoAttachPoint="weather"></label>
      <img dojoAttachPoint="weatherIcon"/>
    </div>
  </form>
  <table class="table table-striped">
    <tbody>
      <tr>
        <td>Wind</td>
        <td dojoAttachPoint="windSpeed"></td>
      </tr>
      <tr>
        <td>Cloudiness</td>
        <td dojoAttachPoint="cloudiness"></td>
      </tr>
      <tr>
        <td>Pressure</td>
        <td dojoAttachPoint="pressure"></td>
      </tr>
      <tr>
        <td>Humidity</td>
        <td dojoAttachPoint="humidity"></td>
      </tr>
      <tr>
        <td>Sunrise</td>
        <td dojoAttachPoint="sunrise"></td>
      </tr>
      <tr>
        <td>Sunset</td>
        <td dojoAttachPoint="sunset"></td>
      </tr>
      <tr>
        <td>Geo coords</td>
        <td dojoAttachPoint="coords"></td>
      </tr>
    </tbody>
  </table>
</div>

无害的 HTML 模板是我们开发天气小部件所需的全部内容,我们用它来显示我们所在位置的当前天气数据。

总结

在本章中,我们详细介绍了实时数据的构成以及如何可视化和获取最新特性。我们将讨论如何处理时态图层以及如何在后续章节中可视化时空图层。因此,我们将能够构建持续刷新的有效网络应用程序。在接下来的章节中,我们将讨论使用要素图层的统计功能的高级可视化技术,并学习有关图表库的知识。

第七章:地图分析和可视化技术

对地图数据进行分析将揭示许多空间模式,否则这些模式将保持隐藏。 API 提供了许多方法来使用数据上的高级统计查询来获取这些信息。结合 API 提供的直观数据可视化方法,您将更接近成为地图数据科学家。在本章中,我们将首先尝试了解一些基本的统计概念,然后通过在 API 提供的分析和渲染模块的帮助下在代码中实际应用这些概念。具体来说,我们将涵盖以下主题:

  • 介绍我们将要开发的人口统计分析门户

  • 基本统计量介绍

  • API 提供的模块来计算要素统计信息

  • 分类方法的简要介绍

  • 使用代码支持的解释来开发具有视觉变量的渲染器

  • 执行多变量映射

  • 使用智能映射执行自动映射

构建人口统计分析门户

我们将构建一个人口统计分析门户,以展示 API 的高级分析功能。人口统计是指根据各种社会经济因素对居住在某一地区的人口进行分类,例如年龄、教育程度、国籍、家庭收入中位数、种族、性别等。人口统计数据主要基于人口普查数据和其他可靠来源。

人口统计数据可用于执行各种分析,并且对政府做出政策决策和企业做出营销决策同样有用。人口统计数据的力量在于执行适当的分析,以便我们可以提取有关居住在某一地区的人口与周围人口的有用信息。让我们考虑这个网址,它提供了关于街区层面的家庭收入中位数的详细统计数据 - demographics5.arcgis.com/arcgis/rest/services/USA_Demographics_and_Boundaries_2015/MapServer

该地图服务显示了 2015 年美国最新的人口统计数据。在提供的数百个人口统计参数中,我们对 2015 年美国家庭收入中位数感兴趣。收入金额以当前美元表示,包括通货膨胀或生活成本增加的调整。中位数是将家庭收入分布为两个相等部分的值。有关此地图的更多信息,包括使用条款,请访问此网址:doc.arcgis.com/en/living-atlas/item/?itemId=6db428407492470b8db45edaa0de44c1&subType=demographics

这些数据是 Esri 的 Living Atlas 项目的一部分。要使用这些数据,您将需要 ArcGIS Online 组织订阅或 ArcGIS 开发人员帐户。要访问此项目,您需要执行以下操作之一:

  • 使用组织订阅的成员帐户登录

  • 使用开发人员帐户登录

  • 如果您没有帐户,可以在此链接[https://developers.arcgis.com/en/sign-up/]注册 ArcGIS 的免费试用版或免费的 ArcGIS 开发人员帐户

基本统计量

让我们讨论一些基本统计数据,以便我们可以充分利用 API 提供的统计功能。在进一步进行之前,我们可能需要清楚地了解五个基本统计参数:

  • 最小值

  • 最大值

  • 平均值

  • 标准差

  • 标准化

最小值

顾名思义,这意味着数据集中的最小值。在我们的案例中,对于街区级别的家庭收入,最小统计数据表示具有最低家庭收入中位数的街区。

最大值

最小类似,最大统计量定义了所有考虑的街区中的最大家庭收入中位数值。

总和

Sum是一个简单而有效的统计量,它给出了所有考虑的数据的总值。

平均值

Average统计定义了所有值的算术平均值。平均值是通过将Sum统计量除以用于计算的数据值的计数来推导的。

Average = Sum / Count

标准差

标准差可能是从任何给定数据中推导出的最重要的统计量。标准差是数据的分散程度或数据偏离平均值或平均值的度量。当我们知道标准差时,通常可以观察到:

  • 68%的数值在平均值加减一个标准差的范围内

  • 95%的数值在平均值加减两倍标准差的范围内

  • 99.7%的数值在平均值加减三倍标准差的范围内

这是基于大多数数据遵循正态分布曲线的事实。当我们对数据进行排序并绘制数值时,直方图看起来像钟形曲线。

标准化

了解标准差和平均值的概念后,我们可以对数据进行标准化。这个过程被称为标准化,从这个过程中得到的统计量被称为标准分数z-score)。当我们有大量值的数据集时,标准化是总结数据和量化数据的有效方法。

因此,要将任何值转换为标准分数(z-score),我们需要首先从平均值中减去该值,然后除以标准差。

z-score = (Value – Mean)/Standard_Deviation

API 提供的统计功能

让我们调查 API 在这些基本统计量方面提供了什么。稍后我们将在我们的应用程序中使用这些统计量,以更好地了解数据。我们还将在我们的可视化技术中使用这些技术。

StatisticDefinition 模块

API 提供了一个名为StatisticalDefinition的模块,可以与查询任务和查询模块一起使用,提取我们刚刚讨论的基本统计量。

模块名称:esri/tasks/StatisticDefinition

以下是用于定义统计定义对象的属性:

  • onStatisticField:用于定义将计算统计量的字段

  • outStatisticFieldName:输出字段的名称

  • statisticType:用于定义统计类型。接受的统计类型包括:

  • min:获取最小统计量

  • max:获取最大统计量

  • sum:获取总和统计量

  • avg:推导平均值统计量

  • stddev:推导标准差统计量

让我们尝试在本章开头提供的人口统计图层 URL 上使用这些并推导这些统计量。

以下截图显示了一个代码片段,并解释了如何为人口统计地图服务中的县级图层推导这些统计量:

StatisticDefinition 模块

可以使用这个简单的代码片段提取所需的统计数据。

代码执行后,控制台屏幕应该如下所示:

**Object {MAX_MEDHINC_CY: 113282, MIN_MEDHINC_CY: 18549, STDDEV_MEDHINC_CY: 10960.43202775655, AVG_MEDHINC_CY: 42115.877187400576}**
**Object {Plus1StdDev: 53076.309215157125, Plus2StdDev: 64036.741242913675, Plus3StdDev: 74997.17327067023, Minus1StdDev: 31155.445159644027, Mius2StdDev: 20195.013131887477…}**

稍后将使用推导的统计量,如Plus1StdDevPlus2StdDevPlus3StdDevMinus1StdDevMinus2StdDevMinus3StdDev来更好地呈现数据。

分类方法

当我们有大量数据时,我们使用渲染方法对其进行分类。我们需要确定一个适当的分类方法来创建类别间断点。API 支持以下分类方法:

  • 等间隔

  • 自然断点

  • 分位数

  • 标准差

让我们简要讨论使用每种分类方法的影响。

等间隔

这种分类方法将数据分成相等的部分。我们需要知道数据范围以使用这种分类方法。当数据分散且分布良好时,应使用此方法。

等间隔

自然断点

自然断点是基于 Jenks 断点算法的分类方法。基本上,该算法在数据更加聚集的位置创建更多的断点。这是通过寻求最小化每个类的平均偏差来实现的,同时最大化每个类与其他组的平均值的偏差。换句话说,该方法旨在减少类内的方差,并最大化类间的方差。

自然断点

分位数

这种方法对数据进行分类,使每个组中的数据点数量相等。

标准差

如前所述,标准差是数据偏离平均值的度量。使用这种分类方法,我们可以找出数据偏离平均值超出三个标准差的程度(异常值的情况),在两个和三个标准差之间的数据(较高和较低的值),以及距离平均值一个标准差内的数据。

标准差

归一化概念

对数据值进行归一化对于计算许多事情都很有用。考虑以下情景:

  • Case 1:我们需要符号化每个州的人口密度。基于人口字段的符号化会给出错误的度量或传达错误的信息。我们可能只需要将每个州的人口除以其地理面积,以得到人口密度的度量。

同样,如果我们需要传达年轻人口(年龄<35 岁)占总人口的百分比,我们需要将拥有年轻人口的字段除以显示总人口的字段。

  • Case 2:当尝试符号化整个世界的收入分布时,我们可能会遇到大范围的值。如果我们使用颜色或不透明度渲染器,一些国家将位于光谱的较高端,而一些国家将位于底部,中间有许多国家,但实际上并没有使用太多的颜色信息。在这种情况下,使用对数刻度来显示收入分布将更有用。

  • Case 3:当我们需要计算值作为总数的百分比时,例如犯罪数据或每个州参加马拉松比赛的参与者人数,我们需要将该值除以总数。

许多渲染器都有normalizationFieldnormalizationType属性来实现这种归一化。

normalizationField让我们定义用于归一化的字段。例如,对于Case 1Area字段和Total Population字段是normalizationField

normalizationType是需要对值执行的归一化类型。normalizationType的三个可能值是 field、log 和 percent-of-total。例如,对于Case 1,我们需要使用normalizationType作为field。对于Case 2,我们需要使用log,对于Case 3,我们需要使用percent-of-total作为normalizationType

要素图层统计

在 API 的 3.13 版本中,引入了这个插件,可以方便地计算要素图层的统计信息。使用要素图层统计插件,我们可以计算以下统计信息:

  • 要素图层上的基本统计信息

  • 类别断点统计

  • 字段中的唯一值

  • 查看图层的建议比例范围

  • 获取样本要素

  • 计算直方图

可以使用以下代码片段将插件添加到要素图层中:

var featureLayerStats = new FeatureLayerStatistics({
          layer: CountyDemogrpahicsLayer
        });

在前面的代码片段中,CountyDemogrpahicsLayer是要添加FeatureLayerStatistics插件的要素图层的名称。

插件中使用的方法所期望的通常参数是fieldclassificationMethodfield插件是指根据其计算统计数据的属性字段的名称。classificationMethod是指根据先前讨论的分类方法之一,计算统计数据的方法:

var featureLayerStatsParams = {
          field: "MEDHINC_CY",
          classificationMethod : 'natural-breaks'
        };

插件上的方法总是返回一个 promise。以下代码片段计算了在featureLayerStatsParams中定义的字段上的基本统计值:

featureLayerStats.getFieldStatistics(featureLayerStatsParams).then(function (result) {
          console.log("Successfully calculated %s for field %s, %o", "field statistics", featureLayerStatsParams.field, result);
        }).otherwise(function (error) {
          console.log("An error occurred while calculating %s, Error: %o", "field statistics", error);
        });

结果在浏览器控制台中如下所示:

**Successfully calculated field statistics for field MEDHINC_CY,**
**Object {  **
 **source:"service-query",**
 **min:20566,**
 **max:130615,**
 **avg:46193.26694241171,**
 **stddev:12564.308382029049,**
 **count:3143,**
 **sum:145185438,**
 **variance:157861845.1187254**
 **}**

之前的结果提供了与我们之前使用的统计定义模块得到的相同或更多的信息。

以下代码片段计算了在featureLayerStatsParams中定义的字段上的类别分隔值:

featureLayerStats.getClassBreaks(featureLayerStatsParams).then(function (result) {
          console.log("Successfully calculated %s for field %s, %o", "class breaks", featureLayerStatsParams["field"], JSON.stringify(result));
        }).otherwise(function (error) {
          console.log("An error occurred while calculating %s, Error: %o", "class breaks", error);
        });

美化后的结果如下:

**{**
 **"minValue": 20566,**
 **"maxValue": 130615,**
 **"classBreakInfos": [**
 **{**
 **"minValue": 20566,**
 **"maxValue": 27349.802772469,**
 **"label": " < -1.5 Std. Dev.",**
 **"minStdDev": null,**
 **"maxStdDev": -1.5**
 **},**
 **{**
 **"minValue": 27349.802772469,**
 **"maxValue": 39912.112219098,**
 **"label": "-1.5 - -0.50 Std. Dev.",**
 **"minStdDev": -1.5,**
 **"maxStdDev": -0.5**
 **},**
 **{**
 **"minValue": 39912.112219098,**
 **"maxValue": 52474.421665726,**
 **"label": "-0.50 - 0.50 Std. Dev.",**
 **"minStdDev": -0.5,**
 **"maxStdDev": 0.5,**
 **"hasAvg": true**
 **},**
 **{**
 **"minValue": 52474.421665726,**
 **"maxValue": 65036.731112354,**
 **"label": "0.50 - 1.5 Std. Dev.",**
 **"minStdDev": 0.5,**
 **"maxStdDev": 1.5**
 **},**
 **{**
 **"minValue": 65036.731112354,**
 **"maxValue": 77599.040558982,**
 **"label": "1.5 - 2.5 Std. Dev.",**
 **"minStdDev": 1.5,**
 **"maxStdDev": 2.5**
 **},**
 **{**
 **"minValue": 77599.040558982,**
 **"maxValue": 130615,**
 **"label": " > 2.5 Std. Dev.",**
 **"minStdDev": 2.5,**
 **"maxStdDev": null**
 **}**
 **],**
 **"source": "service-generate-renderer"**
**}**

使用连续和分级渲染器

连续渲染器是指在连续数值范围上对要素进行符号化的渲染器,与唯一值渲染器不同。我们需要为这些渲染器定义几个stopsbreakpoints。这些stops定义了一个类,渲染器会检查每个值属于哪个类。根据类别,数据会通过可视化变量(如颜色、大小、透明度甚至旋转)进行可视化。

利用可用的统计数据,我们可以使用 API 提供的ClassBreaksRenderer轻松创建分级和连续渲染器。ClassBreaksRenderer根据某些数值属性的值对每个图形进行符号化。

模块名称:esri/renderers/ClassBreaksRenderer

通过使用属性如colorInfoopacityInfosizeInfo,可以在此模块上设置颜色、大小或透明度。ClassBreaksRenderer上提供了以下方法:

  • setColorInfo(colorInfo): 设置colorInfo属性

  • setOpacityInfo(opacityInfo): 根据 info 参数设置渲染器的透明度信息

  • setRotationInfo(rotationInfo): 修改渲染器的旋转信息

  • setSizeInfo(sizeInfo): 设置渲染器的大小信息,以根据数据值修改符号大小

让我们更详细地讨论这些。以下图表提供了一个开发渲染器的简要指南:

使用连续和分级渲染器

ColorInfo

ColorInfo是一个用于定义图层颜色渐变的对象。我们只需要在stops处提供离散的颜色值,有时也可以在渐变中只提供颜色值:

一个简单的ColorInfo对象示例如下:

renderer.setColorInfo({
  field: "MEDHINC_CY",
  minDataValue: featureLayerStats.min,
  maxDataValue: featureLayerStats.max,
  colors: [
    new Color([255, 255, 255]),
    new Color([127, 127, 0])
  ]
});

要创建一个分级颜色渲染器,我们需要定义一个stops对象来定义离散颜色,而不是连续颜色。一个stops对象将包含每个stop处的颜色。在定义stops时,我们需要定义minDataValuemaxDataValue。让我们讨论一下在哪里可以获得适合我们渲染器的合适颜色方案。

选择颜色方案

以下网站为我们提供了一种简单的选择颜色方案的方法,可用于构建colorInfo对象或颜色渐变:colorbrewer2.org/

在这个网站上,你可以做以下事情:

  1. 选择数据类别的数量,默认值为3。API 的默认类别数为5。因此将下拉值更改为5类别。

  2. 选择您数据的性质:

  • sequential: 用于显示递增的数量,如人口或人口密度。选择颜色方案

  • diverging: 用于强调值的差异,特别是在极端端点。例如,当映射收入中位数时,收入范围的较低端可能显示为红色,较高端显示为蓝色。选择颜色方案

  • 定性:当我们需要使用不同颜色区分不同值或类时,使用此颜色方案。选择颜色方案

  1. 选择多色调或单色调颜色方案。

  2. 基于以下约束条件限制颜色色调:

  • 目的:

  • 色盲友好

  • 打印友好

  • 复印机安全

  • 背景:

  • 道路

  • 城市

  • 边界

  1. 将颜色方案导出为:
  • JavaScript 数组对象——这是最方便的函数

  • Adobe PDF

选择颜色方案

创建一个分级颜色渲染器

如前所述,要创建一个分级颜色渲染器,我们需要定义一个stops对象来定义离散颜色,而不是连续颜色。stops对象将包含每个停止点的颜色。stops对象是分配给渲染器对象的数组对象。stops数组对象包含具有以下属性的对象:

  • value

  • color

  • label

stops对象大多看起来像这样:

**var stops =**
**[**
 **{**
 **"value": 27349.802772469,**
 **"color": {      "b": 226,      "g": 235,       "r": 254,      "a": 1    },**
 **"label": " < -1.5 Std. Dev."**
 **},**
 **{**
 **"value": 39912.112219098,**
 **"color": {      "b": 185,      "g": 180,      "r": 251,      "a": 1    },**
 **"label": "-1.5 - -0.50 Std. Dev."**
 **},**
 **{**
 **"value": 52474.421665726,**
 **"color": {      "b": 161,      "g": 104,      "r": 247,      "a": 1    },**
 **"label": "-0.50 - 0.50 Std. Dev."**
 **},**
 **{**
 **"value": 65036.731112354,**
 **"color": {      "b": 138,      "g": 27,      "r": 197,      "a": 1    },**
 **"label": "0.50 - 1.5 Std. Dev."**
 **},**
 **{**
 **"value": 77599.040558982,**
 **"color": {      "b": 119,      "g": 1,      "r": 122,      "a": 1    },**
 **"label": "1.5 - 2.5 Std. Dev."**
 **}**
**]**

现在让我们找到一种自动填充stops对象的方法。记住,我们可以从colorbrewer2.org网站上选择的颜色方案中获取颜色数组。color数组可以用来填充stops对象中每个对象的color属性。stops对象中每个对象的value属性可以从featureLayerStatistics计算的返回对象中派生出来。featureLayerStatistics计算为每个类提供最小值最大值标签值。我们可以将每个类的最大值分配给stops对象中每个对象的value属性:

**//Create a params object for use getClassBreaks method in** 
**// FeatureLayerStatistics module**
**//Define the field upon which Stats is computed,**
**//The classification method which should be one among the following:**
**//standard-deviation, equal-interval, natural-breaks, quantile**
**//Number of classes the data should be classified. Default is 5**
var featureLayerStatsParams_color = {
          field: "MEDHINC_CY",
          classificationMethod: selectedClassificationMethod, 
          numClasses: 5
        };

**//Compute the Class Break Statitics. This returns a promise**

var color_stats_promise = featureLayerStats.getClassBreaks(featureLayerStatsParams_color);
color_stats_promise.then(function (color_stat_result) {

**//The classBreakInfos property of the color_stat_result has all the** 
**//class break values** 

var colorStops = [];

**//Color JavaScript array exported from colorbrewer2.org**
var colors = ['#feebe2', '#fbb4b9', '#f768a1', '#c51b8a', '#7a0177']; 

**//Loop through each Break info provided by the Feature Layer Stats**
              array.forEach(color_stat_result.classBreakInfos, function (classBreakInfo, i) {
                        colorStops.push({
**//Get value property from the Break value's maximum value**
                            value: classBreakInfo.maxValue,
**//Get color from the color Array**
                            color: new Color(colors[i]),
**//Get label value from the label value provided by the Feature Layer //Stats**
                            label: classBreakInfo.label
                        });
                    });

**//Define Default renderer symbol**
var symbol = new SimpleFillSymbol();
symbol.setColor(new Color([255, 0, 0]));
symbol.setOutline(new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([0, 0, 0]), 0.5));

var colorBreakRenderer = new ClassBreaksRenderer(symbol);

**//Set the color stops to the stops property to setColorInfo method of //the renderer**
colorBreakRenderer.setColorInfo({
              field:"MEDHINC_CY",
              stops: colorStops
          });
});

创建一个分级颜色渲染器

opacityInfo

opacityInfo是一个定义特征不透明度如何计算的对象。opacityInfo对象可用于为ClassBreaksRenderer中的类设置不透明度级别。opacityInfo对象也可用于设置连续不透明度渲染器。

colorInfo对象类似,您可以指定不透明度值作为数组,以及最小和最大数据值,或者您可以定义stops对象,在其中可以定义不透明度值。

使用opacityInfo创建一个连续渲染器:

var minOpacity = 0.2;
var maxOpacity = 1;

var opacityInfo = {
  field: "DIVINDX_CY",
  minDataValue:  0,
  maxDataValue:  100,
  opacityValues:   [minOpacity, maxOpacity]
};

使用 opacityInfo 创建一个类别不透明度渲染器

让我们使用opacityInfo来渲染另一个字段,表示每个县的多样性指数。多样性指数在从0100的范围内测量多样性。多样性指数是 Esri 专有的指数,定义为从同一地区随机选择的两个人属于不同种族或民族群体的可能性。多样性指数仅测量区域的多样性程度,而不是其种族构成。

我们的目标是以更高的不透明度值显示多样性指数较高的县,以及以较低的不透明度值显示多样性指数较低的县。可以使用以下代码段将不透明度值在最小值和最大值之间分割:

var opacity = minOpacity + i * maxOpacity / (opacity_stat_result.classBreakInfos.length - 1);

在上一段代码中,opacity_stat_resultFeatureLayerSatistics模块的getClassBreaks()方法的承诺结果:

var featureLayerStatsParams_opacity = {
  field: "DIVINDX_CY",
  classificationMethod: selectedClassificationMethod, //standard-deviation, equal-interval, natural-breaks, quantile and standard-deviation
  numClasses: 5
};

var opacity_stats_promise = featureLayerStats.getClassBreaks(featureLayerStatsParams_opacity);
opacity_stats_promise.then(function (opacity_stat_result) {

  var opacityStops = [];
  array.forEach(opacity_stat_result.classBreakInfos, function (classBreakInfo, i) {
    var minOpacity = 0;
    var maxOpacity = 1;
//Calculate opacity by dividing between 
    var opacity = minOpacity + i * maxOpacity / (opacity_stat_result.classBreakInfos.length - 1);
    opacityStops.push({
      value: classBreakInfo.maxValue,
      opacity: opacity
    });
  });

var symbol = new SimpleFillSymbol();
symbol.setColor(new Color([255, 0, 0]));
var opacityBreakRenderer = new ClassBreaksRenderer(symbol);
opacityBreakRenderer.setOpacityInfo({
   field:"MEDHINC_CY",
   stops: stops
});

CountyDemogrpahicsLayer.setRenderer(opacityBreakRenderer);
CountyDemogrpahicsLayer.redraw();

使用 opacityInfo 创建一个类别不透明度渲染器

SizeInfo

SizeInfo对象定义了特征大小与数据值成比例的符号大小。

API 帮助页面提到符号大小可以代表两种不同类型的数据——距离和非距离。距离数据类型指的是字段上的实际距离,非距离数据类型指的是符号的地图大小。使用sizeInfo根据树冠的实际直径表示树冠是距离数据类型的一个例子。根据交通密度表示道路大小,或者根据人口密度或中位收入表示州的大小,可以增强要素的地图呈现。

RotationInfo

RotationInfo可用于定义标记符号的旋转方式。RotationInfo可用于表示风向、车辆方向等。必须存在指定旋转角度的字段来定义RotationInfo。允许使用两种旋转角度单位。

  • 地理:这表示从地理北方顺时针方向的角度。风速和汽车方向通常用地理角度表示。

  • 算术:这表示逆时针方向测量的角度。

以下图显示了地理和算术角度之间的差异:

RotationInfo

多变量映射

到目前为止,我们一直在讨论使用单个字段名称或变量来渲染要素的功能。我们还讨论了可以用来渲染要素的各种视觉变量,如颜色、不透明度、大小、旋转等。如果我们能够结合这些视觉变量,并根据多个字段值来渲染要素呢?

例如,在县级别进行映射时,我们可以考虑使用颜色来表示人口密度,使用不透明度来表示家庭收入中位数,并使用大小来表示联邦教育支出占人口字段的百分比。我们选择使用的字段数量限于四个视觉变量,即:颜色、不透明度、大小和旋转。

多变量映射是由ClassBreaksRenderer中的visualVariables属性启用的。让我们尝试使用两个视觉变量,即colorInfoopacityInfo,我们用它们来演示两个不同的人口统计参数,即家庭收入中位数和多样性指数。我们当前的目标是使用颜色来表示家庭收入中位数,并同时根据多样性指数确定要素的不透明度值:

function applySelectedRenderer(selectedClassificationMethod) {
        var featureLayerStatsParams_color = {
          field: "MEDHINC_CY",
          classificationMethod: selectedClassificationMethod, //standard-deviation, equal-interval, natural-breaks, quantile and standard-deviation
          numClasses: 5
        };
        var featureLayerStatsParams_opacity = {
          field: "DIVINDX_CY",
          classificationMethod: selectedClassificationMethod, //standard-deviation, equal-interval, natural-breaks, quantile and standard-deviation
          numClasses: 5,
          //normalizationField: 'TOTPOP_CY'
        };

        var color_stats_promise = featureLayerStats.getClassBreaks(featureLayerStatsParams_color);
        var opacity_stats_promise = featureLayerStats.getClassBreaks(featureLayerStatsParams_opacity);
        all([color_stats_promise, opacity_stats_promise]).then(function (results) {
          var color_stat_result = results[0];
          var opacity_stat_result = results[1];

          var colorStops = [];
          var colors = ['#d7191c', '#fdae61', '#ffffbf', '#abd9e9', '#2c7bb6'];
          array.forEach(color_stat_result.classBreakInfos, function (classBreakInfo, i) {
            colorStops.push({
              value: classBreakInfo.maxValue,
              color: new Color(colors[i]),
              label: classBreakInfo.label
            });
          });
          var opacityStops = [];
          array.forEach(opacity_stat_result.classBreakInfos, function (classBreakInfo, i) {
            var minOpacity = 0;
            var maxOpacity = 1;
            var opacity = minOpacity + i * maxOpacity / (opacity_stat_result.classBreakInfos.length - 1);
            opacityStops.push({
              value: classBreakInfo.maxValue,
              opacity: opacity
            });
          });

          var visualVariables = [
            {
              "type": "colorInfo",
              "field": "MEDHINC_CY",
              "stops": colorStops
                            }

            ,
            {
              "type": "opacityInfo",
              "field": "DIVINDX_CY",
              "stops": opacityStops
                        }

                        ];
          console.log(JSON.stringify(visualVariables));
          var symbol = new SimpleFillSymbol();
          symbol.setColor(new Color([0, 255, 0]));
          symbol.setOutline(new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([0, 0, 0]), 0.5));

          var colorBreakRenderer = new ClassBreaksRenderer(symbol);
          colorBreakRenderer.setVisualVariables(visualVariables);
          CountyDemogrpahicsLayer.setRenderer(colorBreakRenderer);
          CountyDemogrpahicsLayer.redraw();
          legend.refresh();
        });
      }

多变量映射

智能映射

有了所有这些统计数据的知识,现在是时候使用 API 提供的智能映射模块进行智能映射了。想象一下,一个模块可以根据一些基本输入自动调用渲染器参数,例如需要生成渲染器的要素图层和分类方法。

模块名称:esri/renderers/smartMapping

智能映射模块提供了几种方法,每种方法都会生成一个渲染器。智能映射模块可以生成的渲染器包括:

  • 基于颜色的分类渲染器

  • 基于大小的分类渲染器

  • 基于类型的渲染器

  • 热力图渲染器

智能映射甚至可以根据底图进行渲染。例如,某些颜色或不透明度渲染器在较暗的底图(如卫星图)上效果良好,而某些渲染器在较亮的底图(如街道地图)上效果良好。

通过三个简单的步骤,您可以让 API 决定颜色方案,并为您创建类颜色渲染器:

  • 从 Esri 样式choropleth模块(导入esri/styles/choropleth)构建一个方案对象

  • 使用以下属性构建一个分类颜色参数对象:

  • basemap

  • classificationMethod

  • layer

  • field

  • scheme——从之前构建的方案对象中选择primaryScheme属性

  • numClasses

  • 将一个分类颜色参数对象分配为智能映射模块的createClassedColorRenderer()方法的参数

  • 将智能映射方法返回的渲染器属性分配给要素图层的setRenderer()方法作为参数

  • 重绘要素图层并刷新图例对象

以下代码解释了如何使用智能映射创建一个分类颜色渲染器:

**//Call this function with the classification method as input**
function applySmartRenderer(selectedClassificationMethod) {

**//Create a scheme object assigning a theme** 
var schemes = esriStylesChoropleth.getSchemes({
 **//The following options are available for theme:** 
 **// high-to-low, above-and-below, centered-on, or extremes.**
  theme: "high-to-low",
  basemap: map.getBasemap(),
  geometryType: "polygon"
});
console.log(JSON.stringify(schemes));

**//Create a classed color Render Parameter object**
var classedColorRenderParams = {
  basemap: map.getBasemap(),
  classificationMethod: selectedClassificationMethod,
  field: 'MEDHINC_CY',
  layer: CountyDemogrpahicsLayer,
  scheme: schemes.primaryScheme,
  numClasses: 5
};

SmartMapping.createClassedColorRenderer(classedColorRenderParams).then(function (result) {
  CountyDemogrpahicsLayer.setRenderer(result.renderer);
 **//Redraw the feature layer**
  CountyDemogrpahicsLayer.redraw();
 **//Update the legend**
  legend.refresh();
}).otherwise(function (error) {
  console.log("An error occurred while performing%s, Error: %o", "Smart Mapping", error);
});

以下屏幕截图显示了使用智能制图模块创建的分级颜色渲染器,分别为等间隔、自然断点、分位数和标准偏差四种不同的分类。用户可以自行决定根据地图数据的目的和受众群体,选择最适合的分类方法。

我们可以通过编辑scheme对象来手动定义颜色方案,该对象是createClassedColorRenderer()方法的参数对象中的一个属性。

智能制图

总结

我们离成为地图数据科学家又近了一步。在本章中,我们涵盖了很多内容,从简要统计概念的复习开始。然后我们看到了代码如何运行,统计定义和要素图层统计模块如何给我们提供宝贵的统计量,可以用来有意义地渲染地图数据。然后我们评估了如何有效地使用视觉变量,如colorInfoopacityInforotationInfosizeInfo在渲染器中。我们还尝试结合这些视觉变量进行多变量渲染。最后,我们尝试使用智能制图模块进行自动渲染。在下一章中,我们将讨论图表和其他高级可视化技术,为用户提供分析信息。

第八章:高级地图可视化和图表库

在地图上渲染可能不是可视化空间数据的唯一方式。为了让数据有所侧重,我们可能需要借助于非空间分析和图表功能,这些功能由 dojo 和其他流行的库提供,以补充地图的空间可视化功能。在本章中,我们将通过图表库和其他可视化方法(如数据聚类)扩展我们在上一章开始构建的人口统计分析门户网站。本章涉及以下主要主题:

  • 使用 dojo 进行图表绘制

  • 使用 D3 库进行图表绘制

  • 使用 Cedar 进行图表绘制

使用 dojo 进行图表绘制

ArcGIS API 与 dojo 的图表绘制非常好地集成在一起。图表功能由 dojo 的实验模块提供,因此称为dojox,其中的x指的是模块的实验性质。然而,这些模块足够稳定,可以集成到任何生产环境中。以下模块被认为是使用 dojo 开发图表功能的最基本模块:

  • dojox/charting

  • dojox/charting/themes/<themeName>

  • dojox/charting/Chart2D

  • dojox/charting/plot2d/Pie

Dojo 图表主题

dojox图表库提供了许多主题,必须在dojox提供的主题列表中选择一个主题名称。可以在以下网址找到dojox提供的所有主题列表:archive.dojotoolkit.org/nightly/dojotoolkit/dojox/charting/tests/theme_preview.html

dojox 图表库提供的主题如下:

JulieThreeDChrisTomClaroPrimaryColorsElectricChargedRenkooAdobebricksAlgaeBahamationBlueDusk DesertDistinctiveDollarGrasshopperGrasslandsGreySkiesHarmonyIndigoNationIrelandMiamiNiceMidwestMintyPurpleRain CubanShirtsRoyalPurplesSageToLimeShroomsTufteWatersEdgeWetlandPlotKit.bluePlotKit.cyanPlotKit.greenPlotKit.orangePlotKit.purplePlotKit.red

测试这些不同图表主题的理想位置是在archive.dojotoolkit.org/nightly/dojotoolkit/dojox/charting/tests/test_themes.html?Julie

Dojo 图表主题

使用弹出模板进行图表绘制

基本的图表功能可以使用popup模板的mediaInfos属性在要素图层的弹出窗口中显示。我们将使用上一章中使用的县级人口统计要素图层来创建此图表。我们对以下字段感兴趣:

字段 描述
NAME 县的名称
STATE_NAME 州的名称
TOTPOP_CY 县的总人口数量
MEDHINC_CY 县的家庭收入中位数
DIVINDX_CY 计算的县的多样性指数
WHITE_CY 白人男性和女性的数量
BLACK_CY 黑人男性和女性的数量
AMERIND_CY 美洲印第安人(男性和女性)的数量
ASIAN_CY 亚洲人(男性和女性)的数量
PACIFIC_CY 太平洋岛民(男性和女性)的数量
OTHRACE_CY 其他种族(男性和女性)的数量

创建mediaInfos对象涉及构建fieldInfos对象,如果需要更改字段名称或在图表中为它们指定别名。mediaInfos对象接受一个theme属性。提到一个 dojo 图表主题名称或您创建的自定义主题:

var template = new PopupTemplate({
  title: "USA Demograpahics",
  description: "Median household income at {NAME}, {STATE_NAME} is ${MEDHINC_CY}",
 **//define field infos so we can specify an alias in the chart**
  fieldInfos: [
    { 
      fieldName: "WHITE_CY",
      label: "White Americans"
    },
    { 
      fieldName: "BLACK_CY",
      label: "Blacks"
    },
    { 
      fieldName: "AMERIND_CY",
      label: "American Indians"
    },
    {   fieldName: "ASIAN_CY",
      label: "Asians"
    },
    { 
      fieldName: "PACIFIC_CY",
      label: "Pacific Islanders"
    },
    { 
      fieldName: "OTHRACE_CY",
      label: "Other Race Count"
    }
    ],
  mediaInfos: [{ //define the bar chart
    caption: "",
    type: "piechart", // image, piechart, barchart,columnchart,linechart
    value: 
    {
      theme: "Dollar",
      fields: ["WHITE_CY", "BLACK_CY", "AMERIND_CY", "ASIAN_CY", "PACIFIC_CY", "OTHRACE_CY"]
    }
    }]
});

使用弹出模板进行图表绘制

dojox 模块提供的 2D 图表类型

我们已经看到了饼图的效果。让我们讨论一些dojox模块提供的更多图表类型以及一些更受欢迎的图表类型的实用性。注意柱状图和柱形图之间的差异,以及散点图和仅标记的图之间的差异。

图表类型 描述
区域 数据线下的区域将被填充
条形 指代水平条
分组条 具有分组数据集的水平条
分组列 具有分组数据集的垂直条
指代具有垂直条的图表
网格 用于向图表添加网格层
线条 基本线图
标记 具有标记的线图
仅标记 仅显示数据点
饼图 通过在圆形直径上表示数据来表示数据的分布
散点图 用于绘制数据
堆叠 数据集相对于先前数据集的图表
堆叠区域 堆叠数据集,填充图表线下的区域
堆叠条 具有水平条的堆叠数据集
堆叠列 堆叠具有垂直条的数据集
堆叠线 使用线堆叠数据集

道场图表方法

图表模块有四个重要的方法,将帮助我们创建图表。它们是:

  • addPlot():定义图表的类型和定义图表的其他辅助属性。

  • setTheme():让我们为图表设置道场主题。主题也可以自定义。

  • addSeries():定义图表使用的数据。

  • render():渲染图表。

定义您的情节

使用addPlot()方法可以定义您的情节。情节接受名称和参数数组:

var chart1 = new Chart2D(chartDomNode);
chart1.addPlot("default", plotArguments);

让我们看看plotArguments对象包括什么。plotArguments的属性根据我们选择使用的图表类型而变化。如果我们选择使用线条、区域或数据点来定义数据的图表类型,则应将线条、区域或标记等属性设置为布尔值。线条选项确定是否使用线条连接数据点。如果选择区域类型,则将填充数据线下的区域。标记选项将确定是否在数据点处放置标记。

plotArguments可以接受以下属性:

  • type:要呈现的图表类型

  • lines:布尔值,指示图表数据是否需要被线条包围

  • areas:布尔值,表示数据是否被区域包围

  • markers:布尔值,确定是否在数据点处放置标记

对于诸如堆叠线或堆叠区域的图表类型,我们可以使用张力和阴影等属性来增强图表的可视化效果。张力平滑连接数据点的线条,阴影属性将在线条上添加阴影。shadow属性本身是一个接受名为dxdydw的三个属性的对象,它定义了阴影线的x偏移、y偏移和宽度:

chart1.addPlot("default", {type: "StackedLines", lines: true, markers: false, tension : 3, shadows: {dx:2, dy: 2, dw: 2}});

在渲染条形图时,使用gap属性表示条形之间的像素数:

chart1.addPlot("default", {type: "Bars", gap: 3});

定义主题

使用前面提到的主题列表,我们可以使用setTheme()方法为我们的图表设置主题:

chart.setTheme(dojoxTheme);

推送数据

我们可以使用addSeries()方法将数据推送到图表中:

chart.addSeries("PopulationSplit", chartSeriesArray);

addSeries()方法接受两个参数。第一个参数提到数据的名称,第二个参数。第二个参数是一个包含实际数据的数组对象。它可以是一维数据,例如[10,20,30,40,50],也可以是二维数据,在这种情况下,可以提到数据的xy属性:

chart.addSeries("Students",[
{x: 1, y: 200 },
{x: 2, y: 185 }
]
});

如果是饼图,可以省略x分量。

图表插件

有一些插件可以添加到道场的图表模块中,为图表功能增加价值。这些插件为图表数据提供交互性,大多数插件会显示有关数据项的额外信息,或者强调悬停在其上的数据项。有些插件通过可视化元素(如图例)提供对数据的整体感知。插件完成的一些功能包括:

  • 向图表添加工具提示

  • 移动饼图片段并放大它

  • 添加图例

  • 突出显示数据项

插件模块,如dojox/charting/widget/Legend,提供了对Legend小部件的支持。dojox/charting/action2d/Tooltip模块支持图表数据的工具提示支持。包括dojox/charting/action2d/Magnify模块将放大悬停在其上的图表数据,从而增强了与图表的交互性。dojox/charting/action2d/MoveSlice模块将图表数据视为一个切片,并移动悬停在其上的图表数据的位置。这与Magnify插件一起,有助于有效地给出用户与图表数据的交互感。dojox/charting/action2d/Highlight模块用不同的高亮颜色(如青色)突出显示悬停在其上的数据。

实施插件也非常容易。以下代码行实现了诸如HighlightTooltipMoveSlice的插件在 dojo 图表对象上的使用:

new Highlight(chart, "default");
new Tooltip(chart, "default");
new MoveSlice(chart, "default");

让我们在要素图层的infotemplate属性上的动态div中创建一个完整的图表。

我们也将在此演示中使用县级人口统计特征图层。我们的目标是创建一个饼图,以显示我们单击的任何县的种族分布。我们将调用一个函数来动态创建每个要素的Infowindow内容:

var template = new InfoTemplate();
template.setTitle("<b>${STATE_NAME}</b>");

**//Get the info template content from the getWindowContent function**
template.setContent(getWindowContent);

var statesLayer = new FeatureLayer("http://demographics5.arcgis.com/arcgis/rest/services/USA_Demographics_and_Boundaries_2015/MapServer/15", {
  mode: FeatureLayer.MODE_ONDEMAND,
  infoTemplate: template,
  outFields: ["NAME", "STATE_NAME", "TOTPOP_CY", "MEDHINC_CY", "DIVINDX_CY", "WHITE_CY", "BLACK_CY", "AMERIND_CY", "ASIAN_CY", "PACIFIC_CY", "OTHRACE_CY"]
});

在返回Infotemplate内容的函数中,我们将执行以下操作:

  1. 创建一个包含两个内容窗格的Tab容器。

  2. 第一个内容将显示有关所选县和家庭收入中位数数据的详细信息。

  3. 第二个内容窗格将包含 dojo 饼图。

  4. 在渲染饼图之前,我们将计算每个种族组占总人口的百分比。

  5. 此外,我们将为每个种族组分配一个标签。在使用图例时将使用此标签。

  6. 此外,饼图数据对象接受一个工具提示属性,我们将在其中提及标签以及数据值。

  7. 我们将尝试使用图表插件,如HighlightTooltipMoveslice来突出显示所选的子数据项。

现在让我们尝试在代码中实现这些步骤。我们将编写一个函数来构建图表,并将图表内容作为dom元素返回。我们将使用infotemplatesetContent()方法来设置以下函数返回的dom元素:

function getWindowContent(graphic) {
  // Make a tab container.
  var tc = new TabContainer({
    style: "width:100%;height:100%;"
  }, domConstruct.create("div"));

// Make two content panes, one showing Median household income       //details. And the second showing the pie chart

  var cp1 = new ContentPane({
    title: "Details",
    content: "<a target='_blank' href='http://en.wikipedia.org/wiki/" + graphic.attributes.NAME + "'>Wikipedia Entry</a><br/>" +  "<br/> Total Population: " + graphic.attributes.TOTPOP_CY + " <br/> Median House Income: $" + graphic.attributes.MEDHINC_CY
  });
 **// Display a dojo pie chart for the racial distribution in %**
  var cp2 = new ContentPane({
    title: "Pie Chart"
  });
  tc.addChild(cp1);
  tc.addChild(cp2);

  // Create the chart that will display in the second tab.
  var c = domConstruct.create("div", {
    id: "demoChart"
  }, domConstruct.create("div"));
  var chart = new Chart2D(c);
  domClass.add(chart, "chart");

  // Apply a color theme to the chart.
  chart.setTheme(dojoxTheme);

  chart.addPlot("default", {
    type: "Pie",
    radius: 70,
    htmlLabels: true
  });
  tc.watch("selectedChildWidget", function (name, oldVal, newVal) {
    if (newVal.title === "Pie Chart") {
      chart.resize(180, 180);
    }
  });

  // Calculate percent of each ethnic race
  //"WHITE_CY", "BLACK_CY", "AMERIND_CY", "ASIAN_CY", "PACIFIC_CY", "OTHRACE_CY"
  var total = graphic.attributes.TOTPOP_CY;
  var white = {
    value: number.round(graphic.attributes.WHITE_CY / total * 100, 2),
    label: "White Americans"
  };
  var black = {
    value: number.round(graphic.attributes.BLACK_CY / total * 100, 2),
    label: "African Americans"
  };
  var AmericanIndians = {
    value: number.round(graphic.attributes.AMERIND_CY / total * 100, 2),
    label: "American Indians"
  };
  var Asians = {
    value: number.round(graphic.attributes.ASIAN_CY / total * 100, 2),
    label: "Asians"
  }
  var Pacific = {
    value: number.round(graphic.attributes.PACIFIC_CY / total * 100, 2),
    label: "Pacific Islanders"
  };
  var OtherRace = {
    value: number.round(graphic.attributes.OTHRACE_CY / total * 100, 2), 
    label: "Other Race"
  };
  var chartFields = [white, black, AmericanIndians, Asians, Pacific, OtherRace];
  var chartSeriesArray = [];
  array.forEach(chartFields, function (chartField) {
    var chartObject = {
      y: chartField.value,
      tooltip: chartField.label + ' : ' + chartField.value + ' %',
      text: chartField.label
    }
    chartSeriesArray.push(chartObject);

  });

  chart.addSeries("PopulationSplit", chartSeriesArray);
  //highlight the chart and display tooltips when you mouse over a slice.
  new Highlight(chart, "default");
  new Tooltip(chart, "default");
  new MoveSlice(chart, "default");

  cp2.set("content", chart.node);
  return tc.domNode;
}

当实现此代码时,我们将在单击任何县后弹出一个弹出窗口。弹出窗口包含两个选项卡——第一个选项卡提供有关该选项卡的总人口和该县的家庭收入中位数的详细信息。整个弹出窗口的标题将提及县名和州名。第一个选项卡的内容将包含动态生成的维基百科链接到县和州。

弹出容器的第一个选项卡如下截图所示:

图表插件

弹出窗口的第二个选项卡显示了 dojo 图表。我们的图表上有一个图例元素。当我们在饼图中悬停在任何数据上时,它会被切片,稍微放大,并突出显示。

图表插件

使用 D3.js 进行图表绘制

D3.js 是一个用于基于数据操作文档的 JavaScript 库。D3 代表数据驱动文档,该库提供了强大的可视化组件和基于数据驱动的 DOM 操作方法。

要在我们的 JavaScript 应用程序中使用 D3,我们可以从D3 网站下载该库。

或者我们可以在我们的脚本标签中使用 CDN:

<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>

更加面向 dojo 的方法是将其作为dojoconfig中的一个包添加,并在define函数中使用它作为一个模块。

以下是将 D3 作为dojoConfig的包添加的片段:

var dojoConfig = {
  packages: 
  [
    {
      name: "d3",
      location: "http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5",
      main: "d3.min"
    }
  ]
};

define函数中使用d3库:

define([
  "dojo/_base/declare",
  "d3",
  "dojo/domReady!"
  ], 
function 
(
  declare,
  d3
) 
{
  //Keep Calm and use D3 with dojo
});

使用 D3 创建柱状图

让我们使用县级人口统计数据使用 D3 创建一个柱状图。我们的目标是使用柱状图显示围绕感兴趣县的家庭收入中位数的四个度量。这四个度量是:

  • 国家最小值或 5 百分位数的值(平均值-三倍标准差)

  • 被点击的县的家庭收入中位数

  • 国家家庭收入中位数的国家平均值

  • 国家最大值或 95 百分位数的值

以下图片是我们打算构建图表的模拟:

使用 D3 创建柱状图

有几个原因我们选择使用 D3 来演示构建这个图表。D3 完全由数据驱动,因此灵活,特别适合数据可视化。许多可视化库都是基于 D3 构建的,了解 D3 甚至可以帮助我们构建直观的图表和数据可视化。

D3 选择

D3 工作在选择上。D3 中的选择与 jQuery 选择非常相似。要选择body标签,你只需要声明:

d3.select("body")

要选择所有具有名为chart的特定样式类的div标签,请使用以下代码片段:

d3.select(".chart").selectAll("div")

要将svg(可缩放矢量图形)标签或任何其他 HTML 标签附加到divbody标签,使用append方法。SVG 元素用于呈现大多数图形元素:

d3.select("body").append("svg")

enter()方法一起使用,表示元素接受输入:

d3.select("body").enter().append("svg")

D3 数据

D3 由数据驱动,正如其名称所示。我们只需要向 D3 选择提供数据,就可以渲染一个简单的图表。数据可以简单到一个数组:

var data = [45, 87, 15, 16, 23, 11];

  var d3Selection = d3.select(".chart").selectAll("div").data(data).enter().append("div");

  d3Selection.style("width", function (d) {
    return d * 3 + "px";
  }).text(function (d) {
    return d;
  });

在上一个代码片段中,我们所做的就是为 D3 选择的样式对象设置宽度属性。然后我们得到了这个:

D3 数据

每个div的宽度值以像素为单位,取自数据数组中每个元素的值乘以 20,柱内的文本值再次取自各个数据的值。在得到这个美丽的图表之前,有一些事情需要做——我们需要为div设置 CSS 样式。这是我们使用的一个简单的 CSS 代码片段:

.chart div {
    font: 10px sans-serif;
    background-color: steelblue;
    text-align: right;
    padding: 3px;
    margin: 1px;
    color: white;
}

D3 缩放

在上一个代码片段中,为了显示一个简单的 D3 图表,我们使用了一个乘数值20,用于每个数据值获取div宽度的像素值。由于我们的容器div大约有 400 像素宽,这个乘数值是合适的。但是对于动态数据,我们应该使用什么样的乘数值呢?经验法则是我们应该使用某种缩放机制来缩放像素值,以便我们的最大数据值舒适地适应图表容器div中。D3 提供了一种机制来缩放我们的数据并计算缩放因子,我们可以方便地使用它来缩放我们的数据。

D3 提供了一个scale.linear()方法来计算缩放因子。此外,我们还需要使用另外两个方法,即domain()range(),来实际计算缩放因子。domain()方法接受一个包含两个元素的数组。第一个元素应该提到最小的数据值或0(适当的话),第二个元素应该提到数据的最大值。我们可以使用 D3 函数d3.max来找到数据的最大值:

d3.max(data)

range函数还接受一个包含两个元素的数组,应列出容器 div 元素的像素范围:

var x = d3.scale.linear()
    .domain([0, d3.max(data)])
    .range([0, 750]);

一旦我们找到缩放因子x,我们就可以将其用作数据项值的乘数,以得出像素值:

d3.select(".chart").selectAll("div").data(data)
  .enter().append("div").style("width", function (d) {
    return x(d) + "px"; 
  }).text(function (d) {
    return d;
  });

将 SVG 集成到 D3 图表中

SVG,虽然在其整体上令人生畏,但在处理数据可视化时提供了几个优势,并支持在 HTML 中呈现许多原始形状。需要注意的一点是 SVG 坐标系统从左上角开始,我们在计算元素所需位置时需要记住这一点。

附加 SVG 元素类似于将div附加到我们的图表类:

var svg = d3.select(".chart").append("svg")
    .attr("width", 500)
    .attr("height", 500)
    .append("g")
    .attr("transform", "translate(20,20)";

在前面的片段中,我们实际上可以设置样式和其他属性,例如宽度和高度。transform是一个重要的属性,通过它我们可以移动svg元素的位置(记住 SVG 坐标系原点在左上角)。

将 SVG 集成到 D3 图表中

由于我们将构建一个柱状图,因此在计算 D3 线性缩放时,由range()方法接受的数组中的第一个元素不应是最小值,而应是像素中的最大高度值。数组中的第二个元素是最小像素值:

  var y = d3.scale.linear()
    .range([700, 0]);

相反,x缩放因子应基于序数比例(意思是,我们不使用数字来计算条的宽度和间距):

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

从之前讨论的特征统计模块中,我们应该能够得到特征层中特定字段的平均值和标准差。

根据前面的两条信息,我们知道如何计算 2.5^(th)百分位数(底部 2.5%的收入)和 97.5^(th)百分位数(顶部 2.5%的收入水平)。我们打算将所选特征的收入中位数与这些值进行比较。计算 2.5^(th)和 97.5^(th)百分位数的公式如下所示:

1st percentile = mean - 2,33 * SD 99th percentile = mean + 2,33 * SD
2.5th percentile = mean - 1.96 * SD 97.5th percentile = mean + 1.96 * SD
5th percentile = mean - 1.65 * SD 95th percentile = mean + 1.65 * SD

根据以前的统计计算,我们知道以下数据:

mean = $46193
SD = $12564

我们需要计算如下的 2.5^(th)和 97.5^(th)百分位数:

2.5th percentile value = mean – 1.96 * SD
                       =   46193 – 1.96*(12564)  
                       =             21567.56  

而对于 97.5^(th):

97.5th percentile = mean + 1.96 * SD
                  = 46193 + 1.96*(12564)
                  = 70818.44

因此,这将是我们图表的数据:

var data = [
  {
    "label": "Top 2.5%ile",
    "Income": 70818
  },
  {
    "label": "Bottom 2.5%ile",
    "Income": 21568
  },
  {
    "label": "National Avg",
    "Income": 46193
  },
  {
    "label": "Selected Value",
    "Income": 0
  }
];

Income值为Selected Value标签设置为0。当我们点击feature类中的特征时,此值将被更新。我们还将定义一个margin对象以及用于图表的widthheight变量。我们定义的margin对象如下所示:

 var margin = {
      top: 20,
      right: 20,
      bottom: 30,
      left: 60
    },
    width = 400 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

在构建图表时,我们将考虑以下步骤:

  1. 确定x缩放因子和y缩放因子。

  2. 定义xy轴。

  3. 清除chart类的div的所有现有内容。

  4. 根据margin对象以及widthheight值定义xy域值。

  5. 定义将容纳我们的图表的 SVG 元素。

  6. 在 SVG 中添加轴以及作为矩形图形元素的图表数据。

我们将在一个函数中编写功能,并根据需要调用该函数:

function drawChart() {

// Find X and Y scaling factor

  var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

  var y = d3.scale.linear()
    .range([height, 0]);

  // Define the X & y axes

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

  var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10);

  //clear existing 
  d3.select(".chart").selectAll("*").remove();
  var svg = d3.select(".chart").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // Define the X & y domains 
  x.domain(data. map(function (d) {
    return d.label;
  }));
  y.domain([0, d3.max(data, function (d) {
    return d.population;
  })]);

  svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

  svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
    .append("text")
    .attr("transform", "translate(-60, 150) rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Population");

  svg.selectAll(".bar")
    .data(data)
    .enter().append("rect")
    .attr("class", "bar")
    .style("fill", function (d) {
      if (d.label == "Selected Value")
        return "yellowgreen";
    })
    .attr("x", function (d) {
      return x(d.label);
    })
    .attr("width", x.rangeBand())
    .attr("y", function (d) {
      return y(d.population);
    })
    .attr("height", function (d) {
      return height - y(d.population);
    });
}

我们可以在特征层的click事件上调用之前的函数。在我们的项目中,特征click事件在一个单独的文件中定义,而 D3 图表代码在另一个文件中。因此,我们可以通过 dojo 主题发送点击结果:

//map.js file

define("dojo/topic",..){
on(CountyDemogrpahicsLayer, "click", function(evt){
            topic.publish("app/feature/selected", evt.graphic);
        });
}

可以通过主题模块下的subscribe()方法在任何其他文件中访问结果。在前面的片段中,可以通过引用名为app/feature/selected的名称来访问结果:

//chart_d3.js file

topic.subscribe("app/feature/selected", function () {
    var val = arguments[0].attributes.MEDHINC_CY;
    var title = arguments[0].attributes.NAME + ', ' + arguments[0].attributes.STATE_NAME;;
    array.forEach(data, function (item) {
      if (item.label === "Selected Value") {
        item.Income = val;
      }
    });

    drawChart(title);
    console.log(JSON.stringify(data));
  });

以下截图是我们代码的输出的表示。D3 图表代表一个具有四个柱的典型柱状图。前三个数据值根据我们的代码是静态的,因为我们可以从特征层数据中计算出顶部和底部的 2.5^(th)百分位数以及国家平均值。最后一栏是特征层中所选特征的实际值。在下面的快照中,我们点击了纽约州的拿骚县,数据值略高于 10 万美元,远高于顶部的 2.5^(th)百分位数标记:

将 SVG 集成到 D3 图表中

在下面的截图中,我们选择了一个收入中位数最低的县。请注意Y轴如何根据数据的最大值重新校准自身。

将 SVG 集成到 D3 图表中

使用 D3 进行 SVG 组件的图表绘制可能会很麻烦,但对这些的基本了解在需要进行高级定制时会大有裨益。

使用 Cedar 进行图表绘制

Cedar 是由 Esri 提供的一个基于 ArcGIS Server 数据创建和共享数据可视化的 beta 版本库。它是建立在 D3 和 Vega 图形库之上的。Cedar 让我们可以使用简单的模板创建高效的数据可视化和图表。

加载 Cedar 库

我们可以使用两种方法加载 Cedar。我们可以使用脚本标签,也可以使用 AMD 模式。后一种方法更受推荐。

使用脚本标签加载

通过包含脚本标签加载 Cedar 及其依赖项。这将使 Cedar 全局可用于我们的应用程序:

<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script type="text/javascript" src="http://vega.github.io/vega/vega.min.js"></script>
<script type="text/javascript" src="https://rawgit.com/Esri/cedar/master/src/cedar.js"></script>

<script>
  var chart = new Cedar({"type": "bar"});
  ...
</script>

使用 AMD 模式加载

或者,我们可以使用 ArcGIS API for JavaScript 捆绑的 dojo 加载程序,将 Cedar 及其依赖项声明为包来加载它们:

var package_path = window.location.pathname.substring(0, window.location.pathname.lastIndexOf('/'));
var dojoConfig = {
packages: [{
    name: "application",
    location: package_path + '/js/lib'
  },
  {
    name: "d3",
    location: "http://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5",
    main: "d3.min"
  },
  {
    name: 'vega',
    location: 'http://vega.github.io/vega/',
    main: 'vega.min'
  }, {
    name: 'cedar',
    location: package_path + '/js/cedar',
    main: 'cedar'
  }]
};

dojo包需要在/js/cedar位置找到一组 Cedar 库文件。我们可以从以下 github 存储库下载所需的文件:github.com/Esri/cedar/tree/develop/src

我们需要在先前提到的 URL 找到的所有文件。将这些文件放在应用程序的/js/cedar文件夹中。

使用 AMD 模式加载

现在我们可以在我们自己的定义函数中加载 Cedar 模块,就像以下代码片段中所示的那样:

define([
  "cedar",
  "dojo/domReady!"
], function (Cedar) 
{
  var chart = new Cedar({
  ...

  });

  chart.show({
    elementId: "#cedarchartdiv",
    width: 900
  });
});

要创建一个简单的图表,我们只需要定义两个属性:

  • type—定义我们试图构建的图表类型(barbubblescatterpie等)。

  • 数据集—定义数据应该来自哪里;这可以是来自 URL 或值(数组)。数据集还接受查询和映射等属性。

  • 数据集的映射属性定义了呈现地图所需的对象。相应类型图表的规范可以在/js/cedar/charts/<chart_type>.js找到。

对于条形图,映射属性需要两个对象,xy。让我们尝试为我们的县级人口统计图创建一个总结。在这里,我们试图总结按州分组的所有县的家庭收入中位数的平均值。以下简单的代码可以做到这一切,并显示一个简单的条形图:

var chart = new Cedar({
  "type": "bar",
  "dataset": {
    "url": "/proxy/proxy.ashx?http://demographics5.arcgis.com/arcgis/rest/services/USA_Demographics_and_Boundaries_2015/MapServer/15",
    "query": {
      "groupByFieldsForStatistics": "ST_ABBREV",

//Find the average value of Median Household Income
      "outStatistics": [{
        "statisticType": "avg",
        "onStatisticField": "MEDHINC_CY",
        "outStatisticFieldName": "AVG_MEDHINC_CY"
      }]
    },
    "mappings": {
      "sort": "AVG_MEDHINC_CY",
      "x": {
        "field": "ST_ABBREV",
        "label": "State"
      },
      "y": {
        "field": "AVG_MEDHINC_CY",
        "label": "Avg. Median Household Income"
      }
    }
  }
});

chart.tooltip = {
  "title": "{ST_ABBREV}",
  "content": "{AVG_MEDHINC_CY} population in {ST_ABBREV}"
}

//show the chart
chart.show({
  elementId: "#cedarchartdiv",
  width: 900
});

先前的代码行就是配置 Cedar 库所需的全部内容,它为我们提供了所有州的平均收入水平的出色可视化,并按升序排列。

使用 AMD 模式加载

这种类型的图表给我们提供了数据的整体图片。让我们动手尝试构建一个散点图,它可以让我们映射多个变量。

我们的目标是将所有州的收入水平沿X轴进行映射,将多样性指数沿Y轴进行映射,并根据州对数据点进行不同的着色。

州级数据的人口统计 URL 是:demographics5.arcgis.com/arcgis/rest/services/USA_Demographics_and_Boundaries_2015/MapServer/21

映射对象应该有一个名为 color 的额外参数:

//Get data from the Query Task

var query = new Query();
var queryTask = new QueryTask("http://demographics5.arcgis.com/arcgis/rest/services/USA_Demographics_and_Boundaries_2015/MapServer/21");
query.where = "1 = 1";
query.returnGeometry = false;
query.outFields = ["MEDHINC_CY", "DIVINDX_CY", "NAME", "TOTPOP_CY"];
queryTask.execute(query).then(function (data) {
  /*scatter*/
  var scatter_chart = new Cedar({
    "type": "scatter",
    "dataset": {
      "data": data,
      "mappings": {
        "x": {
          "field": "MEDHINC_CY",
          "label": "Median Houseold Income"
        },
        "y": {
          "field": "DIVINDX_CY",
          "label": "Diversity Index"
        },
        "color": {
          "field": "NAME",
          "label": "State"
        }
      }
    }
  });

  scatter_chart.tooltip = {
    "title": "{NAME}",
    "content": "Median Income:{MEDHINC_CY}<br/>Diversity:{DIVINDX_CY}"
  }

  scatter_chart.show({
    elementId: "#cedarScatterPlotDiv",
    width: 870,
    height: 600
  });

以下截图是先前给出的代码实现的结果。图表根据不同颜色的值生成图例。在我们的情况下,不同的州被着不同的颜色。如果被着色的值的数量较少,例如如果我们使用颜色来表示被分类为一些区域的州,如北部、东北部、南部、西南部和其他基本方向,这种着色方式会更合适。

使用 AMD 模式加载

创建气泡图表会提供一个额外的处理方式——使用气泡的大小表示第三个变量:

var bubble_chart = new Cedar({
      "type": "bubble",
      "dataset": {
        "data": data,
        "mappings": {
          "x": {
            "field": "MEDHINC_CY",
            "label": "Median Houseold Income"
          },
          "y": {
            "field": "DIVINDX_CY",
            "label": "Diversity Index"
          },
          "size": {
            "field": "TOTPOP_CY",
            "label": "Population"
          }
        }
      }
    });

    bubble_chart.tooltip = {
      "title": "{NAME}",
      "content": "Median Income:{MEDHINC_CY}<br/>Diversity:{DIVINDX_CY}"
    }

    bubble_chart.show({
      elementId: "#cedarBubblePlotDiv"
    });

以下截图显示了一个气泡图;气泡的x位置代表县的家庭收入中位数,气泡的y位置代表县的多样性指数,气泡的半径或大小代表县的总人口:

使用 AMD 模式加载

我们从在Infotemplate中创建一个简单的可定制图表开始,它可以可视化一个变量,到一个可以同时可视化三个变量的图表,从而增强我们对数据的理解,并增加其价值。

摘要

我们已经介绍了如何结合空间数据来提供对我们数据的全面洞察。虽然使用Infotemplate和 dojo 图表很方便,但使用 D3 提供了更大的灵活性和对图形元素的更大控制。Esri 提供的开源数据可视化库 Cedar 非常适合轻松创建全新的数据可视化。一旦我们掌握了这些技术以及统计方法,并学会从不同角度看待我们的数据,我们就可以自称为地图数据科学的旗手。我们在可视化空间数据的方式中还缺少一个组成部分。那个组成部分就是时间。在下一章中,我们将看到如何将时空数据可视化,以及在高级图表功能和 ArcGIS JavaScript API 本身所获得的知识。

第九章:时间感知图层的可视化

我们在之前的章节中处理了读取和显示基于时间的数据,以及使用创新库(如 D3 和 Cedar)进行非空间图表方法。本章将讨论使用空间可视化以及其他非空间可视化辅助工具(如时间滑块和时间图表)来可视化时空数据。本章讨论以下主题:

  • 时间感知图层及其需求

  • 使用时间滑块构建干旱应用程序

  • 使用 D3 基于时间数据进行查询

  • 使用 Cedar 图表进行高级时空可视化

时间感知图层

ArcGIS 10 及以上版本支持时间感知图层。时间感知图层是具有TimeInfo属性的 DynamicMapService 或要素图层。以下屏幕截图显示了时间感知要素图层的服务目录。

查看图像中的TimeInfo属性:

时间感知图层

服务目录中 TimeInfo 信息的快照

让我们讨论TimeInfo对象的组件,这与我们在之前的图像中看到的类似。 TimeInfo属性为我们提供以下信息:

  • 图层中存储时间信息的字段(状态时间字段,结束时间字段)。

  • 数据可用的最小和最大时间(时间范围)。

  • 时间参考属性指的是存储日期时间值的时区(如果为空,则遵循 UTC 时间;这将在详细讨论)。

  • 时间间隔单位是每个要素可用的时间间隔。

  • 具有实时数据属性指的是一个布尔值,表示数据是否持续更新。

  • 导出选项提供了一系列属性,如使用时间、时间数据累积和时间偏移。时间数据累积是一个布尔值,指的是检索到的特征是否随时间累积。

时间感知图层的需求

时间感知图层让我们在时空背景下理解数据;这意味着我们可以看到空间信息如何随时间变化。这种数据具有各种实际应用,例如:

  • 了解城市中随时间变化的犯罪热点位置

  • 追踪飓风并显示其当前位置

  • 在短时间内区域内洪水事件的扩散

  • 显示一个州内油井的扩散

  • 干旱条件如何随时间影响某个地方

理解时间感知图层

对 ArcGIS 中时间概念的基本理解将有助于我们更好地使用时间感知图层。以下几点值得注意:

  • 时间应始终参考协调世界时UTC),这在功能上等同于格林威治标准时间GMT)。

  • 就像地图的空间范围一样,我们可以定义地图的时间范围,其中包含时间感知图层。这将仅影响具有timeInfo属性的地图图层。时间范围由esri/TimeExtent模块提供。我们可以使用以下任一属性定义TimeExtent对象:

  • “开始时间”

  • “结束时间”

  • “开始时间”和“结束时间”

require(["esri/TimeExtent", ... ], 
function(TimeExtent, ... ){
  var timeExtent = new TimeExtent();
  timeExtent.startTime = new Date("1/15/1989 UTC");
  map.setTimeExtent(timeExtent);
});
  • 导出选项对象中的“Time Data Cumulative”属性确定数据是否可以累积。

  • 当时间感知图层中的数据无法在地图显示中累积时,我们应该在时间滑块中使用一个滑块。我们将很快讨论时间滑块。

构建干旱应用程序

让我们构建一个应用程序,显示美国的干旱情况,以了解 ArcGIS 中支持时间感知图层的功能。

以下网址提供了从 2000 年至今美国干旱强度的每周更新值:earthobs1.arcgis.com/arcgis/rest/services/US_Drought/MapServer

您可能需要 ArcGIS 开发人员帐户才能访问这些数据。

一个地区的干旱被定义为水供应和水需求在较长时间内的不平衡。由于干旱可能会直接和间接地对环境、经济和社会产生影响,因此监测干旱对于规划、准备和各级政府的减灾工作至关重要。

我们的应用程序试图显示整个美国当前和历史上的干旱数值。这些数据自 2000 年 1 月 4 日以来每周由美国干旱监测器制作,并且完整的时间序列被存档在这里。每个星期四发布一张新地图,以反映上一周的情况。

使用时间滑块

API 提供的TimeSlider模块能够与时间感知图层进行交互。TimeSlider是 API 提供的一个小部件,我们可以在我们的代码中使用它动态查询时间感知图层。它还支持动画,这样我们就可以看到空间要素随时间如何变化,或者在时间间隔内要素如何累积。

要使用TimeSlider,我们需要加载 Esri 的dijit,名为esri/dijit/TimeSlider。除了TimeSlider dijit之外,我们还可能需要加载名为esri/TimeExtent的模块。时间范围对于定义停止点很有用。以下图片试图展示TimeSlider dijit的组件以及与TimeSlider dijit相关的编程术语的物理表示,比如stopstimeIntervalthumb等等:

使用时间滑块

创建 TimeSlider 的步骤

以下是创建时间滑块的步骤:

  1. 在 DynamicMapService 或要素图层的加载事件上,获取图层的时间范围。

  2. 初始化TimeSlider dijit并将其分配给容器元素,比如div或内容面板。也将时间滑块分配给地图。

  3. 设置时间滑块的其他属性,比如拇指数量,根据图层的时间范围和时间单位创建时间停止点。

  4. 设置拇指的移动速率。

  5. 为时间滑块创建标签。

  6. 启动时间滑块动画。

时间滑块的时间范围可以直接从图层的timeInfo属性中获取:

on(droughtcMapServiceLayer, "load", function (evt) {
var layerTimeExtent = evt.layer.timeInfo.timeExtent;
  _createEsriTimeSlider(layerTimeExtent);
});

以下代码片段解释了如何将时间滑块设置到地图并开始动画:

//Pass the time extent to the function

function _createEsriTimeSlider(layerTimeExtent) {

/*Time Slider*/
  var timeSlider = new TimeSlider({
    style: "width: 100%;"
  }, dom.byId("timeSliderDiv"));
  map.setTimeSlider(timeSlider);

/* We just need one thumb for our time aware data */

  timeSlider.setThumbCount(1);

//Though a weekly data is available, let us Create Time stops
//for Yearly intervals
//

timeSlider.createTimeStopsByTimeInterval(layerTimeExtent, 1, "esriTimeUnitsYears");

//Waits at each stop for 2 seconds

  timeSlider.setThumbMovingRate(2000);

//Start the time slider animation

  timeSlider.startup();

  //add labels for every other time stop

  var labels = array.map(timeSlider.timeStops, function (timeStop, i) {
    if (i % 2 === 0) {
      return timeStop.getUTCFullYear();
    } else {
      return "";
    }
  });

  timeSlider.setLabels(labels);

//Wait for the map service to load at each stop

  timeSlider.on("time-extent-change", function (evt) {
    //update the text

    var currentValString = evt.endTime.getUTCFullYear();
    dom.byId("daterange").innerHTML = "<i>" + currentValString + "<\/i>";
  });

  on(droughtcMapServiceLayer, "update-start", function (evt) {

  //When updating layer, pause the time slider animation

    timeSlider.pause();
  });

  on(droughtcMapServiceLayer, "update-end", function (evt) {

//When update is done, play the time slider animation

    timeSlider.play()
  });
}

使用之前的代码块,我们能够开发一个带有timeSlider小部件的简单应用程序;在初始时间停止时可以看到应用程序的快照:

创建 TimeSlider 的步骤

一旦点击播放按钮,拇指就开始移动。在每个停止点,拇指可能会在播放动画中暂停超出规定的时间间隔。这个暂停是地图服务在特定时间停止点获取动态地图服务所花费的时间。在下面的图像中,时间滑块动画停止了几秒钟,让我们有机会捕捉 2004 年的地图实例:

创建 TimeSlider 的步骤

如果您观察浏览器的网络选项卡,您会注意到每次拇指沿着时间滑块移动时都会发出一个 HTTP GET请求。在每个停止点,都会获取一个新的动态地图图像,对应于特定时间的地图实例。让我们考虑一下这张图像,它显示了 2010 年的一个时间点的快照:

创建 TimeSlider 的步骤

发出 HTTP GET请求以生成之前看到的动态图像的 URL,其中每个查询参数都用新行分隔:

http://localhost:9095/proxy/proxy.ashx?
http://earthobs1.arcgis.com/arcgis/rest/services/US_Drought/MapServer/export?
dpi=96
&transparent=true
&format=png8
&time=946944000000%2C1262563200000
&bbox=-17599814.30461256%2C1159119.7738912208%2C-4234952.783009615%2C6765317.176437697
&bboxSR=102100
&imageSR=102100
&size=1366%2C573
&f=image

URL 提供了关于生成的图像类型的大量信息。应该注意到请求经过代理页面。时间参数表示 2010 年的刻度。

使用 D3 进行基于时间的查询

前面的例子是基于使用 API 提供的内置TimeSlider dijit查询时间感知图层。我们可以进一步利用 D3 库提供的丰富的时间数据支持来增强时间滑块的功能。

我们在本节中的目标是创建一个可以与我们的时间感知图层交互的 D3 时间滑块。

以下代码受到bl.ocks.org/zanarmstrong/ddff7cd0b1220bc68a58上给出的代码清单的启发。

该网页解释了如何有效地使用 D3 对象来读取和显示时间数据在时间滑块中。

使用 D3 基于时间查询

在实现代码之前,有一些重要的概念我们需要理解。

时间的缩放和格式化

在我们之前的章节中,我们讨论了如何使用 D3 函数来缩放数值和序数值。当涉及到时间时,我们需要处理应用于时间的缩放。下面的片段解释了如何将时间范围缩放到容器的宽度:

// parameters
    var margin = {
        top: 10,
        right: 50,
        bottom: 50,
        left: 50
      },
      width = 800 - margin.left - margin.right,
      height = 150 - margin.bottom - margin.top;

// scale function
    var timeScale = d3.time.scale()
      .domain([startDate, endDate])
      .range([0, width])
      .clamp(true);

在前面的片段中,我们假设能够从图层的timeInfo属性中提供开始和结束数据值给 D3 时间缩放函数。我们还需要一个适当的日期格式来呈现我们拥有的日期值。下面的代码行为我们提供了日期的日期-月份-日期格式:

var formatDate = d3.time.format("%Y-%m-%d");

D3 刷子

D3 刷子相当于时间滑块dijit中的拇指。刷子是一个 D3 SVG 元素对象,接受时间范围。在下面的片段中,我们有一个接受 x 轴上时间刻度因子的刷子元素:

    // defines brush
    var brush = d3.svg.brush()
      .x(timeScale)    
      .extent([startingValue, startingValue])
      .on("brush", brushed);

我们需要了解的另一个重要方面是关于刷子的事件,比如mousedown。当用户移动刷子(在mousedown后的mousemove)时,将通过调用timescale.invert重新计算范围。这将让我们设置刷子的新范围。下面的代码解释了这一方面:

svg.on("mousedown", function (data) {
      var value = brush.extent()[0];

      if (d3.event.sourceEvent) { // not a programmatic event
        value = timeScale.invert(d3.mouse(this)[0]);
        brush.extent([value, value]);
      }
      console.log(formatDate(value));
    });

除了网页中提供的代码清单,我们还需要额外的代码来在刷子上的mousedown事件持续至少 500 毫秒时才触发检索时间感知数据的查询。否则,当我们沿着时间刻度移动刷子时,事件将被触发多次。下面的函数在移动刷子时触发,将发布一个名为application/d3slider/timeChanged的主题:

function brushed() {
  var value = brush.extent()[0];

  if (d3.event.sourceEvent) { // not a programmatic event
    value = timeScale.invert(d3.mouse(this)[0]);
    brush.extent([value, value]);
  }

  handle.attr("transform", "translate(" + timeScale(value) + ",0)");
  handle.select('text').text(formatDate(value));
  var reqValue = formatDate(value);

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(function () {
    //alert(reqValue);
    topic.publish("application/d3slider/timeChanged", value);
  }, 500);

}

订阅主题的代码将设置地图到刷子指向的时间范围:

topic.subscribe("application/d3slider/timeChanged", function () {
  console.log("received:", arguments);
  var startDate = arguments[0];
  if (startDate) {
    var timeExtent = new TimeExtent();
    timeExtent.startTime = startDate;
    map.setTimeExtent(timeExtent);
  }
});

查找构建 D3 滑块的完整代码清单:

define([
  "dojo/_base/declare",
  "d3",
  "dojo/topic",
  "dojo/_base/array",
  "dojo/domReady!"
], function (
    declare,  
    d3,  
    topic,
    array) {
    //http://bl.ocks.org/zanarmstrong/ddff7cd0b1220bc68a58

    var isInitilaized = false;

    topic.subscribe("application/d3slider/initialize", function () {
        if (!isInitilaized) {
            console.log("received:", arguments);
            var startDate = arguments[0];
            var endDate = arguments[1];

            var formatDate = d3.time.format("%Y-%m-%d");
            var timer;
            // parameters
            var margin = {
                    top: 10,
                    right: 50,
                    bottom: 50,
                    left: 50
                },
                width = 800 - margin.left - margin.right,
                height = 150 - margin.bottom - margin.top;

            // scale function
            var timeScale = d3.time.scale()
                .domain([startDate, endDate])
                .range([0, width])
                .clamp(true);

            // initial value
            var startValue = timeScale(new Date('2012-03-20'));
            startingValue = new Date('2012-03-20');

            //////////

            // defines brush
            var brush = d3.svg.brush()
                .x(timeScale)
                .extent([startingValue, startingValue])
                .on("brush", brushed);

            var svg = d3.select("#d3timeSliderDiv").append("svg")
                .attr("width", width + margin.left + margin.right)
                .attr("height", height + margin.top + margin.bottom)
                .append("g")
                // classic transform to position g
                .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

            svg.on("mousedown", function (data) {
                var value = brush.extent()[0];

                if (d3.event.sourceEvent) { // not a programmatic event
                    value = timeScale.invert(d3.mouse(this)[0]);
                    brush.extent([value, value]);
                }
                console.log(formatDate(value));
            });

            svg.append("g")
                .attr("class", "x axis")
                // put in middle of screen
                .attr("transform", "translate(0," + height / 2 + ")")
                // inroduce axis
                .call(d3.svg.axis()
                    .scale(timeScale)
                    .orient("bottom")
                    .tickFormat(function (d) {
                        return formatDate(d);
                    })
                    .tickSize(0)
                    .tickPadding(12)
                    .tickValues([timeScale.domain()[0], timeScale.domain()[1]]))
                .select(".domain")
                .select(function () {
                    console.log(this);
                    return this.parentNode.appendChild(this.cloneNode(true));
                })
                .attr("class", "halo");

            var slider = svg.append("g")
                .attr("class", "slider")
                .call(brush);

            slider.selectAll(".extent,.resize")
                .remove();

            slider.select(".background")
                .attr("height", height);

            var handle = slider.append("g")
                .attr("class", "handle");

            handle.append("path")
                .attr("transform", "translate(0," + height / 2 + ")")
                .attr("d", "M 0 -20 V 20");

            handle.append('text')
                .text(startingValue)
                .attr("transform", "translate(" + (-45) + " ," + (height / 2 - 25) + ")");

            slider.call(brush.event);

            function brushed() {
                var value = brush.extent()[0];

                if (d3.event.sourceEvent) { // not a programmatic event
                    value = timeScale.invert(d3.mouse(this)[0]);
                    brush.extent([value, value]);
                }

                handle.attr("transform", "translate(" + timeScale(value) + ",0)");
                handle.select('text').text(formatDate(value));
                var reqValue = formatDate(value);

                if (timer) {
                    clearTimeout(timer);
                }
                timer = setTimeout(function () {
                    //alert(reqValue);
                    topic.publish("application/d3slider/timeChanged", value);
                }, 500);

            }
            isInitilaized = true;
        }
    });
});

当我们在我们的应用程序中加入前面的代码时,我们得到了这个漂亮的 D3 时间滑块,如下图所示,它让我们通过一个连续的时间谱查询,而不是每年停止一次:

D3 刷子

在我们移动或暂时停止 D3 刷子(拇指)在时间滑块上的不同位置时,不会触发动态地图图像的查询。只有当我们让拇指停留在一个位置超过 0.5 秒时才会触发。这是性能和响应性之间的安全折衷。下面的屏幕截图显示了一个时间点(2002 年 8 月 24 日)的动态地图图像:

D3 刷子

使用 Cedar 进行高级时空可视化

时间感知图层提供了有关数据的宝贵信息——每个要素在每个时间停止时的整套值。到目前为止,我们一直在使用时间滑块或 D3 中的类似方法来在不同的时区可视化整个空间数据集。我们从未能够可视化特定要素在整个时间范围内的值,或者至少在多个时间范围内的值。我们在本节中的目标就是这样——可视化所选要素在整个时间范围内的值。

我们将使用以下图层进行可视化,该图层位于earthobs1.arcgis.com/arcgis/rest/services/US_Drought_by_County/FeatureServer/0

该图层显示了美国各县从 2000 年至今的干旱强度。数据的时间范围是 2000 年 1 月 4 日至今,每周四更新以反映上一周的情况。

我们的目标是获取所选要素的所有数据。可以按照以下步骤来实现我们的目标:

  1. 选择一个要素并对其执行识别任务以获取要素 ID。

  2. 使用要素 ID 查询先前的要素图层。

  3. 将数据传递给time类型的 Cedar 图表。

以下代码片段解释了如何形成识别参数。在每次地图点击时执行识别任务:

  function initIdentify () {
            map.on("click", doIdentify);

            identifyTask = new IdentifyTask("http://server.arcgisonline.com/arcgis/rest/services/Demographics/USA_1990-2000_Population_Change/MapServer");

            identifyParams = new IdentifyParameters();
            identifyParams.tolerance = 1;
            identifyParams.layerIds = [3];
            identifyParams.returnGeometry = true;
            identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_ALL;
            identifyParams.width = map.width;
            identifyParams.height = map.height;
    }

地图点击事件调用名为doIdentify()的以下函数:

function doIdentify (event) {
      map.graphics.clear();

//Use the map click point for the identify task

      identifyParams.geometry = event.mapPoint;
      identifyParams.mapExtent = map.extent;
      identifyTask.execute(identifyParams, function (results) {
      console.log(results[0].feature.attributes);

//Initiate a Query Task

      var queryTask = new QueryTask("http://earthobs1.arcgis.com/arcgis/rest/services/US_Drought_by_County/FeatureServer/0");
      var query = new Query();
      query.returnGeometry = true;
      query.outFields = ["*"];

//Query based on the feature id returned by the identify task

      query.where = "countycategories_admin_fips = '"+results[0].feature.attributes.ID+"'";
      query.orderByFields = ["countycategories_date"];
      queryTask.execute(query).then(function(qresult){
      console.log(qresult);

//Send the query result to the topic "some/topic"

      topic.publish("some/topic", qresult);
    });
  });
}

发送查询数据的主题将由构建 Cedar 图表的函数订阅。Cedar 图表类型为time

time类型的 Cedar 图表需要在字段映射中包含以下类型的字段:

  • Esri 日期时间字段

  • 任何数值

在我们的情况下,我们将映射字段,即countycategories_date(日期时间字段)和countycategories_d0(数值字段):

topic.subscribe("some/topic", function () {
            var data = arguments[0];
            var chart = new Cedar({
                "type": "time"
            });
            var dataset = {
                "data": data,
                "mappings": {
                    "time": {
                        "field": "countycategories_date",
                        "label": "Date"
                    },
                    "value": {
                        "field": "countycategories_d0",
                        "label": "Countycategories D0"
                    },
                    "sort": "countycategories_date"
                }
            };

//tool tip field
            chart.tooltip = {
                "title": "Countycategories D0",
                "content": "{countycategories_d0}"
            };

        chart.dataset = dataset;

//show the chart

chart.show({
            elementId: "#droughtHistoryMap",
            autolabels: true,
            height:150,
            width:800
        });
        chart.on('click', function(event,data){
            console.log(event,data);
            topic.publish("application/d3slider/timeChanged", new Date(data.countycategories_date));
        });
    });

将先前的代码合并到应用程序中,我们能够看到所选县的一段时间内干旱数值的基于时间的图表。在下面的图像中,图表表示了所选要素的干旱数值时间线:

使用 Cedar 进行高级时空可视化

这种表示方法以多维视角呈现了多种方式。一种是我们仍然在特定时间点看到整个国家的干旱空间分布。与此同时,我们能够使用非空间可视化辅助工具,比如时间图,来可视化特定特征在县一级的整个时间段内的干旱数值集合。

总结

我们已经介绍了如何使用三种方法以时空方式可视化数据,即时间滑块、D3 和 Cedar。虽然时间滑块是由dijit提供的内置 API,但 D3 解决方案更加广泛和灵活。使用 Cedar time类型图表来绘制时空数据提供了不同的时空数据视角。我们从 API 的基础开始,稳步进入构建具有小部件的完整的 dojo web 应用程序的微妙之处。我们处理了 API 提供的多功能查询功能,并在不同形式的章节中使用了它。显示查询结果是我们后来的重点。查询结果可以显示为空间图形,也可以以表格形式显示。我们后来深入研究了更直观的渲染方式,使用渲染技术在地图上显示我们的空间数据。

然后我们意识到一些统计知识不仅可以帮助我们更好地理解数据,还可以更好地可视化数据,以便用户可以从中获得新的见解。最后三章是关于通过非空间组件(如图表技术和时间维度)为我们的地图添加多个维度。本章在感知我们的地图在所有讨论的维度上达到了高潮,但这当然不是极限。相反,这是像您这样的富有进取心的地图数据科学家的起点!

posted @ 2024-05-22 12:06  绝不原创的飞龙  阅读(84)  评论(0编辑  收藏  举报