Leaflet-js-精要-全-
Leaflet.js 精要(全)
原文:
zh.annas-archive.org/md5/59f23bc77d74e56547eaefa785fc89a1译者:飞龙
前言
制作地图曾经需要广泛的制图知识、昂贵的软件和专业技术。如今,有众多工具可供选择,其中许多是免费的,简化了地图制作过程。本书是关于使用这样一个库,即 Leaflet.js。
Leaflet.js 是一个 JavaScript 库,虽然体积小,但几乎包含了你需要的所有功能。如果一个功能在核心库中不可用,它可能作为众多插件之一提供。最大的地图制作软件供应商,环境系统研究学院(ESRI),甚至已经发布了 Leaflet.js 的插件。如果你对制作地图或数据可视化感兴趣,Leaflet.js 是你要学习的库。
无论你是想构建简单的地图还是高级的地图应用,这本书都将基于你的 JavaScript 知识,帮助你实现目标。这本书旨在让那些对地图制作新手以及那些可能了解地图但刚开始学习编码的人都能轻松阅读。
本书涵盖内容
第一章,使用 Leaflet 创建地图,将引导你了解在 Leaflet.js 中制作地图的基础知识。你将从创建一个包含最少 JavaScript 代码的 HTML 文件开始,以显示地图。你将学习如何选择不同的底图和提供者以及不同的底图格式。然后,你将学习如何显示地理特征,如点、折线和多边形。
第二章,地图 GeoJSON 数据,介绍了 GeoJSON 数据格式的地理版本。你将学习如何创建自己的 GeoJSON 数据以及从其他来源获取数据。在本章中,你将学习如何对数据进行样式化,并通过特征迭代添加弹出窗口。
第三章,创建热力图和等值线图,从简单地显示点转向显示数据的显著性或比较。它建立在之前所学的基础上,并教你如何使用不同的插件创建热力图。你还将学习如何利用你对 GeoJSON 样式的知识来创建等值线图。
第四章,创建自定义标记,将指导你如何定制你在地图中使用的标记。你将学习如何绘制自己的图像或修改现有图像,以便将其用作地图中的标记。你将了解几个提供可定制预制标记的插件。此外,你还将学习如何使标记动画化以及如何结合插件以产生附加效果。
第五章,Leaflet 中的 ESRI,介绍了地图中最常用的数据格式和服务器端点。本章将教会您如何在地图中加载 shapefiles。您还将学习如何连接到一个提供公开 REST 服务的 ESRI 服务器。使用 ESRI-Leaflet 插件,您将学习如何进行地址的地理编码和反向地理编码,从服务器过滤数据,以及通过位置进行查询。
第六章,Node.js、Python 和 C#中的 Leaflet,在您所学的所有内容的基础上,教您如何构建其他框架和语言的应用程序。本章将教会您如何构建前端和后端。您将使用 JavaScript 和 Python 构建服务器。您将了解 NoSQL 数据库和 AJAX,以在不刷新网页的情况下显示和更新数据。最后,您将学习如何通过在 C#中嵌入 Leaflet 来创建 Windows 桌面应用程序。
您需要为本书准备的
至少需要以下软件来完成本书:
- 一个网络浏览器,最好是 Google Chrome,可在
www.google.com/chrome/browser/找到。
对于第五章中的示例,Leaflet 中的 ESRI和第六章,Node.js、Python 和 C#中的 Leaflet,需要以下软件:
-
Node.js,可在
nodejs.org/找到。 -
Python 2.7,最好是 3.x 版本。您可以从
www.python.org/下载任一版本。 -
Visual Studio Express 2010。您可以在
www.visualstudio.com/downloads/download-visual-studio-vs找到免费副本。 -
CherryPy 可在
www.cherrypy.org/找到。 -
MongoDB 可在
www.mongodb.org/找到。 -
Pymongo 是用于 MongoDB 的 Python 库,可在
pypi.python.org/pypi/pymongo/免费获取。 -
MongoDB 的 C#驱动程序可在
github.com/mongodb/mongo-csharp-driver/releases以多种格式提供。 -
WAMP 可以从
www.wampserver.com/en/下载。
本书面向的对象
如果您是一位具有一些 JavaScript 知识的地图制作者,这本书是您理想的资源,它教会您如何将地图带到网络并使其交互式。如果您是一位 JavaScript 开发者,这本书将向您展示如何使用这些技能来构建强大的地图应用程序。
规范
在本书中,您会发现许多不同风格的文本,用于区分不同类型的信息。以下是一些这些风格的示例及其含义的解释。
文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名都按照以下方式显示:“创建一个查询的文本字符串,并初始化你的StringBuilder()方法以保存函数和结果中的 JavaScript。”
代码块按照以下方式设置:
var layer = new L.TileLayer('http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png');
map.addLayer(layer);
当我们希望引起你对代码块中特定部分的注意时,相关的行或项目将以粗体显示:
<style>
html, body, #map {
padding: 0;
margin: 0;
height: 100%;
}
#points.hidden {
display: none;
}
</style>
<body>
<div id="map"></div>
<div id="points"></div>
任何命令行输入或输出都按照以下方式书写:
npm install –g connect
新术语和重要词汇以粗体显示。你在屏幕上看到的,例如在菜单或对话框中的文字,在文本中会这样显示:“你现在需要在解决方案资源管理器窗口中右键单击项目,并选择添加引用。”
注意
警告或重要提示以如下方式显示在框中。
小贴士
小贴士和技巧看起来像这样。
读者反馈
我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢什么或可能不喜欢什么。读者反馈对我们开发你真正能从中获得最大价值的标题非常重要。
要发送给我们一般性的反馈,只需发送电子邮件到<feedback@packtpub.com>,并在你的邮件主题中提及书名。
如果你在一个你具有专业知识的话题上感兴趣,无论是写作还是为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在你已经是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助你从你的购买中获得最大价值。
下载示例代码
你可以从你购买的所有 Packt 书籍的账户中下载示例代码文件。www.packtpub.com。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support,并注册以将文件直接通过电子邮件发送给你。
错误清单
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果你在我们的书中发现错误——可能是文本或代码中的错误——如果你能向我们报告这一点,我们将不胜感激。通过这样做,你可以帮助其他读者避免挫败感,并帮助我们改进本书的后续版本。如果你发现任何错误清单,请通过访问www.packtpub.com/submit-errata,选择你的书籍,点击错误清单提交表单链接,并输入你的错误清单详情。一旦你的错误清单得到验证,你的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从www.packtpub.com/support中选择你的标题来查看。
侵权
在互联网上,版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。
请通过 <copyright@packtpub.com> 联系我们,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们作者方面的帮助,以及我们为您提供有价值内容的能力。
问题和建议
如果您在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。
第一章. 使用 Leaflet 创建地图
在过去的二十年里,基于网络的地图技术迅速发展,从 MapQuest 和 Google 地图到我们手机地图应用中的实时位置信息。过去有一些开源项目用于开发基于网络的地图,例如 MapServer、GeoServer 和 OpenLayers。然而,环境系统研究院 (ESRI) 包含 Flex 和 Silverlight API;这些 API 可以从他们的 ArcServer 服务中创建基于网络的地图。
在过去的几年里,JavaScript 在在线地图领域引起了轰动。2013 年,有一个 JS.geo 会议。受到关注的库是 Leaflet。这是一个用于创建交互式、基于网络的地图的 JavaScript 库。使用它,您可以用尽可能少的 JavaScript 代码行创建一个简单的地图,或者用数百行代码创建复杂、交互式、可编辑的地图。
注意
您可以在 leafletjs.com 上找到有关 Leaflet 的更多信息。
本书假设您对 HTML 和 CSS 有基本的了解,主要是如何链接外部的 .js 和 .css 文件以及如何命名和设置 <div> 元素的大小。它还假设您对 JavaScript 有一定的了解。
在本章中,我们将介绍以下主题:
-
瓦片图层
-
向量图层
-
弹出窗口
-
自定义函数/响应用户事件
-
移动地图
创建简单的基本地图
要使用 Leaflet 创建地图,您需要做以下四件事:
-
引用 JavaScript 和 层叠样式表 (CSS) 文件
-
创建一个
<div>元素来包含地图 -
创建一个
map对象 -
添加瓦片图层(基本图层)
在我们深入构建地图的细节之前,让我们设置一个 HTML 文件,我们可以在整本书中使用它。打开一个文本编辑器并输入以下 HTML:
<!DOCTYPE html><html>
<head><title>Leaflet Essentials</title>
</head>
<body>
</body>
</html>
将文件保存为 LeafletEssentials.html。我们将在本书的其余部分添加到这个文件中。
小贴士
下载示例代码
您可以从您在 www.packtpub.com 的账户中下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册以将文件直接通过电子邮件发送给您。
引用 JavaScript 和 CSS 文件
有两种方法可以将 Leaflet 加载到您的代码中:您可以选择引用一个托管文件,或者将副本下载到您的本地计算机上并引用该副本。接下来的两个部分将介绍如何为托管副本或本地副本设置您的环境。
使用托管版本
我们不会对原始 CSS 或 JS 文件进行任何修改,因此我们将链接到托管版本。
在文本编辑器中打开 LeafletEssentials.html。在 <head> 元素中,并在 </title> 元素之后,添加以下代码:
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css"0.7.3 />
在 <body> 标签之后,添加以下代码:
<script src="img/Leaflet"></script>
链接是标准的 HTML <link> 和 <script>。在您的浏览器中打开任一链接,您将看到文件的内容。
使用本地副本
使用本地副本与托管副本相同,除了文件路径不同。从 leafletjs.com/download.html 下载 Leaflet.js 并将其解压到您的桌面。如果您下载了 Leaflet-0.7.3.zip,您应该有一个同名文件夹。在该文件夹中,您将找到一个名为 images 的子文件夹和以下三个文件:
-
Leaflet.css: 这是一个层叠样式表 -
Leaflet: 这是 Leaflet 的压缩版本 -
Leaflet-src.js: 这是 Leaflet 的完整版本,面向开发者
在 LeafletEssentials.html 的 <head> 标签中添加以下代码:
<link rel="stylesheet"href="\PATH TO DESKTOP\leaflet-0.7.3\leaflet.css" />
在 LeafletEssentials.html 的 <body> 标签中添加以下代码:
<script src="img/Leaflet"></script>0.7.3Leaflet
您现在有了对 Leaflet 库和 CSS 的本地引用。我们使用 Leaflet 文件,因为它更小,加载速度更快。只要您不需要向文件中添加任何代码,您就可以删除 Leaflet-src.js 文件。
创建一个用于包含地图的 <div> 标签
您需要一个放置地图的地方。您可以通过创建一个具有将被地图对象引用的 ID 的 <div> 标签来完成此操作。包含地图的 <div> 标签需要一个定义的高度。为标签设置高度的最简单方法是使用您创建的 <div> 标签中的 CSS。在 Leaflet 文件引用之后,将以下代码添加到 LeafletEssentials.html 的 <body> 标签中:
<div id="map" style="width: 600px; height: 400px"></div>
小贴士
在 HTML 文件中样式化 <div> 标签,而不是 Leaflet.css 文件。如果您这样做,地图 <div> 的大小将适用于使用它的每个页面。
创建一个地图对象
现在您已经有了引用和放置地图的地方,是时候开始使用 JavaScript 编写地图了。第一步是创建一个 map 对象。map 类接受一个 <div> 标签(您在上一步骤中创建的)和 options:L.map(div id, options)。要创建一个名为 map 的地图对象,请在 LeafletEssentials.html 中的 <script> 元素之后添加以下代码:
var map = L.map('map',{center: [35.10418, -106.62987],
zoom: 10
});
或者,您可以使用 setView() 方法缩短代码,该方法接受 center 和 zoom 选项作为参数:
var map = L.map('map').setView([35.10418, -106.62987],10);
在前面的代码中,您创建了一个 map 类的新实例,并将其命名为 map。您可能习惯于使用 new 关键字创建类的实例;这在下述代码中显示:
var map = new L.Map();
Leaflet 实现了工厂,消除了使用 new 关键字的需要。在这个例子中,L.map() 被赋予了 <div> 地图和两个选项:center 和 zoom。这两个选项使用纬度和经度将地图定位在屏幕上,以 <div> 元素为中心,并按所需级别放大或缩小。center 选项接受 [纬度,经度] 参数,而 zoom 接受一个整数;数字越大,缩放越紧。
小贴士
始终分配 center 和 zoom 选项是一个好习惯。没有什么比看到所有数据都位于阿尔伯克基,新墨西哥州的世界地图更糟糕的了。
添加瓦片层
在 Leaflet 中创建您的第一张地图的最后一步是添加瓦片层。瓦片层可以被视为您的底图。这是您将在本书后面添加点、线和多边形的图像。瓦片层是由瓦片服务器提供的一项服务。瓦片服务器通常将层分割成 256 x 256 像素的图像。您通过请求/z/x/y.png的 URL 来检索所需的图像。只有这些瓦片被加载。当您平移和缩放时,新的瓦片会被添加到您的地图上。
瓦片层至少需要瓦片服务器的 URL。在这本书中,我们将使用 OpenStreetMap 作为我们的瓦片层。
备注
使用 OpenStreetMap 瓦片时,您需要遵守服务条款。TOS 可在wiki.openstreetmap.org/wiki/Tile_usage_policy找到。
OpenStreetMap 瓦片服务器的 URL 如下所示:
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
在代码中,我们提供了 OpenStreetMaps 的 URL 模板。我们还调用了addTo()方法,以便绘制图层。我们需要将L.map()作为参数传递给addTo()函数。我们在上一节中命名了我们的L.map()实例为 map(var map = L.map())。
备注
Leaflet 允许方法链:同时在一个对象上调用多个方法。这就是我们在行尾放置.addTo(map)时所做的事情,创建了L.tileLayer()的实例。不使用链式调用将层添加到地图的更长时间方法是先将实例分配给一个变量,然后从该变量调用addTo(),如下所示:
var x = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png');
x.addTo(map);
您现在有一个完整的地图,允许您在世界各地平移和缩放。您的LeafletEssentials.html文件应如下所示:
<html>
<head><title>Leaflet Essentials</title>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
</head>
<body>
<script src="img/Leaflet"></script>
<div id="map" style="width: 600px; height: 400px"></div>
<script>
var map = L.map('map',
{
center: [35.10418, -106.62987],
zoom: 10
});
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
</script>
</body>
</html>
即使有宽松的间距,您也能用六行 JavaScript 构建一个具有平移和缩放功能的完整世界地图。以下截图显示了完成的地图:

瓦片层提供商
现在您已经创建了您的第一张地图,您可能想知道如何更改瓦片层以使用其他内容。有几个瓦片层提供商,其中一些需要注册。本节将向您介绍另外两个选项:Thunderforest 和 Stamen。Thunderforest 提供扩展 OpenStreetMap 的瓦片,而 Stamen 提供更多艺术风格的地图瓦片。这两个服务都可以用来为您的 Leaflet 地图添加不同的底图风格。
Thunderforest 提供五种瓦片服务:
-
OpenCycleMap
-
交通
-
风景
-
户外
-
大地(仍在开发中)
要使用 Thunderforest,您需要将瓦片层指向瓦片服务器的 URL。以下代码显示了如何添加 Thunderforest 瓦片层:
var layer = new L.TileLayer('http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png');
map.addLayer(layer);
上述代码加载了风景瓦片层。要使用其他层,只需将 URL 中的landscape替换为cycle、transport或outdoors。以下截图显示了在 Leaflet 中加载的 Thunderforest 风景层:

Stamen 提供六个瓦片层;然而,我们只会讨论以下三个层:
-
地形(仅在美国可用)
-
水彩
-
素描
其他三个是燃烧地图、火星和树木,以及出租车与犯罪。燃烧地图和火星层需要 WebGL,而树木和出租车与犯罪仅在旧金山可用。虽然这些地图具有明显的震撼力,但它们对我们这里的用途来说并不实用。
注意
了解 Stamen 瓦片层,包括燃烧地图、火星和树木,以及出租车与犯罪,请访问maps.stamen.com。
Stamen 要求你遵循与 Thunderforest 相同的步骤,但还包括添加对 JavaScript 文件的引用。在你的 Leaflet 文件引用之后,添加以下引用:
<script type="text/javascript" src="img/tile.stamen.js?v1.2.4"></script>
Stamen 使用L.StamenTileLayer(tile set name)而不是L.TileLayer()。将瓦片集名称替换为terrain、watercolor或toner。最后,将addLayer()添加到地图中,如下面的代码所示:
var layer = new L.StamenTileLayer("watercolor");
map.addLayer(layer);
Stamen 的瓦片层并非典型的底图层;它们是地图艺术的杰作。
小贴士
Stamen 提供了一个在线工具来编辑地图层并将输出保存为图像。要创建自己的艺术地图图像,请访问mapstack.stamen.com。
以下截图显示了在 Leaflet 中加载的 Stamen 水彩层。当你放大时,你会看到更多细节:

添加 Web 地图服务瓦片层
可以添加到 Leaflet 地图的另一种瓦片层类型是Web 地图服务(WMS)瓦片层。WMS 是通过 HTTP 在网络上请求和传输地图图像的一种方式。它是一个开放地理空间联盟(OGC)规范。
小贴士
关于 WMS 规范的详细技术信息,请参阅 OGC 网站:www.opengeospatial.org/standards/wms。
通过了解如何添加瓦片层,并看到几个示例,你可能已经注意到示例中没有卫星图像。你将添加到地图中的第一个 WMS 层是美国地质调查局(USGS)影像拓扑。
与我们之前使用的L.tileLayer()函数类似,L.tileLayer.wms()函数接受一个 URL 和一组选项作为参数。以下代码将 WMS 层添加到你的地图中:
varusgs = L.tileLayer.wms("http://basemap.nationalmap.gov/ArcGIS/services/USGSImageryTopo/MapServer/WMSServer", {
layers:'0',
format: 'image/png',
transparent: true,
attribution: "USGS"
}).addTo(map);
WMS 的 URL 是从 USGS 网站获取的。你可以在basemap.nationalmap.gov/arcgis/rest/services找到其他 WMS 图层。指定的选项是图层名称、格式、透明度和属性。图层名称将在你连接到的服务的信息页面上提供。格式是图像,透明度设置为true。由于这个图层覆盖了全球,我们没有在其下方放置其他图层,因此透明度可以设置为false。在下一个示例中,你将看到将透明度设置为true如何使另一个图层变得可见。最后,有一个设置为 USGS 的属性。当你将属性分配给图层时,Leaflet 会在地图的右下角添加文本值。使用属性很重要,因为它类似于在文本中引用来源。如果不是你的数据,按照惯例,你应该给予应有的认可。很多时候,这也是版权的要求。由于这个图层来自 USGS,它在图层的属性属性中被认可。
注意
属性值可以包含超链接,如下面的代码所示:
attribution: "<a href='http://basemap.nationalmap.gov/arcgis/rest/services'>USGS</a>"
将 WMS 图层代码插入LeafletEssentials.html,你现在应该有一个带有卫星影像的地图。以下截图显示了加载到 Leaflet 中的卫星影像:

多个瓦片图层
在上一个示例中,你添加了一个 WMS 图层并将透明度设置为true。你需要这样做的原因是因为你可以在每个图层上添加多个瓦片图层,并且当透明度设置为true时,你将能够同时看到它们。在这个示例中,你需要在 USGS 卫星影像上添加国家气象服务(NWS)雷达瓦片 WMS。
注意
国家海洋和大气管理局(NOAA)提供了一系列 WMS 图层的列表;它们可以在以下链接中找到:
nowcoast.noaa.gov/help/mapservices.shtml?name=mapservices
添加额外的 WMS 图层遵循与上一个示例相同的格式,但具有不同的 URL、图层名称和属性。在LeafletEssentials.html中卫星影像代码之后添加以下代码:
Varnexrad = L.tileLayer.wms("http://nowcoast.noaa.gov/wms/com.esri.wms.Esrimap/obs", {
layers: 'RAS_RIDGE_NEXRAD',
format: 'image/png',
transparent: true,
attribution: "NOAA/NWS"
}).addTo(map);
这段代码添加了用于 NWS 雷达镶嵌的 NOAA WMS 层。请注意,URL 和图层已更改,并且归因设置为 NOAA/NWS。RAS_RIDGE_NEXRAD 层是一个网格,当值开始存在时显示值。该图层的名称可以在 NOAA 网站上找到;你不需要记住 RAS_RIDGE_NEXRAD 是天气雷达层。地图上有大量没有数据的部分,由于我们设置了透明度为 true,这些空白区域允许卫星图像变得可见。现在你的地图应该显示带有雷达镶嵌的卫星图像,如下面的截图所示:

如果你将透明度设置为 false,你将允许图层在整个地图上绘制。没有数据区域显示为白色方块,并覆盖下方的卫星图像,如下面的截图所示:

WMS 图层不仅需要作为基础图层;它们也可以用作附加数据。这在之前的示例中已经展示过,你在卫星图像上叠加了雷达。在这个例子中,你使用了卫星图像。你也可以使用来自第一张地图的 OpenStreetMap 瓦片层。再次提醒,只需将透明度设置为 true。WMS 图层可以像点、线和多边形一样添加,这将在以下章节中讨论。
向你的地图添加数据
到目前为止,你已经学会了如何向地图添加瓦片层。在之前的示例中,你在基础瓦片层上添加了一个 WMS 层。现在,你将学习如何绘制需要添加到瓦片层上的自己的图层。你可以添加到地图上的三个矢量数据的基本几何形状通常被称为点、线和多边形。
在本节中,你将学习如何将标记、折线和多边形添加到你的地图上。
点
到目前为止,你的地图并不那么有趣。你经常绘制地图来突出特定的地点或点。Leaflet 有一个 Point 类;然而,它并不是用来在地图上简单地添加一个带有图标来指定地点的点。在 Leaflet 中,点是通过 Marker 类添加到地图上的。至少,Marker 类需要纬度和经度,如下面的代码所示:
Var myMarker = L.marker([35.10418, -106.62987]).addTo(map);
注意
你可以通过简单地调用 L.marker([lat,long]).addTo(map); 来创建一个标记,但将标记分配给一个变量将允许你通过名称与之交互。如果你没有给标记命名,你该如何删除一个特定的标记?
在前面的代码中,你在 [35.10418, -106.62987] 点创建了一个标记,然后,就像瓦片层一样,你使用了 addTo(map) 函数。这就在指定的纬度和经度上创建了一个标记图标。以下截图显示了地图上的标记:

上述示例是一个简化的、几乎无用的标记。Marker类有选项、事件和方法,你可以调用它们来使它们更具交互性和实用性。你将在本章后面学习关于方法——特别是bindPopup()方法——和事件的内容。
创建标记时,你可以指定 10 个选项,如下所示:
-
icon -
clickable -
draggable -
keyboard -
title -
alt -
zIndexOffset -
opacity -
riseOnHover -
riseOffset
选项clickable、draggable、keyboard、zIndexOffset、opacity、riseOnHover和riseOffset都设置为默认值。在第四章“创建自定义标记”中,你将详细了解icon选项。你应该设置的选项是title和alt。title选项是在你用鼠标悬停在点上时显示的工具提示文本,而alt选项是屏幕阅读器用于无障碍阅读的替代文本。这些选项在以下代码中使用:
varmyMarker = L.marker([35.10418, -106.62987],
{title:"MyPoint",alt:"The Big I",draggable:true}).addTo(map);
代码通过添加标题和 alt 文本以及使标记可拖动来扩展了原始标记示例。你将在本章的最后部分使用draggable选项与一个事件。选项设置与创建我们的地图实例时相同;使用花括号来分组选项,并用逗号分隔每个选项。这就是所有对象设置选项的方式。
折线
你将首先学习创建的是一条线。在 Leaflet 中,你将使用Polyline类。折线可以表示单个线段或由多个段组成的线。折线和多边形扩展了path类。你不需要直接调用path,但你有权访问其方法、属性和事件。要绘制折线,你需要提供至少一个经纬度对。折线的选项默认设置,所以除非你想覆盖默认值,否则你不需要指定任何值。以下代码显示了这一点:
var polyline = L.polyline([[35.10418, -106.62987],[35.19738, -106.875]], {color: 'red',weight:8}).addTo(map);
在这个示例中,折线是红色的,宽度为8。weight选项默认为5。如果你想线条更粗,增加数字。如果你想线条更细,减少数字。要向线条添加更多段,只需像以下代码所示添加额外的纬度和经度值:
var polyline = L.polyline([[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]], {color: 'red',weight:8}).addTo(map);
备注
你需要首先提供一个纬度和经度对,因为一条线至少由两个点组成。之后,你可以声明额外的纬度和经度来扩展你的线。
以下截图显示了添加到地图上的折线:

多边形
多边形是一个闭合的折线。多边形通常根据边的数量进行分类,如下所示:
-
三角形(3)
-
六边形(6)
-
八边形(8)
Leaflet 有一个用于绘制两个常见多边形的类:圆和矩形。绘制多边形时,你将指定至少三个坐标。三角形是可以绘制的最简单的多边形。这就是为什么你需要提供至少三个点。你不需要在列表末尾指定起始点。Leaflet 会自动为你关闭多边形。要绘制多边形,只需复制具有三个点的折线代码,并将类更改为 L.polygon(),如下面的代码所示:
var polygon = L.polygon([[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]], {color: 'red',weight:8}).addTo(map);
由于 Leaflet 自动关闭多边形,我们的三点折线可以变成多边形。由于 polyline 和 polygon 继承自 path,选项 color 和 weight 适用于两者。你会注意到 color 和 weight 指的是多边形的轮廓。在绘制多边形时,你会发现 fillColor 和 fillOpacity 两个选项很有用:
var polygon = L.polygon([[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]], {color: 'red',weight:8,fillColor:'blue',fillOpacity:1}).addTo(map);
上述代码绘制了一个边宽为 8 的红色三角形。fillColor 和 fillOpacity 的附加选项设置为 blue 和 1。如果没有选择 fillColor 选项,多边形的填充颜色将设置为默认值。只有当你想要与轮廓不同的填充颜色时,才需要使用 fillColor。
注意
不透明度是一个介于 0 和 1 之间的值,其中 0 是 100% 不透明度,1 是没有不透明度(实心)。
以下截图显示了添加到地图上的红色三角形,其填充颜色为蓝色:

矩形和圆
圆和矩形是 Leaflet 中具有内置类的常见多边形。你也可以通过指定所有线段来手动绘制它们,但这将是一条困难的路。
矩形
要创建一个矩形,你需要一个 L.rectangle() 类的实例,其中包含上左角和下右角的经纬度对作为参数。该类扩展了 L.polygon(),因此你可以访问相同的选项、方法和事件:
var myRectangle = L.rectangle([[35.19738, -106.875],[35.10418, -106.62987]], {color: "red", weight: 8,fillColor:"blue"}).addTo(map);
上述代码使用了折线和三角形的第一个两个点,但顺序相反(上左和下右)。选项与多边形相同,但去除了不透明度。以下截图显示了添加到地图上的矩形:

圆
要创建一个圆,你需要一个 L.circle() 实例,其中包含中心点和一个半径(以米为单位)作为参数。你可以指定与你在矩形中使用的相同选项,因为 circle 类扩展了 path 类。这在上面的代码中显示:
L.circle([35.10418, -106.62987], 8046.72,{color: "red", weight: 8,fillColor:"blue"}).addTo(map);
上述代码指定了中心点、半径为 5 英里(8046.72 米),以及与上一个示例中矩形相同的选项。以下截图显示了添加到地图上的圆:

多多折线和多多多边形
在之前的示例中,您将每条折线和多边形创建为其自己的图层。当您开始创建真实数据时,您会发现您想在单个图层上创建多个折线或多边形。首先,这更符合现实,同时也使得将类似特征作为一个单一实体处理成为可能。如果您想在单个地图上绘制公园和自行车道,将公园作为 MultiPolygon 添加,将自行车道作为 MultiPolyline 添加是有意义的。然后,您可以向用户提供选项,选择开启或关闭任一图层。
小贴士
MultiPolyline 和多边形的括号可能会让人困惑。您需要括号来包含 MultiPolyline 或多边形,每个折线或多边形的括号,以及每个经纬度的括号。
多折线
创建 MultiPolyline 的功能与单个折线相同,只是您传递了多个经纬度;每个多边形一个集合。以下代码显示了这一点:
var multipolyline = L.multiPolyline([[[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]],[[35.11654, -106.58318],[35.13142, -106.48876],[35.07384, -106.52412]]],{color: 'red',weight:8}).addTo(map);
在上述代码中,第一条折线与折线示例相同。添加了第二条折线,其选项也与第一条折线示例相同。以下截图显示了添加到地图上的 MultiPolyline:

多边形
创建 MultiPolygon 与创建 MultiPolyline 相同。由于 Leaflet 会自动关闭折线,只要我们的折线有三个或更多点,我们就可以使用它们。以下代码显示了这一点:
var multipolygon = L.multiPolygon([[[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]],[[35.11654, -106.58318],[35.13142, -106.48876],[35.07384, -106.52412]]],{color: 'red',weight:8}).addTo(map).bindPopup("We are the same layer");
在上述代码中,您可以看到使用的参数与之前 MultiPolyline 示例中使用的参数相同。当我们创建 MultiPolygon 或多折线时,选项将应用于集合中的每个多边形或折线。这意味着它们都必须具有相同的颜色、权重、不透明度等。在上述代码中有一个新方法:.bindPopup("我们属于同一图层")。MultiPolygon 和多折线也共享相同的弹出窗口。弹出窗口将在本章后面讨论。此外,请注意在行 L.multiPolygon().addTo().bindPopup() 中的方法链的使用。以下截图显示了添加到地图上的 MultiPolygon:

图层组
多折线和多边形图层允许您组合多个折线和多边形。如果您想创建不同类型的组图层,例如带有圆圈的标记图层,您可以使用图层组或要素组。
图层组
图层组允许您将多个不同类型的图层添加到地图上,并将它们作为一个单一图层进行管理。要使用图层组,您需要定义几个图层:
var marker=L.marker([35.10418, -106.62987]).bindPopup("I am a Marker");
var marker2=L.marker([35.02381, -106.63811]).bindPopup("I am Marker 2");
var polyline=L.polyline([[35.10418, -106.62987],[35.19738, -106.875],[35.07946, -106.80634]], {color: 'red',weight:8}).bindPopup("I am a Polyline");
上述代码创建了两个标记和一个折线。请注意,在创建图层后,您将不会像在之前的示例中那样使用 addTo(map) 函数。您将让图层组来处理将图层添加到地图上的操作。图层组需要一个包含图层的集合作为参数:
var myLayerGroup=L.layerGroup([marker, polyline]).addTo(map);
在之前的代码中,创建了一个L.layerGroup()的实例作为myLayerGroup。作为参数传递的图层是marker和polyline。最后,将图层组添加到地图中。早期的代码显示了三个图层,但只有两个被添加到图层组中。要在创建时不需要作为参数传递图层,您可以使用图层组的addLayer()方法。此方法接受一个图层作为参数,如下面的代码所示:
myLayerGroup.addLayer(marker2);
现在,所有三个图层都已添加到图层组中,并在地图上显示。以下截图显示了添加到地图中的图层组:

如果您想从图层组中移除一个图层,您可以使用removeLayer()方法并传递图层名称作为参数:
myLayerGroup.removeLayer(marker);
如果您从组中移除一个图层,它将不再在地图上显示,因为对图层组而不是单个图层调用了addTo()函数。如果您想显示图层但不再希望它成为图层组的一部分,可以使用前面代码中显示的removeLayer()函数,然后按照早期示例将其添加到地图中。以下代码显示了这一点:
marker.addTo(map);
所有样式选项和弹出窗口都需要在创建图层时分配。您不能将样式或弹出窗口分配给整个图层组。这就是特征组可以发挥作用的地方。
特征组
特征组类似于图层组,但它扩展了它以允许鼠标事件并包括bindPopup()方法。特征组的构造函数与图层组相同:只需传递一组图层作为参数。以下代码显示了一个特征组的示例:
VarmyfeatureGroup=L.featureGroup([marker, marker2, polyline])
.addTo(map).setStyle({color:'purple',opacity:.5})
.bindPopup("We have the same popup because we are a group");
在前面的代码中,添加的图层与在图层组中添加的相同三个图层。由于特征组扩展了图层组,您可以将样式和弹出窗口一次性分配给所有图层。以下截图显示了添加到地图中的特征组:

在上一个示例中创建折线时,您将其颜色设置为红色。注意现在,由于您通过将颜色设置为紫色向特征组传递了样式信息,折线从特征组中获取了信息并丢弃了其原始设置。如果您从特征组中移除折线,它也将从地图中移除。如果您尝试使用addTo()方法将折线添加到地图中,就像之前的示例一样,它仍然会是紫色并具有新的弹出窗口。即使您向特征组传递了样式信息,标记仍然是蓝色。setStyle()方法仅适用于具有setStyle()方法的特征组中的图层。由于折线扩展了path类,它具有setStyle()方法。标记没有setStyle()方法,因此其颜色没有改变。
弹出窗口
最后几个示例介绍了弹出窗口。弹出窗口提供了一种使你的图层交互或向用户提供信息的方式。将弹出窗口添加到标记、折线或多边形的最简单方法是使用bindPopup()方法。此方法将弹出窗口的内容作为参数。使用我们之前创建的marker变量,我们可以用以下代码将弹出窗口绑定到它:
marker.bindPopup("I am a marker");
bindPopup()方法允许你输入 HTML 作为内容。这在上面的代码中显示:
marker.bindPopup("<h1>My Marker</h1><p>This is information about the marker</p><ul><li>Info 1</li><li>Info 2</li><li>Info 3</li></ul>")
当你需要添加大量详细信息时,使用 HTML 在弹出窗口中非常有用。它允许在弹出窗口中使用图片和链接。以下截图显示了添加到地图上的标记的 HTML 格式弹出窗口:

你还可以创建popup类的实例,然后将其分配给多个对象:
var mypopup = L.popup({keepInView:true,closeButton:false})
.setContent("<h1>My Marker</h1><p>This is information about the marker</p><ul><li>Info 1</li><li>Info 2</li><li>Info 3</li></ul>");
marker.bindPopup(mypopup);
marker2.bindPopup(mypopup);
在前面的代码中,你创建了一个L.popup()类的实例,并将其分配给变量mypopup。然后,你可以使用mypopup作为参数,在marker和marker2上调用bindPopup()方法。这两个标记将具有相同的弹出窗口内容和选项。
在本章的最后部分,你将学习如何创建一个函数,该函数允许你创建带有选项的弹出窗口,并将内容作为参数传递。
移动制图
你迄今为止制作的地图已经在桌面端进行了测试。JavaScript 制图的一个好处是,移动设备可以在标准网页浏览器中运行代码,无需任何外部应用程序或插件。Leaflet 可以在 iPhone、iPad 和 Android 等移动设备上运行。任何带有 Leaflet 地图的网页在移动设备上都可以正常工作,无需任何更改;然而,你可能希望为移动设备定制地图,使其看起来像是专门为移动设备构建的。
最后,L.map()类有一个locate()方法,它使用 W3C 地理位置 API。地理位置 API 允许你通过 IP 地址、无线网络信息或设备上的 GPS 找到并跟踪用户的位置。你不需要知道如何使用 API;当你调用locate()时,Leaflet 会处理所有这些。
HTML 和 CSS
将你的 Leaflet 地图转换为移动版本的第一步是确保它在移动设备上正确显示。当你用手机打开一个网站时,你总能判断开发者是否花了时间使其移动设备可访问。你有多少次在网站上看到页面加载,但你只能看到左上角,你必须放大缩小才能阅读页面。这不是一个好的用户体验。在LeafletEssentials.html的<head>标签中,在 CSS 文件的<link>标签之后,添加以下代码:
<style>
body{
padding: 0;
margin: 0;
}
html, body, #map {
height: 100%;
}
</style>
在前面的 CSS 代码中,您将padding和margin值设置为0。将网页想象成一个盒模型,其中每个元素都存在于自己的盒子中。每个盒子都有一个外边距,这是它与其他盒子之间的空间,还有一个内边距,这是盒子内内容与盒子边框之间的空间(即使边框没有实际绘制)。将padding和margin值设置为0使<body>内容适合页面大小。最后,您将<html>、<body>和<div id = 'map'>元素的height值设置为100%。
注意
在 CSS 中,#是 ID 选择器。在代码中,#map告诉我们选择具有id = 'map'行的元素。在这种情况下,它是我们包含地图的<div>元素。
下图显示了网页设置的概览:

最后一步是在<head>部分和</title>元素之后添加以下代码:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
上述代码修改了网站通过的可视区域。此代码将可视区域设置为设备宽度,并以 1:1 的比例渲染。最后,它禁用了调整网页大小的功能。然而,这并不会影响您在地图上缩放的能力。
使用 JavaScript 创建移动地图
现在您已经配置了网页以在移动设备上正确渲染,是时候添加将获取用户当前位置的 JavaScript 代码了。为此,执行以下步骤:
-
创建地图实例,但不要使用
setView:var map = L.map('map'); -
添加瓦片层:
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map); -
定义一个函数以成功找到位置:
Function foundLocation(e){} -
定义一个函数以失败地找到位置:
functionnotFoundLocation(e){} -
为
foundLocation()和notFoundLocation()添加事件监听器:map.on('locationfound', foundLocation); map.on('locationerror', notFoundLocation); -
使用
locate()设置地图视图:map.locate({setView: true, maxZoom:10});
代码创建地图并添加瓦片层。然后跳过函数和事件监听器,并尝试定位用户。如果它能够定位用户,它将运行foundLocation()中的代码并将视图设置为用户的纬度和经度。如果不能定位用户,它将执行notFoundLocation()中的代码并显示缩放后的世界地图。
为了使此示例更易于使用,将以下代码添加到notFoundLocation()中:
function notFoundLocation(e){
alert("Unable to find your location. You may need to enable Geolocation.");}
alert()函数在浏览器中创建一个弹出窗口,显示传递给它的消息。任何浏览器无法定位用户时,他们都会看到以下消息。虽然一些设备没有定位功能,但有时需要在它们的设置中允许它们:

现在,将以下代码添加到foundLocation()中:
function foundLocation(e){
varmydate = new Date(e.timestamp);
L.marker(e.latlng).addTo(map).bindPopup(mydate.toString());
}
当用户的位置被找到时,上述代码将执行。foundLocation(e) 中的 e 是一个事件对象。它在事件被触发并传递给负责处理该特定事件类型的函数时发送。它包含您想要了解的事件信息。在上述代码中,我们首先获取的是 timestamp 对象。如果您要在弹出窗口中显示时间戳,您会得到一串数字:1400094289048。时间戳是自 1970 年 1 月 1 日 00:00:00 UTC 以来经过的毫秒数。如果您创建一个 date 类的实例并将其传递给 timestamp 对象,您将收到一个可读的日期。接下来,代码创建一个标记。纬度和经度存储在 e.latlng 中。然后您将标记添加到地图上,并绑定一个弹出窗口。弹出窗口需要一个字符串作为参数,因此您可以使用 date 类的 toString() 方法,或者使用 String(mydate) 将其转换为字符串。以下截图显示了用户点击时的日期和时间弹出窗口:

事件和事件处理程序
到目前为止,您已经创建了显示数据的地图,并添加了一个在用户点击标记时显示的弹出窗口。现在,您将学习如何处理其他事件,并将这些事件分配给事件处理函数以处理它们,并作为结果执行某些操作。
您首先将学习如何处理 map 事件。map 类中有 34 个事件可以订阅。本例将重点介绍 click 事件。要订阅事件,您使用事件方法 .on();因此,对于 map 事件,您使用 map.on() 方法并传递事件和处理函数作为参数。这在上面的代码中显示:
map.on('click', function(){alert("You clicked the map");});
代码指示 Leaflet 在用户点击地图时发送一个包含文本 您点击了地图 的警告弹出窗口。在移动示例中,您创建了一个具有命名函数的监听器,该函数执行 foundLocation()。在上述代码中,函数被作为参数放入。这被称为匿名函数。该函数没有名称,因此只能在用户点击地图时调用。
记得移动示例中的 e 吗?如果您将 e 传递给函数,您就可以获取用户点击的位置的 longlat 值,如下所示:
map.on('click',function(e){
var coord=e.latlng.toString().split(',');
var lat=coord[0].split('(');
var long=coord[1].split(')');
alert("you clicked the map at LAT: "+ lat[1]+" and LONG:"+long[0])
});
上述代码以更易读的方式进行了排版,但您也可以将其全部放在一行上。该代码显示用户在地图上点击的位置的经纬度。第二行将变量 coord 赋值为 e.latlng 的值。接下来的两行从值中提取经纬度,以便您可以清晰地显示它们。
您可以通过添加标记来扩展这个示例,只需将代码替换为以下内容:
L.marker(e.latlng).addTo(map);
上述代码与移动示例中的代码相同。不同之处在于,在移动示例中,它仅在 locate() 成功时执行。在这个例子中,它会在用户每次点击地图时执行。
在标记部分,你创建了一个具有属性 draggable:true 的标记。标记有三个与拖动相关的事件:dragstart、drag 和 dragend。执行以下步骤以在 dragend 时在弹出窗口中返回标记的经纬度:
-
创建标记并将可拖动属性设置为
true:varmyMarker = L.marker([35.10418, -106.62987],{title:"MyPoint",alt:"The Big I",draggable:true}).addTo(map); -
编写一个函数将弹出窗口绑定到标记并调用
getLatLong()方法:myMarker.bindPopup("I have been moved to: "+String(myMarker.getLatLng())); -
订阅事件:
myMarker.on('dragend',whereAmI);
打开地图并点击标记。按住鼠标左键并将标记拖动到地图上的新位置。释放鼠标左键并再次点击标记以触发弹出窗口。弹出窗口将显示标记的新纬度和经度。
自定义函数
你订阅了一个事件并用函数处理了它。到目前为止,你只传递了 e 作为参数。在 JavaScript 中,你可以向函数发送任何你想要的内容。此外,函数可以在你的代码的任何地方调用。你不必仅在响应事件时调用它们。在这个简短的例子中,你将创建一个返回弹出窗口的函数,该函数在调用时被触发,而不是由事件触发。
首先,创建一个标记并将其绑定到弹出窗口。对于弹出窗口的内容,输入 createPopup(Text as a parameter)。按照以下代码将标记添加到地图中:
var marker1 = L.marker([35.10418, -106.62987]).addTo(map).bindPopup(createPopup("Text as a parameter"));
创建第二个标记并将弹出窗口的内容设置为 createPopup (Different text as a parameter):
var marker2 = L.marker([35, -106]).addTo(map).bindPopup(createPopup("Different text as a parameter"));
在之前的示例中,你通过传递文本或弹出实例来创建弹出窗口。在这个例子中,你使用字符串作为参数调用函数 createPopup(),如下所示:
functioncreatePopup(x){
return L.popup({keepInView:true,closeButton:false}).setContent(x);function createPopup(x){
returnL.popup({keepInView:true,closeButton:false}).setContent(x);
}
该函数接受一个名为 x 的参数。在标记中,当你调用该函数时,你传递一个字符串。这个字符串被发送到函数并存储为 x。当弹出窗口创建时,setContent() 方法被赋予 x 的值而不是硬编码的字符串。这个函数在你有很多弹出窗口选项并且希望它们都相同的情况下很有用。它限制了你需要重复相同代码的次数。只需将弹出窗口的文本传递给函数,你就可以得到一个新的具有标准化格式选项的弹出窗口。以下截图显示了由自定义函数生成的两个弹出窗口:

摘要
本章涵盖了创建 Leaflet 地图所需的大部分主要主题。你学习了如何添加来自多个提供者的瓦片图层,包括卫星影像。你现在可以向地图中添加点、线和多边形,以及多边形的集合。你可以将不同类型的图层分组到图层或要素集合中。本章还涵盖了对象的样式化和添加弹出窗口。你学习了如何通过响应用件与用户交互,并创建了自定义函数,让你可以通过编写更少的代码来实现更多的编码。
在下一章中,你将学习如何将 GeoJSON 数据添加到你的地图中。
第二章. 地图 GeoJSON 数据
在 第一章 使用 Leaflet 创建地图 中,所有几何元素——点、线和多边形——都是逐个创建的。您学习了如何使用图层和要素组以及多折线和多边形类创建要素组。在本章中,您将学习如何将 GeoJSON 数据添加到您的地图中。数据将包含多个几何体,并与其相关的描述性数据。
在本章中,我们将涵盖以下主题:
-
什么是 GeoJSON?
-
如何将其添加到您的地图中
-
如何样式化它
-
遍历要素
-
如何从外部源调用 GeoJSON
理解 GeoJSON 的根源
在 GeoJSON 之前,有 JavaScript 对象表示法(JSON),而在 JSON 之前,有 可扩展标记语言(XML)。随着计算机开始通过互联网相互通信,从服务向客户端发送数据的能力变得更加重要。XML、JSON 和 GeoJSON 是表示和传输数据的格式。XML 是一种尝试创建人类可读的格式,可以存储和发送数据。XML 使用开闭标签来分隔数据。JSON 是 XML 的替代品,更接近 JavaScript 中创建对象的方式。JSON 使用键值对,通常比 XML 小。
探索 GeoJSON
GeoJSON 是一种编码几何体的 JSON 格式。GeoJSON 可以编码点、线字符串和多边形。它还允许多部分几何体。您可以编码多点、多线字符串和多边形。这些应该听起来很熟悉,因为它们与您在 第一章 使用 Leaflet 创建地图 中学到的绘制几何体非常相似。以下 GeoJSON 代码显示了特征集合中的两个点:
{"type":"FeatureCollection",
"features":[
{"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[-106.62987,35.10418]
},
"properties":{
"name":"My Point",
"title":"A point at the Big I"
}
}, {"type":"Feature",
"geometry":{
"type":"Point",
"coordinates":[-106,35]
},
"properties":{
"name":"MyOther Point",
"title":"A point near Moriarty and I40"
}
}
]
}
上述代码中的要素集合不是一个几何体,而是一组与 第一章 中描述的要素层类似的几何体。
要查看完整的 GeoJSON 规范,您可以访问 geojson.org/geojson-spec.html。
注意
要查看可以帮助您编写和检查 JSON 的工具,请参阅 www.jsoneditoronline.org/ 或 geojsonlint.com/。
Leaflet.js 中的 GeoJSON
GeoJSON 只是您要添加到地图中的另一种数据格式。它可以作为一个硬编码的变量添加。Leaflet.js 几何体——标记、折线和多边形——可以转换为 GeoJSON。您可以样式化数据,为每个要素应用选项,甚至过滤数据。下一节将涵盖这些主题,从将 GeoJSON 作为硬编码变量添加开始。
GeoJSON 作为变量
将 GeoJSON 添加到你的地图上最简单的方法是将数据硬编码到一个变量中。在 Leaflet.js 中,你将首先创建一个将包含 GeoJSON 的变量。在以下代码中,由两个点组成的 GeoJSON 数据被分配给 geojson 变量:
vargeojson = [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-106.62987,35.10418]
},
"properties": {
"name": "My Point",
"title": "A point at the Big I"
}
},{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-106, 35]
},
"properties": {
"name": "My Other Point",
"title": "A point near Moriarty and I40"
}
}];
一旦你有了前述代码所示的 GeoJSON 数据变量,将其添加到地图上与添加你迄今为止学到的任何其他几何形状没有区别。以下代码将 GeoJSON 添加到地图上:
vargeoJsonLayer = L.geoJson(geojson).addTo(map);
前述代码创建了一个 geoJsonLayer 变量。这个变量是 L.geoJson() 类的一个实例。它接受一个包含 GeoJSON 数据的变量作为参数,然后你将 .addTo(map) 链接到末尾。
注意
对象是在 第一章 使用 Leaflet 创建地图 中使用 (纬度,经度) 创建的;然而,请注意,在 GeoJSON 中,格式是 (经度,纬度)。
此代码的结果将是一个包含两个标记的地图,如下截图所示:

GeoJSON 中的多个几何形状
在前述示例中,GeoJSON 仅包含点。虽然 GeoJSON 文件通常只包含一个地理要素,但这不是必需的。Leaflet.js 可以在单个 GeoJSON 文件中加载包含多个几何形状的 GeoJSON。在本例中,你将学习如何创建并添加包含点、线字符串和多边形的 GeoJSON 文件。以下 GeoJSON 代码包含三种不同的几何形状:
vargeojson = [{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-106.62987, 35.10418]
},
"properties": {
"name": "My Point",
"title": "A point at the Big I"
}
},{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates":[[-106.67999, 35.14097],
[-106.68892, 35.12974],
[-106.69064, 35.1098]]
},
"properties": {
"name": "My LineString",
"title": "A line along the Rio Grande"
}
},{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates":[[[-106.78059, 35.14574],
[-106.7799, 35.10559],
[-106.71467, 35.13704],
[-106.69716, 35.17942],
[-106.78059, 35.14574]]]
},
"properties": {
"name": "My Polygon",
"title": "Balloon Fiesta Park"
}
}];
在单个 GeoJSON 文件中创建不同的几何形状,你只需指定类型并包含适当的坐标,如前述代码所示。对于线字符串,你必须包含至少两个点。在 Leaflet.js 中,多边形不需要你通过在列表末尾包含起始坐标来关闭它们。GeoJSON 确实要求你关闭多边形。前述代码中的多边形以 [-106.78059, 35.14574] 开始和结束。前述代码将生成以下截图所示的地图:

带孔的多边形
GeoJSON 中的多边形可以是甜甜圈,也就是说,你可以从另一个多边形的中间切出一个多边形。以下代码展示了具有两个多边形(外多边形和内多边形)的多边形特征:
vargeojson = [{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates":[
[[-106.875, 35.20074],
[-106.82281, 34.9895],
[-106.50146, 35.00525],
[-106.47949, 35.1985],
[-106.875, 35.20074]],
[[-106.6745, 35.1463],
[-106.70403, 35.05192],
[-106.55296, 35.05979],
[-106.53854, 35.17212],
[-106.6745, 35.1463]]
]
},
"properties": {
"name": "My Polygon with a hole",
"title": "Donut"
}
}];
在前述代码中,第一组点创建了一个四边形。第二组点——下一级缩进——在第一个多边形中间创建了一个四边形。结果如下截图所示:

前述截图中的多边形中间是空心的。如果你向多边形添加一个弹出窗口,它只有在点击蓝色环时才会打开。
从 Leaflet.js 对象生成的 GeoJSON
你在第一章,使用 Leaflet 创建地图中学习到的每个几何形状都有一个toGeoJson()方法。此方法将几何形状转换为可以添加到地图的 GeoJSON 对象。以下代码展示了如何将标记转换为 GeoJSON 图层:
varmyMarker=L.marker([35.10418, -106.62987]);
varmarkerAsGeoJSON = myMarker.toGeoJSON();
vargeoJsonLayer = L.geoJson(markerAsGeoJSON).addTo(map);
之前的代码创建了一个标记,正如你在第一章,使用 Leaflet 创建地图中“向地图添加数据”部分所做的那样。其次,它调用了.toGeoJSON()方法,该方法返回一个 GeoJSON 对象并将其存储为markerAsGeoJSON。最后,markerAsGeoJSON被添加到地图中作为 GeoJSON。
样式化 GeoJSON 图层
GeoJSON 图层有一个style选项和一个setStyle()方法。使用style选项,你可以指定一个将用于样式化图层的函数。以下代码展示了如何使用style选项样式化 GeoJSON 图层:
functionstyleFunction(feature){
switch (feature.properties.type) {
case 'LineString': return {color: "red"}; break;
case 'Polygon': return {color: "purple"}; break;
}
}
vargeoJsonLayer = L.geoJson(geojson,{style:styleFunction}).addTo(map);
之前的代码创建了一个样式函数,该函数根据 GeoJSON 要素名称返回一个颜色。如果是线字符串,则颜色为红色,如果是多边形,则颜色为紫色。
注意
你还可以使用其他选项如stroke、weight、opacity和fillColor来样式化 GeoJSON 数据。完整的列表可在leafletjs.com/reference.html#path-options找到。
最后一行创建 GeoJSON 图层,调用样式函数,然后将它添加到地图中。结果可以在下面的屏幕截图看到:

setStyle()方法允许你在样式已经应用之后更改样式或通过使用事件。以下代码展示了如何通过事件调用setStyle()方法来更新 GeoJSON 图层的颜色:
functionstyleFunction(){return {color: "purple"}; }
functionnewStyle(){geoJsonLayer.setStyle({color:"green"});}
vargeoJsonLayer = L.geoJson(geojson,{style:styleFunction}).addTo(map);
geoJsonLayer.on('mouseover',newStyle);
geoJsonLayer.on('mouseout',function(e){geoJsonLayer.resetStyle(e.target);});
之前的代码首先创建了一个名为styleFunction()的函数,该函数在代码的第四行使用style选项被调用,如前例所示。它将 GeoJSON 图层的颜色设置为紫色。接下来,还有一个名为newStyle()的函数,它将颜色设置为绿色。最后,有两个事件:mouseover和mouseout。当用户将鼠标悬停在 GeoJSON 图层上时,会调用newStyle()函数,图层被着色为绿色。一旦鼠标从图层上移开,就会调用一个匿名函数。此函数使用 GeoJSON 方法resetStyle()传递事件的靶标——GeoJSON 图层,并将图层恢复到其原始样式。
考虑以下示例代码:
function styleFunction(){return {color: "purple"}; }
function newStyle(){geoJsonLayer.setStyle({color:"green"});}
function oldStyle(){geoJsonLayer.setStyle({color:"purple"});}
var geoJsonLayer = L.geoJson(geojson,{style:styleFunction}).addTo(map);
geoJsonLayer.on('mouseover',newStyle);geoJsonLayer.on('mouseout',oldStyle);
上述代码首先创建了一个名为styleFunction()的函数,该函数在第四行代码中使用style选项(如前例所示)被调用。它将 GeoJSON 图层的颜色设置为紫色。接下来,还有两个其他函数:newStyle()和oldStyle()。前者将颜色设置为绿色,后者将颜色恢复到原始的紫色。最后,有两个事件调用样式函数:mouseover和mouseout。当用户将鼠标悬停在 GeoJSON 图层上时,调用newStyle()函数,图层被着色为绿色。一旦鼠标从图层上移开,就调用oldStyle()函数,颜色被恢复到紫色。
遍历要素
在 Leaflet.js 中,你可以在将 GeoJSON 文件中的要素添加到地图之前遍历这些要素并对其执行操作。这可以通过onEachFeature、pointToLayer或filter选项来完成。
使用 onEachFeature 附加弹出窗口
Leaflet.js 中的 GeoJSON 图层有一个onEachFeature选项,它会在数据中的每个要素上被调用。这可以用来在要素被添加到地图时将其绑定到一个弹出窗口上。以下代码使用onEachFeature选项来绑定一个弹出窗口:
L.geoJson(geojson, {
onEachFeature: function(feature,layer) {
layer.bindPopup(feature.properties.title);
}
}).addTo(map);
在上述代码中,对每个要素调用了一个匿名函数。该函数在第三行将弹出窗口绑定到要素的title属性值。你可以使用feature.properties.NameOfProperty选择要素的任何属性。结果如下面的截图所示:

使用 pointToLayer 从点创建图层
pointToLayer选项与点一起工作,因为它们与折线和多边形处理方式不同。在下面的代码中,根据每个要素的名称为每个要素创建了一个标记并进行了样式设置:
var options2 = {
draggable: false,
title: "A point near Moriarty and I40"
};
var x;
var y;
L.geoJson(geojsonFeature, {
pointToLayer: function(feature,latlng) {
switch (feature.properties.name) {
case "My Point": x = L.marker(latlng,{draggable:true,title:"A point at the
Big I"}).bindPopup(feature.properties.name); return x;
case "My Other Point": y =
L.marker(latlng,options2).bindPopup(feature.properties.name);
return y;
}
}
}).addTo(map);
上述代码首先创建了一个包含样式信息的 JSON 变量。接下来,创建了标记将要创建的图层作为x和y。然后,创建了 GeoJSON 图层并使用匿名函数调用了pointToLayer选项。该函数包含一个switch语句,根据标记的name属性来设置标记的样式。第一个case语句在创建标记时添加了属性信息。第二个case语句传递了包含样式信息的 JSON 变量。两者都有效,所以如果你想要将样式应用到所有要素上,你可以在变量中一次性编写它,并在创建标记时调用它。因为代码将图层分配给了变量x和y,所以你可以使用map.removeLayer(x)来添加或删除图层。
使用 filter 显示数据子集
有时候,您可能从外部加载 GeoJSON,但不想显示数据中的所有特征。过滤将允许您根据您设置的准则不显示某些特征。修改之前点对层示例中的 case 语句,您将学习如何根据特征的名称过滤数据。以下代码显示了如何进行此操作:
L.geoJson(geojsonFeature, {
filter: function(feature,latlng) {
switch (feature.properties.name) {
case "My Point": return true;
case "My Other Point": return false;
}
}
}).addTo(map);
上述代码应与之前的示例类似。您创建 GeoJSON 层,然后传递 filter 选项。该选项使用匿名函数。在这个例子中,该函数是一个 switch 语句,它接受特征的名称作为参数。由于该函数决定是否显示一个特征,因此返回值是一个布尔值。在这个例子中,如果特征的名称是 My Other Point,则它将不会显示。以下截图显示了结果;地图上只添加了一个标记:

摘要
在本章中,您学习了如何在 Leaflet.js 中添加和样式化 GeoJSON 层。最后,您学习了如何遍历 GeoJSON 文件中的特征并执行绑定弹出窗口或根据特征中的属性应用样式等操作。GeoJSON 是一种流行且常用的数据格式。了解如何在 Leaflet.js 中使用它是一项重要的技能。本章为您提供了一个坚实的基础,您可以在其中继续学习关于 GeoJSON 的知识。
在下一章中,您将学习如何使用几个可用的插件创建热图。
第三章:创建热力图和面状图
在前两章中,你学习了如何制作地图并添加点、线、多边形,甚至是 GeoJSON。现在,你将使用这些技能来创建两种类型的专题地图:热力图和面状图。这些地图使用两种不同的表示风格来展示点的集中或统计变量的分布。
在本章中,我们将涵盖以下主题:
-
什么是热力图?
-
我该如何创建一个热力图?
-
什么是面状图?
-
我该如何创建一个面状图?
什么是热力图?
热力图是在地图上添加的一个彩色网格。颜色通常从冷色调,如蓝色,到暖色调,如黄色、橙色和红色。热力图以两种方式之一表示点数据:密度或强度。在密度图中,当多个点彼此靠近时,网格被着色为红色,而当点分散时,则被着色为蓝色。高密度的点产生热量。在强度图中,点被分配一个值或强度分数。分数或强度越高,点所在位置的网格颜色越热;相反,分数越低,点所在位置的网格颜色越冷。
注意
热力图是通过在地图上放置一个网格并通过对一个区域内的点进行多变量核密度估计的过程来创建的。对于详细解释和使用的确切公式,你可以访问en.wikipedia.org/wiki/Multivariate_kernel_density_estimation。
使用 Leaflet.heat 创建热力图
你将要制作的第一个热力图将是一个密度热力图,使用 Leaflet.heat 插件。你可以从 github.com/Leaflet/Leaflet.heat 下载 JavaScript 插件。以下步骤将指导你创建你的第一个热力图:
-
使用
LeafletEssentials.html,通过以下代码添加对Leaflet.heat.js的引用,无论是远程副本的 URL 还是本地副本的路径:<script src="img/Leaflet-heat.js"></script> or, <script src="img/Leaflet-heat.js"></script> -
添加一个点数组。你的点可以包含其他信息,但必须包含纬度和经度作为前两个元素。以下代码显示了代码中的三个点。完整的代码包含更多点,这将允许你创建热力图:
var points = [ [35.1555 , -106.591838 , "<img src='http://farm8.staticflickr.com/7153/6831137393_fa38634fd7_m.jpg'>"], [35.0931 , -106.664177 , "<img src='http://farm3.staticflickr.com/2167/2479129916_0d861b2600.jpg'>"], [35.1143 , -106.577991 , "<img src='http://farm2.staticflickr.com/1416/908720823_e390a242f4.jpg'>"]]; -
最后,创建热层并将其添加到地图中:
var heat = L.heatLayer(points).addTo(map);
你的地图应该看起来像以下截图所示:

上述截图是默认的热力图;它并不十分时尚。
使用选项来设置地图样式
Leaflet.heat 允许你向构造函数传递选项。选项如下:
-
最大缩放级别 -
最大值 -
半径 -
模糊度 -
渐变
最重要的选项是 blur、maxZoom 和 radius。
改变模糊值
模糊将点合并在一起,或者不合并。低模糊值将创建单独的点,而高数值将使点相互合并并看起来更流畅。模糊过度会导致点变得模糊。以下截图显示了不同的模糊值。
以下截图显示了模糊值设置为 1:

以下截图显示了模糊值设置为 40:

以下截图显示了模糊值设置为 80:

注意在 80 时,模糊消除了地图上的任何热点;它变得模糊。找到完美值需要一些时间。从默认值 15 开始是一个好主意。
更改最大缩放值
maxZoom 选项将点设置为指定缩放的最大强度。如果您设置了地图的 maxZoom 选项,则可以忽略此设置。您应该将其设置为地图看起来最好的缩放级别。如果您设置得太远,当您放大时,点会失去其热度,如果您设置得太紧,用户可能无法一次性看到地图上的所有点。
更改半径值
此选项应该是显而易见的。它调整点的半径。数字小则点小,数字大则点大。数据点的数量会影响点的适当半径。点越多,每个点可以越小,同时仍然可以读出地图。将半径设置得太大会创建一个难以解释的大值块。
设置渐变选项
gradient 选项允许您指定不同级别的颜色。默认设置为 {.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"}。您可以从 0 到 1 指定范围。最外层的颜色是 0,中心是 1。默认设置通常是大多数人都理解的常见颜色范围。保留默认设置是最佳选择,但如果出于某种原因需要更改颜色,您也可以更改。
注意
要创建一个视觉上令人愉悦的颜色组合,您可以使用像 Color Brewer 2 这样的工具。它可在 colorbrewer2.org/ 获取。
Leaflet.heat 方法
除了样式化热图选项外,Leaflet.heat 还提供了以下四种方法:
-
setOptions(options) -
addLatLng(latlng) -
setLatLngs(latlngs) -
redraw()
您可以重置样式、添加新数据、加载所有新数据并重新绘制地图。您将使用最多的方法是 addLatLng() 方法。此方法允许您将数据追加到地图中。在先前的示例中,您可以将以下代码作为最后一行添加:
heat.addLatLng([35,-106]);
前面的代码使用addLatLng()方法将点(35,-106)添加到地图上。在Leaflet.github.io/Leaflet.heat/demo/draw.html有一个使用addLatLng()与事件结合的出色示例。当你移动鼠标时,点会实时添加到地图上。
小贴士
redraw()方法由setOptions()、addLatLng()和setLatLngs()调用,这样你就不需要在执行这些方法之后调用它。
如果你想在单个地图上显示多个数据集,你可以编写一个自定义函数来添加另一个集。以下代码添加了一个数据集。你需要用你的其他数据集填充newPoints变量:
function add(){
heat.setLatLngs(newPoints);
}
heat.setLatLngs(newPoints);
在前面的代码中,添加了一个名为newPoints的数据集到地图,并移除了旧的数据集 points。在你的 HTML 中,创建一个按钮来执行该函数:
<button onclick="addNewPoints()">Addonclick="add ()">Add New Points</button>
前面的代码是当点击时调用addNewPoints()函数的 HTML。
将标记添加到热力图
你需要一系列点来创建热力图,为什么不利用它们将弹出层附加到热层。在数据中,有一个纬度、经度和一个包含图像 URL 的第三个字段。以下代码展示了如何将数据转换为带有弹出层的标记:
for(var i=0;i<points.length;i++)
{
L.marker([parseFloat(points[i][0]),parseFloat(points[i][1])],{opacity:0}).bindPopup(points[i][2],{keepInView:true}).addTo(map);
}
上述代码是一个以0开始的循环标准,直到你迭代完所有点——points.length。通过传递每个点的纬度和经度,points[i][0]和points[i][1],并将它们转换为浮点值来创建一个标记。接下来,将opacity选项设置为0。这使得点变得不可见。这些点位于地图上,可以点击,但你看不见它们。这给人一种包含弹出层的热点层的外观。最后,将bindPopup()方法传递给图像的 URL,bindPopup(points[i][2]),并将其添加到地图上。以下截图显示了带有不可见标记和热层的弹出层:

使用 heatmap.js 创建热力图
使用heatmap.js在 Leaflet 中创建使用强度的热力图是可行的。你可以在www.patrick-wied.at/static/heatmapjs/index.html获取heatmap.js。这包括leaflet.js和其他地图包的插件。创建热力图的过程与前面的例子类似。以下步骤将指导你创建热力图:
-
使用
LeafletEssentials.html,通过以下代码将heatmap.js和heatmap-Leaflet的引用添加到其中,无论是远程副本的 URL 还是本地副本的路径:<script type="text/javascript" src="img/heatmap.js"></script> <script type="text/javascript" src="img/heatmap-Leaflet"></script> -
添加一个具有强度
max值的 JavaScript 对象和一个数据数组:Var myData={max: 46, data: [{lat: 33.5363, lon:-117.044, value: 1},{lat: 33.5608, lon:-117.24, value: 1}]}; -
创建热层并设置选项:
Var heatmapLayer = L.TileLayer.heatMap({ radius: 20, opacity: 0.8, gradient: { 0.45: "rgb(0,0,255)", 0.55: "rgb(0,255,255)", 0.65: "rgb(0,255,0)", 0.95: "rgb(255,255,0)", 1.0: "rgb(255,0,0)" } }); -
将数据添加到地图中。因为数据在一个对象中,您使用点符号,将其引用为
objectname.data;在这种情况下,myData.data。您也可以使用myData['data']:heatmapLayer.addData(myData.data); -
最后,修改您的
map对象以添加图层:var map = new L.Map('map', { center: new L.LatLng(35,-106), zoom: 12, layers: [baseLayer, heatmapLayer] });
注意
请注意,目前您可能需要参考 Leaflet 的旧版本。这将在插件的未来版本中更新。
您的地图应该看起来像以下截图所示:

修改热图选项
热图允许您修改三个设置:radius、opacity 和 gradient。与上一个示例一样,gradient 控制地图中每个点的大小。opacity 选项允许您指定介于 0 和 1 之间的值。0 表示完全透明,热图层将不会显示在地图上。值 1 将使热图层变为实心,您将无法看到每个点下面的内容。介于 .70 和 .80 之间的值似乎查看热层和下面的基础层是完美的透明度。最后,梯度,虽然最好保持不变,但可以通过设置值从 0 到 1 并分配颜色来修改。梯度中的颜色可以是 RGB 值,或者您可以使用颜色名称:红色、黄色、蓝色或青色。
向地图添加更多数据
在地图绘制完成后,您最终可能需要向地图添加更多数据。为此,您需要向对象中追加值,然后再次添加该对象。首先,要向 JavaScript 对象中添加数据,您可以使用以下代码:
myData. push({lat:35,lon:-106,value:46});
myData 对象有一个键 data,它是一个数组。您可以通过使用 myData.data[index] 来引用它。您可能不知道数组中有多少项,因此使用数组的长度作为索引,您将始终获得下一个可用的索引。这是因为长度是项目数,但索引从 0 开始。所以,对于有三个项目的数组,长度是三,但最后一个索引是二。使用长度为您提供下一个空索引。然后,只需将值分配给索引,它将被追加到对象中。最后,再次将数据添加到地图中:
heatmapLayer.addData(myData.data);
注意
如果您不使用索引,您将用要添加的一个项目覆盖数据。
创建交互式热图
热图是点图的一种替代可视化方式。点图经常因为大标记而变得杂乱,难以找到热点。在强度热图中,单个点可能是一个热点。热图中值的颜色编码使得查看数据中的模式变得容易。热图还可以用于可视化其他空间数据,例如跟踪鼠标在网页上的移动或人们在屏幕上阅读时眼睛的移动。在这个例子中,您将学习如何创建一个对用户在地图上点击鼠标做出响应的热图:
-
首先,包含对
Leaflet.heat.js的引用:<script src="img/Leaflet-heat.js"></script> -
接下来,在地图上禁用
doubleClickZoom选项。由于用户将点击地图来创建热力图,你需要这样做,以便当用户点击得太快时(他们会的),地图不会缩放:var map = L.map("map",{doubleClickZoom:false}).setView([35.10418, -106.62987], 10); -
创建一个空白的数据集,可以添加到地图中。这允许用户在新的画布上绘制:
var points=[]; -
将数据添加到地图中:
var heat = L.heatLayer(points,{maxZoom:10}).addTo(map); -
创建一个处理用户点击的函数。这个函数将通过捕捉鼠标点击的纬度和经度来向图层添加点。
e参数是一个事件对象,它在地图点击时自动发送。该对象包含有关事件的信息,在这个例子中,你取纬度和经度,如下面的代码所示:Function addpoint(e){ heat.addLatLng(e.latlng); } -
将函数连接到事件,在这种情况下,是
click事件:map.on('click',addpoint);
你的地图在点击几次后,应该看起来像以下截图所示:

动画热力图
到目前为止,你已经创建了一个显示当前点密度和强度的热力图,但如果你想要显示随时间变化的热力图呢?在这个最后一个例子中,你将学习如何创建热力图动画。
动画热力图并不像听起来那么复杂。动画不过是向地图添加和删除数据,你已经在本章的早期掌握了这些技能。这个例子中的技巧在于数据的组织和利用 JavaScript 中的时间事件。以下步骤将指导你制作一个动画热力图:
-
引用热力图插件:
<script src="img/Leaflet-heat.js"></script> -
接下来,将你的数据按时间周期分别放入一个数组中。用相同的名字命名你的数据,然后根据时间周期增加一个数字。以下代码可以用于此目的:
var points1=[[35,-106],[35,-106]]; var points2=[[35.10418, -106.62987],[32,-104]]; var points4=[[33, -104.],[35,-107]]; -
将起始数据集添加到地图中:
var heat = L.heatLayer(points1,{maxZoom:10}).addTo(map); -
接下来,创建一个将遍历数据集的变量和一个包含数据集名称的字符串。请注意,迭代器从
2开始。这是因为你在循环之前加载了points1:x=2; var name=""; -
创建一个区间,并传递一个函数以及以毫秒为单位的时间(1,000 毫秒等于一秒):
var interval = setInterval(function(){run()},1000); -
创建一个执行动画的函数。以下代码创建了一个
name字符串,即数据名称与迭代数字的连接。当前图层从地图中移除,下一个图层被添加。你不能使用字符串作为名称来调用变量,所以我们使用window[name]。最后,代码增加x迭代器:function run(){ name="points"+x.toString(); map.removeLayer(heat); heat = L.heatLayer(window[name],{maxZoom:10}).addTo(map); var x++; }
当你加载地图时,你应该看到第一个数据集:

然后,数据将每秒更新一次。以下截图显示了经过几秒后地图将看起来像什么:

使用 Leaflet 创建分级热力图
在前面的例子中,您使用了热图来根据点的密度或强度对地图进行着色编码。等值线图也测量统计变量的强度或密度,但是在多边形内。一个流行的等值线图是按县划分的人口密度。等值线图不需要任何插件,就像热图示例中那样。等值线图通常是通过根据属性对 GeoJSON 进行样式化来创建的。
GeoJSON 数据
当将大量 GeoJSON 数据添加到地图中时,将代码放在单独的 JavaScript 文件中会更简单。这将清除您的 HTML 文件中的数百行代码,这使得很难专注于构建地图。当您将 GeoJSON 代码放在 JavaScript 文件中时,您将声明它为一个变量,如下面的代码所示:
var ct = {"type": "FeatureCollection", "features": [{"geometry": {"type": "Polygon", "coordinates": [[[-106.501132, 35.093911], [-106.501231, 35.09385], [-106.501481, 35.09376], [-106.50201, 35.09371], [-106.50344, 35.093728], [-106.50424, 35.093709], [-106.50574, 35.093706], [-106.50634, 35.093748], [-106.50748, 35.09368], [-106.508548, 35.0937], [-106.50984, 35.093646], [-106.51071, 35.093618], [-106.51177, 35.093641], [-106.51295, 35.093613], [-106.513934, 35.09361], [-106.51528, 35.093581], [-106.51533, 35.09648], [-106.51534, 35.096889], [-106.515398, 35.09966], [-106.515437, 35.101462], [-106.51419, 35.101452], [-106.51366, 35.10143], [-106.51334, 35.10141], [-106.51308, 35.1014], [-106.51198, 35.101329], [-106.5109, 35.1013], [-106.50975, 35.10123], [-106.50872, 35.10119], [-106.50641, 35.101106], [-106.50643, 35.101358], [-106.50441, 35.101237], [-106.50362, 35.10119], [-106.50289, 35.101146], [-106.50187, 35.101085], [-106.50083, 35.10101], [-106.50058, 35.100989], [-106.50021, 35.10096], [-106.49947, 35.100954], [-106.499153, 35.100933], [-106.49887, 35.100905], [-106.49849, 35.100892], [-106.497853, 35.100895], [-106.49745, 35.10086], [-106.497415, 35.10086], [-106.49766, 35.094788], [-106.49881, 35.09483], [-106.49947, 35.09487], [-106.49977, 35.09498], [-106.50022, 35.095099], [-106.5007, 35.094291], [-106.50093, 35.094034], [-106.501132, 35.093911]]]}, "type": "Feature", "properties": {"NAME10": "1.26", "AWATER10": 0.0, "TRACTCE10": "000126", "ALAND10": 1315885.0, "INTPTLAT10": "+35.0975423", "FUNCSTAT10": "S", "observed": "", "NAMELSAD10": "Census Tract 1.26", "COUNTYFP10": "001", "STATEFP10": "35", "MTFCC10": "G5020", "GEOID10": "35001000126", "id": 6059554, "INTPTLON10": "-106.5067917"}}]};
之前的代码是针对单个要素的。完整的文件包含 153 个要素。这将向您的 HTML 文件中添加超过五十页 8.5" x 11"的页面。请注意,该文件不是 GeoJSON,而是 JavaScript。它是一个变量声明。当您在 HTML 中引用此文件时,您可以在脚本中调用ct变量。一旦您在 JavaScript 文件中将数据作为变量,就在LeafletEssentials.html中链接到它:
<script src="img/censustracts.js"></script>
之前的代码显示了您如何引用任何包含的 JavaScript 文件。
使用函数设置颜色
制作等值线图的下一步是为每个要素根据您要映射的统计变量分配颜色。定义一个函数来处理值的范围,如下面的代码所示:
function color(x) {
return x > 200000 ? '#990000' :
x> 100000 ? '#d7301f' :
x> 30000 ? '#ef6548' :
x> 20000 ? '#fc8d59' :
x> 10000 ? '#fdbb84' :
x> 5000 ? '#fdd49e' :
x> 0 ? '#fee8c8' :'#fff7ec';
}
之前的代码接受一个参数并测量一个值,返回颜色。对于更高的值,返回更深的颜色。始终最好坚持标准的着色进度。使用从浅到深的单色进度——单一颜色——应该会对你大有裨益。使用 Color Brewer 这样的工具将帮助你确保使用一个好的配色方案。您可以在colorbrewer2.org/找到它。这将为您提供 RGB、CMYK 和 HEX 格式的颜色值。
样式化 GeoJSON 数据
接下来,您需要创建一个函数来样式化 GeoJSON 数据。使用函数允许您根据属性样式化每个单独的要素。以下代码将样式化要素:
Function myStyle(feature) {
return {
fillColor: color(feature.properties.AWATER10),
weight: 1,
opacity: 1,
color: 'white',
fillOpacity: 0.85
};
}
该函数接受要素作为参数,并使用一些选项对其进行样式化。重要的选项是fillColor选项。这是您调用color()函数并传递每个要素的AWATER10值的地方。最后,将GeoJSONLayer变量添加到地图中,并使用样式函数作为参数,如下面的代码所示:
var GeoJSONLayer = L.GeoJSON(ct, {style: myStyle}).addTo(map);
之前的代码使用样式函数将图层添加到地图中。结果将类似于以下截图所示:

使用总值,例如人口普查区的水域面积,可以通过使用土地面积来标准化你的数据来改进。这一点很重要,因为没有标准化,值可能会偏斜。例如,假设有两个地点,土地面积分别为 100 英亩和 50 英亩。如果它们都有一片 10 英亩的湖,并且你使用标准化进行着色,它们将呈现相同的颜色。当你进行标准化时,值将是.1和.2。第二个地点是 20%的水,而第一个地点只有 10%。没有标准化,这个事实就会丢失。在下一个例子中,你将构建一个显示总值和标准化值的地图。
创建标准化的面状图
在这个例子中,你将创建一个面状图,显示总面积和总面积除以总面积的值。以下步骤将指导你完成这个过程:
-
使用前一个示例中的代码,添加另一个具有新范围值的颜色函数。它们的值将比总值的要小得多:
function densitycolor(x) { return x > 0.15 ? '#990000' : x> 0.12 ? '#d7301f' : x> 0.06 ? '#ef6548' : x> 0.03 ? '#fc8d59' : x> 0.01 ? '#fdbb84' : x> 0.005 ? '#fdd49e' : x> 0 ? '#fee8c8' :'#fff7ec'; } -
接下来,定义另一个样式函数。这个函数的关键区别在于传递给颜色函数的值将是
水/土地:function densityStyle(feature) { return { fillColor: densitycolor(feature.properties.AWATER10/feature.properties.ALAND10), weight: 1, opacity: 1, color: 'white', fillOpacity: 1 }; } -
在地图底部创建两个按钮以选择要显示的哪一种面状图。将这些按钮连接到一个函数:
<button onclick="total()">Total</button> <button onclick="density()">Water/Land</button> -
最后,编写显示图层的函数。在显示正确的图层后,移除其他图层:
function total(){ var GeoJSONLayer = L.GeoJSON(ct, {style: myStyle}).addTo(map); removeLayer(densitylayer); } function density(){ var densitylayer=L.GeoJSON(ct,GeoJSON {style: densityStyle}).addTo(map); removeLayer(GeoJSONLayer); }
你的地图现在应该是空的。你没有在地图上加载任何图层。你现在可以通过点击地图底部的按钮之一来选择你想要查看的图层。尝试点击另一个按钮。点击标有水/土地的按钮。你的地图应该看起来像以下截图所示:

注意,一旦你将土地面积标准化,拥有最多水的区域——最深红色区域——是靠近里奥格兰德地区的小块土地。这些大面积的土地有水,但这只占总面积的一小部分。
摘要
在本章中,你已经将你的制图技能从点、线、多边形和 GeoJSON 扩展到创建地图可视化——热力图和面状图。你已经学习了如何使用两个不同的插件来制作热力图以及如何为最大视觉效果进行样式化。你还学习了如何使热力图交互式,并创建动画来显示时间序列数据。面状图不需要插件。你学习了如何样式化 GeoJSON 数据以创建面状图。最后,你学习了总数据与标准化数据之间的区别。
在下一章中,你将学习如何创建自己的标记。你还将了解几个插件,这些插件可以动画化和增强地图上的标记。
第四章。创建自定义标记
在第三章中,您学习了如何通过修改默认标记来进一步自定义地图样式,以创建热图和面状图。Leaflet 允许您通过创建自定义标记图标来进一步自定义地图样式。您还将了解可用于样式和效果的可用标记插件。
创建自定义标记
在 Leaflet 中,标记由两个图像组成:一个表示标记的图像和一个作为阴影以创建深度的第二个图像。当您下载 Leaflet 时,有一个images文件夹。这个文件夹包含默认标记:您在地图上看到的蓝色图钉以及一个小阴影图像。这些图像被命名为marker-icon.png和marker-shadow.png。默认标记和阴影在以下屏幕截图中显示:


在 GIMP 中准备您的作图空间
要创建您自己的自定义标记,您需要在绘画应用程序中绘制一个图像。在这个例子中,您将使用免费的GNU 图像处理程序(GIMP)。GIMP 是一个功能强大的图像处理程序,类似于 Adobe Photoshop,可以在大多数操作系统上运行,并且完全免费。要下载 GIMP,请访问www.gimp.org/downloads/并点击下载 GIMP 2.8.10。
注意
您不需要选择 32 位或 64 位版本。GIMP 包括两者,安装程序运行时会确定适当的版本。
安装 GIMP 后,启动应用程序。应用程序包含三个窗口:屏幕左侧和右侧的两个面板以及中心的主窗口。您可以通过导航到窗口 | 单窗口模式将三个窗口合并为一个标准的单窗口应用程序。要创建一个新的图像文件,请导航到文件 | 新建...。您将看到一个新图像对话框。输入图标图像的宽度和高度。如果高级选项未展开,请点击菜单以展开窗口。您可以输入分辨率或接受默认值。填充选项是最重要的。您必须从下拉菜单中选择透明度。如果您不选择透明度,则您的图标将是一个带有背景颜色的正方形或矩形。这不会在您的地图上看起来很吸引人。对话框现在应该看起来像以下截图:

绘制并保存您的图像
现在画布已经设置好了,您可以绘制您的图像。我们如何绘制图像超出了本书的范围。
注意
如果您想了解 GIMP,可以查看Gimp 2.6 烹饪书,作者胡安·曼努埃尔·费雷拉,Packt 出版公司,可在www.packtpub.com/gimp-2-6-cookbook/book找到。
如果您在 GIMP 中绘制感到舒适,创建一个可以用作图标的图片。一旦创建图片,导航到文件 | 导出为...。如果您尝试使用保存或另存为…选项,您将不会得到 PNG 格式的选项。导出图片对话框允许您选择文件名和您希望保存文件的位置。在对话框的左下角,展开标有选择文件类型(按扩展名)的菜单。向下滚动到PNG 图片并点击导出。您将看到一个导出图片为 PNG的对话框。您必须勾选保存透明像素的颜色值选项。选项表单应类似于以下截图所示:

点击导出按钮保存图片。以下截图是完成后的图标:

绘制标记阴影
在 GIMP 中创建另一个新的图片,但选择更大的宽度和较小的长度:60 x 40。阴影需要从标记尖端开始,并向右大约 45 度延伸。在 GIMP 中,您可以将光标移到图片上,并看到光标的像素坐标。阴影图片在 20 像素处水平绘制。以下截图显示了图标尖端的光标和窗口左下角的坐标:

在阴影图片上,从像素 20 开始向图片右上角进行着色。完成后的阴影图片将类似于以下截图所示:

使用图片作为图标
您也可以使用图片作为图标。图片不一定是 PNG 格式或具有透明背景。您可以在 GIMP 中纠正所有这些问题。在 GIMP 中,导航到文件 | 打开并选择您的图片。本例中的图片是带有白色背景的 JPG 格式。
在图层面板中,您应该看到一个包含您图片的单个图层。右键点击图层并选择添加 Alpha 通道。

现在您可以选择橡皮擦工具或魔术棒来移除背景颜色。按照上一节中的说明导出图片。您仍然需要为您的标记图标绘制阴影。按照前一个示例中的阴影步骤进行,并保存图片。
现在您已经有了两个图片——图标和阴影——是时候在您的 Leaflet 地图中使用它们了。
在 Leaflet 中使用自定义标记
要在 Leaflet 中创建标记图标,你需要创建L.icon类的实例。L.icon类接受 10 个选项,如下所示:
-
iconUrl -
iconRetinaUrl -
iconSize -
iconAnchor -
shadowUrl -
shadowRetinaUrl -
shadowSize -
shadowAnchor -
popupAnchor -
className
唯一必需的选项是iconUrl。在这个例子中,你将忽略视网膜图像和类名。打开LeafletEssentials.html并添加以下代码:
var myIcon = L.icon({
iconUrl: 'mymarker.png',
shadowUrl: 'shadow.png',
iconSize: [40, 60],
shadowSize: [60, 40],
iconAnchor: [20, 60],
shadowAnchor: [20, 40],
popupAnchor: [0, -53]
});
前面的代码设置了选项。iconUrl选项将 URL 指向图标图像,而shadowUrl选项将 URL 指向阴影图像。iconSize和shadowSize选项需要以宽度乘以高度格式的图像尺寸。
iconAnchor选项设置标记和图标接触地图的点以及弹出窗口接触图标的位置。标记在水平像素 20 处有一个点,因此这将是以锚点加上图像的像素高度。阴影是在标记的点处绘制的,因此其锚点将是 20,其高度将是 40 像素。你希望弹出窗口在标记的顶部中心绘制,因此你必须相应地设置其锚点。
popupAnchor选项相对于iconAnchor选项设置。图标点水平居中,因此弹出窗口锚点将是 0 像素,使其锚定在 20 像素处。要将锚点放置在标记的顶部,你减去像素。选择-53 作为弹出窗口锚点将弹出窗口正好放在图标上方。

接下来,你需要创建一个标记并告诉它使用你创建的新图标。以下代码将做到这一点:
var marker = L.marker([35.10418, -106.62987],{icon: myIcon}).addTo(map).bindPopup("I am a custom marker.");
在第一章 使用 Leaflet 创建地图 中,你使用几个选项创建标记——其中一个选项是draggable:true。标记类也接受一个图标作为选项。在前面的代码中,icon选项接受一个L.Icon对象的名称。
保存LeafletEssentials.html并在浏览器中打开它。你应该看到一个与以下截图类似的地图:

定义 L.Icon 类
你也可以扩展L.Icon类来创建你自己的标记类。这允许你创建各种颜色的标记,并且只需指定一次大小和锚点选项。这个例子将非常类似于上一个例子。将以下代码添加到你的LeafletEssentials.html文件中:
var MyIcon = L.Icon.extend({
options:{
shadowUrl: 'shadow.png',
iconSize: [40, 60],
shadowSize: [60, 40],
iconAnchor: [20, 60],
shadowAnchor: [20, 40],
popupAnchor: [0, -53] }
});
前面的代码看起来几乎与上一个示例中的代码相同,除了以下差异:
-
第一行,它不是创建一个新的
L.icon类,而是扩展它 -
选项在第二行被包裹在一个对象中
-
没有提供
iconUrl选项
通过将选项包裹在一个对象中,你可以在创建标记时传递额外的选项。将以下代码添加到LeafletEssentials.html文件中:
var redIcon= new myIcon({iconUrl: 'mymarker.png'});
var blueIcon=new myIcon({iconUrl: 'mybluemarker.png'});
上一段代码为每个新的 icon 对象设置了 iconUrl 选项。现在,在一行代码中,您就拥有了一个红色和蓝色的图标。您现在可以将图标分配给标记,如下面的代码所示:
var marker = L.marker([35.10418, -106.62987],{icon: redIcon}).addTo(map).bindPopup("I am a custom marker.");
var marker = L.marker([35, -106],{icon: blueIcon}).addTo(map).bindPopup("I am a custom marker.");
在上一段代码中,每个标记被分配了不同颜色的图标。结果如下面的截图所示:

使用插件预定义标记
在前三个章节中,您使用了默认的 Leaflet 标记。在本章中,您刚刚学习了如何绘制自己的或使用现有的图像。自己绘制并不总是实用的,尤其是如果您不擅长绘画。在本节中,您将了解两个插件,它们提供了您可以自定义并用于 Leaflet 地图的时尚标记:Maki 标记和 Bootstrap/Awesome 标记。
使用 Mapbox Maki 标记
Mapbox 是一家提供地图平台和工具的公司。其图标已通过 Leaflet.MakiMarkers 插件提供。您可以在 github.com/jseppi/Leaflet.MakiMarkers 下载此插件。
注意
您可以通过访问他们的网站 mapbox.com 了解 Mapbox。
Maki 标记是一个拥有超过 100 个可用标记的开源图标库。您可以在 Leaflet.MakiMarkers.js 文件中找到它们的完整名称列表,或访问网站 www.mapbox.com/maki/。以下截图显示了所有图标:

上一张截图中的图标放置在彩色标记符号上。在您的地图中使用标记只需要两行代码和三个选项。以下步骤将帮助您创建 Maki 标记并将其放置在您的地图上:
-
添加对 JavaScript 文件的引用。此插件不需要 CSS 文件:
<script src="img/Leaflet.MakiMarkers.js"></script> -
创建一个图标。您必须选择三个选项:您想要使用的图标图像、标记的十六进制颜色值以及大小(
s、m、l):var icon = L.MakiMarkers.icon({icon: "rocket", color: "#0a0", size: "l"}); -
将图标添加到标记中,并将标记添加到地图中:
L.marker([35.10418, -106.62987], {icon: icon}).addTo(map);
当您选择一种颜色时,将会有相同颜色的浅色轮廓。以下截图显示了上一段代码的结果:

使用 Bootstrap 和 Font Awesome 标记
另一个允许您使用预定义标记的 Leaflet 插件是 Leaflet.awesome.markers。此插件允许您选择 Twitter Bootstrap 标记或 Font Awesome 标记。如果您无法选择,您始终可以使用两者。不同的库为您的标记提供不同的图标和略微不同的功能。您使用哪个是个人偏好。您可以在 github.com/lvoogdt/Leaflet.awesome-markers 下载此插件。
使用 Leaflet.awesome.markers 的过程几乎与您在 Maki 标记示例中使用的相同。您可以执行以下步骤:
-
添加对 Twitter Bootstrap 或 Font Awesome 或两者的引用。还要添加对
Leaflet.awesome.markers的 CSS 和 JavaScript 的引用:<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"> <link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet"> <link rel="stylesheet" href="Leaflet.awesome-markers.css"> <script src="img/bootstrap.min.js"></script> <script src="img/Leaflet.awesome-markers.js"></script> -
创建一个 Twitter Bootstrap 标记并将其添加到地图中。Bootstrap 标记是默认的。你只需要设置图标图像和颜色选项。创建标记并将其添加到地图中:
var redMarker = L.AwesomeMarkers.icon({ icon: 'tint', markerColor: 'red' }); L.marker([35.10418, -106.62987], {icon: redMarker}).addTo(map); -
创建一个 Font Awesome 标记并将其添加到地图中。由于 Bootstrap 是默认的,你需要使用
prefix选项并设置值为fa来使用 Font Awesome。此示例还使用了spin:true选项来创建一个旋转的动画标记。创建标记并将其添加到地图中:var blueMarker = L.AwesomeMarkers.icon({ prefix:'fa', spin:true, icon: 'spinner', markerColor: 'blue' }); L.marker([35, -106], {icon: blueMarker}).addTo(map);
以下代码将生成以下截图所示的地图:

你可能没有足够的时间来创建自己的标记,并且当你有使用 Mapbox、Twitter 或 Font Awesome 图标的选择时,为什么还要重新发明轮子呢?利用已经完成的工作,并且做得非常好。
使用 Leaflet.markercluster 进行标记簇
随着你创建更多的地图,你最终会遇到包含数千个点的数据集。在地图上显示 10,000 个点会导致加载时间慢,缩放和平移时动画滞后,并且使得用户难以选择单个标记或理解数据。簇功能允许你将标记分组到簇中——随着缩放级别的增加而展开的单个点。这样,你可以获得数据规模的感觉,而不会因为点的数量众多而感到视觉上被淹没。如果你需要查看单个点,你可以放大到感兴趣的区域或点。Leaflet.markercluster是一个快速且功能强大的簇实现,同时也具有视觉吸引力。
注意
你可以从github.com/Leaflet/Leaflet.markercluster下载此插件。
编写你的第一个簇地图
标记簇只是 Leaflet 中层的另一个例子。因此,创建一个标记簇应该对你来说非常熟悉。你需要执行以下步骤:
-
使用
LeafletEssentials.html,添加对Leaflet.markerclusterCSS 文件和 JS 文件的引用,如下面的代码所示:<link rel="stylesheet" href="MarkerCluster.Default.css" /> <script src="img/Leaflet.markercluster.js"></script> -
你可以将一系列标记添加到层中,但由于你将加载 723 个点,你将使用包含数据的 JS 文件。数据可以具有额外的属性。在这个例子中,有一个指向图像文件的链接。添加对包含数据的 JS 文件的引用:
<script src="img/art.js"></script> -
由于你现在知道如何创建自定义标记图标,以下代码创建了一个图标类和一个在簇展开时可以使用的图标:
var abqIcon = L.Icon.extend({ options: { shadowUrl: 'vase-shadow.png', iconSize: [50, 64], shadowSize: [50, 64], iconAnchor: [25, 64], shadowAnchor: [0, 64], popupAnchor: [-3, -64] } }); var vase = new abqIcon({iconUrl: 'vase.png'}); -
现在,通过创建
MarkerClusterGroup类的实例来创建一个markercluster层。将showCoverageOnHover选项设置为false:var markers = new L.MarkerClusterGroup({showCoverageOnHover:false}); -
要将标记添加到组中,您需要一个函数,该函数使用数据文件中的数组来遍历每个数据点,并将纬度、经度和您想在弹出窗口中使用的任何其他属性添加到标记中。循环创建一个标记,绑定一个弹出窗口,并将标记作为图层添加到
markercluster组中。然后,调用该函数以开始加载数据:function populate() { for (var i = 0; i < artPoints.length; i++) { var a = artPoints[i]; var title = a[2]; var marker = new L.Marker(new L.LatLng(a[0], a[1]), { icon: vase , title: title }); marker.bindPopup(title); markers.addLayer(marker); } } populate(); -
最后,使用以下代码将
markercluster组图层添加到地图中:map.addLayer(markers);
您的地图应该看起来像以下截图:

当您将地图缩小时,簇应该展开,分组变得较小。然后,它们将展开以显示单个标记,如下面的截图所示:

标记簇图层可用的方法和事件
markercluster图层有几个选项和方法,可以用来创建和交互您的图层。
默认为 true 的选项
有四个默认选项,都设置为true,如下所示:
-
showCoverageOnHover -
zoomToBoundsOnClick -
spiderfyOnMaxZoom -
removeOutsideVisibleBounds
第一个选项显示一个多边形,代表簇中标记的覆盖区域。这可能会令人困惑,因为一个彩色多边形会出现在地图上。在先前的示例中,您将此选项设置为false。
当您点击一个簇时,第二个选项会缩放到您点击的簇的覆盖区域多边形。当您将地图缩放到最大或使用定义的maxZoom选项时,簇将展开以显示其内的标记。
最后一个选项通过不显示当前地图视图附近不紧密的簇来提高性能。如果您正在查看洛杉矶,则不需要看到纽约的簇。
其他选项和事件
您可能还想设置的选项是animateAddingMarkers和maxClusterRadius。动画标记可以创建一个视觉上有趣的地图,但如果您正在使用大量数据点,可能会降低您地图的性能。这是一种应该谨慎使用并在正确条件下使用的效果。调整簇的半径可以创建更大或更小的簇。默认值为 80 像素。如果您正在显示紧密分组的数据,则需要较小的数字,如果您正在显示分散的数据,可能需要更大的半径。在先前的示例中,如果半径设置为5,则标记会覆盖整个地图,因为它们由于半径太小而没有被聚类。以下截图显示了半径设置为5时的地图:

上述截图中标记众多,几乎使地图无法阅读。markercluster 层有您可以订阅的事件。通常,您使用 map.on(click, function) 在地图上订阅一个事件。对于 markercluster 层,您将一个簇添加到可用的层事件中,以便它们应用于 markercluster 层,例如 markers.on(clusterclick,function)。
使用插件动画标记
在接下来的两个部分中,您将学习如何使用 Leaflet.BounceMarker 和 Leaflet.AnimatedMarker 插件来动画化标记。动画可以为您的地图增添吸引力,但如果过度使用,可能会使地图看起来不够专业。
弹跳您的标记
Leaflet.BounceMarker 插件没有很多选项来自定义标记或其行为,但它提供了一个简单的动画,当您在地图上添加标记或在悬停事件时非常有用。您可以在 github.com/maximeh/leaflet.bouncemarker 下载并了解更多关于该插件的信息。
以下步骤将向您展示如何将弹跳标记添加到您的地图中:
-
添加对 JavaScript 文件的引用。此插件不需要 CSS 文件:
<script src="img/bouncemarker.js"></script> -
创建弹跳标记与创建标准 Leaflet 标记完全相同;插件向
L.Marker类添加了一个额外的选项。因此,弹跳标记有一个bounceOnAdd选项,默认为false。您创建的每个标记都会弹跳,除非您指定了其他设置。将此选项设置为true以使任何您希望弹跳的标记弹跳。将标记添加到地图中。marker = new L.Marker([35.10418, -106.62987], {bounceOnAdd: true,}).addTo(map);
您可以在弹跳标记上指定的唯一其他选项是高度、持续时间和完成时的回调函数。您按照以下代码设置它们:
marker.bounce({duration: 1000, height: 200}, function(){alert("Finished")});
高度以像素为单位,持续时间以毫秒为单位。动画运行需要资源,所以请确保您不会创建运行速度过快的动画,否则您会发现标记消失了,只能看到阴影。此外,如果您打算在移动设备上使用地图,性能可能比在桌面电脑上慢。
bounce() 方法与悬停事件配合得很好。当有多个标记并且它们紧密分组时,使您悬停的标记弹跳可以帮助确保您点击的是正确的标记。
要在悬停事件上使标记弹跳,订阅该事件并在鼠标悬停在标记上时调用一个函数:
marker.on('mouseover',function(){marker.bounce({duration: 500, height: 100});});
上述代码订阅了 mouseover 事件,并在鼠标悬停在标记上时执行一个匿名函数。匿名函数调用 bounce() 方法,使标记在您将鼠标移至其上时进行弹跳。
注意
Leaflet.BounceMarker 插件是一个优秀的插件;然而,您可能会在动画过程中遇到一些小问题。这并不是插件的问题,而是动画的本质和它们所需的大量资源所导致的。
制作你的标记移动
使用Leaflet.AnimatedMarker插件,您可以使标记沿着多边形移动。当您想要绘制路线的注意力时,这非常有用。沿着路线移动的标记比地图上的线更能吸引人的注意。有关更多信息以及下载插件,请访问github.com/openplans/Leaflet.AnimatedMarker。
注意
一个需要注意的问题是,如果用户在动画运行时放大地图,标记将从其路径移动并尝试返回。在动画完成之前禁用地图缩放可能是一个好主意。
要动画化您的标记,请执行以下步骤:
-
添加对 JavaScript 文件的引用。此插件不需要 CSS 文件:
<script src="img/AnimatedMarker.js"></script> -
创建一个多边形来表示标记将动画化的路径:
var line = L.polyline([[35.10306, -106.58695],[35.1046, -106.60137],[35.10727, -106.61734],[35.1046, -106.63794],[35.10601, -106.69287]]); -
创建一个
animatedMarker变量。标记接受纬度和经度数组。要获取它,使用您创建的线的getLatLngs()方法:var animatedMarker = L.animatedMarker(line.getLatLngs()); -
将动画标记层添加到地图中。在这个例子中,您还将添加线路,以便您可以将其作为参考:
map.addLayer(line); map.addLayer(animatedMarker);
当您打开地图时,它应该看起来像以下截图:

由于您没有向标记传递任何选项,它需要一分钟才能开始移动,并且将以相当慢的速度穿过蓝色线。该插件有以下选项:
-
距离 -
间隔 -
autoStart -
onEnd
距离和间隔选项设置标记沿线移动的速度。距离以米为单位测量,间隔以毫秒为单位。由于这是一个速度,选项{distance:100, interval:1000}会比选项{distance:300, interval:1000}慢。在第一种设置中,标记在一秒内覆盖 100 米,而在第二种设置中,它会在相同的时间内覆盖三倍的距离。
autoStart选项默认设置为true。如果您将其设置为false,您可以在准备就绪时在标记上调用start()方法。在以下步骤中使用的代码中,您将创建一个带有两个按钮的地图:开始和停止。使用autoStart:false,您将允许用户确定何时开始标记以及何时在路径上停止它:
-
在使用相同线路和标记的先前示例的基础上,向标记添加
distance和interval选项,并将autoStart设置为false:var line = L.polyline([[35.10306, -106.58695],[35.1046, -106.60137],[35.10727, -106.61734],[35.1046, -106.63794],[35.10601, -106.69287]]); var animatedMarker = L.animatedMarker(line.getLatLngs(),{autoStart: false, distance: 600, interval: 900}); -
编写一个
start()和stop()函数来控制动画。在相应的函数中调用标记的start()和stop()方法:function start(){animatedMarker.start();} function stop(){animatedMarker.stop();} -
在 HTML 中,在
</body>标签之前添加两个按钮,并将它们的onClick事件设置为相应的函数:<button onclick="start()">Start</button> <button onclick="stop()">Stop</button>
您的地图应该看起来像以下截图:

标记器将在用户点击开始按钮之前不会移动。当用户点击停止按钮时,标记器不会立即停止。动画在每个线段的每个部分发生。当标记器到达线段的终点时,它将停止,并且不会重新开始,直到用户再次点击开始。
最后一个设置是onEnd。此选项允许您指定一个回调函数,当标记到达线的末端时将运行。在以下步骤中的代码中,您将使用本章前面学到的弹跳标记插件来使标记弹跳,并在完成时消失。按照以下步骤创建您的地图:
-
在上一个示例的基础上,添加对弹跳标记插件的引用:
<script src="img/bouncemarker.js"></script> -
在线的最后一点创建弹跳标记:
b = new L.Marker([35.10601, -106.69287], {bounceOnAdd: true}); -
编辑动画标记以包含
onEnd选项和匿名函数。匿名函数将添加弹跳标记到地图上,使其弹跳,删除动画标记,然后等待 900 毫秒并调用一个bye()函数,该函数将删除弹跳标记。等待将允许标记在弹跳完成后消失。这将减慢过程,使动画不会如此突然。您也可以使用弹跳标记可用的回调函数而不是bye()函数:var animatedMarker = L.animatedMarker(line.getLatLngs(),{autoStart: false, distance: 600, interval: 900, onEnd: function() {b.addTo(map);b.bounce({duration: 100, height: 50});map.removeLayer(animatedMarker);setTimeout('bye()',900);}});50});map.removeLayer(animatedMarker);setTimeout('bye()',900);}});
您的地图将看起来与上一个示例完全相同。当标记到达线的末端时,它将看起来弹跳并从地图上消失。一个有趣的项目是使用自定义和动画标记来重现波士顿马拉松,为每个完成者设置一个自定义标记,并设置其实际比赛速度。当您点击开始按钮时,您可以重新播放比赛。
使用标记进行数据可视化
您已经了解了几种不同的标记类型,它们仍然看起来像您典型的标记。在本节中,您将学习如何添加创建饼图和柱状图的标记——这并不完全符合标准标记。
使用 Leaflet 数据可视化框架插件
Leaflet 数据可视化框架插件允许您创建仅是形状的标记:一个带有形状切割的标准大头针样式标记、星形标记和多边形标记。它还允许您向地图添加饼图和柱状图标记。
注意
Leaflet 数据可视化框架插件还具有径向柱状图、科克斯组合图、堆叠和径向仪表标记的标记,以及数据层、渐变层和图例控件。这是一个值得探索的插件。您可以在humangeo.github.io/leaflet-dvf/下载它。
创建基本标记
创建基本标记很简单。以下步骤中使用的代码将指导您制作标记、多边形标记和星形标记:
-
首先,创建对 CSS 和两个 JavaScript 文件的引用:
<link rel="stylesheet" href="dvf.css" /> <script src="img/Leaflet-dvf.js"></script> <script src="img/Leaflet-dvf.markers.js"></script> -
接下来,创建标记。真正使这个插件与标准标记区分开来的是,你可以使用
L.Path类中的任何选项。这允许你完全自定义你的标记。创建标记需要你选择标记类型——MapMarker、RegularPolygonMarker或StarMarker——然后选择选项:var marker = new L.MapMarker([35.10418, -106.62987], { radius: 30, fillOpacity:0.5, fillColor:'orange', color:'purple', innerRadius:7, numberOfSides:4, rotation:10 }); map.addLayer(marker); var polygonmarker = new L.RegularPolygonMarker([35,-106], { numberOfSides: 3, rotation: 10, radius: 10, fillColor:'green', fillOpacity:1, opacity:1, weight:1, radius:30 }); map.addLayer(polygonmarker); var star = new L.StarMarker([35,-107], {numberOfPoints:8, opacity:1, weight:2, fillOpacity:0,radius:30}); map.addLayer(star);
当你打开你的地图时,它将看起来像以下截图:

选项太多,无法在此列出,但要看它们,请访问插件文档github.com/humangeo/Leaflet-dvf/wiki/6.-Markers和 Leaflet path类的文档Leafletjs.com/reference.html#path。以下章节将解释你将为每个标记最常使用的插件选项。
地图标记选项
使用的MapMarker选项如下:
-
numberOfSides: 内孔的大小由边的数量决定:三角形为三边,正方形为四边。边的数量越多,形状越接近圆形。如果你留空这个选项,它将默认为圆形。 -
rotation: 这有助于你旋转中间的孔。当然,这仅适用于非圆形形状。 -
radius: 这是标记的大小。 -
innerRadius: 这是中间孔的大小。
RegularPolygonMarker 选项
使用的RegularPolygonMarker选项如下:
-
numberOfSides: 这是标记的形状。 -
rotation: 这是标记的方向。如果你创建一个四边形并且留空这个选项,它将是一个正方形。如果你添加旋转,你可以使角落指向任何方向。 -
radius: 这是标记的大小。 -
innerRadius: 这是中间孔的大小。
星形标记选项
使用的StarMarker选项如下:
-
numberOfPoints: 这定义了起始点应该有多少个 -
rotation: 这是标记的方向 -
radius: 这是标记的大小 -
innerRadius: 这是中间孔的大小
条形图和饼图标记
将图表标记添加到你的地图中允许你显示单个点或多边形的多份数据。例如,你可以将你州的所有人口普查区绘制在地图上,并使用每个区的中心点放置一个显示年龄分布的图表。这允许你快速直观地展示大量数据。
使用 Leaflet 数据可视化框架插件制作条形图或饼图只需要你创建一个options对象并将其传递给标记。以下步骤将展示如何进行这两步:
-
创建一个包含数据和图表选项的
options对象。数据需要数据类别的名称和值。在图表选项对象中,你传递所有选项来设置你的图表样式。三个重要的选项是minValue、maxValue和maxHeight。在大多数情况下,这些应该对所有类别相同。如果你允许一个类别的maxHeight比其他类别高,它可以显示为一个比具有更高值的类别更大的条形。把这想象成在 Excel 中设置x轴和y轴的比例。所有数据都应该在最低值和最高值相同的比例范围内。更改maxHeight选项也会使你的图表变大或变小。图表选项之外的是用于图表的描边或轮廓的选项。当创建饼图时,
radius选项允许你调整标记的大小:var options = { data: { 'data1': 20, 'data2': 50, 'data3': 10, 'data4': 20 }, chartOptions: { 'data1': { fillColor: 'blue', minValue: 0, maxValue: 50, maxHeight: 30, }, 'data2': { fillColor: 'red', minValue: 0, maxValue: 50, maxHeight: 30, }, 'data3': { fillColor: 'green', minValue: 0, maxValue: 50, maxHeight: 30, }, 'data4': { fillColor: 'yellow', minValue: 0, maxValue: 50, maxHeight: 30, } }, weight: 1, color: '#000000', radius:30, fillOpacity:1 }; -
接下来,创建标记,传递选项,并将它们添加到地图中:
var bar = new L.BarChartMarker([35.10418, -106.62987], options); map.addLayer(bar); var pie= new L.PieChartMarker([35,-107],options); map.addLayer(pie);
当你打开你的地图时,它应该看起来像下面的截图。当你悬停在某个数据类别上时,你会看到颜色、类别名称和值。

摘要
在本章中,你学习了如何向你的 Leaflet 地图添加自定义标记。你现在可以绘制自己的标记或使用现有的图像。你也可以使用插件从 Twitter、Font Awesome 和 Mapbox 加载标记。最后,你现在知道如何创建条形图和饼图标记,以标记的形式可视化数据。到目前为止,你已经掌握了足够的 Leaflet 知识,可以构建你所能想到的几乎任何风格的地图。
在下一章中,你将学习如何在你的 Leaflet 地图中使用经济与社会研究学院(ESRI)的数据。作为最广泛使用的 GIS 平台,你很可能会遇到其数据格式。
第五章. Leaflet 中的 ESRI
当你开始制作更多地图并寻找用于工作的地理空间数据时,你几乎肯定会遇到文件类型 shapefile(.shp)。经济和社会研究机构(ESRI)是广泛使用的 GIS 系统 ArcGIS 的创建者,shapefile 是它们的数据格式之一。
你可能会看到另一种格式,称为地理数据库,具有.gdb扩展名。即使你从未遇到过 shapefile 或地理数据库,你最终也会遇到一个 REST 服务,它是 ArcServer 安装的端点。
注意
ArcServer 是 ESRI 的一个产品,用于分发 GIS 服务和网络地图应用程序。它与 ArcGIS 分开,ArcGIS 是指用于创建地图和地理数据的桌面应用程序。
你知道如何在 Leaflet 地图中消费更多数据格式,你将需要花费的时间来转换数据以适应你的需求就越少。在本章中,你将学习如何在 Leaflet 中消费 ESRI 格式和服务。
在本章中,我们将涵盖以下主题:
-
ESRI 底图
-
与 shapefile 一起工作
-
显示动态地图图层
-
热图
-
地理编码和反向地理编码
-
查询图层
ESRI 底图
ESRI 提供了八种不同的底图,你可以在 Leaflet 地图中使用。这八个图层如下:
-
街道
-
地形
-
国家地理
-
海洋
-
灰色
-
深灰色
-
影像
-
投影
除了八个底图之外,还有六个底图标签图层,OceansLabels、GrayLabels、DarkGrayLabels、ImageryLabels、ImageryTransportation和ShadedReliefLabels,以补充底图。如果还不够,还有每个底图的视网膜版本。
要使用 ESRI 底图,请按照以下步骤操作:
-
首先,添加对 ESRI-leaflet 文件的引用。它处于测试版,但这并不意味着它不完全功能:
<script src="img/esri-Leaflet"></script>注意
在其 GitHub 仓库中,ESRI 表示该库预计将在 2014 年从测试版移至生产版。您可以在
github.com/Esri/esri-leaflet/找到更多信息并下载附加文件。 -
接下来,创建一个 ESRI 底图图层,传递八个选项之一。在这个例子中,使用
Gray。始终记得将其添加到地图中:var gray = L.esri.basemapLayer("Gray").addTo(map); -
现在你有一个带有 ESRI 底图图层的地图。前面的代码是添加底图所需的最小代码。ESRI 底图图层继承自 Leaflet 的
L.TileLayer类,因此,你可以使用任何其他 LeafletL.TileLayer类中可用的所有选项、方法和事件。在构建移动地图时,一个非常有用的选项是detectRetina选项。要使用此选项,只需将其传递给底图名称之后,如以下代码所示:var gray = L.esri.basemapLayer("Gray",{detectRetina: true}).addTo(map);
你在文档中找到的许多示例将创建图层,而不将它们分配给变量,如以下来自 ESRI 网站的代码所示:
var map = L.map('map').setView([37.75,-122.45], 12);
L.esri.basemapLayer("Topographic").addTo(map);
当你这样做时,除非你将它们链接起来,否则你无法在图层上调用方法或事件。在上面的例子中,你将底图分配给了变量gray,因此你可以访问所有的方法和事件,如下面的代码所示:
gray.setOpacity(.75);
gray.on("load", alertme);
function alertme(){alert("ESRI Basemap Loaded");}
上述代码修改了底图层的透明度,并订阅了load事件。当图层加载时,它执行alertme()函数并弹出一个提示,表明操作已完成。
你可能需要做的最后一件事是在你的 ESRI 底图层上添加相应的标签层。为此,只需添加另一个底图层,并将标签层按以下代码所示传递:
var grayLabel = L.esri.basemapLayer("GrayLabels").addTo(map);
现在,你将拥有一个看起来像这样的地图:

使用 shapefiles 在 Leaflet 中
Shapefile 是你最有可能遇到的常见地理文件类型。Shapefile 不是一个单独的文件,而是用于在地图上创建地理要素的几个文件。当你下载 shapefile 时,你至少会有.shp、.shx和.dbf文件。这些文件是包含几何形状、索引和属性数据库的 shapefile。你的 shapefile 很可能还包括一个投影文件(.prj),它将告诉应用程序数据的投影,以便坐标对应用程序有意义。在示例中,你还将有一个.shp.xml文件,它包含元数据和两个空间索引文件,.sbn和.sbx。
要查找 shapefiles,通常可以搜索开放数据和城市名称。在这个例子中,我们将使用来自 ABQ Data,阿尔伯克基市数据门户的 shapefile。你可以在www.cabq.gov/abq-data找到更多数据。当你下载 shapefile 时,它很可能是 ZIP 格式,因为它将包含多个文件。
要在 Leaflet 中使用leaflet-shpfile插件打开 shapefile,请按照以下步骤操作:
-
首先,添加对两个 JavaScript 文件的引用。第一个是
leaflet-shpfile插件,第二个依赖于 shapefile 解析器,shp.js:<script src="img/leaflet.shpfile.js"></script> <script src="img/shp.js"></script> -
接下来,创建一个新的 shapefile 层并将其添加到地图中。将层路径传递给压缩的 shapefile:
var shpfile = new L.Shapefile('council.zip'); shpfile.addTo(map);
你的地图应该显示如下截图所示的 shapefile:

执行上述步骤将 shapefile 添加到地图上。你将无法看到任何单个要素属性。当你创建 shapefile 层时,你指定数据,然后指定选项。选项传递给L.geoJson类。要添加弹出窗口或样式化要素,你使用与你在第二章“映射 GeoJSON 数据”中学到的相同过程。以下代码显示了如何向你的 shapefile 层添加弹出窗口:
var shpfile = new
L.Shapefile('council.zip',{onEachFeature:function(feature, layer) {
layer.bindPopup("<a
href='"+feature.properties.WEBPAGE+"'>Page</a><br><a href='"+feature.properties.PICTURE+"'>Image</a>");
}});
在前面的代码中,你将council.zip传递给形状文件,并使用onEachFeature选项作为选项,它接受一个函数。在这种情况下,你使用匿名函数并将弹出窗口绑定到层。在弹出窗口的文本中,你将你的 HTML 与你想显示的属性的名称连接起来,使用格式feature.properties.属性名称。要找到形状文件中属性的名称,你可以打开.dbf并查看列标题。然而,这可能很麻烦,你可能想在不了解其内容的情况下添加目录中的所有形状文件。如果你不知道给定形状文件的属性名称,以下示例显示了如何获取它们,然后以它们的值在弹出窗口中显示它们:
var holder=[];
for (var key in feature.properties){
holder.push(key+": "+feature.properties[key]+"<br>");
popupContent=holder.join("");
layer.bindPopup(popupContent);
}
shapefile.addTo(map);
在前面的代码中,你首先创建一个数组来存储你的弹出窗口中的所有行,每对键/值一个。然后你运行一个for循环,遍历对象,获取每个键并将键名与值和换行符连接起来。你将每一行推入数组,然后将所有元素连接成一个单独的字符串。当你使用.join()方法时,它将在新字符串中用逗号分隔数组的每个元素。
你可以传递空引号来删除逗号。最后,你将弹出窗口与字符串内容绑定,然后将形状文件添加到地图中。
你现在有一个看起来像以下屏幕截图的地图:

形状文件也接受样式选项。你可以传递任何路径类选项,例如颜色、不透明度或描边,以更改层的外观。以下代码创建了一个红色多边形,带有黑色轮廓,并设置它略微透明:
var shpfile = new
L.Shapefile('council.zip',{style:function(feature){return {color:"black",fillColor:"red",fillOpacity:.75}}});
消费 ESRI 服务
在本章的第一个例子中,你学习了如何使用esri-leaflet插件进行底图。然后你学习了如何使用插件处理最常见的 ESRI 文件格式:形状文件。虽然你肯定会遇到形状文件,但你将越来越多地发现自己遇到提供端点以连接并消费地理服务的 ESRI 服务。使用esri-leaflet插件,你可以连接到这些服务,除了底图之外,还可以显示五种其他图层类型:
-
瓦片地图层
-
动态地图层
-
功能层
-
聚类功能层
-
热力图功能层
一旦你知道如何添加这些图层之一,你就可以添加任何其他的,因为过程几乎相同。唯一的不同是可用的选项和方法,这些在 API 的esri.github.io/esri-leaflet/api-reference/中有很好的文档。在本章的后面,我们将学习如何创建热量功能层,但现在让我们看看如何添加动态地图层。
在阿尔伯克基市数据页面 www.cabq.gov/abq-data 上,选择公共艺术数据集。你将看到目录的内容。你可以阅读 MetaData.pdf 文件来了解数据源,下载一个 Google Earth .KMZ 文件,下载或链接到一个 JSON 文件,或者消费一个 PublicArtREST 服务。
注意
来自阿尔伯克基市数据页面的 JSON 文件是 ESRI JSON。它不是 GeoJSON,因此,没有一些转换将不会兼容。
点击 PublicArtREST 链接,你将看到该服务的详细信息。滚动到页面底部将告诉你可用的字段。在设计弹出窗口时这将非常有用。现在你知道了如何找到该服务,请按照以下步骤将其添加到你的地图中:
-
首先,添加对 ESRI-leaflet 文件的引用:
<script src="img/esri-Leaflet"></script> -
通过复制 REST 服务的链接来创建一个动态地图图层。所有动态地图图层都以
/mapserver结尾。我们已从 URL 中移除了/0,这意味着我们现在正在加载整个地图文件。在以下代码中,将不透明度选项设置为0.75并将图层添加到地图中:var art = L.esri.dynamicMapLayer("http://coagisweb.cabq.gov/arcgis/re st/services/public/PublicArt/MapServer).addTo(map); -
最后,使用一个将返回内容的函数绑定弹出窗口。在以下代码中,使用格式
feature.features[0].properties.NAME-OF-PROPERTY:art.bindPopup(function (err, feature) { return feature.features[0].properties.TITLE+"<br> by: <b>"+feature.features[0].properties.ARTIST; });
你的地图现在将看起来像这样:

在 Leaflet 中使用 ESRI 的热力图
在第三章 创建热力图和面状图 中,你学习了几个可以用来创建热力图的插件。esri-leaflet 插件也包含一个热力图层,允许你传递一个 ESRI 服务作为数据。要使用 esri-leaflet 插件创建热力图,请按照以下步骤操作:
-
首先,添加对 ESRI-leaflet 文件的引用,并且由于热力图层不包括在
esri-leaflet插件的核心构建中,你将需要引用一个额外的 ESRI 文件,esri-leaflet-heatmap-feature-layer.js。ESRI 热力图层需要leaflet-heat.js,因此你还需要添加对该文件的引用:<script src="img/esri-Leaflet"></script> <script src="img/esri-leaflet-heatmap-feature- layer.js"></script> <script src="img/leaflet-heat.js"></script> -
按照常规创建你的地图和底图,然后添加热力图层。热力图层需要一个链接到要素图层服务的链接,并获取
leaflet-heat.js中所有可用的选项。将图层添加到地图中:url= ("http://services.arcgis.com/pmcEyn9tLWCoX7Dm/arcgis/rest/ services/USGS_Earthquakes_Excel_Layer/FeatureServer/0"; var heatmap = new L.esri.HeatmapFeatureLayer(url, { radius: 50, blur:90, maxZoom:10 }).addTo(map);注意
要查看 ESRI 提供的服务列表,请浏览到
services.arcgis.com/rOo16HdIMeOBI4Mb/ArcGIS/rest/services/。ArcServer 服务的位置默认为http://Server Name/ ArcGIS/rest/services。
你的地图将看起来如下截图所示:

在 Leaflet 中进行地址地理编码
地理编码是将地址输入并带到地图上某个点的过程。地理编码功能不是 Esri-leaflet 核心的一部分,而是一个独立的插件。您可以在 esri-leaflet-geocoder GitHub 页面上找到更多信息:github.com/Esri/esri-leaflet-geocoder。
地理编码 – 从地址到点
地理编码插件在缩放控制下方放置一个搜索框。随着您输入地址,搜索将自动完成并显示可能的选项。您可以直接输入整个地址,或者当您想要的地址可用时从列表中选择。点击选项或按回车键将在地图上该位置放置一个标记并放大。要创建具有地理编码功能的地图,请按照以下步骤操作:
-
引用 CSS 和 JS 文件:
<script src="img/esri-leaflet-geocoder.js"></script> <link rel="stylesheet" type="text/css" href="http://cdn- geoweb.s3.amazonaws.com/esri-leaflet-geocoder/0.0.1- beta.3/esri-leaflet-geocoder.css"> -
创建控件:
var searchControl = new L.esri.Controls.Geosearch().addTo(map); -
创建一个放置结果的图层:
var results = new L.LayerGroup().addTo(map); -
订阅结果事件并添加标记:
searchControl.on("results", function(data){ results.clearLayers(); results.addLayer(L.marker(data.results[0].latlng));
您的地图现在在缩放控制下将有一个小放大镜,如下面的截图所示:

当您点击放大镜时,它将展开成一个文本框。随着您输入,文本框将自动完成并猜测您正在输入的位置。一旦您看到想要的地址,从列表中选择它。您的地图应该看起来像下面的截图:

一旦您点击了选择,地图将自动在位置放置一个标记并放大。现在您将有一个看起来像下面的地图:

在下一个示例中,您将使用 URL 来映射地址。
从 URL 参数进行地理编码
在最后一个例子中,用户能够加载地图并输入地址以找到它。在这个例子中,您将允许用户在 URL 中输入地址,并显示一个放大到标记位置的地图。要创建地图,请按照以下步骤操作:
-
首先,添加对
esri-leaflet-geocoder.js文件的引用。由于您没有添加搜索框,因此不需要 CSS 文件,如前一个示例中所示:<script src="img/esri-leaflet- geocoder.js"></script> -
接下来,您需要从 URL 中获取参数。在这个例子中,
a被选为包含地址的变量。要获取 URL 参数,请使用location.search。这将获取问号之后的所有内容。您只需要地址,因此按等号分割,然后获取返回数组的第二个元素,y[1]。这将返回 URL 中每个空格处的%20,因此使用decodeURIComponent(y[1])来移除它们:var x = location.search; var y = x.split("="); var temp=y[1]; var address = decodeURIComponent(temp); var geocodeService = new L.esri.Services.Geocoding(); -
创建地理编码服务,传递地址、参数和回调函数。该函数将从第一个结果创建一个标记,然后设置视图以放大标记:
geocodeService.geocode(address, {}, function(error, result){ L.marker(result[0].latlng).addTo(map); map.setView(result[0].latlng,8); });
在URLgeocode.html文件后添加?a=400 roma ave ne,albuquerque,nm,usa。您的地图将加载并看起来像这样:

反向地理编码 – 使用点查找地址
反向地理编码与地理编码正好相反。它从一个地图上的点找到其地址。在这个例子中,您将允许用户点击地图并添加一个带有地址作为弹出窗口的标记。要创建地图,请按照以下步骤操作:
-
首先,添加对 Esri-leaflet 和
esri-leaflet-geocoder.js文件的引用:<script src="img/esri-Leaflet"></script> <script src="img/esri-leaflet-geocoder.js"></script> -
创建一个新的地理编码服务:
var geocodeService = new L.esri.Services.Geocoding(); -
订阅
click事件并添加一个函数,该函数调用reverse(),传递经纬度选项和一个回调函数。回调函数将创建一个标记,将其添加到地图中,并绑定一个弹出窗口。地址存储在结果对象中作为result.address。此代码将在您每次点击地图时添加一个点。要只显示一个点,在创建标记之前添加map.removeLayer(r):map.on('click', function(e){ geocodeService.reverse(e.latlng, {}, function(error, result){ r = L.marker(result.latlng).addTo(map).bindPopup(result.address ).openPopup(); }); });
当您完成时,您的地图将看起来像这样:

根据属性查询
当使用服务时,通常加载整个层。有时,您可能只想加载层数据的一个子集。使用查询将允许您只加载您感兴趣的子集。在这个例子中,您将查询涂鸦层以获取开放和关闭的案件。要创建地图,请按照以下步骤操作:
-
参考您在先前的例子中看到的 Esri-leaflet 文件。您不需要任何额外的文件。使用 CSS 样式化
<div>查询:<style> #query { position: absolute; top: 10px; right: 10px; z-index: 10; background: white; padding: 1em; } #query select { font-size: 16px; } </style> -
创建选择元素并添加
Open和Closed作为选项:<label> Status <select id="caseStatus"> <option value=''>Clear Screen</option> <option value='Open'>Open</option> <option value='Closed'>Closed</option> </select> </label> -
添加一个连接到涂鸦服务的特征层。使用
pointToLayer选项为每个特征创建一个标记并将它们添加到地图中:var graffiti = L.esri.featureLayer('http://services.arcgis.com/ rOo16HdIMeOBI4Mb/ArcGIS/rest/services/Graffiti_Locations3 /FeatureServer/0', { pointToLayer: function (geojson, latlng, feature) { return L.marker(latlng); }, }).addTo(map); -
创建一个
popupTemplate变量。您可以通过浏览特征层的链接来找到参数。通过创建一个返回模板的函数来绑定弹出窗口。该模板允许您将包含在 ESRI 层中的字段传递到模板中。字段名称放在大括号中。然后,您可以使用模板作为bindPopup()中的字符串:var popupTemplate = "<h3>Details:</h3>Address:{Incident_Address_Display}<br> Borough: {Borough}<br>Community Board: {Community_Board}<br>Police Precinct: {Police_Precinct}<br>City_Council_District: {City_Council_District}<br>Created_Date: {Created_Date}<br>Status: {Status}<br>Resolution_Action: {Resolution_Action}<br>Closed_Date: {Closed_Date}<br>City: {City}<br>State: {State}"; graffiti.bindPopup(function(feature){ return L.Util.template(popupTemplate, feature.properties) }); -
创建一个当选择元素改变时的事件。将当前选择的值传递给
setWhere()方法。此方法根据where查询刷新特征层。在这个例子中,where是status属性的值:caseStatus.addEventListener('change', function(){ graffiti.setWhere('Status="'+caseStatus.value+'"'); });
当您完成时,您可以选择Closed,地图将看起来像这样:

根据邻近度查询
在上一个例子中,您根据属性查询了特征层。您也可以根据其与点的邻近度查询特征层。在这个例子中,您将根据鼠标点击的位置查询层。以下说明将指导您创建邻近度查询:
-
参考之前示例中的方式引用 Esri-leaflet 文件。将要素层添加到地图中。你需要传递
pointToLayer选项,为每个要素返回circleMarker。你需要创建圆点标记,以便在后续步骤中更改标记的颜色:var graffiti = L.esri.featureLayer('http://services.arcgis.com/ rOo16HdIMeOBI4Mb/ArcGIS/rest/services/Graffiti_Locations3/FeatureServer/0', { pointToLayer: function (geojson, latlng) { return L.circleMarker(latlng); }, }).addTo(map); -
使用要素属性创建一个弹出模板。将弹出窗口绑定到要素,如下所示:
var popupTemplate = "<h3>Details:</h3>Address: Incident_Address_Display}<br>Borough: {Borough}<br>Community Board: Community_Board}<br>Police Precinct: {Police_Precinct}<br>City_Council_District: City_Council_District}<br>Created_Date: {Created_Date}<br>Status: Status}<br>Resolution_Action: {Resolution_Action}<br>Closed_Date: Closed_Date}<br>City: {City}<br>State: {State}"; graffiti.bindPopup(function(feature){ return L.Util.template(popupTemplate, feature.properties) }); -
创建一个查询。如果你浏览到该服务——在你的浏览器中放置查询的 URL 并滚动到页面底部,你将在最后一行看到这个服务支持查询。将查询传递到你想要查询的层:
var query = L.esri.Tasks.query('http://services.arcgis.com/ rOo16HdIMeOBI4Mb/ArcGIS/rest/services/ Graffiti_Locations3/FeatureServer /0'); -
创建一个鼠标点击事件,并使用
runQuery()函数进行订阅:map.on('click', runQuery); -
创建一个函数
runQuery(),当用户点击地图时执行。这个函数将执行三个操作:使用nearby()方法执行查询,传递鼠标点击的经纬度和 804 米(半英里)的距离;将所有圆点标记的样式设置为蓝色;并将查询结果传递给一个style函数,将它们变为绿色。我们在第二步使用了圆点标记,以便我们可以更改颜色以突出显示查询结果:function runQuery(e){ graffiti.query().nearby(e.latlng,804).ids(function (error,ids){ graffiti.setStyle(function(){return { color: "blue"};}); for(var i=0;i<ids.length;i++){graffiti.setFeatureStyle(ids[i], {color:"green"});}}); }
当你点击地图时,它应该看起来像这样:

绿色标记都在用户点击位置半英里范围内。
摘要
在本章中,你学习了如何使用地理数据的最常见文件格式:shapefile。你还学习了如何使用 esri-leaflet 插件连接到 ESRI 服务,并添加底图以及五种其他 ESRI 层类型。你之前已经学习了热力图,但在这章中,你还学习了如何消费 ESRI 服务并将它们作为热力图添加。你学习了如何将地址地理编码到地图上,以及如何将点反向地理编码到街道地址。最后,你学习了如何首先通过属性然后通过位置查询 ESRI 服务。
在下一章中,你将结合所学的一切,使用 Leaflet 和其他编程语言创建一个应用程序。
第六章。Leaflet 在 Node.js、Python 和 C# 中
在前五章中,你学习了 Leaflet.js 的基础知识。你现在知道如何从多个来源和多种不同格式中添加各种底图。你可以绘制简单的几何图形,以及从服务器、GeoJSON 和 ESRI 文件格式中显示数据。在第三章中,创建热力图和面状图,介绍了如何从你的数据中创建可视化。你现在还知道如何自定义标记的外观和感觉,以及如何利用插件来为你的地图添加额外功能。
在最后一章中,你将学习如何使用三个流行的编程框架构建 Leaflet.js 应用程序:Node.js、Python 和 C#。在 Node.js 和 Python 中,你将构建一个服务器来渲染你的网页,并允许 AJAX 调用来显示更多数据。在最后一个示例中,使用 C#,你将构建一个桌面 Windows 应用程序,该应用程序将网页嵌入到表单中,连接到 MongoDB,并使用通用搜索和空间搜索检索数据。
虽然你不需要掌握这三个框架中的任何一个,Node.js 是一个 JavaScript 框架,示例应该很容易理解。Python 和 C# 的示例可能与你习惯的不同;然而,主要思想应该很容易掌握,它们将给你一个如何在大规模应用程序中使用 Leaflet 的想法。此外,开始思考 Leaflet 如何与服务器端交互,将扩展你构思新和令人兴奋应用程序的能力,利用来自多个框架的库和资源。
第一个示例将从最熟悉的框架 Node.js 开始。
使用 Node.js 构建 Leaflet 应用程序
Node.js 是一个基于 JavaScript 的平台,用于构建非阻塞应用程序。非阻塞特性使 Node.js 非常受欢迎。想想你是如何编码的。你按步骤完成任务。你可能会在代码中跳来跳去,调用函数并响应事件,但在开始下一个动作之前,你会等待一个动作完成。在 Node.js 中,你分配回调并继续下一个任务或处理下一个请求。以数据库搜索为例。在下面的伪代码中,你以传统方式检索记录并对其进行处理:
var result = SELECT * from MyTable;
document.getElementById['results'].innerHTML= result;
在示例中,你等待数据库发送回结果,然后继续显示它们。在 Node.js 中,你会做类似的事情,但会分配一个回调函数,如下面的示例所示:
function showResults(){document.getElementById['results'].innerHTML= result;};
var result = query(SELECT * from MyTable, showResults);
doWhateverElseYouNeed();
上述代码将查询数据库,并继续执行 doWhateverElseYouNeed 函数,直到查询完成,此时将执行 showResult 回调函数。这可能会非常令人困惑,并使你的代码难以阅读,但它非常强大,在服务器端,它允许大量连接。
现在您已经了解了 Node.js 能做什么,您可以在nodejs.org/下载它。根据您的操作系统遵循说明安装 Node.js。安装完成后,您将在 Windows 上有一个命令行界面和一个 Node.js 窗口。使用命令行,您可以启动应用程序并安装额外的包,您将在后面的例子中学习如何做。
基于 Leaflet 的基本 Node.js 服务器
在这个第一个例子中,您将创建一个简单的 Node.js 服务器并服务LeafletEssentials.html。您可以在任何文本编辑器中编写代码,并将其保存为.js文件。
创建一个文件夹来存储您的文件,并将LeafletEssentials.html的副本放入文件夹中。这是我们打算用 Node.js 服务的文件。接下来,您将创建服务器,如下面的代码所示:
require('http').createServer(function (req, res) {
if ('/' == req.url){
res.writeHead(200, { 'Content-Type': 'text/html' });
require('fs').createReadStream('leafletessentials.html') .pipe(res);
}
}).listen(3000);
上一段代码使用了两个模块:http和fs。您可以使用require(module)导入这些模块。这两个模块都是 Node.js 的标准模块,不需要任何额外的下载。上一段代码导入了http模块,然后调用了createServer()方法。它使用了一个匿名函数,该函数接受一个请求和一个响应——分别是req和res。if块检查请求是否指向服务器的根目录;在这个例子中,浏览器是否指向http://localhost:3000。代码的最后一行是在端口 3000 上监听。如果请求是根目录,则代码会写入一个头部。本书的范围不包括 HTTP 协议和头部。然而,要知道,当发送响应时,如果成功则状态为 200,如果不成功则状态为 404,响应的内容类型为text/html。最后,代码导入了fs模块,并使用pipe()读取并写入LeafletEssentials.html的内容。管道是发送文件的推荐方法;然而,您也可以使用res.end('the HTML')手动将 HTML 作为字符串写入。管道允许您做一些很酷的事情,例如读取和写入视频文件,以便用户在从服务器接收数据的同时播放它。将 HTML 作为字符串写入会使您的代码变得冗长且复杂;更不用说尝试逃避大多数 HTML 中所需的引号了。在最后一个例子中,您将了解一个模板库,您可以在其中存储您的 HTML。
使用命令行工具,导航到包含您的server.js代码的目录,通过输入node server.js来运行它。将浏览器指向http://localhost:3000。您应该看到如图所示的加载地图:

上一段示例只是简单地服务单个文件,如果用户将浏览器指向任何其他 URL,例如http://localhost:3000/about.html,他们将看不到任何内容,甚至没有错误消息。下一个例子将解决这个问题。
Node.js、AJAX 和 Leaflet
现在您已经有一个 Node.js 服务器正在运行并托管 Leaflet 网页,您可以使用同一个服务器来发出异步 JavaScript 和 XML(AJAX)调用。您正在用 JavaScript 编程,尽管 AJAX 中有 XML,但您应该使用 JSON;它在 JavaScript 中处理起来比 XML 容易得多。在第一个示例的基础上,以下代码添加了另一个页面,并在不良请求时发送错误消息:
require('http').createServer(function (req, res) {
if ('/' == req.url){
res.writeHead(200, { 'Content-Type': 'text/html' });
require('fs').createReadStream('leafletessentialsAjax.html') .pipe(res);
} else if ('/getpoints'==req.url){
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{"lat":35,"long":-106}]));
} else {
res.writeHead(404);
res.end('The page you requested '+req.url+' was not found');
}
}).listen(3000);
上述代码对第一个示例进行了两项更改;它添加了两个新的路由。第一个if语句与之前相同,返回LeafletEssentials.html。else if语句检查浏览器是否指向http://localhost:3000/getpoints。如果是,则服务器返回一个 JSON 字符串,[{"lat":35,"long":-106}]。最后,如果用户请求一个不存在的页面,服务器将返回一个 404 错误消息,说明页面未找到,并将返回他们所查找的页面的值——req.url。
上述服务器需要更改您的LeafletEssentials.html文件。您需要一个click事件的订阅者,并在事件发生时进行 AJAX 调用。在 AJAX 之前,您需要提交一个表单或向服务器发出请求,然后被重定向到一个新页面,该页面将显示结果。AJAX 允许您向服务器发出请求,获取结果,并在不重新加载整个页面的情况下显示它们。在这个例子中,您将向getpoints URL 发出 AJAX 调用。您将收到一个点的 JSON 表示。然后,您将添加一个标记来表示返回的点——所有这些都不需要刷新网页:
map.on('click',function(){
var xhReq = new XMLHttpRequest();
xhReq.open("GET", "getpoints", true);
xhReq.send();
var serverResponse = xhReq.responseText;
var d=JSON.parse(serverResponse);
L.marker([d[0].lat,d[0].long]).addTo(map).bindPopup("Added via AJAX call to Node.js").openPopup();
});
不深入 AJAX 的细节,上述代码创建了一个XMLHttpRequest实例并打开了getpoints.html网页。
注意
要了解关于XMLHttpRequest的快速课程,请访问 W3Schools 网站www.w3schools.com/xml/xml_http.asp。
然后,它接收响应,解析出由逗号分隔的点,并将它们作为标记添加到地图上。您只接收一个点,因此d变量只有一个值,它由d[0]表示。在 JavaScript 中,通过调用对象然后是字段的值来使用 JavaScript 对象,在这种情况下,d[0].lat和d[0].long。
您的地图将看起来与第一个示例完全一样。当您点击地图时,您将看到另一个点,您的地图将看起来如下截图所示:

此示例在用户点击地图时返回相同的点。此示例可以通过每次用户点击地图时返回不同的点来改进。要做到这一点,只需使用随机数生成器来返回新的纬度和经度。关键是设置最大和最小值,以便点接近我们的当前位置。以下代码使用Math.random()来返回不同的值。为此,只需将服务器代码中的res.end(JSON.stringify([{"lat":35,"long":-106}]));行替换为以下代码:
var lat=Math.random()*(36-35)+35;
var lon=Math.random()*(-107+106)-106;
res.end(JSON.stringify([{"lat":lat,"long":lon}]));
现在,当用户点击地图时,点将随机出现。点击几次后,您的地图应该看起来像这样:

Node.js, Connect, and Leaflet
在前面的例子中,您必须为每个可能的 URL 编写路径。用户可以在您的域名中输入该路径。您允许两种可能性,并对其他所有可能性发送错误。如果您有一个包含许多页面的网站,您不希望为每个 URL 编写一个if语句。Connect 是一个模块,它为常见任务提供了中间件代码。中间件允许您通过您的工作量最小化来完成这些常见任务;您只需要使用use()函数。
注意
您可以在www.senchalabs.org/connect/了解 Connect。
要安装 Connect,打开 Node.js 的命令行工具,并输入以下命令:
npm install –g connect
npm 是 Node.js 的包管理器。前面的命令启动了包管理器,并要求它安装 Connect。–g选项是为了全局安装它,以便在机器的任何地方都可以使用。当 Connect 安装完成后,您的命令提示符将看起来像这样:

注意
注意版本号,因为您将在后续步骤中需要它。
一旦您安装了 Connect,您就可以开始示例。此示例将创建一个简单的服务器,它将只提供静态文件。按照以下步骤创建服务器:
-
创建一个
project文件夹,并在其中放置另一个名为www的文件夹。 -
在目录中放置几个 HTML 文件,特别是
LeafletEssentials.html和getpoints.json。您可以使用书中包含的示例,或者创建自己的示例。一个最小的getpoints.json文件将包含以下内容:[{"lat":35,"long":-106}]文件的内容是一个用 JSON 表示的单个点。您可以添加尽可能多的点。您甚至可以编写一个脚本来在设定的时间间隔更新文件的内容。
-
接下来,在主项目文件夹中,创建一个包含以下内容的文件,并将其命名为
package.json。请注意,此示例使用的是 Connect 3.0 之前的版本:{ "name": "leafletessentials", "version": "0.0.1", "dependencies": {"connect": "2.21.1"} }此文件用于构建项目。在命令行中,导航到您的
project目录,并输入npm install。包管理器将读取package.json文件,并在您的project文件夹中创建一个名为Node_modules的新子文件夹。 -
打开文件夹,您将看到另一个包含 Connect 模块所有文件的文件夹。在编写服务器之前,您需要在
LeafletEssentialsAjax.html中进行更改。AJAX 调用的代码需要指向getpoints.json文件。现在您已准备好编写服务器。
首件事是使用require()导入 Connect 模块并将其分配给一个变量。通过调用connect()创建一个服务器。最后,调用中间件static()并要求它处理www目录中的所有文件。_dirname变量获取当前目录并将其与/www目录连接,从而得到文件的路径,如下面的代码所示。监听任何可用的端口:
var connect = require('connect')
var server = connect();
server.use(connect.static(__dirname+ '/www'));
server.listen(3000);
代码比前面的例子要短得多。没有if ('/' == req.url)语句。中间件知道目录中的所有文件,如果请求的 URL 与文件名匹配,它将被发送。如果不存在,中间件将发送错误页面。如果您添加新的 HTML 文件,它将在放入文件夹并请求后立即被提供。现在,当您连接并获取地图时,您可以点击它,getpoints.json的内容将被返回并在地图上显示。
Node.js、Express、Jade 和 Leaflet
在前两个例子中,您必须为服务器创建一个静态 HTML 文件以供客户端使用。在这个例子中,您将使用一个模板,允许您在加载 HTML 时传递变量。这将允许您创建动态的数据驱动网站。
对于这个例子,您需要为 Node.js 安装 Jade 模块。为此,打开命令行工具并输入以下命令。这与前面的例子相同。
echo '{}' > package.json
npm install jade –save
现在,您已全局安装了 Jade 模块。您还需要安装 Express。Express 是 Node.js 中最流行的 Web 框架之一。它与 Connect 类似,但在这个例子中,它是允许我们使用视图引擎 Jade 的工具。再次,输入以下命令,并确保您注意已安装的版本:
echo '{}' > package.json
npm install express –save
注意
如需了解更多关于 Express 和 Jade 的信息,您可以访问 Express 网站expressjs.com/和 Jade 网站jade-lang.com/。
现在您已安装了这两个模块,为您的应用程序创建一个文件夹。在文件夹中,您需要创建package.json文件。在这个例子中,您有两个依赖项,所以您的文件应该看起来像以下代码:
{
"name": "leafletessentials",
"version": "0.0.1",
"dependencies": {
"express": "4.4.5",
"jade": "1.3.1"
}
}
使用命令行工具,导航到应用程序目录,并使用 npm 通过以下命令添加依赖项:
npm install
您现在将有一个 Node.js_modules 文件夹,其中包含 Jade 和 Express 子文件夹。您需要一个目录来保存或查看模板文件。这个文件夹需要命名为 views,因为 Express 会在这里查找。创建该目录,然后打开文本编辑器来创建您的视图。为了快速了解 Jade,您可以阅读在 jade-lang.com/tutorial/ 的教程。需要注意的是,Jade 对空白字符敏感,因此缩进必须精确。这可能会在最初非常令人沮丧。以下模板是您在之前的示例中使用的修改后的 LeafletEssentials.html 文件。一个关键的区别是第四行:title = title。这一行将 HTML 文档的标题设置为服务器代码中变量 title 的值:
doctype html
html(lang="en")
head
title= title
link(rel='stylesheet', href='http://cdn.leafletjs.com/leaflet- 0.7.2/leaflet.css')
script(src="img/leaflet.js")
body
#map(style='width:'+900+'px;'+'height:'+800+'px')
script(type='text/javascript').
var map = L.map('map', {center: [35.10418, -106.62987], zoom: 9});
var base = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
L.marker([35.10418, -106.62987]).addTo(map).bindPopup("A Lonely Marker").openPopup();
map.on('click',function(){var xhReq = new XMLHttpRequest();xhReq.open("GET", "getpoints", false);xhReq.send(null);var serverResponse = xhReq.responseText;var d=JSON.parse(serverResponse);L.marker ([d[0].lat,d[0].long]).addTo(map).bindPopup ("Added via AJAX call to Node.js").openPopup();});
将前面的代码保存到您视图文件夹中名为 LeafletEssentials.jade 的文件中。现在,您已经准备好编写服务器了。
注意
Jade 是一个流行且强大的模板模块;然而,还有其他的选择。最初可能更容易学习的两个是 HAML,在 haml.info/,以及 EJS,在 embeddedjs.com/。
服务器的代码如下:
var express=require('express')
var app = express();
app.set('view engine', 'jade');
app.get('/', function(req,res){
res.render('LeafletEssentials',{title:"Leaflet Essentials"});
});
app.get('/getpoints', function(req,res){
res.send([{'lat':35,'long':-106])
});
var server = app.listen(3000);
前面的代码导入 Express 模块并将其分配给一个变量。然后使用 Express 创建一个应用。默认视图引擎是 Jade,但如果您想使用另一个,您需要第三行来设置适当的引擎。接下来的几行使用 app.get() 来指定我们的应用程序将返回的两个 URL。第一个将返回我们的视图,第二个用于 AJAX 调用并返回一个 JSON 格的点。在第一个 AJAX 示例中,当您返回点时需要指定 JSON.stringify()。使用框架的一个原因就是它为您处理了许多常见任务。在这种情况下,Express 会知道您返回的是什么,并相应地设置值。在这个例子中,您返回一个 JSON 字符串,Express 会自动为您将其 JSON 化。
您的地图将看起来就像之前的示例中的那样,当用户点击时,会添加一个点。接下来的示例将使用 Python 来提供 Leaflet 应用程序。
使用 Python 和 CherryPy 创建 Leaflet
Python 编程语言非常强大,拥有大量的标准库和其他第三方库。它对于简单任务来说也相当容易上手。关于该语言的文档非常丰富,有大量的书籍和不同的库可供选择。您可以从 Python 网站下载 Python,网址为 www.python.org/downloads/。版本 3 是最新的;然而,版本 2.7 仍在使用中。可能最好从版本 3 开始学习,但如果您有 v2.7,它将适用于这些示例。
在这个例子中,你将使用 CherryPy 库。你可以在 www.cherrypy.org/ 下载这个库。
注意
想要了解更多关于 CherryPy 和 Python 网络开发的书籍,请访问 www.packtpub.com/CherryPy/book 或 www.packtpub.com/python-3-web-development-beginners-guide/book。
CherryPy 相比 Django 或 Pyramid(前身为 Pylons)是一个更小的网络框架。在这个例子中,它将允许你快速启动而无需太多开销。要手动安装第三方 Python 库,将其提取到一个文件夹中,并运行以下命令:
python setup.py install
运行命令后,你将能够在你的 Python 代码中导入这个库。在这个例子中,你将连接到一个 NoSQL 数据库:MongoDB。MongoDB 是一个文档数据库。它将所有内容存储为 JSON 风格的文档,而不是关系型表格。虽然它的空间功能不如 PostgreSQL 的扩展 PostGIS 强大,但它有一些空间功能,使其成为 Leaflet 后端的一个优秀选择。你可以在 www.mongodb.org/ 下载 MongoDB。
要使用 Python 与 MongoDB 一起使用,你还需要下载并安装 PyMongo。你可以在 pypi.python.org/pypi/pymongo/ 下载这个库。一旦你的环境设置完成,你可以通过运行应用程序 mongod 来启动 MongoDB。
注意
如果你收到一个关于缺失路径的错误,你需要添加 C:\data\db 目录。只需创建文件夹,然后重新运行 mongod。在 Linux 和 OS X 上,执行 mkdir -p /data/db 以添加数据目录。
你的数据库是空的。这些书籍网站上可用的 pa.py Python 文件将创建一个数据库,并用本书早期章节中使用的公共艺术数据填充它。文件看起来像以下代码:
from pymongo import Connection
from pymongo import GEO2D
db=Connection().albuquerque
db.publicart.create_index([("loc",GEO2D)])
db.publicart.insert({"loc":[35.1555,-106.591838],"name":"Almond Blossom/Astronomy","popup":http://farm8.staticflickr.com/7153/6831137393_fa38634fd7_m.jpg })
db.publicart.insert({"loc":[35.0931,-106.664177], "name":"Formas Esperando Palabra de Otros Mundos","popup": "http://farm3.staticflickr.com/2167/2479129916_0d861b2600.jpg"})
print " Completed…"
上述代码从 PyMongo 库中导入了两个模块:Connection 和 GEO2D。第一个处理我们与数据库的连接,第二个允许我们启用空间功能。下一行创建了一个名为 albuquerque 的数据库的连接。接下来,为名为 publicart 的集合创建了一个空间启用索引,并对其 loc 字段进行了索引。接下来的两行是插入到集合中的公共艺术点。它们各自包含一个位置、名称和一个包含该作品图片 URL 的弹出字段。
通过输入 python pa.py 来执行文件。现在你的 MongoDB 将有一个数据库、集合以及足够的数据,允许你尝试一些示例。
注意
如果你曾经删除、损坏或只是想要刷新你的数据库,你可以再次运行这个文件来开始一个新的。
现在你已经运行并填充了数据库,并且安装了 CherryPy 和 PyMongo 的 Python,你现在可以开始编写你的第一个服务器了:
-
第一步是按照以下方式导入 Python 库:
import cherrypy from pymongo import Connection,GEO2D -
接下来,你创建一个类和一个函数,该函数将代表你的应用程序的 URL。在这个例子中,它将是
index函数:class mongocherry(object): def index(self): -
函数首先会创建一个数组来存储 HTML 文件的内容。你可以将
LeafletEssentials.html的内容追加到添加瓦片图层底图之前:output =[] output.append("<HTML><HEAD><TITLE>QUERY MONGODB</TITLE></HEAD><BODY><h1>Query MongoDB</h1><link rel='stylesheet' href='http://cdn.leafletjs.com/leaflet- 0.7.2/leaflet.css' /><style> html, body, #map {padding: 0;margin: 0;height: 100%;}</style></head><body><script src='http://cdn.leafletjs.com/leaflet- 0.7.2/leaflet.js'></script><div id='map'></div><script>var map = L.map('map',{center: [35.10418, -106.62987],zoom: 9});L.tileLayer ('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);") -
现在,创建数据库连接并搜索名为
publicart的集合中的所有文档。find()函数将返回大量记录。在每条记录上,你将追加一个 HTML 字符串,使用记录创建一个标记。位置字段创建标记,名称和弹出字段添加到标记的弹出窗口中:db=Connection().albuquerque for x in db.publicart.find(): output.append("L.marker(["+str(x["loc"][0])+","+str(x["loc" ][1])+"]).addTo(map).bindPopup(\""+x["name"]+"<img src='"+x["popup"]+"'>\");") -
一旦所有文档都添加完毕,你可以将关闭 HTML 标签追加到数组中。然后,将数组转换为字符串,以便在用户请求应用程序的索引时返回,如下所示:
output.append('</SCRIPT></BODY></HTML>') i=0 html="" while i<len(output): html+=str(output[i]) i+=1 return html -
最后,你需要公开
index函数,设置应用程序将使用的地址和端口,然后通过调用类名来启动服务器:index.exposed = True cherrypy.config.update({'server.socket_host': '127.0.0.1', 'server.socket_port': 8000, }) cherrypy.quickstart(mongocherry())
完成后,通过打开命令行并输入以下命令来运行程序:
python mongocherry.py
打开浏览器并将它指向http://127.0.0.1:8000。你应该会看到一个像以下这样的地图:

应用程序返回了你的 MongoDB 内容,并在 Leaflet 地图中显示它们。现在你已知道如何在应用程序中创建 URL 路由,让我们扩展这个例子以添加一个用于空间搜索的 AJAX 调用。
使用 Python、MongoDB 和 Leaflet 进行空间查询
MongoDB 允许你访问空间查询。你可以搜索靠近单个点的结果,通过设置最大距离来搜索靠近一个点,在边界矩形内或在一个圆内。在这个例子中,你将查询靠近一个点的结果。
导入所需的库。在以下代码中,你将导入来自cherrypy库的json模块和两个新模块工具:
import cherrypy
from pymongo import Connection,GEO2D
from cherrypy import tools
import json
在导入库之后,执行以下步骤:
-
创建类。使用工具,你将通过
@符号公开函数。连接到数据库并写出 HTML 代码。在这个例子中,HTML 代码是不同的。你将添加一个监听click事件的监听器。这个代码块的代码将向getdata页面发起 AJAX 调用,并传递click事件的(x,y)坐标。返回的数据将只包含三个对象,因此你可以直接编写 HTML 而不是运行for循环,如下所示:class mongocherry(object): @cherrypy.expose def index(self): db=Connection().albuquerque output =[] output.append("<HTML><HEAD><TITLE>QUERY MONGODB</TITLE></HEAD><BODY><h1>Query MongoDB</h1><link rel='stylesheet' href='http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css' /><style> html, body, #map {padding: 0;margin: 0;height: 100%;}</style></head><body><script src='http://cdn.leafletjs.com/leaflet- 0.7.2/leaflet.js'></script><div id='map'></div><script>var lat; var lon; var map = L.map('map',{center: [35.10418, - 106.62987],zoom: 9});L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png') .addTo(map);map.on('click',function(e){var a=String(e.latlng).split(\",\");lat=a[0].split(\"(\");lon=a [1].split(\")\");var xhReq = new XMLHttpRequest();var s=\"getdata?x=\";var s2=String(lat[1]);var s3=\"&y=\";var s4=String(lon[0]);var url=s.concat(s2,s3,s4); xhReq.open(\"GET\", url, false); xhReq.send(null); var serverResponse = xhReq.responseText; var d=JSON.parse(serverResponse);L.marker([d[0].lat,d[0].long]) .addTo(map);L.marker([d[1].lat,d[1].long]).addTo(map);L.mar ker([d[2].lat,d[2].long]).addTo(map);});") -
接下来,关闭 HTML 标签,将它们转换为字符串,并在调用
page函数时返回它们:output.append("</SCRIPT></BODY></HTML>") i=0 html="" while i<len(output): html+=str(output[i]) i+=1 return html -
现在,您将定义并公开另一个 URL 函数。这个函数将被命名为
getdata,它将处理用户点击的 AJAX 调用。此函数接收x和y变量。这些将是用户点击的坐标。在此示例中,查询与前面的示例不同。请注意,您使用了find(),但添加了$near并传递了用户点击的坐标。搜索设置为仅返回三个结果。最后,您使用@tools.json_out()将结果作为 JSON 返回,如下所示:@cherrypy.expose @tools.json_out() def getdata(self,x,y): db=Connection().albuquerque data=[] lat=float(x) long=float(y) for doc in db.publicart.find({"loc": {"$near": [lat, long]}}).limit(3): data.append({'lat':str(doc["loc"][0]),'long':str(doc["loc"][1])}) return data -
最后,设置服务器的 IP 地址和端口号。然后运行它:
cherrypy.config.update({'server.socket_host': '127.0.0.1', 'server.socket_port': 8000, }) cherrypy.quickstart(mongocherry())
现在,您可以运行文件,并将浏览器指向 http://127.0.0.1:8000。您将看到一个空白地图。点击地图上的任何位置,您将看到三个点出现。这些是您点击位置最近的点。点击一次后,您的地图将看起来如下截图所示:

使用 Python 连接到您的 MongoDB 允许您不仅查询数据库以显示结果,而且通过编写更多代码,您还可以使用它来保存地图的结果。您可以允许用户在地图上点击他们想要添加点的地方,然后使用 (x,y) 坐标并执行 insert() 方法而不是 find() 函数。前面提供的示例简要概述了如何使用 Python 提供 Leaflet 地图并处理 AJAX 查询。接下来的示例将转向使用 C# 制作带有 Leaflet 的桌面应用程序。
使用 Leaflet 的 C# 桌面应用程序
Leaflet 用于网页中;然而,使用 C#,您可以在 Windows 表单中嵌入网页浏览器,创建看起来像是桌面应用程序的东西。本节中的示例将向您展示如何将地图添加到 C# 应用程序中,如何通过从 C# 调用 JavaScript 函数来添加一个点,以及如何使用 C# 连接到 MongoDB 并在地图上显示结果。
将地图添加到 C# 应用程序
要在 C# 中构建应用程序,您需要安装 Microsoft Visual Studio Express。您至少需要 Visual Studio C# 2010。您可以在 www.visualstudio.com/downloads/download-visual-studio-vs 下载它。此程序是商业 Visual Studio 的精简版。它允许您快速构建 Windows 表单应用程序并将您的代码编译成易于分发的 Windows 可执行文件。
启动应用程序,并从对话框中创建一个新的 Windows 表单应用程序,如图下所示:

您的应用程序将是一个空白表单。选择窗口左上角的工具箱,并将网页浏览器拖到表单上,如图下所示:

点击你拖到表单中的网页浏览器,并修改URL属性,使其指向运行在你网络服务器上的LeafletEssentials.html实例。保存应用程序。点击调试菜单,然后开始调试。你的应用程序将启动,你将看到你的 Leaflet 地图在 Windows 表单中加载,如下面的截图所示:

现在你已经在一个 C#应用程序中拥有了一张地图,而且没有任何代码。接下来的示例将给你的应用程序添加一些功能。
在 C#中添加标记
在这个示例中,你将通过添加一个标记来构建上一个示例。你需要做的第一件事是使用工具箱将一个按钮拖到表单的底部。在按钮的属性中,将text属性从button1更改为Add Marker。然后,双击按钮。
你现在看到的是当你创建应用程序时 Visual Studio 为你创建的代码,并且它已经添加了一个处理按钮点击的功能。当你点击按钮时,它会编写这个函数。在你编写按钮代码之前,你需要添加对MSHTML.dll的引用。这个文件将允许你使用你需要的网络和 HTML 对象来使你的地图工作。在你的代码顶部,你会看到一些以using开头的行。这就是你将所需的库导入到应用程序中的地方。最常见的已经添加了。在列表的末尾,输入代码using MSHTML;。它会被下划线标记,并且不会找到。现在你需要右键点击解决方案资源管理器窗口中的项目,并选择添加引用。添加一个 COM 引用到微软 HTML 对象库,如以下截图所示:

现在你已经添加了引用,下划线将消失,你可以开始编写button1_Click()函数。将以下代码添加到函数中:
HtmlElement head = webBrowser1.Document.GetElementsByTagName("head")[0];
HtmlElement scriptEl = webBrowser1.Document.CreateElement("script");
IHTMLScriptElement element = (IHTMLScriptElement)scriptEl.DomElement;
element.text = "var mymarker; function addPoints() { mymarker = new L.marker([36.104743, -106.629925]); map.addLayer(mymarker); mymarker.bindPopup('HELLO <br>Added By C#.'); }";
head.AppendChild(scriptEl);
webBrowser1.Document.InvokeScript("addPoints");
之前的代码抓取了通过网页浏览器属性加载的LeafletEssentials.html文件的<head>标签。然后创建一个<script>元素,以便你可以在 HTML 中添加 JavaScript 并执行它。然后创建脚本元素,并传递一个text字符串。这个字符串是用于在地图上添加点的 JavaScript 函数。
你必须将你的代码包裹在一个函数中,因为这是 C#调用和执行它的方式。然后,将<script>标签追加到文档的<head>标签中,并告诉网页浏览器调用addPoints()函数。所以现在,当用户点击按钮时,JavaScript 函数将被添加到LeafletEssentials.html中,并执行。保存并调试项目。当应用程序启动时,点击按钮,你的应用程序应该看起来像这样:

要允许 C# 应用程序修改 Leaflet 应用程序,将一个 JavaScript 函数插入到基础 HTML 文件中,然后通过事件(如按钮点击)执行它。下一个示例将连接到 MongoDB。
使用 C# 和 Leaflet 的 MongoDB
就像 Python 示例一样,要在 C# 中使用 MongoDB,将需要驱动程序。你可以在 github.com/mongodb/mongo-csharp-driver/releases 下载 C# 驱动程序。此示例使用 .zip 文件。在你的项目中添加另一个引用,但这次不是选择 COM,而是浏览到你从 .zip 文件中提取驱动程序的位置。文件夹应包含 MongoDB.Bson.dll 和 MongoDB.Driver.dll。添加引用后,你必须使用以下代码导入所需的库:
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using MongoDB.Driver.Builders;
导入库后,你可以修改你的按钮以连接到 MongoDB 并加载点。以下说明将指导你通过代码连接到 MongoDB:
-
首先,你需要一个字符串来保存将要添加点的 JavaScript 函数。在 C# 中,你会使用
StringBuilder()来能够向字符串中追加内容。你可以从追加函数名和第一个大括号开始:StringBuilder myString = new StringBuilder(); myString.Append("function addPoints() {"); -
接下来,你设置连接到 MongoDB。连接到 IP 和端口——默认是 localhost 的 27017 端口。获取服务器和名为
albuquerque的数据库。最后,连接到publicart集合:var client = new MongoClient("mongodb://localhost:27017"); var server = client.GetServer(); var database = server.GetDatabase("albuquerque"); var collection = database.GetCollection("publicart"); -
现在,你可以执行查询。查询将找到所有文档并返回每一个。代码追加一个字符串,通过连接每个文档的位置、名称和弹出信息来创建标记:
foreach (var document in collection.FindAll()) { myString.Append("L.marker([" + document["loc"][0] + "," + document["loc"][1] + "]).addTo(map).bindPopup(\"" + document["name"] + "<br><img src='" + document["popup"] + "'>\");" + "\r\n"); } -
使用最后一个大括号关闭字符串:
myString.Append("}"); -
最后一个代码块与上一个示例相同。创建 HTML 元素,并通过将
StringBuilder.toString()转换为字符串来插入字符串:HtmlElement head = webBrowser1.Document.GetElementsByTagName("body")[0]; HtmlElement scriptElement = webBrowser1.Document.CreateElement("script"); IHTMLScriptElement addPointsElement = (IHTMLScriptElement)scriptElement.DomElement; addPointsElement.text = myString.ToString(); head.AppendChild(scriptElement); webBrowser1.Document.InvokeScript("addPoints");
保存并调试项目。当应用程序启动时,点击按钮,你的应用程序应该看起来像以下截图:

最后一步是选择 debug 菜单,而不是调试,选择 build solution。如果你浏览到 project 文件夹,你将有一个名为 bin 的目录。在该目录中,你现在有一个 .exe 文件。
现在,你有一个用 C# 编写并编译为 .exe 的 MongoDB 集合 Leaflet 地图。为了让它在另一台机器上运行,你只需要让你的 MongoDB 部署在真实的 IP 地址上,并允许从你的网络外部访问。
最后一个示例将允许用户点击地图并返回最近的点。
使用 C#、Leaflet 和 MongoDB 进行查询
你已经学会了如何通过编写 JavaScript 函数、将其注入到 HTML 文件中并执行它,将数据从 C# 传递到 Leaflet。从 JavaScript 返回 C# 的方法略有不同。你可以传递数据的一种方式是让 JavaScript 将内容写入 <div>,然后 C# 可以读取它。关键是要将 <div> 标签设置为不可见。以下步骤将指导你完成最后一个示例:
-
首先,通过添加一个新的
<div>并设置样式,将LeafletEssentials.html修改为将显示设置为none。如果你将其设置为hidden,它将在文档中占用空间,并在你的地图下方留下空白区域:<style> html, body, #map { padding: 0; margin: 0; height: 100%; } #points.hidden { display: none; } </style> <body> <div id="map"></div> <div id="points"></div> -
接下来,创建一个
click事件的监听器,并编写一个函数来创建一个标记,显示click事件的地点,清理返回的纬度和经度文本,并将结果写入<div>:map.on('click',function(e){L.marker(e.latlng).addTo(map).bi ndPopup("SEARCH LOCATION").openPopup(); var a=String(e.latlng).split(","); var lat=a[0].split("("); var lon=a[1].split(")"); document.getElementById("points").innerHTML = lat[1]+","+lon[0]; }); -
HTML 文件准备就绪后,你现在可以修改 C#。第一步是从
<div>标签中读取点,然后解析它们,以便每个点都在自己的变量中,如下所示:string hiddenHTML = webBrowser1.Document.GetElementById("points").InnerHtml; string[] thePoints = hiddenHTML.Split(','); -
设置与 MongoDB 的连接。连接到 IP 和端口。获取服务器,然后是名为
albuquerque的数据库。最后,连接到publicart集合:var client = new MongoClient("mongodb://localhost:27017"); var server = client.GetServer(); var database = server.GetDatabase("albuquerque"); var collection = database.GetCollection("publicart"); -
创建一个查询的文本字符串,并初始化你的
StringBuilder()函数以保存函数和结果:var query = Query.Near("loc", double.Parse(thePoints[0]), double.Parse(thePoints[1])); StringBuilder myLocString = new StringBuilder(); myLocString.Append("function addLocPoints() {"); -
使用
near()函数在循环中执行查询。将结果传递到字符串中,构建 JavaScript 函数:foreach (BsonDocument item in collection.Find(query).SetLimit(5)) { BsonElement loc = item.GetElement("loc"); string g = loc.Value.ToString(); string x = g.Trim(new Char[] { '[', ']' }); String[] a = x.Split(','); myLocString.Append("L.marker([" + a[0] + "," + a[1] + "]).addTo(map).bindPopup(\"" + item["name"] + "<br><img src='" + item["popup"] + "'>\");" + "\r\n"); } -
最后一个代码块与前面的两个示例相同。创建 HTML 元素,并通过将
StringBuilder.toString()转换为字符串来插入字符串:myLocString.Append("}"); HtmlElement head = webBrowser1.Document.GetElementsByTagName("body")[0]; HtmlElement scriptElement = webBrowser1.Document.CreateElement("script"); IHTMLScriptElement addPointsElement = (IHTMLScriptElement)scriptElement.DomElement; addPointsElement.text = myLocString.ToString(); head.AppendChild(scriptElement); webBrowser1.Document.InvokeScript("addLocPoints"); -
保存并调试项目。当应用程序启动时,点击按钮,你的应用程序应该看起来像以下截图:
![使用 C#、Leaflet 和 MongoDB 查询]()
摘要
在最后一章中,你学习了如何在其他编程语言和框架中使用 Leaflet.js。从 Node.js 开始,你学习了如何在前端和后端使用 JavaScript。你创建了一个 Node.js 服务器,返回一个 Leaflet 网页。然后你修改了代码,允许 AJAX 调用返回到服务器以更新地图而不重新加载页面。
你还学习了如何使用 Python 和 CherryPy 创建服务器并允许 AJAX 请求。Python 示例介绍了 NoSQL 数据库,特别是 MongoDB。你学习了如何编写查询以返回数据库集合中的所有文档,以及如何使用 AJAX 仅查询用户点击点附近的点。
最后——对于完全不同的事情——你学习了如何将网页浏览器嵌入到 Windows 表单中,并使用 Leaflet 运行桌面应用程序。这些应用程序在表单上使用按钮来执行注入到 LeafletEssentials.html 文件中的 JavaScript 函数。然后你从 JavaScript 方向传递数据回到 C#——捕捉地图上的鼠标点击并使用它们来查询 MongoDB 并返回结果。你构建的 C# 应用程序可以随后编译成 .exe 文件并分发给任何可以连接到你的 MongoDB 和 LeafletEssentials.html 文件的人。



浙公网安备 33010602011771号