谷歌地图-JavaScript-API-秘籍-全-

谷歌地图 JavaScript API 秘籍(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

目前,对于 Google Maps JavaScript API,既有开源也有专有替代方案,但使 API 对开发者特别特殊的是,它是一个包含基本地图、覆盖层和技术能力的完整解决方案。

对于开发者来说,API 特别令人兴奋,因为它非常容易构建通用结果,同时,它在其同一个盒子中拥有自己的技巧和窍门以及高级功能。因此,当你使用 API 时,你可以浮在水面或深入水底。

Google Maps JavaScript API v3 使移动场景的快速和简单开发成为可能,便于基于位置解决方案的开发者深入研究该主题。关于移动开发的增长,尤其是基于位置的应用程序,Google Maps JavaScript API v3 值得应有的关注。

最后但同样重要的是,没有哪个地图 API 比谷歌地图 API 在没有持续更新和彻底处理矢量数据和卫星数据支持的情况下取得如此成功。谷歌投入了巨大的资源来维护矢量数据的统一结构和其制图质量,这些努力在 API 使用方面得到了回报。

本书涵盖的内容

第一章, Google Maps JavaScript API 基础,指导你如何围绕一个主要食谱创建一个简单的 Google Maps 应用程序。通过添加细节到食谱中,将介绍地图对象及其主要选项,包括地图类型。

第二章, 添加栅格图层,通过一系列食谱展示了如何通过添加像瓦片、交通、公交和天气图层这样的 Google 图层来添加外部栅格数据。

第三章, 添加矢量图层,介绍了如何绘制矢量特征,以及如何显示外部矢量数据源,如 KML 和 GeoRSS。

第四章, 使用控件,详细解释了控件。本章将介绍为 Web 和移动设备创建和自定义自定义用户界面的方法。

第五章, 理解 Google Maps JavaScript API 事件,详细描述了如何响应地图、图层或标记的行为,例如缩放结束、图层更改或标记添加。事件将为地图编程增加更多交互性。

第六章, Google Maps JavaScript 库,详细解释了将扩展 Google Maps JavaScript API 功能的库。这些库具有不同的能力,可以增强 Google Maps JavaScript API 的功能。

第七章,使用服务,详细介绍了将扩展 Google Maps JavaScript API 的服务。这些服务包括地理编码和街景,展示了 Google Maps JavaScript API 在地图方面的真正实力。

第八章,通过高级食谱精通 Google Maps JavaScript API,解释了外部 GIS 服务器和服务与 Google Maps JavaScript API 的集成。这些包括 ArcGIS Server、GeoServer、CartoDB 和 Google Fusion Tables,以及 OGC 服务如 WMS。

你需要这本书什么

Google Maps JavaScript API 与 HTML、CSS 和 JavaScript 代码一起工作。因此,一个具有 HTML、JavaScript 和 CSS 处理能力的文本编辑器在探索这本书时会成为你的好朋友。

对于 Mac 用户来说,有很多商业或免费的文本编辑器,例如 TextWrangler、BBEdit、Sublime Text 或 WebStorm。它们都能很好地处理 HTML、JavaScript 和 CSS。

对于 Windows 用户来说,也有不同的文本编辑器,但 Notepad++ 是最常用和推荐的。

选择编辑器取决于你的电脑习惯,因此没有确切的方法或推荐供用户选择一个编辑器。每个人都有不同的看法,这些选择会受到这些看法的影响。

实现这些食谱还需要一个 HTTP 服务器。有很多 HTTP 服务器,包括 Apache、IIS 等。但是,独立服务器的安装过程可能对大多数用户来说是个问题。我们鼓励您使用将 HTTP 服务器、数据库服务器和脚本语言捆绑在一起的一体化解决方案。XAMPP 和 MAMP 分别是 Windows 和 Mac OS X 平台上的这类解决方案。

为了提供更好的用户体验,我们创建了一个主应用程序,允许运行所需的食谱并显示其源代码。假设你已经安装并配置了一个本地 Web 服务器,如 XAMPP 或 MAMP,并且捆绑代码已复制到 googlemaps-cookbook 文件夹中的 HTTP 服务器根内容文件夹内,用户可以通过在浏览器中访问 http://localhost/googlemaps-cookbook/index.html URL 来运行主应用程序。

这本书面向的对象

这本书非常适合对在他们的网站上嵌入简单联系地图感兴趣的开发者,以及那些希望开发现实世界复杂 GIS 应用程序的人。它也适合那些想从他们的地理相关数据创建基于地图的信息图形,如热图的人。假设你已经具备一些 HTML、CSS 和 JavaScript 的经验,以及与 GIS 相关的简单概念的经验,并且对一些 GIS 服务器或服务有所了解。

习惯用法

在这本书中,你会找到许多不同风格的文本,以区分不同类型的信息。以下是一些这些风格的示例,以及它们含义的解释。

文本中的代码词如下所示:"我们可以通过使用include指令来包含其他上下文。"

代码块设置如下:

<!DOCTYPE html>
<html>
    <head>
        <!-- Include Google Maps JS API -->
        <script type="text/javascript"
        src="img/js?key=INSERT_YOUR_MAP_API_KEY_HERE&sensor=false"></script

新术语重要词汇以粗体显示。

注意

警告或重要注意事项以如下框中显示。

小贴士

小贴士和技巧看起来是这样的。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中受益的标题非常重要。

要发送一般反馈,只需发送一封电子邮件到 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果您在某个主题上具有专业知识,并且您对撰写或为书籍做出贡献感兴趣,请参阅我们在 www.packtpub.com/authors 的作者指南。

客户支持

现在,您已经成为 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您最大限度地利用您的购买。

下载示例代码

您可以从您在 www.packtpub.com 的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问 www.packtpub.com/support 并注册,以便将文件直接通过电子邮件发送给您。

错误清单

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问 www.packtpub.com/submit-errata,选择您的书,点击错误清单提交表链接,并输入您的错误清单详情。一旦您的错误清单得到验证,您的提交将被接受,错误清单将被上传到我们的网站,或添加到该标题的错误清单部分。任何现有的错误清单都可以通过从 www.packtpub.com/support 选择您的标题来查看。

盗版

互联网上版权材料的盗版是一个跨所有媒体持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以追究补救措施。

请通过以下链接联系我们 <copyright@packtpub.com>,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们作者和提供有价值内容方面的帮助。

问题

如果你在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决。

第一章. Google Maps JavaScript API 基础

在本章中,我们将涵盖以下主题:

  • 在自定义 DIV 元素中创建简单地图

  • 创建全屏简单地图

  • 从 Web 移动到移动设备

  • 通过编程方式更改地图属性

  • 更改基础地图

简介

位置正变得越来越受欢迎,而 Google 是这个领域的主要变革者之一。大多数网站都有一个带有 Google Maps 的联系页面,显示企业的位置。这是 Google Maps JavaScript API 的最简单用法。还有其他更高级的用法,可以在地图上显示不同的信息。本书包含多个关于 Google Maps JavaScript API 的用法食谱,从入门到高级主题。Google Maps JavaScript API 由多个部分组成,如栅格/矢量图层、控件、事件和服务,这些内容都在以下章节中进行了介绍。

Google Maps JavaScript API 有开源和商业替代品,例如 OpenLayers、Leaflet、Bing Maps、MapQuest 和 Here Maps(以前称为诺基亚地图),但 Google Maps JavaScript API 在基础地图、卫星图像和 API 本身方面有很好的支持。例如,API 可以用来在地图上显示一个位置或政府机构的所有数据。

Google Maps JavaScript API 不是一个免费的地图展示工具,但它的免费使用限制对于大多数开发者来说已经足够了。每天每个网站有 25,000 次地图加载的限制,这发生在地图在网页上初始化时计算。

在自定义 DIV 元素中创建简单地图

当你使用地图应用工作时,创建地图是你能做的最重要的任务。地图是应用的主要部分,用户与之交互,所有可视化操作都发生在这里。这部分可能看起来很 trivial,但所有后续章节都依赖于这个应用的部分。

本食谱将指导你通过在网页上创建简单地图视图的过程。

注意

如前言所述,我们需要一个网络服务器来托管我们的 HTML、JavaScript 和 CSS 文件,以及一个网络浏览器来在客户端解释它们。

准备工作

如前所述,Google Maps JavaScript API 与 HTML、CSS 和 JavaScript 代码一起工作。因此,在探索本书时,拥有一个具有 HTML、JavaScript 和 CSS 处理能力的文本编辑器会是一个很好的帮手。

对于 Mac 用户,有很多商业或免费的文本编辑器,例如 TextWrangler、BBEdit、Sublime Text 或 WebStorm。它们都能很好地处理 HTML、JavaScript 和 CSS。

对于 Windows 用户来说,也有不同的文本编辑器,但 Notepad++ 是最常用和推荐的。

选择编辑器取决于你的计算机使用习惯,因此没有确切的方法或推荐给用户选择特定类型的编辑器。每个人都有不同的感知,这些感知会影响这些选择。

你可以在 Chapter 1/ch01_simple_map.html 找到源代码。

小贴士

下载示例代码

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

如何操作…

这里是我们将使用 Google Maps JavaScript API 创建第一个地图的步骤。

  1. 创建一个名为 map.html 的新空文件,并将以下代码块插入其中。此块是每个使用 Google Maps JavaScript API 的应用程序所必需的。您必须在以下代码中的 URL 中插入您的 Google Maps JavaScript API 密钥。

    <!DOCTYPE html>
    <html>
        <head>
            <!-- Include Google Maps JS API -->
            <script type="text/javascript"
            src="img/js?key=INSERT_YOUR_MAP_API_KEY_HERE&sensor=false"></script>
    

    小贴士

    请确保您从 Google APIs Console (code.google.com/apis/console) 获取了您的 Google Maps JavaScript API 密钥,并将其替换为 YOUR_API_KEY。如果您不更改代码的这一部分,由于 Google 的 API 规则,您的地图将无法显示。同时,确保在将地图文档发布到其他位置或生产环境之前更改 API 密钥。

  2. 为了将地图放置在所需的位置,以下部分是必需的。在 <head> 部分中,添加以下 <style> 元素的 HTML 样式代码,以创建宽度为 800 像素和高度为 500 像素的地图:

    <style type="text/css">
        #mapDiv { width: 800px; height: 500px; }
    </style>
    
  3. 将以下 JavaScript 代码行添加到代码中,以便使用 Google Maps JavaScript API 运行。不要忘记在函数外部定义 map 对象,以便从代码的任何部分访问它。

      <!-- Map creation is here -->
      <script type="text/javascript">
        //Defining map as a global variable to access from //other functions
      var map;
      function initMap() {
        //Enabling new cartography and themes
        google.maps.visualRefresh = true;
    
        //Setting starting options of map
        var mapOptions = {
          center: new google.maps.LatLng(39.9078, 32.8252),
          zoom: 10,
          mapTypeId: google.maps.MapTypeId.ROADMAP
        };
    
        //Getting map DOM element
        var mapElement = document.getElementById('mapDiv');
    
        //Creating a map with DOM element which is just //obtained
        map = new google.maps.Map(mapElement, mapOptions);
      }
    
  4. 添加以下行以完成代码。这部分定义了添加地图的位置和初始化地图的时间的 <html> 标签。

            google.maps.event.addDomListener(window, 'load', initMap);
        </script>
      </head>
      <body>
          <b>My First Map </b>
          <div id="mapDiv"></div>
      </body>
    </html>
    
  5. 在您最喜欢的浏览器中输入您本地服务器的 URL,其中存储着您的 map.html 文件,并查看结果。您将看到一个带有导航控件在左上角和基本地图控件在右上角的地图。如何操作…

如前述截图所示,我们已经使用 Google Maps JavaScript API 创建了我们的简单地图。

工作原理…

让我们一步一步地检查代码。首先,使用代码 <!DOCTYPE html> 定义了 HTML5 文档。然后添加了 <html><head> 标签。

<style> 元素之前,使用 <script> 元素将 Google Maps JavaScript API 作为引用包含如下:

<script type="text/javascript"
          src="img/js?key= INSERT_YOUR_MAP_API_KEY_HERE&sensor=false">
   </script>

然后在代码中添加一个新的 <script> 标签。在 <head> 部分之后,开始 <body> 部分。

    <body>

以下代码行监听文档的加载。当页面完全加载时,此事件触发 initMap 函数。这可以防止由于 DOM 及其相关元素而出现的不可预测的行为。

google.maps.event.addDomListener(window, 'load', initMap);

最后,我们有创建我们页面的 HTML 标签。具有 id="mapDiv"<div> 元素是我们地图将显示的位置。此元素从之前定义的 CSS 标签中获取样式,宽度为 800 像素,高度为 500 像素。

注意

mapDiv元素的样式与可以在 W3Schools 网站上详细找到的 CSS 规则直接相关(www.w3schools.com/css)。

如前所述,初始化地图的主要 JavaScript 代码将详细解释。首先,将map对象定义为全局对象,以便从稍后添加的每个函数中访问地图。

var map;

然后定义initMap函数如下:

function initMap() {

}

在创建地图之前,以下代码被调用以将地图的主题更改为 2013 年 5 月在 2013 年 Google IO 上宣布的最新主题。这个主题是用于新 Google Maps 的新外观;地图的制图和组件样式都是新颖且最新的;然而,使用这个新功能是可选的。如果您不使用以下代码行,您将使用旧主题。

google.maps.visualRefresh = true;

然后,地图选项将被定义为以下内容:

   var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        mapTypeId: google.maps.MapTypeId.ROADMAP
   };

地图选项有三个重要参数。

  • center:这是地图的纬度和经度中心。之前定义的参数是土耳其安卡拉的坐标。如果您不知道如何获取一个地方的坐标,请参考第五章中提供的菜谱,理解 Google Maps JavaScript API 事件

  • zoom:此参数是一个整数,定义了地图显示的级别。Google Maps 的缩放级别从 0(世界级别)到 21+(街道级别)。当缩放级别增加时,用户可以看到更多细节但面积更小。

  • mapTypeId:此参数定义了地图上显示的基本地图类型。此参数的详细信息将在本章后面的菜谱中给出。

在创建地图对象之前,必须获取地图将显示的div元素。这通过以下经典 DOM 方法getElementById完成:

    var mapElement = document.getElementById('mapDiv');

最后,我们已准备好创建地图对象:

    map = new google.maps.Map(mapElement, mapOptions);

此代码获取mapElementmapOptions对象以创建地图。第一个参数是地图将被放置的div元素,另一个是包含地图起始参数的mapOptions对象。上一行代码在给定的div元素上使用给定的选项创建地图,并返回地图对象以便稍后与地图交互。

注意

此菜谱是书中最简单的一个,但也是开始使用 Google Maps JavaScript API 最重要的一个。地图有很多参数或选项,将在后面的章节和菜谱中讨论。

还要记住,在以下菜谱中,基本代码将不会包含在书中,以便为您提供更多菜谱。以下章节和菜谱中只给出更改或必需的代码行,但您可以通过 Packt Publishing 网站(www.packtpub.com/support)获取完整的代码,包括省略的行。

创建一个简单的全屏地图

应用程序可以以不同的格式进行映射。其中一些在鼠标点击或事件后显示地图,而另一些则直接以全屏模式显示。

这个配方将向您展示如何创建一个全屏地图,该地图将在 Web 或移动应用程序中使用。

准备工作

如前所述,一些配方将只显示代码中的更改行,以便为更多配方腾出空间。这个配方是之前配方在自定义 DIV 元素中创建简单地图的修改版本。

您可以在第一章/ch01_full_screen_map.html中找到源代码。

如何操作…

您可以通过遵循以下步骤轻松创建一个简单的全屏地图:

  1. 让我们从创建一个名为full_screen_map.html的新空文件开始。然后,将之前 HTML 文件(map.html)中的所有代码复制并粘贴到这个文件中。

  2. 在新文件中找到以下代码行:

        <style type="text/css">
             #mapDiv { width: 800px; height: 500px; }
        </style>
    
  3. 添加以下行并根据新值进行更改。widthheight值更改为100%,以便在浏览器视口中使地图全屏。此外,将body元素的margin值更改为0以移除地图周围的全部空间。

    <style type="text/css">
        html { height: 100% }
        body { height: 100%; margin: 0; }
        #mapDiv { width: 100%; height: 100%; }
    </style>
    
  4. 在您最喜欢的浏览器中输入存储full_screen_map.html文件的本地区域 URL,查看结果。您将看到地图在左上角有导航控件,右上角有填充整个浏览器区域的基图控件。如何操作…

因此,我们已经成功创建了一个简单的全屏地图。

它是如何工作的…

Google Maps JavaScript API 使用 HTML 标准的div组件来显示地图。div组件从 CSS 规则中获取其样式和属性,这些规则定义在顶部的<head>元素中。#mapdivwidthheight属性表明div组件将填充整个浏览器空间。您可以轻松修改这些widthheight属性以根据您的需求更改地图尺寸。

更多内容...

地图的尺寸直接与 CSS 样式相关,地图尺寸与 Google Maps JavaScript API 之间没有直接关系。包含 Google Maps JavaScript API 的基本地图和覆盖层的DIV元素只是一个空白容器,随着DIV元素变大,您的地图也会变大。

参见

  • 在自定义 DIV 元素中创建简单地图的配方

从网络设备转移到移动设备

现在,移动设备越来越受欢迎;所有流行的网站都在准备他们的移动应用或网站,以便人们可以在任何地方访问它们。自从在 HTML5 中引入了使用适当 API 访问设备位置的功能后,地图应用也变得更加流行。

在本教程中,我们将准备一个将在全屏模式下在移动浏览器中运行的映射应用程序,并且它将借助 W3C 地理位置 API 缩放到设备的地理位置。此 API 也可以从桌面浏览器访问以获取您的位置。

准备工作

此代码将在移动设备或模拟器上运行,因此请确保您的代码可以从您的移动设备或模拟器访问。在本教程中,我建议您将代码上传到托管服务器或网站,以便可以从您的移动设备或模拟器轻松访问。

您可以在第一章/ch01_mobile_map.html找到源代码。

如何操作…

如果您想创建一个适合移动设备的地图,应遵循以下步骤:

  1. 让我们从创建一个名为mobile_map.html的新空文件开始。然后,将介绍在在自定义 DIV 元素中创建简单地图教程中的 HTML 文件(map.html)中的所有代码复制并粘贴到新文件中。

  2. 在新文件中找到以下代码行:

    <!-- Include Google Maps JS API -->
    <script type="text/javascript"
        src="img/js?key=INSERT_YOUR_MAP_API_KEY_HERE&sensor=false">
    </script>
    
  3. 在之前的代码块之前插入以下行。此行告诉移动浏览器当前 Web 应用程序是为移动设备设计的:

    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    
  4. 添加以下CSS样式以使地图全屏显示。

        <style type="text/css">
            html { height: 100% }
            body { height: 100%; margin: 0; }
            #mapDiv { width: 100%; height: 100%; }
        </style>
    
  5. 然后,在创建map对象后添加以下代码块。此代码块将检查您的浏览器是否支持地理位置 API,并根据设备坐标设置地图中心。

    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
            var lat = position.coords.latitude;
            var lng = position.coords.longitude;
            //Creating LatLng object with latitude and //longitude.
            var devCenter =  new google.maps.LatLng(lat, lng);
            map.setCenter(devCenter);
            map.setZoom(15);
        });
    }
    
  6. 将您的文件上传到适当的托管站点,并在您的移动设备或模拟器上检查此 URL。您将被询问是否允许读取您的位置。如果您允许,您将看到您位置处的地图。如何操作…

这就是如何实现为移动设备创建简单地图的目标。

它是如何工作的...

<meta>标签由浏览器和搜索引擎使用,对用户不可见。它们帮助浏览器了解如何表现。在我们的情况下,以下<meta>标签用于告诉浏览器当前网站针对移动浏览器进行了优化:

<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />

<meta>标签解决了用户缩放时的缩放问题,因为缩放应该使地图放大或缩小,而不是文档本身。

为了获取设备位置,我们使用浏览器实现的 W3C 地理位置 API。HTML5 标准中有一个 navigator 命名空间,如果浏览器支持地理位置 API,则首先检查 Geolocation 子命名空间。如果navigator.geolocation返回一个对象,我们可以通过getCurrentPosition函数的帮助获取坐标。回调函数获取设备的纬度和经度并创建google.maps.LatLng对象。然后,使用之前创建的devCenter对象触发map对象的setCenter方法。这将根据设备位置更改地图的中心。

回调函数的最后一行用于设置地图的缩放级别。这可以根据您的需求进行更改。

还有更多...

HTML5 标准仍在进行中,W3 Geolocation API 可能会有变化。如果您想了解更多关于地理位置的信息,请参考 W3C 文档网站(dev.w3.org/geo/api/spec-source.html)。

参见

  • 在自定义 DIV 元素中创建简单地图 的配方

通过编程更改地图属性

到目前为止,地图在其内部是交互式的。用户可以缩放地图、拖动地图、更改用户界面或启用/禁用鼠标交互。如果您想在地图之外玩地图,您应该访问地图并更改您想要的属性,或者您可以在不同情况下通过编程更改属性。通过编程更改地图属性是 Google Maps JavaScript API 的重要部分之一。在大多数地图应用中,用户搜索一个地点,应用应该聚焦在地图上的该点。这是通过 map 对象的函数实现的。有很多地图函数,但我们将只介绍最常用的。

在本配方中,我们将创建一个用户可以在地图外部与之交互的地图应用。使用按钮来与地图交互。

准备工作

在您继续之前,必须创建一个地图对象以便与之交互。如果没有定义地图对象,您将得到一个错误。这类问题通常是由于 JavaScript 的异步行为引起的。

您可以在 Chapter 1/ch01_interaction_map.html 中找到源代码。

如何操作...

如果按照给定的步骤操作,更改地图属性相当简单:

  1. 让我们从创建一个名为 interaction_map.html 的新空文件开始。然后,将 在自定义 DIV 元素中创建简单地图 配方中引入的 HTML 文件(map.html)中的所有代码复制并粘贴到新文件中。

  2. initmap() 函数之后添加以下函数。这些函数由 HTML 中定义的按钮调用,用于与地图交互。函数将在本配方中稍后解释。

    function zoomToIstanbul () {
        var istanbul = new google.maps.LatLng(41.0579,29.0340);
        map.setCenter(istanbul);
    }
    
    function zoomToStreet () {
        map.setZoom(18);
    }
    
    function disableDrag () {
        map.setOptions ({ draggable: false });
    }
    
    function setMaxZoom () {
        map.setOptions ({ maxZoom: 12 });
    }
    
    function setMinZoom () {
        map.setOptions ({ minZoom: 5 });
    }
    
    function changeUI () {
        map.setOptions ({ disableDefaultUI: true });
    }
    
    function disableScroll () {
        map.setOptions ({ scrollwheel: false });
    }
    
  3. 接下来,添加以下函数以监听步骤 5 中定义的 HTML 代码中按钮的点击事件。

    function startButtonEvents () {
        document.getElementById('btnZoomToIst' ).addEventListener('click', function(){
            zoomToIstanbul();
        });
        document.getElementById('btnZoomToStr' ).addEventListener('click', function(){
            zoomToStreet();
        });
        document.getElementById('btnDisableDrag' ).addEventListener('click', function(){
            disableDrag();
        });
        document.getElementById('btnMaxZoom' ).addEventListener('click', function(){
            setMaxZoom();
        });
        document.getElementById('btnMinZoom' ).addEventListener('click', function(){
            setMinZoom();
        });
        document.getElementById('btnChangeUI' ).addEventListener('click', function(){
            changeUI();
        });
        document.getElementById('btnDisableScroll' ).addEventListener('click', function(){
            disableScroll();
        });
    }
    
  4. 在初始化地图时必须调用 startButtonEvents 函数,因此添加了以下代码行:

    startButtonEvents();
    
  5. 然后,在 <body> 标签内添加以下 HTML 代码行。这些是将在网页上显示的 <button> 标签。每个 button 元素都监听点击事件以触发相关函数。

    <input id="btnZoomToIst" type="button" value="Zoom To Istanbul">
    <input id="btnZoomToStr" type="button" value="Zoom To Street Level">
    <input id="btnDisableDrag" type="button" value="Disable Drag">
    <input id="btnMaxZoom" type="button" value="Set Max Zoom to 12">
    <input id="btnMinZoom" type="button" value="Set Min Zoom to 5">
    <input id="btnChangeUI" type="button" value="Change UI">
    <input id="btnDisableScroll" type="button" value="Disable Scroll Zoom">
    
  6. 在您最喜欢的浏览器中输入您本地服务器的 URL,其中存储着您的 interaction_map.html 文件,并查看结果。您将看到带有按钮的地图在顶部。每个按钮都会触发不同的函数以与地图交互。如何操作…

作为菜谱的结果,我们可以通过编程方式更改地图属性。

它是如何工作的...

之前定义的每个 JavaScript 函数都用于更改地图的不同侧面。最常用的函数是更改地图的中心和缩放级别。大多数时候,人们只是在地图上从一个位置移动到另一个位置。例如,如果您正在显示咖啡连锁店的地点,地图应该聚焦在每个咖啡店的地点。以下代码创建了一个google.maps.LatLng对象,它将成为setCenter()函数的输入。41.057929.0340值分别是土耳其伊斯坦布尔的纬度和经度。您将用您自己的坐标值替换这些坐标值以更改地图的中心。此函数将仅更改地图的中心,而不会更改缩放级别。

       var istanbul = new google.maps.LatLng(41.0579,29.0340);
       map.setCenter(istanbul);

如果您想放大或缩小地图以覆盖/显示一个区域,您还应该调整缩放值。例如,您的咖啡店位置在缩放级别 6 时无法为您的客户提供有效的指导。它至少应该是 15 级或更高,以便看到街道名称和确切位置。以下代码可以实现这一点:

       map.setZoom(18);

在某些情况下,您可能不希望用户与地图进行交互,例如通过禁用鼠标拖动或滚轮滚动来固定地图位置。以下是一些google.maps.MapOptions对象属性的示例。这些属性与地图的属性直接相关。如果您想更改地图的一个或多个属性,您应该创建一个 JSON 对象并调用以下地图函数:

        map.setOptions ({
            draggable: false,
            maxZoom: 12
        });

使用setOptions()函数,您还可以启用或禁用默认控件,但这将在第四章使用控件中讨论。您可以使用setOptions()函数设置一个或多个属性。您可以在属性旁边的注释中找到简短的解释:

        map.setOptions ({
            draggable: false, //Disables the map drag
            maxZoom: 12, //Sets maximum zoom level
            minZoom: 5, //Sets minimum zoom level
            disableDefaultUI: true, //Removes the default controls
            scrollwheel: false //Disables the mouse scroll wheel
        });

小贴士

访问地图对象

注意将地图对象定义为全局对象以便在任何地方访问。在编写 JavaScript 时,这可能会在某些情况下引起问题。请查看以下链接以获取有关 JavaScript 和作用域的更多信息:coding.smashingmagazine.com/2009/08/01/what-you-need-to-know-about-javascript-scope/

参见

  • 在自定义 DIV 元素中创建简单地图的菜谱

更改基础地图

基础地图是映射 API 过程中的一个重要部分。基础地图显示道路、卫星图像或地形,可用于不同的情况。例如,道路图可能适合显示您的咖啡店位置,但卫星图像则不适合。卫星图像也可以用于显示地块信息,以检查它们是否绘制正确。Google Maps JavaScript API 有四种不同的基础地图,如ROADMAPSATELLITEHYBRIDTERRAIN。所有这些基础地图都可以在以下截图中看到,它们可以相互比较。

更改基础地图

在这个菜谱中,我们将了解 Google Maps 基础地图,并学习如何通过编程方式更改它们。

准备工作

在这个菜谱中,我们将使用 JavaScript 数组来使函数的输入参数易于阅读。如果您没有相关经验,建议您在 Google 上查找 JavaScript 数组。

您可以在Chapter 1/ch01_base_map.html中找到源代码。

如何操作…

  1. 如果您遵循给定的步骤,您就可以更改地图的基础地图。

  2. 让我们从创建一个名为base_map.html的新空文件开始。然后,复制在在自定义 DIV 元素中创建简单地图菜谱中介绍的 HTML 文件中的所有代码,并将其粘贴到新文件中。

  3. initMap()函数之后添加以下函数。它将监听在步骤 4 中添加到 HTML 代码中的按钮的点击事件。它简单地根据按钮的 ID 设置基础地图。

    function startButtonEvents () {
        document.getElementById('btnRoad' ).addEventListener('click', function(){
            map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
        });
        document.getElementById('btnSat' ).addEventListener('click', function(){
            map.setMapTypeId(google.maps.MapTypeId.SATELLITE);
        });
        document.getElementById('btnHyb' ).addEventListener('click', function(){
            map.setMapTypeId(google.maps.MapTypeId.HYBRID);
        });
        document.getElementById('btnTer' ).addEventListener('click', function(){
            map.setMapTypeId(google.maps.MapTypeId.TERRAIN);
        });
    }
    
  4. startButtonEvents函数必须在初始化地图时调用,因此添加了以下代码行在地图初始化之后。

    startButtonEvents();
    
  5. 然后,在地图的div元素之前添加以下 HTML 代码行。这些是用于更改基础地图的 HTML 按钮:

    <input id="btnRoad" type="button" value="RoadMap">
    <input id="btnSat" type="button" value="Satellite">
    <input id="btnHyb" type="button" value="Hybrid">
    <input id="btnTer" type="button" value="Terrain">
    
  6. 在您喜欢的浏览器中输入本地服务器的 URL,其中存储着您的base_map.html文件,然后查看结果。您将看到顶部带有按钮的地图。每个按钮都会根据其名称更改基础地图。如何操作…

如前述截图所示,您可以轻松更改 Google 提供的基础地图。

工作原理...

大部分魔法都是由 API 本身完成的;您只需选择要切换到的地图类型。

这些地图类型是预定义的,但您有可能向 API 添加自己的基础地图或样式地图,并切换到它们。在第二章中介绍了添加自己的基础地图或样式地图,添加栅格图层

您也可以在mapOptions对象中定义起始基础地图,如下所示:

   var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        mapTypeId: google.maps.MapTypeId.TERRAIN
   };

在更改地图选项后,您的地图将以TERRAIN基础地图类型打开。

还有更多...

改变基础地图可能看起来是一个简单的话题,但它们背后的数学和技术并不像使用它们那样简单。在 Google Maps JavaScript API 中使用的基础地图和叠加图层是在 Web Mercator 投影系统中处理的。在这个投影系统中,角度被保留,但大型物体的尺寸和形状会发生变化。因此,看起来极地似乎比北美更大,这完全不是事实。这种投影是一种很好的方式,可以在同一张地图上展示整个世界。

请参阅后续章节以获取详细信息,或查看维基百科上的文章en.wikipedia.org/wiki/Mercator_projection

参见

  • 在自定义 DIV 元素中创建简单地图 的配方

第二章。添加栅格层

在本章中,我们将涵盖:

  • 谷歌基础地图的样式

  • 使用不同的瓦片源作为基础地图

  • 向地图添加瓦片叠加

  • 向地图添加图像叠加

  • 更改叠加的透明度

  • 创建热图

  • 添加交通图层

  • 添加公交图层

  • 添加自行车图层

  • 添加天气和云层

  • 添加 Panoramio 图层

简介

本章将涵盖关于使用栅格层的所有内容。食谱集合由在谷歌地图 JavaScript API 中处理栅格层的最常见用例组成。

栅格GIS世界中的主要数据类型之一。谷歌地图 JavaScript API 提供了一套广泛的工具,用于集成外部图像源。此外,API 还允许应用程序开发者使用几乎无限的选择范围更改其基础地图的样式。

本章将向您介绍如何更改基础地图的样式,然后继续介绍如何显示栅格数据,重点关注外部TMS瓦片地图服务),其中栅格层由地图显示中的组织化瓦片组成(例如,OpenStreetMap)。最后,还有许多栅格层(交通、公交、天气、自行车和 Panoramio),可以通过与谷歌地图 JavaScript API 集成在地图上展示。

谷歌基础地图的样式

谷歌基础地图显示了各种细节,如水体(海洋、海域、河流、湖泊等)、道路、公园和建成区(住宅、工业等)。正如您在第一章中观察到的,所有这些都在预定义的制图参数中显示。通过基础地图的样式功能,您在基础地图的制图表示方面几乎有无限的选择。

在您的 Web 或移动应用程序中,拥有多样化的表示(使用不同的色彩方案和不同的强调)非常有益,这样可以保持您的观众更加投入;地图可以完美地融入您的网站设计。

本食谱将指导您通过更改基础地图的样式。

准备工作

我们可以从第一章中的在自定义 DIV 元素中创建简单地图食谱继续,因为我们不需要回顾创建地图的基本知识。

如何操作…

如果您按照给定的步骤操作,本食谱的最终产品将类似于蓝色的谷歌地图:

  1. 创建如下样式数组:

    var bluishStyle = [
        {
            stylers: [
                { hue: "#009999" },
                { saturation: -5 },
                { lightness: -40 }
            ]
         }
        {
            featureType: "road",
            elementType: "geometry",
            stylers: [
                { lightness: 100 },
                { visibility: "simplified" }
            ]
        },
        {
            featureType: "water",
            elementType: "geometry",
            stylers: [
                { hue: "#0000FF" },
                {saturation:-40}
            ]
        },
        {
            featureType: "administrative.neighborhood",
            elementType: "labels.text.stroke",
            stylers: [
                { color: "#E80000" },
                {weight: 1}
            ]
         },
         {
            featureType: "road",
            elementType: "labels.text",
            stylers: [
                { visibility: "off" }
            ]
        },
        {
            featureType: "road.highway",
            elementType: "geometry.fill",
            stylers: [
                { color: "#FF00FF" },
                {weight: 2}
            ]
        }
    ]
    
  2. 将您的style数组添加到initMap()函数中。

  3. initMap()函数中,创建一个styledMapType对象,并使用其名称引用它,与style数组相关联:

    var bluishStyledMap = new google.maps.StyledMapType(bluishStyle,
        {name: "Bluish Google Base Maps with Pink Highways"});
    
  4. 将具有mapTypeIds属性的mapTypeControlOptions对象添加到您的原始mapOptions对象中:

    var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        mapTypeControlOptions: {mapTypeIds: 
            [google.maps.MapTypeId.ROADMAP, 'new_bluish_style']}
    };
    
  5. 将新的mapTypeId属性与您的styledMapType对象相关联:

    map.mapTypes.set('new_bluish_style', bluishStyledMap);
    
  6. 最后,将此新的mapTypeId属性设置为显示:

    map.setMapTypeId('new_bluish_style');
    
  7. 您现在可以观察以下截图中的蓝色风格的 Google 基础地图:如何操作…

它是如何工作的...

首先,让我们看看由一个或多个 google.maps.MapTypeStyle 对象组成的 bluishStyle 数组,这些对象按照以下代码所示排列:

var bluishStyle = [
  {
    featureType: '',
    elementType: '',
    stylers: [
      {hue: ''},
      {saturation: ''},
      {lightness: ''},
            ...
    ]
  },
  {
    featureType: '',
        ...
  }
]

在此数组中,您可以包括针对不同地图特征及其相应元素(如几何形状、标签等)的几种样式(所有这些都在 google.maps.MapTypeStyleElementType 中指定)。

地图特征包括在基础地图中找到的地理表示类型。行政区域、景观特征、兴趣点、道路和水体是地图特征的例子。

除了这些地图特征的通用定义之外,Google Maps JavaScript API 允许您指定这些特征的子类型。例如,您可能希望通过给它们指定 featureType 属性来更改特定 poi 类型的默认样式,如下所示:

featureType: 'poi.school'

或者,您可以对景观地图功能进行更具体的说明:

featureType: 'landscape.man_made'

小贴士

关于 google.maps.MapTypeStyleFeatureType 对象的更多信息

MapTypeStyleFeatureType 对象的完整列表可以在developers.google.com/maps/documentation/javascript/reference#MapTypeStyleFeatureType找到。

请注意,我们的 bluishstyle 数组的第一个元素不包含任何 featureType 属性,这使得样式选项对整个基础地图有效,如下面的代码所示:

{
    stylers: [
        { hue: "#009999" },
        { saturation: -5 },
        { lightness: -40 }
    ]
}

除了 google.maps.MapTypeStyleFeatureType 及其常量之外,您还可以详细说明其地图的每个功能,例如几何形状、几何形状的描边和填充、标签、标签文本(也包括文本填充和描边)以及标签图标。利用这个机会,您可以为与相关图标不同的设置中的道路几何形状进行样式设计。

在我们的配方中,我们已禁用了所有道路标签文本的可见性,没有触及它们的几何形状或标签图标,如下面的代码所示:

{
     featureType: "road",
     elementType: "labels.text",
     stylers: [
        { visibility: "off" }
     ]
}

小贴士

关于 google.maps.MapTypeStyleElementType 对象的更多信息

MapTypeStyleElementType 对象的完整列表可以在developers.google.com/maps/documentation/javascript/reference#MapTypeStyleElementType找到。

对于每个特征类型及其元素类型,您可以指定一个 google.maps.MapTypeStyler 对象,该对象覆盖了 huelightnesssaturationgammainverse_lightnessvisibilityweight 作为数组。在我们的配方中,使高速公路道路以粉色出现的样式选项如下:

{
    featureType: "road.highway",
    elementType: "geometry.fill",
    stylers: [
        { color: "#FF00FF" },
        {weight: 2}
    ]
}

在这里,stylers 数组中的 color 选项是一个粉色调的 RGB Hex 字符串,而 weight 定义了该特征在像素中的权重。

小贴士

关于 google.maps.MapTypeStyler 对象的更多信息

MapTypeStyler 对象的完整规范列表可以在developers.google.com/maps/documentation/javascript/reference#MapTypeStyler找到。

在我们的 initMap() 函数中定义 style 数组后,我们创建了一个 StyledMapType 对象:

var bluishStyledMap = new google.maps.StyledMapType(bluishStyle,
    {name: "Bluish Google Base Maps with Pink Highways"});

此对象接受两个参数——第一个是 style 数组,第二个是 google.maps.StyledMapTypeOptions 对象。在这里,我们只包括了 name 属性;然而,您还可以包括 maxZoomminZoom 属性,在这些属性之间将显示 StyledMapType 对象。在本配方截图中,您可以看到我们分配给 name 属性的值在界面中显示。

在我们创建了 StyledMapType 对象后,我们添加了一个额外的对象,称为 mapTypeControlOptions,它接受 mapOptions 对象中的 mapTypeIds 数组,替换了 mapTypeId 属性:

var mapOptions = {
    center: new google.maps.LatLng(39.9078, 32.8252),
    zoom: 10,
    mapTypeControlOptions: {mapTypeIds: 
        [google.maps.MapTypeId.ROADMAP, 'new_bluish_style']}
};

这使我们能够除了标准的 ROADMAP 地图类型外,添加多种样式。

接下来是步骤,将我们在 mapTypeIds 数组中指定的 mapTypeId ('new_bluish_style') 属性与 StyledMapType 对象(bluishStyledMap)链接:

map.mapTypes.set('new_bluish_style', bluishStyledMap);

在将 mapTypeId 属性与 StyledMapType 对象链接后,我们可以使用以下代码行结束,以便地图界面以我们意图的样式打开基础地图:

map.setMapTypeId('new_bluish_style');

在我们的配方中,我们介绍了如何根据我们的喜好来样式化基础地图。我们使用了 google.maps.MapTypeStyle 对象来选择特征类型(google.maps.MapTypeStyleFeatureType)和相关元素(google.maps.MapTypeStyleElementType),并使用 google.maps.MapTypeStyler 对象来样式化它们。然后,我们将我们的 StyledMapType 对象添加到地图中,展示了我们对谷歌地图基础地图的自定义样式。

还有更多...

使用 StyledMapType 对象只是处理 Google Maps Javascript API 中用户定义的样式化基础地图的多种方法之一。

另一种更简单的用法是在 mapOptions 对象的 styles 属性中指定 style 数组:

var mapOptions = {
    center: new google.maps.LatLng(39.9078, 32.8252),
    zoom: 10,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    styles: bluishStyle
};

另一种选择是在定义我们的 mapOptions 对象后,可以使用以下代码稍后添加 styles 属性:

map.setOptions({styles: bluishStyle });

使用 StyledMapType 对象与 mapOptions 对象的 style 属性之间存在一个重要的区别。使用 StyledMapType 对象使我们能够定义许多(实际上无限)样式作为地图类型。此外,这些地图类型可以在地图界面的地图类型控制中看到,因此用户很容易在地图类型之间切换。

然而,如果样式是通过mapOptions对象的style属性附加到地图上的,用户将无法更改多个样式。实际上,在地图类型控制中,将有一个选项供您选择新的样式,因为样式没有附加到StyledMapType对象上,因此不能被识别为地图类型。

注意

您可以在gmaps-samples-v3.googlecode.com/svn/trunk/styledmaps/wizard/index.html上获取有关如何使用样式地图向导的信息。

准备style数组是一项包含许多制图细节的工作。在样式化器中找到每个特征和元素类型的正确组合将花费太多时间,尤其是在只有通过文本编辑器进行编辑的情况下。谷歌通过创建样式地图向导来简化这项耗时的工作,做得非常出色。它允许您在界面中执行所有样式化任务,因此您可以实时查看您正在更改的内容。完成工作后,您可以导出您的样式作为 JSON,以便在您的应用程序中用作style数组。

使用不同的瓦片源作为基本地图

谷歌基本地图显示大量内容(本地 POI 信息、道路等级、驾驶方向等)和大量的样式调色板。此外,它在其 JavaScript API 中提供了更改基本地图样式的工具。

此外,您还可以在谷歌地图界面中将其他地图瓦片源显示为基本地图。此功能使您能够在谷歌地图界面中显示您的瓦片地图,利用大多数谷歌地图 JavaScript API 的工具。

在这个菜谱中,我们将通过使用 JavaScript API 在谷歌地图界面中显示 OpenStreetMap 瓦片作为基本地图来操作。

准备工作

我们可以继续从第一章的在自定义 DIV 元素中创建简单地图菜谱,因为我们不需要重复介绍将地图显示在屏幕上的基础知识。

如何操作…

通过这个菜谱,您将在完成给定的步骤后看到 OpenStreetMap 瓦片覆盖在谷歌地图瓦片之上:

  1. 在您的initMap()函数中,使用以下代码创建一个ImageMapType对象:

    var osmMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "http://tile.openstreetmap.org/" + zoom + 
            "/" + coord.x + "/" + coord.y + ".png";
        },
        tileSize: new google.maps.Size(256, 256),
        name: "OpenStreetMap",
        maxZoom: 18
    });
    
  2. 将具有mapTypeIds属性的google.maps.mapTypeControlOptions对象添加到您的原始google.maps.MapTypeId对象和ImageMapType对象中:

    var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        {mapTypeIds: [google.maps.MapTypeId.ROADMAP,  
        'OSM']}
    };
    
  3. 将新的mapTypeId数组关联到您的ImageMapType对象:

    map.mapTypes.set('OSM', osmMapType);
    
  4. 最后,设置这个新的google.maps.mapTypeId对象以显示:

    map.setMapTypeId('OSM');
    
  5. 您可以在以下屏幕截图中看到 OpenStreetMap 瓦片覆盖在谷歌基本地图瓦片之上:如何操作…

它是如何工作的...

你可能已经观察到,Google 基础地图样式 配方的代码摘录结构与这个配方几乎没有区别。在前者中,我们使用了 StyledMapType 对象来更改原始基础地图的样式,而在这个配方中,我们使用了 ImageMapType 对象。

这是因为 StyledMapTypeImageMapType 对象都是 MapType 对象(google.maps.MapType)的特殊类型,除了在上一章中介绍的原生基础地图类型,如路线图、卫星图、混合图和地形图。

让我们一步一步来:

var osmMapType = new google.maps.ImageMapType({
    getTileUrl: function(coord, zoom) {
        return "http://tile.openstreetmap.org/" + zoom + "/" + 
        coord.x + "/" + coord.y + ".png";
     },
    tileSize: new google.maps.Size(256, 256),
    name: "OpenStreetMap",
    maxZoom: 18
});

这部分配方创建了一个 osmMapType 对象,它是 ImageMapType 类型。要创建一个 ImageMapType 对象,我们必须提供两个必需的属性:getTileUrltileSize

在填写这两个参数之前,我们必须确保我们有一个可以使用的瓦片地图服务。OpenStreetMap (www.openstreetmap.org/) 是一种地图服务,它是由全球社区的努力建立和存在的。

瓦片地图服务以这种方式组织,即对于每个预定的缩放级别,将地图图像分割成部分(瓦片)。这些瓦片应位于 x 和 y 轴旁边,以便瓦片地图服务消费者(如 Google Maps API)可以识别它们在以下图中所示的位置:

如何工作...

左上角的瓦片坐标是 (0,0),它被称为原点瓦片。在 Google Maps 中,原点位于地图界面的左上角(西北角)。

记住,对于 getTileUrl 属性,我们提供了一个具有两个参数的函数:coordzoom

getTileUrl: function(coord, zoom) {
    return "http://tile.openstreetmap.org/" + zoom + "/" + 
    coord.x + "/" + coord.y + ".png";
}

coord 参数正是前面截图中所介绍的瓦片坐标值所对应的坐标对。换句话说,在左上角,coord.x 应该是 0,而 coord.y 应该是 0。

假设我们处于缩放级别 0,我们可以尝试从为 getTileUrl 属性提供的 Openstreetmap URL 中获取一个瓦片:

"http://tile.openstreetmap.org/" + zoom + "/" + coord.x + "/" + 
coord.y + ".png"

这将给出以下输出:

"http://tile.openstreetmap.org/0/0/0.png"

如果你将此 URL 复制到浏览器的地址栏,你将得到以下截图所示的输出:

如何工作...

这张图像是 Openstreetmap 在缩放级别 0 的单个瓦片。理解到单个 OpenStreetMap 瓦片在缩放级别 0 时覆盖整个世界。

现在,让我们继续缩放级别 1:

tile.openstreetmap.org/1/0/0.png

如何工作...

你可能已经注意到,随着缩放级别从 0 增加到 1,细节级别有所提高。此外,每个瓦片的覆盖区域已经大幅减少(在这个例子中是四分之一)。

你可以在以下截图中看到缩放级别 1 的完整瓦片布局:

如何工作...

瓦片地图服务的一个重要特性是,在每一个缩放级别,前一个缩放级别的每个瓦片都会再次瓦片化,以拥有当前缩放级别所需的细节级别。

返回到我们的 osmMapType 创建,getTileUrl 属性的函数用于放置外部源(在我们的菜谱中是 OpenStreetMap)的瓦片。名为 coordzoom 的两个参数由 Google Maps API 本身处理。API 检测地图的边界框并为每个缩放级别构建瓦片布局。因此,API 识别出在哪个缩放级别应该请求哪个瓦片坐标。剩下要做的就是向您提供外部瓦片地图源瓦片的 URL,这正是您在 getTileUrl 属性中所做的。

第二个属性是 tileSize 属性,它接受一个 google.maps.Size 对象。正如其名称所暗示的,该属性以像素值定义了每个瓦片的宽度和高度。Google Maps 的瓦片布局为 256 像素乘以 256 像素的瓦片;因此我们提供 google.maps.Size(256,256),其中第一个参数代表宽度,第二个参数代表高度。

maxZoom 属性设置了瓦片地图服务的最大显示缩放级别。在此菜谱中,由于 maxZoom 设置为 18,外部基础地图在缩放级别 19 时将不会显示。

name 属性用于设置瓦片地图服务的名称。它直接显示在地图界面右上角的 mapTypeControl 对象中。

最后两行与上一菜谱中的相同;第一行与 mapOptions 属性中 mapTypeControlOptions 指定的 mapTypeID 对象设置为 OSMImageMapType 对象的 osmMapType 相关:

map.mapTypes.set('OSM', osmMapType);
map.setMapTypeId('OSM');

参见

在接下来的章节中,将涵盖与投影细节相结合的瓦片坐标、像素坐标和世界坐标的详细解释。

此外,下一菜谱将涵盖将瓦片地图服务作为基础地图的叠加层使用。

向地图添加瓦片叠加

Google Maps 提供了一系列基础地图,包括街道地图和卫星图像,我们已在上一章中讨论过;现在我们将讨论如何将额外的基础地图引入 Google Maps 界面。

我们还可以将瓦片地图服务用作基础地图的叠加层。通过叠加,你可以想象在基础地图上放置一张单独的地图瓦片。你可以观察叠加层的细节以及基础地图。叠加层的例子可能包括感兴趣区域的边界、在 Google Maps 基础地图中找不到的特殊 POI、以空中或点样式展示的统计结果等等。

作为基础地图使用的瓦片地图服务在技术上可以用作叠加层在 Google Maps JavaScript API 中。然而,使用这些瓦片地图服务(如 OpenStreetMaps)作为叠加层会导致阻塞 Google Maps 的原有基础地图,因为叠加的瓦片地图服务(原本旨在作为基础地图)的地图上不会有空白空间。这是因为 Google Maps 的基础地图和叠加的瓦片地图服务都被设计成基础地图。因此,不建议使用旨在作为基础地图的另一个瓦片地图服务作为 Google Maps 基础地图上的叠加层。

在这个菜谱中,我们将介绍如何在 Google Maps 界面中使用 JavaScript API 将 OpenStreetMap 瓦片显示为叠加层。

准备工作

我们可以使用前一个菜谱的代码,并对其进行一些修改,以便消除重写osmMapType对象细节的需要。

如何做到这一点...

在这个菜谱中,如果您按照给定的步骤操作,您将看到 OpenStreetMap 瓦片作为叠加层:

  1. 在您的initMap()函数中,将osmMapType对象保持原样:

    var osmMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "http://tile.openstreetmap.org/" + 
            zoom + "/" + coord.x + "/" + coord.y + ".png";
        },
        tileSize: new google.maps.Size(256, 256),
        name: "OpenStreetMap",
        maxZoom: 18
    });
    
  2. 在您的mapOptions对象中更改具有mapTypeIdsgoogle.maps.MapTypeId.ROADMAPgoogle.maps.MapTypeId.SATELLITEgoogle.maps.mapTypeControlOptions对象:

    var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        mapTypeControlOptions:
            {mapTypeIds: [google.maps.MapTypeId.ROADMAP, 
            google.maps.MapTypeId.SATELLITE]}
    };
    
  3. 删除以下代码行(因为在上一个步骤中没有指定其他基础地图):

    map.mapTypes.set('OSM', osmMapType);
    
  4. ROADMAP地图类型设置为要显示的基础地图:

    map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
    
  5. 在基础地图上叠加osmMapType地图类型:

    map.overlayMapTypes.insertAt(0, osmMapType);
    

    如何做到这一点…

  6. 您现在已经有了 OpenStreetMap 瓦片作为叠加层,如图中所示的前一个屏幕截图。

它是如何工作的...

如您可能已经注意到的,前一个菜谱和这个菜谱之间几乎没有区别。我们使用了相同的自定义osmMapType对象,即google.maps.imageMapType。我们使用了另一个瓦片地图服务,但结构保持不变。

修改从mapOptions对象的mapTypeControlOptions属性中使用的mapTypes对象开始:

mapTypeControlOptions:
{mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.SATELLITE]}

我们已经包括了ROADMAPSATELLITE地图类型,以便作为基础地图进行选择。

第 3 步很重要;我们删除以下行:

map.mapTypes.set('OSM', osmMapType);

我们正在删除它,因为我们不希望osmMapType对象被视为基础地图。我们只将其用作叠加。

在下一个步骤中,我们正在选择默认的基础地图为ROADMAP。您可以更改SATELLITE的代码行,或者您可以从地图界面中的mapTypeControl对象进行切换。

最后一行是我们叠加操作发生的行:

map.overlayMapTypes.insertAt(0, osmMapType);

在这里,overlayMapTypes属性是一个数组(google.maps.MVCArray)。MVCArrayinsertAt方法在指定的索引处插入对象,并且我们在索引 0 处插入了我们的imageMapType对象。

小贴士

关于 google.maps.MVCArray 的更多内容

google.maps.MVCArray 数组是 Google 对普通 JavaScript 数组的实现。您可以从一个数组中构建一个 MVC 数组。更多详细信息可以在developers.google.com/maps/documentation/javascript/reference#MVCArray找到。

您可以在 Google 地图基础地图上拥有多个叠加层。您必须使用 overlayMapTypes 属性通过 insertAt 方法的第一个参数设置叠加地图的关联顺序,如下所示:

map.overlayMapTypes.insertAt(1, anotherMapType1);
map.overlayMapTypes.insertAt(2, anotherMapType2);

还有更多...

叠加层放置在基础地图之上,打开和关闭它们以查看基础地图是一个好习惯。如果您需要关闭叠加层,需要包含以下代码:

map.overlayMapTypes.setAt(0, null);

这使得叠加层从地图界面消失,但请记住,overlayMapTypes 数组中的槽位已经被 insertAt 方法分配。因此,如果您想给用户提供切换叠加层打开和关闭的机会(通过复选框等方式),您可以遵循以下步骤(在继续之前复制您当前菜谱的代码):

  1. 在您的 HTML 代码的 <body> 标签中添加一个复选框:

    <input type="checkbox" id="OSM" class="overlayMaps" onclick="toggleOverlayMaps()" />
    <label 
        for="OSM">OpenStreetMap Layer</label>
    
  2. initMap() 函数外部将 osmMaptype 设为全局变量:

       var osmMapType;
    
  3. initMap() 函数中的 osmMapType 声明更改为分配新的全局变量:

    osmMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "http://tile.openstreetmap.org/" + zoom + 
            "/" + coord.x + "/" + coord.y + ".png";
        },
        tileSize: new google.maps.Size(256, 256),
        name: "OpenStreetMap",
        maxZoom: 18
    });
    
  4. insertAt 方法替换为 pull(null) 方法:

    map.overlayMapTypes.push(null);
    
  5. 添加叠加层切换功能:

    function toggleOverlayMaps() {
        var OSMLayer = document.getElementById("OSM");
        if (OSMLayer.checked)
        {
            map.overlayMapTypes.setAt(0, osmMapType);
        }
        else
        {
            map.overlayMapTypes.setAt(0, null);
        }
    }
    
  6. 上述代码片段的主要技巧是首先在 initMap() 函数的 overlayMapTypes 数组中打开一个空间。之后,您可以调用 setAt() 方法来打开或关闭叠加层。

向地图添加图像叠加

叠加瓦片地图服务是一个强大的功能。它通过 Google Maps API 使各种瓦片地图服务进入场景。现有的瓦片地图服务通常是全球地图服务,这意味着它们覆盖整个世界或至少某些大陆/国家。

例如,我们可能对叠加一个大学校园的地图感兴趣,手头有该校园的大致规划。或者,我们可能找到了一些历史地图,并希望利用它。或者,我们可能有一个特定建筑的内部建筑规划,并希望在 Google 地图上查看这个建筑规划。

是否可以在 Google 地图上叠加这些微尺度图像?当然可以!实际上,在技术上,使用校园规划或建筑规划而不是瓦片地图服务作为叠加层,两者之间没有区别。需要注意的是,这些规划图应该与基础地图上的瓦片地图服务对齐。

在这个菜谱中,我们不会详细介绍准备瓦片的过程,而是通过 Google Maps JavaScript API 来使用它们。为了方便,我们将使用 2010 年 Google I/O 在旧金山 Moscone 中心的场地规划。

注意

有工具可以准备图像瓦片,这些瓦片可以用作叠加层。最突出的是 MapTiler (www.maptiler.org) 和 GDAL2Tiles (www.klokan.cz/projects/gdal2tiles/)。使用这些工具,您可以为您选择的缩放级别对图像进行地理参照、正射和瓦片化。

准备工作

我们可以使用 使用不同的瓦片源作为基础地图 菜谱中的代码,因为需要的修改很少。

如何做到这一点…

如果您遵循以下步骤,您将在 Google Maps 基础地图上有一个叠加层——一座建筑:

  1. 插入一个 bounds 对象:

    var bounds = {
        17: [[20969, 20970], [50657, 50658]],
        18: [[41939, 41940], [101315, 101317]],
        19: [[83878, 83881], [202631, 202634]],
        20: [[167757, 167763], [405263, 405269]]
    };
    
  2. osmMapType 对象替换为 buildPlanMapType 对象:

    var buildPlanMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            if (zoom < 17 || zoom > 20 ||
                bounds[zoom][0][0] > coord.x ||
                coord.x > bounds[zoom][0][1] ||
                bounds[zoom][1][0] > coord.y ||
                coord.y > bounds[zoom][1][1]) {
                    return null;
            }
            return ['http://www.gstatic.com/io2010maps/tiles/5/L2_', zoom, '_', coord.x, '_', coord.y, '.png'].join('');
        },
        tileSize: new google.maps.Size(256, 256),
        name: "Google IO Building Plan",
        maxZoom: 20
    });
    
  3. 将最后一行更改为:

    map.overlayMapTypes.insertAt(0, buildPlanMapType);
    
  4. 将地图中心定位在 Moscone Center:

    map.setCenter(new google.maps.LatLng(37.78320, -122.40421));
    

    如何做到这一点…

您可以在先前的屏幕截图中看到,在 Google Maps 基础地图上叠加了作为图像提供的建筑平面图,该图像按瓦片组织。

如何工作…

您可能已经注意到,主要结构与先前的菜谱相同。首先,您定义一个 google.maps.ImageMapType 对象,然后使用 map 对象的 overlayMapTypes.insertAt 数组将其叠加在基础地图上。

在这个菜谱中引入的唯一更改是由于示例建筑计划的边界(边界框)。在先前的菜谱中,叠加瓦片地图服务的边界是全球的,而在这个菜谱中,我们将其限制在建筑上,相当于在一个更高的比例尺上。

因此,我们包括了定义每个缩放级别边界的 bounds 对象。我们将缩放级别限制在 17 到 20 之间,因为较低的缩放级别(<17)不会以合理的方式显示建筑。在缩放级别 16 和 15 中,建筑将只是一个小的矩形,并且它不会在低于 14 的缩放级别中显示。

bounds 对象的每个缩放级别属性中,我们都有一个 x 和 y 瓦片坐标的数组,x 是第一个,y 是第二个。在这些数组内部,可以找到瓦片坐标的下限和上限。

在这一点上,您可能想知道这些特定数字是如何找到的:

var bounds = {
var bounds = {
    17: [[20969, 20970], [50657, 50658]],
     ...
}

这些数字实际上是与以下截图所示的 Moscone Center 边界相交的瓦片坐标:

如何工作...

您可以观察到在第 17 个缩放级别中,x 坐标必须在 20969 和 20970 之间,y 坐标必须在 50657 和 50658 之间,以实现我们的图像叠加。

bounds 对象用于在 buildPlanMapType 对象的 getTileUrl 函数定义中创建限制特定缩放级别的约束,以便获取瓦片。getTileUrl 属性的函数会检查每个瓦片坐标与 bounds 对象的项目,这样 API 就不会尝试获取与建筑计划边界不交叉的瓦片。

改变叠加层的透明度

Google Maps JavaScript API 支持第三方瓦片地图服务或图像叠加在基础地图之上。然而,覆盖层存在一个问题;它们叠加在基础地图之上,使其变得不可见。当然,你可以根据你的选择打开或关闭它们;然而,如果你想要同时看到基础地图和覆盖层,这并不是一个解决方案。

实际上,你只需修改覆盖层的透明度,就可以看到基础地图和覆盖层。

本食谱专注于更改最后两个食谱中引入的覆盖层的透明度。

准备工作

我们可以使用本章“向地图添加瓦片覆盖”食谱中引入的代码,稍作修改以实现所需的结果。请务必首先复制食谱中的代码。

如何操作…

完成以下代码中展示的单步操作后,你将能够使你的覆盖层变得透明:

  1. 只需更改osmMapType对象:

    var osmMapType = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "http://tile.openstreetmap.org/" + zoom + 
            "/" +coord.x + "/" + coord.y + ".png";},
        tileSize: new google.maps.Size(256, 256),
        name: "OpenStreetMap",
        maxZoom: 18,
        opacity:0.6
    });
    
  2. 你可以调整你的覆盖层透明度,如以下截图所示:如何操作…

它是如何工作的...

改变覆盖层的透明度非常简单。将以下代码添加到imageMapType对象中即可完成任务:

opacity:0.6

imageMapType对象的透明度属性根据提供的值使传入的瓦片透明。透明度属性的值必须在 1 和 0 之间,其中 1.0 表示完全不透明的覆盖层,而 0 表示完全不透明的覆盖层。

创建热力图

Google Maps API 为包括热力图在内的基于地图的分析奠定了基础。热力图是一种通过渐变颜色刻度显示点浓度的可视化形式。

在这方面,点可能是任何地理上表示的项目(如医院、房屋、学校)、带有坐标的海污染测量值、垃圾收集箱的位置等等的表示。这个列表实际上几乎是无限的。

热力图是地理统计学非常重要的输入。在地图显示中,你捕捉到的是某一时刻的强度,与仅显示点位置的情况相比:

创建热力图

上述截图展示了截至 7 月的土耳其伊斯坦布尔的 OpenStreetMap 点层位置。你可能只是从上述截图中得到关于点浓度的一个大致概念,但以下截图显示了更清晰的情况。最密集的地点用红色标出,而浓度较低的地点用黄色标出。而且,正如你可能已经猜到的,绿色标出的地点是浓度最低的。

创建热力图

在这个食谱中,我们将使用 Google Maps JavaScript API 从一组点创建热力图。值得注意的是,这个功能在标准的 Google Maps API 中不可用;你需要将可视化库加载到 API 中,如本食谱所示。

准备工作

我们可以使用第一章的第一部分,Google Maps JavaScript API 基础,作为基础,因为我们不需要重复编写地图显示的代码。请记住,在编辑此食谱之前,从原始食谱中复制代码。

如何操作…

如果你遵循给定的步骤,你将使用你使用的点集创建热力图覆盖层:

  1. 在引用 Google Maps API 的地方引用visualization库:

        <script type="text/javascript"
            src="img/js? sensor=false&libraries=visualization">
        </script>
    
  2. 在包含我们的 HTML 文件的同一目录下打开一个新的文本文件,并将其命名为ch2_heatMapPoints.js

  3. 在上一步骤中创建的新的 JavaScript 文件中创建一个google.maps.LatLng对象的数组(完整的数组由 217 个对象组成,你可以从下载的代码中获得):

    var heatmapPoints = [
    new google.maps.LatLng(41.0182827999113,28.973224999734),
    new google.maps.LatLng(41.0150707003526,28.9764445996386),
    new google.maps.LatLng(41.01140130003,28.9831846001892),
    new google.maps.LatLng(41.0148609002104,28.9764469999292),
    new google.maps.LatLng(41.0149687001455,28.9764550002981),
    new google.maps.LatLng(41.0148247996249,28.9757389996552),
    new google.maps.LatLng(41.0020956002318,28.9736237995987),
    ];
    
  4. 在你的 HTML 代码中引用ch2_heatMapPoints.js文件:

        <script type="text/javascript" src="img/ch2_heatMapPoints.js"></script>
    
  5. 创建热力图层:

        var heatmap = new google.maps.visualization.HeatmapLayer({
            data: heatmapPoints
        });
    
  6. 将热力图层添加到地图:

        heatmap.setMap(map);
    
  7. 现在你应该已经从你的点集中创建出了热力图覆盖层,如下面的截图所示:如何操作…

它是如何工作的...

首先,在 Google Maps JavaScript API 中创建热力图层需要将 API 的visualization库添加到引用 Google Maps JavaScript API 的部分:

    &libraries=visualization

通过这个添加,你可以使用google.maps.visualization.HeatmapLayer对象来创建热力图层。

google.maps.visualization.HeatmapLayer对象需要一个 JavaScript 数组或google.maps.MVCArray对象(一个数组),其中其元素是google.maps.LatLng对象:

    var heatmap = new google.maps.visualization.HeatmapLayer({
        data: heatmapPoints
    });

使用经纬度坐标对构造google.maps.LatLng对象,以提及一个点:

    new google.maps.LatLng(41.0182827999113,28.973224999734)

我们在另一个 JavaScript 文件中创建了包含 217 个点的数组对象及其内容,因为它们在我们的原始 HTML 文件中会占用太多空间。此外,将我们的数据和相关的对象放在另一个文件中是一种良好的实践,以避免潜在的架构问题。

最后,我们可以使用以下代码将我们的热力图层添加到当前地图中:

    heatmap.setMap(map);

正如你可以切换之前食谱中引入的覆盖层一样,你也可以使用以下方法切换热力图层:

    heatmap.setMap(null);

更多内容...

热力图是由一组点创建的,这些点可以位于不同的地方,也可以位于相同的地方。换句话说,多个google.maps.LatLng对象可以放置在同一个地方。以下代码是一个示例:

    new google.maps.LatLng(41.0182827999113,28.973224999734),
    new google.maps.LatLng(41.0182827999113,28.973224999734),
    new google.maps.LatLng(41.0182827999113,28.973224999734)

这将增加这个点的强度;它很可能会在热力图中以红色显示。

但如果共享相同坐标对的 google.maps.LatLng 对象实例增加呢?一种方法是复制前面代码中显示的行。然而,还有一种更智能的方法:

    {location: new  
        google.maps.LatLng41.0182827999113,28.973224999734), 
        weight: 3
    },

此对象是 google.maps.visualization.WeightedLocation,它接受两个属性:一个是 google.maps.LatLng 对象,另一个是 weight。此 weight 参数接受任何表示点发生次数的数值。

默认情况下,LatLng 对象本身具有权重 1。因此,具有 weight 属性设置为 3WeightedLocation 对象等同于具有相同的 LatLng 对象。

可以在为热力图层的 data 属性提供的数组中一起使用 WeightedLocationLatLng 对象。

热力图层对象具有包括 gradientradiusopacitymaxIntensitydissipating 在内的多种选项。

gradient 选项接受一个颜色数组:

    var gradientScheme = [
        'rgba(0, 0, 255, 0)',
        'rgba(0, 60, 200, 1)',
        'rgba(0, 120, 120, 1)',
        'rgba(125, 125, 125, 0)',
        'rgba(125, 120, 60, 0)',
        'rgba(200, 60, 0, 0)',
        'rgba(255, 0, 0, 1)'
    ];

你可以通过向 HeatmapLayer 构造函数添加属性来设置渐变属性:

    var heatmap = new google.maps.visualization.HeatmapLayer({
        data: heatmapPoints,
        gradient: gradientScheme
    });

或者,你可以稍后设置选项:

    heatmap.setOptions({
        gradient: gradientScheme
    });

dissipating 选项用于调整显示不同缩放级别强度所需的像素数。其默认值为 false,这允许在缩放级别增加时每个点有更多的像素用于强度着色。

通过使用 maxIntensity 属性,你可以滚动热力图层:

    heatmap.setOptions({
        maxIntensity: 2
    });

之前的代码使本食谱的输出呈绿色,因为强度增加了。在相同位置需要更多的点来使热力图看起来呈红色。

你可以调整 radius 属性来调整每个点的强度半径。单位是像素。

参见

你可以使用云端的 Fusion Tables 创建热力图,这将在本书的后续部分介绍。在浏览器或云端创建热力图各有优缺点;这将在本书的后续部分详细讨论。

关于 LatLng 对象及其用于创建点矢量覆盖的使用将在下一章中介绍。

添加交通层

在当今世界,交通状况是城市中非常重要的信息。如果路上发生事故或近期有施工阻碍了重要街道,这会影响你一整天。在地图上直接获取实时交通信息会有多有用?

Google Maps JavaScript API 具有一个非常实用的功能,允许你在基础地图上实时获取交通信息。

在本食谱中,我们将介绍如何在 Google Maps 上显示交通信息。

准备工作

我们可以使用 第一章 的第一个食谱,Google Maps JavaScript API 基础,作为基本地图显示的基础。在复制原始食谱的代码后,你可以继续前进。

如何实现...

下面是显示交通层的步骤:

  1. 首先,将地图中心更改为提供交通层的服务位置(详细信息将在如何工作...部分中提供)。例如,对于西班牙巴塞罗那,使用以下值:

        center: new google.maps.LatLng(41.3854, 2.1822),
    
  2. 构建一个TrafficLayer对象:

        var trafficLayer = new google.maps.TrafficLayer();
    
  3. TrafficLayer对象添加到地图中:

        trafficLayer.setMap(map);
    
  4. 您可以在以下屏幕截图中看到根据实时交通状况密度着色的交通层:如何操作…

它是如何工作的...

在第一步中,您可能已经注意到我们切换到了西班牙巴塞罗那,因为在一些国家,交通层不可用。要查看具有交通层可用性的国家列表,您必须查看gmaps-samples.googlecode.com/svn/trunk/mapcoverage_filtered.html上的电子表格。您可以通过筛选交通列来查看所有具有交通层的国家。

TrafficLayer对象的构建及其添加到地图中非常直接。在构建TrafficLayer对象的过程中不涉及任何属性。

您可以使用以下代码关闭TrafficLayer对象:

        trafficLayer.setMap(null);

添加交通层

公共交通线路在城市中具有极其重要的地位,尤其是对于城市中的游客和外国人。将这些交通线路(公共汽车、地铁等)映射到几个城市的基图中是一项繁琐的任务,这正是 Google Maps JavaScript API 通过其特殊对象提供的。

在本菜谱中,我们将向 Google Maps 地图界面添加交通层。

准备工作

从上一个菜谱“添加交通层”继续,这将简化我们的工作,因为我们只需将TrafficLayer对象替换为TransitLayer类。请记住复制原始菜谱中的代码。

如何操作…

下面是显示公共交通线路作为叠加层的步骤:

  1. 删除与TrafficLayer相关的行(最后两行)。

  2. 不要使用TrafficLayer对象,而是使用TransitLayer对象:

        var transitLayer = new google.maps.TransitLayer();
    
  3. TransitLayer对象添加到地图中:

        transitLayer.setMap(map);
    
  4. 您可以在您偏好的区域看到 Google Maps 上的公共交通线路叠加,如下面的屏幕截图所示:如何操作…

它是如何工作的...

交通层在世界某些城市的某些地区提供,您可以在www.google.com/intl/en/landing/transit/上找到这些城市的完整列表。

TransitLayer对象可以带来多种颜色的公共交通线路。颜色选择并非随机;它们是根据不同的交通线路运营商选择的。

TransitLayer对象的构建和显示与TrafficLayer对象相同。

添加自行车层

在基础地图上显示自行车路径和常用路线的信息是很有用的;Google Maps 提供此叠加层。

在本配方中,我们将介绍自行车图层及其在 Google Maps JavaScript API 中作为覆盖层的用法。

准备工作

从之前的配方添加交通图层继续,将简化我们的工作,因为我们只需将TransitLayer对象替换为BicyclingLayer类。请记住复制原始配方的代码。

如何操作…

显示自行车路径和路线所需的步骤如下所示:

  1. 将包含TransitLayer的两行代码替换为包含BicyclingLayer对象的代码:

        var bicyclingLayer = new google.maps.BicyclingLayer ();
        bicyclingLayer.setMap(map);
    
  2. 您可以在以下屏幕截图中观察 Google Maps 基础地图上的自行车路径和路线:如何操作…

它是如何工作的…

自行车图层在其样式中有自己的细分;深绿色路线代表专门用于骑行的路径,而浅绿色路线代表有自行车道的街道。最后,虚线路线代表推荐使用但不是专门为自行车设计的路径和街道。

显示自行车图层的步骤与交通或交通图层完全相同,因此没有关于自行车图层具体细节的额外说明。

添加天气和云图层

温度和天气状况的信息与地图显示结合在一起,在电视上的天气预报中非常常见;Google 在其 API 中添加了一个功能,这样我们就可以在我们的地图中拥有这些信息。

在本配方中,我们将学习如何在基础地图上作为覆盖层显示与天气相关的信息。

准备工作

我们可以继续从第一章中的在自定义 DIV 元素中创建简单地图配方开始,因为对于基本地图显示我们不需要深入了解。

如何操作…

下面是显示您地图中相应温度和云状况的步骤:

  1. 将天气库添加到 Google Maps JavaScript API 引用的末尾:

        <script type="text/javascript"
         src="img/js? sensor=false&libraries=weather">
        </script>
    
  2. mapOptions对象中更改地图的中心和缩放,以便我们可以使用相关的图层:

        center: new google.maps.LatLng(38.0, 20.4),
        zoom: 5,
    
  3. 构造一个名为weatherLayergoogle.maps.weatherLayer对象实例:

        var weatherLayer = new google.maps.weather.WeatherLayer
        ({
           temperatureUnits: 
           google.maps.weather.TemperatureUnit.CELCIUS
        });
    
  4. weatherLayer添加到地图中:

        weatherLayer.setMap(map);
    
  5. 构造一个名为cloudLayergoogle.maps.weather.cloudLayer实例:

        var cloudLayer = new google.maps.weather.CloudLayer();
    
  6. cloudLayer添加到地图中:

        cloudLayer.setMap(map);
    
  7. 您可以在以下屏幕截图中看到您的 Google Maps 应用程序中的相应温度和云状况:如何操作…

它是如何工作的…

为了看到天气特定的图层,我们必须以与引用热力图覆盖的visualization库相同的方式精确引用weather库。

我们更改了地图的中心和缩放是为了某个目的。缩放设置为 5,因为云层仅在 0 到 6 的缩放级别之间可见。此外,我们将地图中心定位在地中海,以便查看几个大城市的天气信息。

重要的是要注意,与天气层一起,街道和城市名称等行政标签不会显示。此外,天气层适用于 0 到 12 的缩放级别之间。

在构建google.maps.weather.weatherLayer对象时,你可以通过temperatureUnits属性指定温度单位。可能的值在google.maps.weather.TemperatureUnit中定义:

google.maps.weather.TemperatureUnit.CELCIUS
google.maps.weather.TemperatureUnit.FAHRENHEIT

你可以通过调用各自的setMap()方法并将map对象作为唯一参数来将weatherLayercloudLayer层添加到地图中。

显示天气层,你可以通过www.weather.com提供的温度信息查看城市的天气状况。显示在城市的图标将根据实时天气变化,无论是太阳、云还是雨。此外,点击图标将打开一个详细弹出窗口,显示未来四天的天气状况。

还有更多...

你可以为weatherLayer对象调整除了temperatureUnits以外的其他属性。你可以抑制详细弹出窗口,或者你可以设置风速的单位等等。

小贴士

关于 WeatherLayerOptions 的更多信息

WeatherLayerOptions的完整列表可以在developers.google.com/maps/documentation/javascript/reference#WeatherLayerOptions找到。

添加 Panoramio 层

Panoramio 是一个带有地理标签的图片分享网站。这意味着只要你对这些图片进行地理标签(地理参考),你就可以上传它们。地理标签涉及将一对坐标附加到目标对象上,无论它是照片、视频还是任何其他资源。你可以在www.panoramio.com/找到有关如何使用 Panoramio 的详细信息。

你可以在 Google Maps 上查看 Panoramio 照片,本食谱将涵盖如何做到这一点的基础知识。

准备工作

我们可以从第一章的“在自定义 DIV 元素中创建简单地图”食谱继续,因为地图显示的基本知识已经在这里介绍。

和往常一样,请在继续之前复制原始食谱。

如何操作...

如果你遵循所提供的步骤,你可以在 Google Maps 上叠加各种 Panoramio 图像库存:

  1. 将 Panoramio 库添加到引用库中:

        <script type="text/javascript"
         src="https://maps.googleapis.com/maps/api/js? 
         sensor=false&libraries=panoramio">
        </script>
    
  2. 在创建地图对象后,创建一个名为panoramioLayer的新google.maps.panoramio.PanoramioLayer()对象:

        var panoramioLayer = new google.maps.panoramio.PanoramioLayer();
    
  3. panoramioLayer对象添加到地图中:

        panoramioLayer.setMap(map);
    
  4. 现在您可以将 Panoramio 图像叠加到 Google Maps 上,如下面的截图所示:如何操作…

它是如何工作的...

在技术上,添加 Panoramio 层与我们之前所做的没有区别。我们添加相关的参考库,构建层,并以相同的方式将其添加到地图中。

您可以点击缩略图照片,将打开一个详细弹出窗口。在这个弹出窗口中,您可以查看照片的更大尺寸,以及上传照片的人的用户名。

还有更多...

Panoramio 层提供了额外的能力来自定义叠加层。例如,您可以使用 setTag() 方法通过过滤标签来限制照片:

    panoramioLayer.setTag("Eiffel");

这将过滤显示的缩略图,只显示标签中包含 Eiffel 关键词的照片。

此外,您还可以通过调用 setUserId() 方法并根据用户 ID 进行过滤,将 userId 字符串作为参数提供。

您还可以通过使用 PanoramioLayer 对象的 suppressInfoWindows 属性来抑制打开的详细弹出窗口:

    var panoramioLayer = new google.maps.panoramio.PanoramioLayer({
        suppressInfoWindows:true
    });

小贴士

关于 PanoramioLayer 的更多信息

PanoramioLayer 的完整列表可以在 developers.google.com/maps/documentation/javascript/reference#PanoramioLayerOptions 找到。

第三章。添加矢量图层

在本章中,我们将涵盖:

  • 向地图添加标记

  • 向标记或地图添加弹出窗口

  • 向地图添加线条

  • 向地图添加多边形

  • 向地图添加圆形/矩形

  • 向地图添加动画线条

  • 添加 KML/GeoRSS 图层

  • 将 GeoJSON 添加到 Google Maps JavaScript API

  • 将 WKT 添加到 Google Maps JavaScript API

简介

本章是关于矢量图层,它与栅格图层完全不同。本章为你提供了在使用 Google Maps JavaScript API 时可能需要的最常见和最重要的食谱。

在 GIS 世界中,矢量和栅格图层在不同情况下都会被使用。矢量通常用于表示地球的特征。例如,兴趣点POI),如咖啡店或餐厅,以点表示;河流或道路以折线表示;公园或建筑物以多边形表示。正如你所看到的,这里有三种不同的矢量类型:点、折线和多边形。请记住,所有矢量都由点组成,点是矢量的基本构建块。

在 Google Maps JavaScript API 中,所有类型的矢量都称为 覆盖层。除了矢量之外,弹出窗口和符号也包含在覆盖层中。所有与它们相关的食谱都包含在本章中。

地图主要用于可视化,因此在某些情况下静态地图是不够的。一些添加到折线上的动画可以产生差异。例如,用河流显示流向对科学家来说是非常显著的。Google Maps JavaScript API 也支持动画折线,这是本章中的一个食谱。

Google Maps JavaScript API 是一个功能强大的 API,支持 KML 和 GeoRSS,但一些行业事实标准不支持默认设置,例如 GeoJSON 和 WKT。GeoJSON 和 WKT 是行业中最常用的矢量发布格式,尤其是在开源库中。这些格式将由额外的库支持,这些库也包含在本章中。

让我们开始探索这些食谱。

向地图添加标记

地图在网站中有许多用途,但最常用的用途是显示公司或企业的位置。在 LBS 或 GIS 领域,公司或企业的位置可以称为 POI,这是矢量图层的一种点类型。在 Google Maps JavaScript API 中,POI 或点以 标记 的形式显示。

本食谱展示了如何使用 google.maps.LatLnggoogle.maps.Marker 类将标记添加到地图中。

准备工作

在第一章《Google Maps JavaScript API 基础》中,你学习了如何创建地图。因此,本食谱只涵盖将标记添加到地图所需的附加代码行。

你可以在 Chapter 3/ch03_adding_markers.html 找到源代码。

如何做到这一点...

以下是我们需要添加标准和图标标记到地图的步骤:

  1. 让我们添加边界框BBOX)的纬度和经度的最小和最大值,以限制随机标记的区域。这个边界框几乎定义了土耳其覆盖的区域。同时定义了markerId来命名随机标记。所有变量都必须在函数外部定义:

    var minLat = 36,
        maxLat = 42,
        minLng = 25,
        maxLng = 44,
        markerId = 1;
    
  2. initMap()函数之后添加以下函数。这个函数开始监听按钮的点击事件:

    function startButtonEvents() {document.getElementById('addStandardMarker').addEventListener('click', function(){
          addStandardMarker();
      });
    
      document.getElementById('addIconMarker' ).addEventListener('click', function(){
          addIconMarker();
      });
    }
    
  3. startButtonEvents()函数之后添加以下函数。这个函数根据本节开头给出的值创建一个随机纬度和经度,并返回google.maps.LatLng对象。

    function createRandomLatLng() {
      var deltaLat = maxLat - minLat;
      var deltaLng = maxLng - minLng;
      var rndNumLat = Math.random();
      var newLat = minLat + rndNumLat * deltaLat;
      var rndNumLng = Math.random();
      var newLng = minLng + rndNumLng * deltaLng;
      return new google.maps.LatLng(newLat, newLng);
    }
    
  4. 然后,添加了addStandardMarker()函数。这个函数创建了一个标准的谷歌地图红色标记。它从前面步骤中创建的函数中获取随机的LatLng对象值。代码块中有一行注释,稍后会进行解释:

    function addStandardMarker() {
      var coordinate = createRandomLatLng();
      var marker = new google.maps.Marker({
        position: coordinate,
        map: map,
        title: 'Random Marker - ' + markerId
      });
    // If you don't specify a Map during the initialization //of the Marker you can add it later using the line //below
    //marker.setMap(map);
      markerId++;
    }
    
  5. 在这个步骤中,还有一个名为addIconMarker()的另一个函数进行了描述。这个函数用于添加带有随机图像的随机标记:

    function addIconMarker() {
      var markerIcons = ['coffee', 'restaurant_fish', 'walkingtour', 'postal', 'airport'];
      var rndMarkerId = Math.floor(Math.random() * markerIcons.length);
      var coordinate = createRandomLatLng();
      var marker = new google.maps.Marker({
        position: coordinate,
        map: map,
        icon: 'img/' + markerIcons[rndMarkerId] + '.png',
        title: 'Random Marker - ' + markerId
      });
      markerId++;
    }
    
  6. 最后,我们将添加 HTML 标签来完成代码。这些链接将帮助触发事件监听器中定义的函数:

    <a id="addStandardMarker" href="#">Add Standard Marker</a>
    <a id="addIconMarker" href="#">Add Icon Marker</a>
    
  7. 在您最喜欢的浏览器中转到您本地存储 HTML 文件的 URL,查看结果。最初,您将看到一个空地图。然后点击地图上的链接以添加随机标记。最终地图可以参考以下截图:如何操作…

如前一个截图所示,我们创建了一个包含标准和图标标记的地图。

它是如何工作的...

谷歌地图使用 Web Mercator 投影系统为其瓦片系统,但坐标仍然是基于 WGS 84 基准的 GPS 坐标。坐标基于介于-90 到 90 和-180 到 180 度之间的纬度和经度。纬度和经度的组合定义了地球上的一个点。谷歌地图 JavaScript API 使用google.maps.LatLng类来创建一个点。这个类也在第一章,谷歌地图 JavaScript API 基础中用于设置地图的中心。以下行定义了伊斯坦布尔土耳其的坐标:

var istanbul = new google.maps.LatLng(41.038627, 28.986933);

google.maps.Marker 类使用一个必需的参数 google.maps.MarkerOptions 创建标记。MarkerOptions 类还有一个名为 position 的必需参数。此参数获取 google.maps.LatLng 对象以定义标记的坐标。在代码中,还有 maptitle 参数,它们不是必需的,但它们是显示标记在地图上和设置标记标题所必需的。如果你想立即在创建标记后显示标记,你应该使用 map 参数。但在某些情况下,你可能想创建标记并在稍后将其显示在地图上。在这种情况下,你应该使用 markersetMap 方法与你的地图引用。

var marker = new google.maps.Marker({
  position: new google.maps.LatLng(41.038627, 28.986933)
});
marker.setMap(map);

如果你想要从地图中删除标记,你必须将地图值设置为 null。不要忘记保留你的标记引用,以便从地图中删除它们:

marker.setMap(null);

默认的带有红色图标的标记并不适用于所有情况。Google Maps JavaScript API 允许你自定义标记的图标。基本上,你应该将 icon 参数添加到 google.maps.MarkerOptions 中来自定义标记图标。此参数接受三种不同类型:Stringgoogle.maps.Icon 对象或 google.maps.Symbol 对象。如果你有一个简单的图标图像,你将使用字符串类型并指定图像的路径。否则,你需要创建图标或符号对象来为标记设置复杂的可视化效果。通过 String 参数显示图标可以按照以下方式完成:

var marker = new google.maps.Marker({
  position: coordinate,
  icon: 'img/coffeeshop.png',
  title: 'My Coffee Shop'
});

还有更多...

在这个菜谱中,使用随机坐标来显示标记。如果你有一个包含坐标的数据源,你可以很容易地使用 JavaScript 技术将其添加到地图上,而无需在创建标记时进行任何更改。请确保在从外部源添加标记到地图时了解 JavaScript 的异步行为,因为由于异步行为,你的数据在你需要时可能不可用。

参见

  • 在 第一章 的 在自定义 DIV 元素中创建简单地图 菜谱中,Google Maps JavaScript API 基础

向标记或地图添加弹出窗口

几乎每个地图应用都有显示与显示在地图上的功能相关的信息的能力。同时显示所有相关信息对于开发者来说是一项不可能完成的任务,对于用户来说也是无用的。因此,开发者不是在地图上显示所有信息,而是通过弹出窗口或信息窗口等技术,将相关信息的显示添加到点、折线或多边形上。

弹出窗口或信息窗口可以包含任何可以用 HTML 标签编写的元素,例如图片、视频或标准文本。

如果你成功完成菜谱的整个过程,你会看到以下类似的截图:

向标记或地图添加弹出窗口

准备工作

这个食谱是之前名为 添加标记到地图 的食谱的修改版本。

你可以在 Chapter 3/ch03_adding_popups.html 找到源代码。

如何做到这一点...

你可以通过以下步骤轻松地将弹出窗口添加到标记或地图上:

  1. 首先,通过在创建 map 对象后添加以下代码行修改 initMap() 函数。这将使地图在首次初始化时在地图中心打开一个信息窗口或弹出窗口:

    var infowindow = new google.maps.InfoWindow({
      content: '<div style="width:200px; height:100px; "><b>Popup Content :</b><br><br>This popups show the center of map, which is Ankara, Turkiye</div>',
      position: new google.maps.LatLng(39.9078, 32.8252)
    });
    
    infowindow.open(map);
    
  2. 接下来,将以下函数添加到监听 HTML 中定义的按钮的点击事件:

    function startButtonEvents() {document.getElementById('addStandardMarker').addEventListener('click', function(){
          addStandardMarker();
      });
    }
    
  3. 在初始化地图时必须调用 startButtonEvents() 函数,因此,在 map 初始化后,向 initMap() 函数中添加以下行:

    startButtonEvents();
    
  4. 然后,通过在标记创建后添加以下代码行修改 addStandardMarker() 函数:

    var infowindow = new google.maps.InfoWindow({
      content: 'Marker Info Window – ID : ' + markerId
    });
    
    google.maps.event.addListener(marker, 'click', function() {
      infowindow.open(map, marker);
    });
    
  5. 前往你喜欢的浏览器中存储 HTML 文件的本地 URL,查看结果。你将看到地图开始处有一个信息窗口。你还可以点击地图上的链接来添加随机标记,但与之前的标记不同,因为当用户点击它们时,它们将打开一个弹出窗口。

它是如何工作的...

Google Maps JavaScript API 有一个默认的 InfoWindow 类来创建信息窗口或弹出窗口。这个类可以通过两种方式初始化。一种方式是在信息窗口选项中提供一个位置,使用 LatLng 对象。通过这种方式,你可以在地图上的任何位置打开一个弹出窗口。这可以附加到一个函数或事件。例如,你可以将 click 事件附加到地图上,从服务器查询某些内容,并在弹出窗口中显示结果。这在 Google Maps JavaScript API 中很常见。以下代码在 39.9078(纬度)和 32.8252(经度)的位置创建了一个信息窗口,并带有 HTML 内容。它的 open 方法使用 map 输入显示了附加到给定地图引用的信息窗口:

var infowindow = new google.maps.InfoWindow({
content: '<div style="width:200px; height:100px; "><b>Popup Content :</b><br><br>This popups show the center of map, which is Ankara, Turkiye</div>',
  position: new google.maps.LatLng(39.9078, 32.8252)
});
infowindow.open(map);

使用弹出窗口的另一种方法是将其绑定到标记上。而不是给出一个位置,信息窗口将锚定到一个 marker 对象。以下代码中给出的 infoWindow 对象没有 position 属性,这意味着它将锚定到一个 marker 对象。记住,marker 对象是 Google Maps JavaScript API 中 MVCObject 类的子类。它们是 InfoWindowopen 方法的一种锚定参数:

var infowindow = new google.maps.InfoWindow({
  content: 'Marker Info Window – ID : ' + markerId
});
google.maps.event.addListener(marker, 'click', function() {
  infowindow.open(map, marker);
});

在前面的代码中,marker 对象关联了一个事件,这是 第五章,理解 Google Maps JavaScript API 事件 的主题。因此,按照代码原样使用;这将在稍后详细解释,但基本上这个代码片段监听 marker 对象,并在 click 事件上打开创建的 infowindow 对象。

如本菜谱所示,您可以在信息窗口中使用简单的字符串和复杂的 HTML 内容。这意味着您甚至可以在信息窗口中添加 YouTube 视频或 Flash 内容。

相关内容

  • 在 第一章 的 在自定义 DIV 元素中创建简单地图 菜谱中,Google Maps JavaScript API 基础

  • 向地图添加标记 菜谱

向地图添加线条

在 GIS 中,线条或多边形是一系列相互连接的点,用于在地球上显示诸如道路、路径或河流等特征。地图上多边形的属性与地球上表示的特征的属性相似。例如,地球上的道路通过其颜色和宽度来区分。相同的属性也在 Google Maps JavaScript API 中定义,以精确地表示地图上的道路。

本菜谱的重点是展示地图上的线条/多边形,以显示从 伊斯坦布尔安卡拉 的路线。

准备工作

本菜谱使用与 第一章 中定义的相同地图创建过程,Google Maps JavaScript API 基础,但在 zoom 级别和中心坐标上有些细微的变化,以详细显示路线。

您可以在 Chapter 3/ch03_adding_lines.html 找到源代码。

如何操作…

如果您想向地图添加线型几何形状,您应执行以下步骤:

  1. 让我们打开 第一章 中提到的第一个菜谱的源代码,并将其保存为 adding_lines.html

  2. 然后,在代码的 JavaScript 部分定义 map 对象之后添加以下代码行。本步骤中定义的数组是从 伊斯坦布尔安卡拉 的经纬度路线坐标:

    var lineCoordinates = [
      [41.01306,29.14672],[40.8096,29.4818],
      [40.7971,29.9761],[40.7181,30.4980],
      [40.8429,31.0253],[40.7430,31.6241],
      [40.7472,32.1899],[39.9097,32.8216]
    ];
    
  3. 然后,创建 addPolyline 函数:

    function addPolyline () {
    
    }
    
  4. 我们需要创建一个新的数组,由函数开头定义的 LatLng 对象组成:

    //First we iterate over the coordinates array to create a 
    // new array which includes objects of LatLng class.
    var pointCount = lineCoordinates.length;
    var linePath = [];
    for (var i=0; i < pointCount; i++) {
      var tempLatLng = new google.maps.LatLng( lineCoordinates[i][0] , lineCoordinates[i][1]
      );
      linePath.push(tempLatLng);
    }
    
  5. 然后,我们需要创建 polyline 对象,如下所示:

    //Polyline properties are defined below
    var lineOptions = {
      path: linePath,
      strokeWeight: 7,
      strokeColor: '#FF0000',
      strokeOpacity: 0.8
    }
    var polyline = new google.maps.Polyline(lineOptions);
    
  6. 让我们将 polyline 对象添加到 map 中:

    //Polyline is set to current map.
    polyline.setMap(map);
    
  7. 现在,在 initMap() 函数的末尾调用 addPolyline() 函数,如下所示:

    addPolyline();
    
  8. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL,查看结果。您将在地图上看到从 伊斯坦布尔安卡拉 的红色路线,如下面的截图所示:如何操作…

因此,我们成功创建了一个带有线型几何形状的地图,在这个例子中,它表示从一个地方到另一个地方的路线。

工作原理...

如前所述,多边形由点组成。点由 Google Maps JavaScript API 中的 LatLng 类定义,因此应将经纬度数组转换为 LatLng 数组。以下代码块创建了一个由 LatLng 对象组成的新数组。为此,使用以下循环遍历数组的经典方法:

var pointCount = lineCoordinates.length;
var linePath = [];
for (var i=0; i < pointCount; i++) {
  var tempLatLng = new google.maps.LatLng(lineCoordinates[i][0], lineCoordinates[i][1]);
  linePath.push(tempLatLng);
}

通过将PolylineOptions类的实例作为参数的Polyline类将创建一个路线。PolylineOptions类有许多属性,但我们只添加了最常用的。

定义路线特征的path属性包含一个LatLng对象的数组。strokeWeight属性用于定义线的宽度(以像素为单位)。strokeColor属性定义线的颜色,以String类型表示,采用带前导#符号的HEX格式。

strokeOpacity属性的用法是可选的,但在显示多个图层时可能很有用。此参数的值从 0.0 到 1.0。0.0 表示你的线是不可见的,1.0 表示你的线不是透明的。如果你有多个图层,你应该定义你线条的不透明度以显示其他特征或图层。

此配方显示了在 HTML 中定义的静态路线;但在某些情况下,你可以从远程源加载数据。在这种情况下,你应该通过Polyline类的setPath()方法更改路径数组。此方法获取与PolylineOptions类中定义的相同的数组。例如,你创建一个名为newRoute的新路径数组。要将坐标更改为新路线,你应该调用以下代码:

polyline.setPath(newRoute);

如果你想要完全从地图中移除polyline,那么你应该将map属性设置为 null 或调用Polyline类的setMap(null)方法。

相关内容

  • 第一章中的在自定义 DIV 元素中创建简单地图配方

  • 添加多边形到地图配方

将多边形添加到地图中

多边形类似于多段线,多段线是一系列相互连接的点。然而,多边形是封闭的环路,用于显示地球上的特征,如公园、地块或区域。除了多段线的属性外,多边形内部还有一个填充区域。

此配方专注于在地图上显示多边形以显示安卡拉周围的区域。

准备工作

此配方使用在第一章中定义的相同的地图创建过程,Google Maps JavaScript API 基础知识,但在zoom级别和中心坐标上有些小的变化,以详细显示区域。

你可以在Chapter 3/ch03_adding_polygons.html找到源代码。

如何操作…

如果你执行以下步骤,你可以将多边形类型的几何形状添加到你的地图中:

  1. 让我们打开在第一章中提到的第一个配方的源代码,Google Maps JavaScript API 基础知识,并将其保存为adding_polygons.html

  2. 然后,在 JavaScript 部分的开始处定义地图对象后,添加以下代码行。在此步骤中定义的数组是围绕安卡拉的经纬度随机区域的区域坐标:

    var areaCoordinates = [
      [40.0192,32.6953],[39.9434,32.5854],
      [39.7536,32.6898],[39.8465,32.8106],
      [39.9139,33.0084],[40.0318,32.9260],
      [40.0402,32.7832],[40.0192,32.6953]
    ];
    
  3. 然后创建addPolygon函数:

    function addPolygon () {
    
    }
    
  4. 我们需要创建一个新的数组,该数组由函数开头定义的数组中的 LatLng 对象组成:

    //First we iterate over the coordinates array to create a 
    // new array which includes objects of LatLng class.
    var pointCount = areaCoordinates.length;
    var areaPath = [];
    for (var i=0; i < pointCount; i++) {
      var tempLatLng = new google.maps.LatLng(areaCoordinates[i][0] , areaCoordinates[i][1]);
      areaPath.push(tempLatLng);
    }
    
  5. 然后,我们需要按照以下方式创建 polygon 对象:

    //Polygon properties are defined below
    var polygonOptions = {
      paths: areaPath,
      strokeColor: '#FF0000 ,
      strokeOpacity: 0.9,
      strokeWeight: 3,
      fillColor: '#FFFF00',
      fillOpacity: 0.25
    }
    var polygon = new google.maps.Polygon(polygonOptions);
    
  6. 让我们将 polygon 对象添加到 map 中:

    //Polygon is set to current map.
    polygon.setMap(map);
    
  7. 现在,在 initMap() 函数的末尾调用 addPolygon() 函数,如下所示:

    addPolygon();
    
  8. 在您最喜欢的浏览器中转到存储 HTML 文件的本地 URL,并查看结果。您将看到一个由红色边界围绕的黄色区域,该区域位于 安卡拉 附近,如下面的截图所示:如何操作…

  9. 这就是我们在地图上添加多边形类型几何形状的方法。

工作原理...

Polygon 类在 Google Maps JavaScript API 中与 Polyline 类非常相似。PolylinePolygon 类之间有一些细微的差异,所以我们只在这个菜谱中详细介绍这些差异。请参考之前的菜谱以获取更多详细信息。

如前一个菜谱所述,Polygon 类通过包含许多多边形参数的 PolygonOptions 类来创建对象。pathstrokeWeightstrokeColorstrokeOpacity 参数与 PolylineOptions 类共享。这些参数的用法和目的对多边形和折线都是相同的。主要区别在于多边形填充一个区域。必须有一些新参数来定义多边形的填充。

fillColor 属性以 String 类型定义填充区域的颜色,格式为带前缀 #HEX 格式。fillOpacity 属性的使用可以是可选的,但在同时显示多个图层时也可能很有用。此参数的值从 0.0 到 1.0。0.0 表示您的多边形不可见,1.0 表示您的多边形不透明。此参数在多边形中比在折线中更重要,因为多边形填充区域,这可能会成为某些标记或折线的障碍。

添加或删除多边形与折线的 API 使用方式相同,因此在这里无需讨论。

最后要提到的一点是,多边形是折线的封闭版本,因此我们在开始和结束处添加相同的坐标。这是一个好的用法,但不是必需的。即使您没有添加与起始坐标相同的结束坐标,Google Maps JavaScript API 也会无错误地关闭多边形。

参见

  • 在 第一章 的 在自定义 DIV 元素中创建简单地图 菜谱中,Google Maps JavaScript API 基础

  • 在地图上添加线条 菜谱

在地图上添加圆形/矩形

圆形和矩形在具有描边和填充颜色、权重和透明度方面与多边形相似。它们与多边形的主要区别在于定义几何形状。如前几个菜谱所示,PolygonOptions 类有一个 path 参数,该参数由 LatLng 对象数组组成。另一方面,CircleOptions 类有 centerradius 参数,而 RectangleOptions 类有用于定义 CircleRectangle 类几何形状的边界参数。

在这个菜谱中,我们将根据城市的人口添加圆形,并添加一个矩形以将地图边界映射到土耳其。结果地图将显示土耳其的边界框和主要城市的人口图。

准备工作

在这个菜谱中,我们将使用 第一章 中定义的第一个菜谱作为模板,以跳过地图创建。

您可以在 Chapter 3/ch03_circle_rectangle.html 找到源代码。

如何做到这一点...

如果您执行以下步骤,将圆形或矩形添加到地图上相当简单:

  1. 让我们从创建一个名为 circles_rectangles.html 的新空文件开始。然后,复制在 第一章 中介绍的 HTML 文件中的所有代码,该代码位于 在自定义 DIV 元素中创建简单地图 菜谱中,Google Maps JavaScript API 基础,并将其粘贴到新文件中。

  2. 添加以下行以定义函数中使用的全局变量:

    // Defining coordinates and populations of major cities in // Turkey as Ankara, Istanbul and Izmir
    var cities = [{
      center: new google.maps.LatLng(39.926588, 32.854614), 
      population : 4630000
    },
    {
      center: new google.maps.LatLng(41.013066, 28.976440), 
      population : 13710000
    },
    {
      center: new google.maps.LatLng(38.427774, 27.130737),  
      population : 3401000
    }
    ];
    
    // Defining the corner coordinates for bounding box of 
    // Turkey
    var bboxSouthWest = new google.maps.LatLng(35.817813, 26.047461);
    var bboxNorthEast = new google.maps.LatLng(42.149293, 44.774902);
    
  3. 然后,在 initMap() 函数之前添加 addCircle()addRectangle() 函数:

    function addCircle() {
    
    }
    
    function addRectangle() {
    
    }
    
  4. 现在,将以下代码块添加到 addCircle() 函数中以初始化圆形:

    // Iterating over the cities array to add each of them to // map
    for (var i=0; i < cities.length; i++) {
      var circleOptions = {
        fillColor: '#FFFF00',
        fillOpacity: 0.55,
        strokeColor: '#FF0000',          
        strokeOpacity: 0.7,
        strokeWeight: 1,
        center: cities[i].center,
        radius: cities[i].population / 100
      };
      cityCircle = new google.maps.Circle(circleOptions);
      cityCircle.setMap(map);
    }
    
  5. 接下来,向 addRectangle() 函数添加以下行以初始化矩形:

    var bounds = new google.maps.LatLngBounds(bboxSouthWest, bboxNorthEast);
    var rectOptions = {
      fillColor: '#A19E98',
      fillOpacity: 0.45,
      strokeColor: '#FF0000',
      strokeOpacity: 0.0,
      strokeWeight: 1,
      map: map,
      bounds: bounds
    };
    
    var rectangle = new google.maps.Rectangle(rectOptions);
    
  6. 然后,在 initMap() 函数中根据您的需求更改地图的 zoom 级别和 center。本例使用以下参数:

    var mapOptions = {
      center: new google.maps.LatLng(39.9046, 32.75926),
      zoom: 5,
      mapTypeId: google.maps.MapTypeId.ROADMAP
    };
    
  7. 最后,在 initMap() 函数的末尾添加 addRectangle()addCircle() 函数,如下所示:

    addRectangle();
    addCircle();
    
  8. 前往您最喜欢的浏览器中存储 circles_rectangles.html 文件的本地 URL,并查看结果。您将看到地图上有三个圆形和一个灰色的矩形,如下面的截图所示:如何做到这一点…

这是该菜谱的结果,它同时在地图上显示了圆形和矩形。这可以是一个很好的例子,用于可视化你在地图上的表格数据。

它是如何工作的...

首先,让我们谈谈圆形。圆形是多边形的一种类型,但它们由 LatLng 对象中的中心和一个以米为单位的半径定义,而不是路径。其他参数与 PolygonOptions 类相同。

在这个菜谱中,首先选择了安卡拉伊斯坦布尔伊兹密尔这三个城市。这些城市的中心和人口使用数组中的 JSON 对象定义。城市的中心使用LatLng对象定义,因此不需要额外的转换。下面的代码块遍历城市数组,并使用在 JSON 对象中定义的center参数创建一个圆:

  var citiesLen = cities.length;
  for (var i=0; i < citiesLen; i++) {
      var circleOptions = {
        fillColor: '#FFFF00',
        fillOpacity: 0.55,
        strokeColor: '#FF0000',
        strokeOpacity: 0.7,
        strokeWeight: 1,
        center: cities[i].center,
        radius: cities[i].population / 100
      };
      cityCircle = new google.maps.Circle(circleOptions);
      cityCircle.setMap(map);
    }

radius参数被定义为人口的一个参数,即人口除以100;它显示了人口的规模。如图所示,人口越高,圆就越大。这可以用来表示人口分布,而无需知道确切的数字。如图所示,其他参数,如fillColorfillOpacitystrokeColorstrokeOpacitystrokeWeight,与PolygonOptions类的使用方式相同。setMap()函数也像在多边形或折线菜谱中使用的那样使用。

另一个元素,矩形,也是一种多边形,只不过其几何形状是由LatLngBounds对象定义的。在理论上,LatLngBounds对象由两个LatLng对象组成,分别定义为矩形的西南和东北坐标。这些坐标也可以定义为纬度和经度的最小和最大点。在这个菜谱中,定义的矩形显示了土耳其的边界框。BBOX 可用于简单的几何计算,如“点在多边形内”或“交集”。由于几何形状简单,使用 BBOX 进行计算可以给出快速的结果,但这个计算总是存在误差。如图所示,一些区域不在土耳其的边界上,但它们在边界框内。如果您想使用 BBOX 方法获取与土耳其相交的几何形状,可以轻松地获取其他位于土耳其真实几何对象之外的几何形状。如图所示,使用 BBOX 方法得到的某些几何形状在 BBOX 的交点处可能位于真实区域的之外。Rectangle类的使用方法如下:

var bounds = new google.maps.LatLngBounds(bboxSouthWest, bboxNorthEast);

var rectOptions = {
  fillColor: '#A19E98',
  fillOpacity: 0.45,
  strokeColor: '#FF0000',
  strokeOpacity: 0.0,
  strokeWeight: 1,
  map: map,
  bounds: bounds
};
var rectangle = new google.maps.Rectangle(rectOptions);

Google Maps JavaScript API 为开发者提供了许多机会,可以使他们的生活更加轻松。圆和矩形都可以用于应用程序中的几何形状或其他可视化技术。

更多内容...

在这个菜谱中,我们无序地添加圆和矩形。在先前的地图中显示的是最近添加的一个。在这个例子中,首先添加矩形是为了更好地显示圆。如果您想改变标记、信息窗口、折线、多边形、圆或矩形的显示顺序,应该更改选项类的zIndex参数,或者通过setZIndex(3)setOptions({ zIndex: 3 })方法进行更改。

参见

  • 第一章中的在自定义 DIV 元素中创建简单地图的配方Google Maps JavaScript API 基础

  • 将线条添加到地图中的配方

  • 将多边形添加到地图中的配方

将动画线条添加到地图中

折线是地球特征在地球上的表示,但有时它们不足以展示地球特征的移动性。例如,河流可以通过折线来显示,但河流的流向不能仅通过折线来展示。动画折线可以作为一个展示地球特征移动性的解决方案。河流的流向可以通过动画折线来展示。

Google Maps JavaScript API 具有符号功能,可以将基于矢量的图像以符号的形式添加到折线中。您可以使用 Symbol 类创建自己的符号,或者您也可以使用从 SymbolPath 类访问的预定义符号。

在这个配方中,我们将从上一个配方创建一个动画折线。这个动画显示一辆车从 伊斯坦布尔 移动到 安卡拉

准备工作

在这个配方中,我们将使用本章的第四个配方作为模板。

您可以在 第三章/ch03_animating_line.html 找到源代码。

如何操作...

以下是将动画线条型几何图形添加到您的地图中所需的步骤:

  1. 首先,将 ch03_adding_lines.html 的内容复制到您的新 HTML 文件中。

  2. 然后,在 map 对象之后添加以下行以使其全局。这在动画线条时使用:

    var polyline;
    
  3. 将函数名称从 addPolyline() 更改为 addAnimatedPolyline() 并添加以下代码块来定义您的符号:

    // Defining arrow symbol
    var arrowSymbol = {
      strokeColor: '#000',
      scale: 3,
      path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW
    };
    
  4. 接下来,按照以下方式更改折线选项:

    var lineOptions = {
      path: linePath,
      icons: [{
        icon: arrowSymbol,
        offset: '100%'
      }],
      strokeWeight: 3,
      strokeColor: '#FF0000',
      strokeOpacity: 0.8
    }
    
  5. 现在,在以下步骤之后添加以下函数调用以启动动画,该动画将在以下步骤之后定义:

    // Calling the arrow animation function
    animateArrow();
    
  6. initMap() 函数之前添加以下函数块:

    function animateArrow() {
      var counter = 0;
      var accessVar = window.setInterval(function() {
        counter = (counter + 1) % 200;
        var arrows = polyline.get('icons');
        arrows[0].offset = (counter / 2) + '%';
        polyline.set('icons', arrows);
      }, 50);
    }
    
  7. 最后,在 initMap() 函数中将函数调用从 addPolyline() 更改为 addAnimatedPolyline() 以添加新的动画折线:

    addAnimatedPolyline();
    
  8. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL,并查看结果。您将看到箭头在地图上从 伊斯坦布尔安卡拉 的路线上进行动画,如下面的截图所示:如何操作…

通过这个配方,我们可以将动画线条型几何图形添加到我们的地图中,显示车辆在路线上的移动。

如何工作...

动画折线包括使用 JavaScript setInterval 方法和 Google Maps JavaScript API 的 PolylineOptions 类的图标属性的一个技巧。正如所述,您可以创建自己的符号或使用预定义的符号。

预定义的符号可以通过 SymbolPath 类访问,如下面的截图所示,就像在 Google Maps JavaScript API 文档中一样:

如何工作...

在这个食谱中,我们将使用 FORWARD_CLOSED_ARROW 类型。这个符号定义如下:

  var arrowSymbol = {              
    strokeColor: '#000',
    scale: 3,
    path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW
  };

strokeColor 属性用于定义符号的颜色。path 属性用于定义符号类型,而 scale 属性用于定义符号的大小。你还可以更改预定义符号(如多边形)的 fillColorfillOpacity 属性。

如前一个截图所示,预定义的符号是有限的。如果你需要更多类型的符号,你应该在 Symbol 类中自己定义它们。你需要通过 Symbol 类的 path 属性使用 SVG 路径符号来定义自定义符号。

SVG 路径符号是 SVG 命令(如 moveto (M)、lineto (L) 或 closepath (Z))中形状的定义。例如,以下路径符号定义了一个三角形:

var triangle = 'M 100 100 L 300 100 L 200 300 Z';               

路径符号的解释如下:移动到点 (100,100),从 100,100300,100 绘制一条水平线,然后从 (300,100) 到 (200, 300) 绘制第二条线。这个形状看起来像一个三角形。最后,使用 Z 命令关闭路径。你可以使用这种符号绘制任何形状,但你应该注意可用于地图的区域。Google Maps JavaScript API 允许一个 22 x 22 px 的正方形区域显示定义的形状。如果形状大于这个区域,你应该使用 scale 参数将形状调整到该区域。以下代码块将预定义的箭头形状更改为在同一路线上移动的黄色三角形:

  var arrowSymbol = {                
    path : 'M 100 100 L 300 100 L 200 300 Z',
    anchor: new google.maps.Point(175,175),
    scale: 0.15,
    fillColor: '#FFFF00',
    fillOpacity: 0.8,
    strokeColor: '#000000',
    strokeWeight: 3
  };

如何工作...

如果你注意到了,这里有一个额外的参数名为 anchor。这个参数用于定义符号相对于折线的位置。如果你不添加这个参数,你的符号将默认从 (0,0) 点固定到折线上。一般来说,使用符号的中心作为 anchor 点可以得到最佳结果。anchor 参数接受 Point 类。它也以像素为单位获取其 x 和 y 参数。

这个食谱中最棘手的部分是动画。在 animateArrow() 函数中,我们定义了一个触发器,通过 window.setInterval 方法每 50 毫秒动画化之前定义的符号。在这个触发器中定义了一个匿名函数如下:

  function() {
    counter = (counter + 1) % 200;
    var arrows = polyline.get('icons');
    arrows[0].offset = (counter / 2) + '%';
    polyline.set('icons', arrows);
  }

这个函数获取图标数组中的第一个对象,并根据 counter 变量更改定义的图标中 offset 参数的动态参数。每 50 毫秒运行此函数将符号移动到折线上。

在匿名函数中,你可能已经注意到折线对象有 get()set() 方法,这些方法在文档中并未定义。由于 Polyline 类扩展了 MVCObject 类,我们也可以使用 MVCObject 类的方法。因此,我们可以使用父类的 get()set() 方法。

使用符号和计时器可以在不添加额外库的情况下,通过 Google Maps JavaScript API 实现不同的可视化。

更多内容...

SVG可缩放矢量图形的缩写。它是一种基于 XML 的二维矢量图像格式,支持交互性和动画。SVG 被所有现代浏览器支持。在某些情况下,如本例,它可以是地图平台的良好解决方案。SVG 是一个完全不同的主题,超出了本书的范围。

注意

关于 SVG 路径表示法的更多信息

更多详细信息可以在 W3C 网站上找到(www.w3.org/TR/SVG/paths.html#PathData)。还有一些编辑软件可以在不学习语言的情况下获取路径表示法。以下地址可用于创建 SVG 和获取路径表示法:svg-edit.googlecode.com/svn/branches/2.6/editor/svg-editor.html

参考信息

  • 在第一章的“在自定义 DIV 元素中创建简单地图”菜谱中,Google Maps JavaScript API 基础知识

  • 向地图添加线条菜谱

添加 KML/GeoRSS 图层

关键孔标记语言 (KML) 已在 Google Earth 中引入,在 Google 收购之前原名为 Keyhole Earth Viewer。KML 于 2008 年成为 OGC 标准。它是一种 XML 表示法,用于在地理启用查看器中显示特征。GeoRSS也是一种用于在地理启用查看器中共享地球特征的新兴标准,主要用于 Web 订阅或服务。这两个标准都可以通过 Google Maps JavaScript API 进行消费。

在本菜谱中,将通过 Google Maps JavaScript API 消耗动态服务。我们将使用美国地质调查局(USGS)的 Web 服务在地图上显示最近的地震。这些服务定期更新以反映最近的事件。

准备工作

在本菜谱中,我们将使用在第一章中介绍的简单地图菜谱,Google Maps JavaScript API 基础知识,作为模板。

该菜谱的源代码位于第三章/ch03_kml_georss.html

如何操作…

如果您执行以下步骤,可以将您的 KML/GeoRSS 文件添加到地图中:

  1. 首先,将ch01_simple_map.html的内容复制到我们的新 HTML 文件中。

  2. 接下来,将以下变量定义为全局变量:

    var georssLayer, kmlLayer;
    
  3. 在定义全局变量后,添加以下函数。此函数触发将 GeoRSS 数据添加到地图中:

    function addGeoRSSLayer() {
      georssLayer = new google.maps.KmlLayer( 'http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_month.atom');
      georssLayer.setMap(map);
    }
    
  4. 然后,在之前的函数之后添加其他函数。此函数也会触发将 KML 数据添加到地图中:

    function addKMLLayer() {
      kmlLayer = new google.maps.KmlLayer(' http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month_depth.kml');
      kmlLayer.setMap(map);
    }
    
  5. 现在,在initMap()函数之前添加以下函数,clearMap()

    function clearMap() {
      if (georssLayer != undefined) {
        georssLayer.setMap(null);
        georssLayer = null;
      }
    
      if (kmlLayer != undefined) {
        kmlLayer.setMap(null);
        kmlLayer = null;
      }
    }
    
  6. 接下来,添加以下函数以监听第 8 步中定义的 HTML 按钮的点击事件:

    function startButtonEvents () {
      document.getElementById('linkGeoRSS' ).addEventListener('click', function(){
            addGeoRSSLayer();
      });
      document.getElementById('linkKML' ).addEventListener('click', function(){
            addKMLLayer();
      });
      document.getElementById('linkClearMap' ).addEventListener('click', function(){
            clearMap();
      });
    }
    
  7. startButtonEvents函数必须在初始化地图时调用,因此以下行在地图初始化后添加到 HTML 文件中:

    startButtonEvents();
    
  8. 最后,将以下行添加到 HTML body 标签中,以便在点击链接时触发函数:

    <a id="linkGeoRSS" href="#">Add GeoRSS Layer</a>
    <a id="linkKML" href="#">Add KML Layer</a>
    <a id="linkClearMap" href="#">Clear Map</a>
    
  9. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL,查看结果。您将看到开始时是一个空地图。当您点击地图上的链接时,您将看到地图上有两个不同的图层,如下所示:如何操作…

如前一张截图所示,您可以使用 Google Maps JavaScript API 轻松地将您的 GeoRSS 文件或服务添加到地图中。

如何操作…

这是将 KML 图层添加到您的地图中的结果。使用 Google Maps JavaScript API,您可以轻松地将您的 KML 文件或服务添加到地图中。

它是如何工作的...

添加 KML/GeoRSS 图层是最简单的一种。添加这两个图层只有一个类,名为KmlLayer。这个类从本地或远程位置读取 KML 或 GeoRSS 源,并决定要渲染的内容。类的使用非常简单:

var vectorLayer = new google.maps.KmlLayer('URL_TO_FEED');

在创建图层后,您必须使用setMap(map)方法设置地图,以便在地图上显示图层。如果您想从地图中移除图层,必须使用前面章节中描述的setMap(null)方法。

请记住,Google Maps JavaScript API v3 没有像 v2 那样的清除所有图层或覆盖物的功能。处理图层状态的所有责任都在您的肩上。在实际应用中,Google Maps JavaScript API 文档建议您将所有图层保存在一个数组中,并通过setMap()方法自行管理添加/移除函数。正如我们在clearMap()函数中所做的那样,我们检查图层是否已定义。如果是,我们将其移除;如果不是,我们不做任何事情,这样我们就不会得到错误。

参见

  • 在第一章的在自定义 DIV 元素中创建简单地图配方中,Google Maps JavaScript API 基础知识

将 GeoJSON 添加到 Google Maps JavaScript API

XML 是 Web 2.0 区域中服务的第一个英雄。借助 XML 的帮助,服务或机器可以轻松地相互通信。XML 也可以被人类阅读。但是,随着浏览器的演变,JSON 由于其 JavaScript 的原生可读性和与 XML 相比的轻量级,变得更加流行。GeoJSON 是一种包含简单特征集合的 JSON 形式,如点、折线或多边形。GeoJSON 不是 OGC 的标准,但它是一种被大多数 GIS 软件或服务使用的新事实标准。

Google Maps JavaScript API 本身不支持 GeoJSON,但可以通过几行代码或使用一些额外的库来添加 GeoJSON 支持。通过编码,我们将遍历 JSON 格式并逐个读取坐标。然后,我们将根据其类型(可以是点、折线或多边形)在地图上显示特征。

在这个菜谱中,我们将通过 jQuery 函数从本地文件读取 GeoJSON,并在地图上显示它们。这个 GeoJSON 文件由安卡拉省边界的简化版本、一条示例河流和一些 POI 组成。

准备工作

在这个菜谱中,我们将使用在第一章中介绍的简单地图菜谱作为模板。

你可以在Chapter 3/ch03_adding_geojson.html找到源代码。

如何操作…

如果你执行以下步骤,你可以将你的 GeoJSON 文件添加到你的地图中:

  1. 首先,将ch01_simple_map.html的内容复制到我们新的 HTML 文件中。

  2. 接下来,我们将添加一个 jQuery JavaScript 库,以便轻松访问本地或远程的 GeoJSON 文件。在这个菜谱中,我们将从 Google CDN 添加库。这个代码块将添加在<head>部分,在 Google Maps JavaScript API 之前。

    <script src="img/jquery.min.js"></script>
    
  3. 然后,在定义全局map变量后添加以下drawGeometry()函数。这个函数绘制从 GeoJSON 文件中读取的每个几何形状。我们有三种类型的几何形状,所以我们将为每种类型添加代码块:

    function drawGeometry(geom) {
    
    }
    
  4. 现在,在新的函数中添加以下if块。这个块将在几何形状的类型为Point时添加几何形状:

    if (geom.type == 'Point') {
      var coordinate = new google.maps.LatLng(geom.coordinates[1], geom.coordinates[0]);
      var marker = new google.maps.Marker({
        position: coordinate,
        map: map,
        title: 'Marker'
      });
    }
    
  5. 接下来,if块用于显示地图上的LineString折线:

    else if (geom.type == 'LineString') {
      var pointCount = geom.coordinates.length;
      var linePath = [];
      for (var i=0; i < pointCount; i++) {
        var tempLatLng = new google.maps.LatLng(geom.coordinates[i][1], geom.coordinates[i][0]);
        linePath.push(tempLatLng);
      }
    
      var lineOptions = {
        path: linePath,
        strokeWeight: 7,
        strokeColor: '#19A3FF',
        strokeOpacity: 0.8,
        map: map
      };
      var polyline = new google.maps.Polyline(lineOptions);
    }
    
  6. 最后,使用if块显示多边形如下:

    else if (geom.type == 'Polygon') {
      var pointCount = geom.coordinates[0].length;
      var areaPath = [];
      for (var i=0; i < pointCount; i++) {
        var tempLatLng = new google.maps.LatLng( geom.coordinates[0][i][1], geom.coordinates[0][i][0]);
        areaPath.push(tempLatLng);
      }
    
      var polygonOptions = {
        paths: areaPath,
        strokeColor: '#FF0000',
        strokeOpacity: 0.9,
        strokeWeight: 3,
        fillColor: '#FFFF00',
        fillOpacity: 0.25,
        map: map
      };
    
      var polygon = new google.maps.Polygon(polygonOptions);
    }
    
  7. 然后,添加以下函数来读取 GeoJSON 文件并遍历几何形状:

    function parseGeoJSON() {
      $.getJSON('geojson.js', function(data) {
        $.each(data.features, function(key, val) {
          drawGeometry(val.geometry);
        });
      });
    }
    
  8. 最后,在initMap()函数的末尾调用parseGeoJSON()函数:

    parseGeoJSON();
    
  9. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL,查看结果。您将在地图上看到三种不同类型的几何形状及其样式如下:如何操作…

因此,我们已经成功地将包含多种几何形状的 GeoJSON 文件添加到地图中。

工作原理...

GeoJSON 是我们之前描述的矢量格式。它是一种 JSON 形式。GeoJSON 格式可以由同一文件中的不同类型组成,如下所示:

{ "type": "FeatureCollection",
  "features": [
    { "type": "Feature",
      "geometry": {"type": "Point", "coordinates": [102.0, 0.5]},
      "properties": {"prop0": "value0"}
    },
    { "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]
        ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": 0.0
      }
    },
    { "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0],
            [100.0, 1.0], [100.0, 0.0] ]
          ]
      },
      "properties": {
        "prop0": "value0",
        "prop1": {"this": "that"}
      }
    }
  ]
}

www.geojson.org网站获取的示例 GeoJSON。从代码中可以看出,它由同一文件中的点、折线和多边形组成,这些元素通过feature关键字分隔。每个feature都有一个名为geometryproperties的 JavaScript 对象。geometry部分存储对象的几何形状,而properties部分存储相关联的信息。geometry部分基于使用 WGS84 基准的地理坐标参考系统,默认情况下坐标以十进制度数表示,直到在crs对象中定义。type对象存储几何形状的类型,如PointPolylinePolygoncoordinates数组是实际存储点坐标数组的部分。GeoJSON 中坐标的顺序与 Google Maps JavaScript API 在经纬度方面不同。

注意

更多关于 GeoJSON

更多详细信息可以从 GeoJSON 的非官方网站 (www.geojson.org) 获取。还有一些工具可以查看或编辑 GeoJSON 而无需任何编码。GitHub (www.github.com) 可以轻松地在地图上显示您的 GeoJSON 文件。www.geojson.io 网站也是 MapBox 的一个工具,可以在浏览器上显示和编辑您的 GeoJSON 文件,无需任何软件或编码。请检查这些网站以详细了解 GeoJSON。

在这个配方中,我们将借助 jQuery 的 getJSON() 方法读取本地 GeoJSON 文件。使用 jQuery 是为了专注于 Google Maps JavaScript API 的编码。否则,我们将在多个浏览器平台上处理远程文件读取。

此方法获取 geojson.js 本地文件的内容,并将它们放入 data 变量中。然后,我们将使用 jQuery 的 each() 方法遍历 GeoJSON 特征。最后,我们获取每个特征的 geometry 部分,并将其发送到稍后将要检查的 drawGeometry() 函数:

$.getJSON('geojson.js', function(data) {
  $.each(data.features, function(key, val) {
    drawGeometry(val.geometry);
  });
});

drawGeometry() 函数中编写的代码可能看起来很复杂,但这并不是因为我们将使用本章中为添加标记、线条和多边形编写的所有代码。此函数首先检查几何类型,然后为点、折线或多边形准备适当的选项和坐标。

在折线或多边形中,需要遍历 geometry 字段的 coordinates 数组,以创建 PolylineOptionsPolygonOptions 类的路径或路径。

var pointCount = geom.coordinates.length;
var linePath = [];
for (var i=0; i < pointCount; i++) {
  var tempLatLng = new google.maps.LatLng( geom.coordinates[i][1], geom.coordinates[i][0]);
  linePath.push(tempLatLng);
}

在这个配方中,我们将使用我们的函数处理 GeoJSON,这些函数不能绘制所有类型的 GeoJSON 几何形状。我们只处理简单的形状,以向您展示如何自己处理 GeoJSON。如果您需要进行更复杂的 GeoJSON 处理,有两种方法。一种方法是通过阅读 GeoJSON 的完整规范并将其添加到您的函数中。另一种方法,也很简单,是使用一个专门为此目的编写的库。有一个名为 GeoJSON to Google Maps 的库,由 Jason Sanford 在 GitHub (github.com/JasonSanford/geojson-google-maps) 上编写。借助这个库,您无需处理 GeoJSON 规范。您只需使用自己的样式添加几何形状即可。

参见

  • 在 第一章 Google Maps JavaScript API 基础 中,在自定义 DIV 元素中创建简单地图 的配方

  • 添加标记到地图 的配方

  • 添加线条到地图 的配方

  • 将多边形添加到地图 的配方

  • 将 WKT 添加到 Google Maps JavaScript API 的配方

将 WKT 添加到 Google Maps JavaScript API

已知文本WKT)是根据维基百科,在地图上表示矢量几何对象的一种文本标记语言。此格式最初由开放地理空间联盟OGC)定义,它也是一个标准。

除了 XML 或 JSON 之外,WKT 是一种定义的文本格式,它只定义几何形状而不定义属性,与 GeoJSON 相比。这是一个过时且陈旧的格式,但仍有软件或服务支持此格式。有 18 种不同的几何对象代表地球特征,但在这个菜谱中只观察到简单的几何形状。

Google Maps JavaScript API 本身不支持 WKT,但可以通过几行代码或使用一些额外的库来添加 WKT 支持。通过编码,我们将遍历 WKT 格式,将它们拆分为数组,并逐个读取坐标。然后,我们将根据其类型(可以是点、多段线或多边形)在地图上显示特征。

在这个菜谱中,我们将通过 jQuery 函数从本地文件中读取 WKT,并在地图上显示它们。WKT 几何形状位于 JSON 属性中,以便于迭代。此 WKT 文件由安卡拉省边界的一个简化版本、一条示例河流和一些 POI 组成。

准备工作

在这个菜谱中,我们将使用在第一章中介绍的简单地图菜谱作为模板。

您可以在Chapter 3/ch03_adding_wkt.html中找到源代码。

如何操作…

在执行以下步骤后,您可以轻松地将 WKT 几何形状添加到地图中:

  1. 首先,将ch01_simple_map.html的内容复制到我们的新 HTML 文件中。

  2. 接下来,我们将添加一个 jQuery JavaScript 库,以便轻松访问带有 WKT 几何形状的本地或远程 JSON 文件。在这个菜谱中,我们将从 Google CDN 添加库。此代码块将在 Google Maps JavaScript API 之前的<head>部分添加:

    <script src="img/jquery.min.js"></script>
    
  3. 然后,在定义全局map变量之后添加以下drawGeometry()函数。此函数绘制从 JSON 文件中读取的每个 WKT 几何形状。我们有三种类型的几何形状,因此我们将为每种类型切换代码块:

    function drawGeometry(geom) {
      var slices = geom.split('(');
      var geomType = slices[0];
    }
    
  4. 现在,在新的函数中添加以下if代码块。此代码块将在几何形状的类型为POINT时添加几何形状:

    if (geomType == 'POINT') {
      var coords = slices[1].split(')')[0].split(',');
      var finalCoords = coords[0].split(' ');
      var coordinate = new google.maps.LatLng(finalCoords[1], finalCoords[0]);
      var marker = new google.maps.Marker({
        position: coordinate,
        map: map,
        title: 'Marker'
      });
    }
    
  5. 接下来,添加用于在地图上显示多段线的if代码块LINESTRING

    else if (geomType == 'LINESTRING') {
      var coords = slices[1].split(')')[0].split(',');
      var pointCount = coords.length;
      var linePath = [];
      for (var i=0; i < pointCount; i++) {
        if (coords[i].substring(0,1) == ' ') {
          coords[i] = coords[i].substring(1);
        }
        var finalCoords = coords[i].split(' ');
        var tempLatLng = new google.maps.LatLng(finalCoords[1], finalCoords[0]);
        linePath.push(tempLatLng);
      }
    
      var lineOptions = {
        path: linePath,
        strokeWeight: 7,
        strokeColor: '#19A3FF',
        strokeOpacity: 0.8,
        map: map
      }
      var polyline = new google.maps.Polyline(lineOptions);
    }
    
  6. 最后,使用以下方式使用if代码块显示多边形:

    else if (geomType == 'POLYGON') {
      var coords = slices[2].split(')')[0].split(',');
      var pointCount = coords.length;
      for (var i=0; i < pointCount; i++) {
        if (coords[i].substring(0,1) == ' ') {
          coords[i] = coords[i].substring(1);
        }
        var finalCoords = coords[i].split(' ');
        var tempLatLng = new google.maps.LatLng(finalCoords[1], finalCoords[0]);
        areaPath.push(tempLatLng);
      }
    
      var polygonOptions = {
        paths: areaPath,
        strokeColor: '#FF0000',
        strokeOpacity: 0.9,
        strokeWeight: 3,
        fillColor: '#FFFF00',
        fillOpacity: 0.25,
        map: map
      }
    
      var polygon = new google.maps.Polygon(polygonOptions);
    }
    
  7. 然后,添加以下函数以读取 JSON 文件并遍历 WKT 几何形状:

    function parseWKT() {
      $.getJSON('wkt.js', function(data) {
        $.each(data.objects, function(key, val) {
          drawGeometry(val.geom);
        });
      });
    }
    
  8. 最后,在initMap()函数的末尾调用parseWKT()函数:

    parseWKT();
    
  9. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL,查看结果。您将在地图上看到三种不同类型的几何形状及其样式,如下面的截图所示:如何操作…

这就是我们将多种类型的几何形状添加到地图中的方法。

它是如何工作的...

WKT 是我们之前定义的矢量格式,与 GeoJSON 不同。GeoJSON 定义了地球特征的几何和属性,但 WKT 仅用于定义几何。以下表格展示了 WKT 的简单类型示例:

几何类型 WKT 示例
POINT POINT(31.541742 40.730608)
POLYLINE LINESTRING(35.24414 41.742627, 34.859619 41.586688, 34.7717285 41.508577, 34.832153 41.364441)
POLYGON POLYGON((33.759299 38.779907, 33.73552 38.758208, 33.73187 38.748987, 33.703537 38.723535, 33.677514 33.800384 38.876017, 33.783532 38.842548, 33.759299 38.779907))

WKT 几何与在将 GeoJSON 添加到 Google Maps JavaScript API配方中使用的几何完全相同,但它们是以 WKT 几何格式进行格式化的。由于它们的文本格式,WKT 几何并不是孤立的。因此,我们将它们放入一个带有geom属性的 JSON 文件中。这样做是为了便于解析。如果你有包含 WKT 几何的不同类型的格式,你应该用 JavaScript 解析它们。

首先,我们将读取 JSON 文件以获取 WKT 几何。我们将使用 jQuery 方法getJSON()读取本地 JSON 文件。jQuery 用于专注于 Google Maps JavaScript API,而不是为每个浏览器编写和修复 JavaScript 代码。否则,我们将在多个浏览器平台上处理远程文件读取。此方法获取wkt.js本地文件的内容,并将它们放入data变量中。然后,我们将使用 jQuery 方法each()遍历 JSON 对象。最后,我们获取每个对象的geom部分,并将其发送到稍后将要讨论的drawGeometry()函数:

$.getJSON('wkt.js', function(data) {
  $.each(data.objects, function(key, val) {
    drawGeometry(val.geom);
  });
});

WKT 的解析比 GeoJSON 要困难得多,因为我们需要处理文本解析。使用drawGeometry()函数,我们将 WKT 文本分割成更小的数组并使它们变得有意义。在解析每种类型之前,我们需要获取它们的几何类型。由于它们没有单独的属性来定义类型,我们需要从 WKT 文本中提取类型。从示例中可以看出,类型是用(字符与坐标分开的。如果我们从(字符开始切片字符串,第一个数组元素就是几何类型的类型。这是按照以下方式完成的:

var slices = geom.split('(');
var geomType = slices[0]; 

geomType的内容可以是POINTLINESTRINGPOLYGON。然后,我们将检查不同块中的每种几何类型。让我们从点类型开始逐一介绍每种几何类型。

点是使用 JavaScript 解析的最简单的 WKT 几何形状。首先,我们获取slice数组的第二个元素,并从)字符开始切片,只获取由逗号分隔的坐标。然后,我们将结果文本按逗号分割成一个数组。这个数组只有一个元素,因此我们可以轻松访问坐标。要访问坐标,我们必须使用空格字符切片最终文本。这个最终数组包含纬度和经度。Google Maps JavaScript API 使用纬度和经度来定义一个点,但 WKT 使用经度和纬度的顺序来定义一个点。如将 GeoJSON 添加到 Google Maps JavaScript API菜谱中所述,坐标的顺序对于 WKT 也是相同的,它是与 Google Maps JavaScript API 相反的顺序,即经度和纬度:

var coords = slices[1].split(')')[0].split(',');
var finalCoords = coords[0].split(' ');
var coordinate = new google.maps.LatLng(finalCoords[1], finalCoords[0]);

第二种类型是 WKT 中定义为LINESTRING的折线。解析折线比解析点要复杂得多。首先,我们通过分割)和逗号来获取坐标数组,如前所述。然后,我们将在这个数组中迭代以获取每个坐标。在用空格分割文本之前,我们必须检查文本开头是否有空格。如果有空格,我们将获取文本的其余部分以获取有效的纬度和经度的数字。

var coords = slices[1].split(')')[0].split(',');
var pointCount = coords.length;
var linePath = [];
for (var i=0; i < pointCount; i++) {
  if (coords[i].substring(0,1) == ' ') {
    coords[i] = coords[i].substring(1);
  }
  var finalCoords = coords[i].split(' ');
  var tempLatLng = new google.maps.LatLng(finalCoords[1], finalCoords[0]);
  linePath.push(tempLatLng);
}

最后,简单类型是多边形,在 WKT 中也定义为POLYGON。解析多边形与解析折线非常相似,除了多边形定义有两个括号,比折线多一个。我们将获取数组的第三个元素而不是第二个,因为 WKT 可以包含多个多边形几何形状,所以在这种情况下我们只有一个。如果您有多个几何形状,那么您应该遍历这些几何形状。唯一的区别如下:

var coords = slices[2].split(')')[0].split(',');

drawGeometry()函数中编写的代码块可能看起来很复杂,但它们并不复杂,因为我们将在本章中使用为添加标记、线和多边形编写的所有代码。这个菜谱的结果与将 GeoJSON 添加到Google Maps JavaScript API 的菜谱完全相同,这是预期的结果。我们并不想象在改变矢量格式时我们会得到不同的输出。

在这个菜谱中,我们将使用我们自己的函数处理 WKT,而这些函数无法绘制其标准中定义的所有 WKT 几何形状。我们只处理简单的几何形状,以向您展示如何自行处理 WKT。如果您需要处理更复杂的 WKT 几何形状,那么有两种方法。一种方法是通过阅读 WKT 的完整规范并将其添加到您的函数中。另一种,更简单的方法是使用为此工作而专门编写的库。有一个名为Wicket的库,由K. Arthur Endsley在 GitHub 上编写(github.com/arthur-e/Wicket)。借助这个库,您无需处理 WKT 规范。您只需添加具有您自己风格的几何形状即可。

参见

  • 在 第一章 的 在自定义 DIV 元素中创建简单地图 配方,Google Maps JavaScript API 基础

  • 向地图添加 标记 的配方

  • 向地图添加 线条 的配方

  • 向地图添加 多边形 的配方

  • GeoJSON 添加到 Google Maps JavaScript API 的配方

第四章 使用控件

在本章中,我们将涵盖:

  • 添加和删除控件

  • 改变控件的位置

  • 创建并添加地理位置控件

  • 为图层创建目录控件

  • 添加自己的标志作为控件

简介

本章涵盖了 Google Maps JavaScript API 中找到的控件。一般来说,控件是与用户交互的 UI 元素。在非常基本的层面上,它们由简单的 HTML 元素或它们的组合组成。

控件使用户能够平移地图、放大或缩小、测量距离或面积等。复杂的控件涉及以目录控件(目录)或绘图矢量特征编辑工具栏的形式管理多个叠加层(在第二章,添加栅格图层)。

Google Maps JavaScript API 为开发者提供了使用和自定义内置控件的机会,以及从头开始构建自定义控件。

本章将首先详细讨论处理内置控件及其配置,包括 UI 的自定义。然后,从非常基础的控件到复杂的控件(如目录)的创建将被涵盖。

添加和删除控件

Google Maps 默认 UI 包含一些默认显示或满足某些条件时显示的控件。这些包括:

  • 缩放控制

  • 平移控制

  • 地图类型控制

  • 规模控制

  • 街景控制

  • 旋转控制

  • 概览地图控制

Google Maps JavaScript API 为开发者提供了选择加入或退出这些控件或根据功能或外观进行自定义的机会。

在这个菜谱中,我们将介绍通过添加或删除内置控件来改变 UI 的方法,以及如何通过提供的选项更改它们的属性。

准备工作

第一章的第一道菜谱,Google Maps JavaScript API 基础知识,将完成我们的工作。我们将为此菜谱进行修改。

如何操作…

如果您执行以下步骤,您将选择内置控件的外观:

  1. 按照以下方式修改mapOptions对象:

    var mapOptions = {
      center: new google.maps.LatLng(43.771094,11.25033),
      zoom: 13,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      panControl: true,
      scaleControl: false,
      zoomControl: true,
      zoomControlOptions: {
        style: google.maps.ZoomControlStyle.SMALL
      },
      overviewMapControl: true,
      overviewMapControlOptions: {
        opened: true
      },
      mapTypeControl: false
    }
    

    如何操作…

您可以根据前一个截图显示的样式,根据您的喜好将内置控件设置为开启或关闭。

它是如何工作的...

您可能已经注意到,我们在mapOptions对象中添加了很多内容。这是因为您可以在google.maps.mapOptions对象中使控件可见或不可见:

panControl: true,
scaleControl: false,
zoomControl: true,
mapTypeControl: false,
overviewMapControl: true

通过分配布尔值(true/false),您可以显示panControlzoomControloverviewMapControl,而scaleControlmapTypeControl则被隐藏。

一些控件默认显示。例如,我们未在mapOptions对象中提及streetViewControl;然而,由于默认存在,它在界面中显示。内置控件及其在 UI 中的默认存在如下:

控件名称 默认存在
缩放控件
平移控件
比例尺控件
地图类型控件
街景控件
旋转控件 是(用于斜视图像)
概览地图控件

虽然 rotateControl 默认显示,但您可能已经注意到它未出现在界面中,因为它仅在显示斜视图像时出现。按照以下方式调整 mapOptions 对象,我们可以查看控件:

  1. 启用 mapTypeControl 以在 UI 中选择卫星图像,如图中所示:

      mapTypeControl: true
    

    如何工作...

您可以在前面的截图中看到 mapTypeControl

在某些位置提供斜视图像(45 度图像),并且无论何时存在,mapTypeControl 都会更新自身,包括用于显示斜视图像的下拉菜单切换。

RotateControl 控件位于平移和缩放控件之间。它允许用户以 90 度间隔旋转斜视图像。此外,平移控件也被修改为带有环形,以便在显示斜视图像时更改其航向:

如何工作...

还有更多...

Google Maps JavaScript API 允许我们不仅可以在开和关之间切换内置控件,还可以自定义它们的属性和样式。例如:

overviewMapControlOptions: {
  opened: true
}

这将概述地图控件设置为 打开 状态。请记住,概述地图控件的默认状态是折叠的,并且此设置在您应用程序开始时将控件设置为 打开。您可以通过按控件右下角的小箭头来随时折叠或打开控件。

对于缩放控件,菜谱中提供的选项如下:

zoomControlOptions: {
  style: google.maps.ZoomControlStyle.SMALL
}

此选项将缩放控件样式设置为小,包含两个小按钮,一个用于放大,另一个用于缩小。zoomControlOptionsstyle 属性的其他选项如下:

google.maps.ZoomControlStyle.LARGE
google.maps.ZoomControlStyle.DEFAULT

LARGE 选项将缩放控件显示为长棒状,您可以在不同的缩放级别之间进行切换。DEFAULT 选项根据屏幕大小决定显示大缩放控件还是小缩放控件。

您可能已经注意到,控件的选项由带有 Options 后缀的对象处理。同样,mapTypeControl 也有 MapTypeControlOptions 对象中的选项。在代码中添加以下行将进行一些修改:

mapTypeControl: true,
mapTypeControlOptions: {
  mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.HYBRID],
  style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
}

mapTypeControlOptions属性中的设置使得mapTypeControl只提供ROADMAPHYBRID地图类型,因此你将无法选择SATELLITETERRAIN地图类型。正如你从第二章中回忆的那样,添加栅格图层mapTypeIds属性不仅接受内置地图类型,还可以通过StyledMapType对象接受样式化地图类型,以及通过ImageMapType对象接受任何瓦片图像源——无论是作为基础地图还是叠加地图。

第二个属性stylemapTypeControl设置为以下拉菜单的形式显示,而不是标准水平栏。mapTypeControlOptionsstyle属性的其它选项包括:

google.maps.MapTypeControlStyle.HORIZONTAL_BAR
google.maps.MapTypeControlStyle.DEFAULT

DEFAULT选项是动态选择缩放控制,根据屏幕大小以水平栏或下拉菜单的形式显示。

注意

控制选项的完整列表

控制选项的完整列表可以在 Google Maps JavaScript API 参考文档的 URL 中找到 developers.google.com/maps/documentation/javascript/reference

更改控制的位置

Google Maps 的控制有它们默认的位置,Google Maps JavaScript API 提供了改变这些默认位置的一定灵活性。你还可以根据 UI 中提供的以下截图中的位置来放置你的自定义控制:

更改控制的位置

前面的截图显示了你可以放置控制的可能位置。值得注意的是,TOP_LEFT不等于LEFT_TOP,其中TOP_LEFT是顶部第一个。

在这个菜谱中,我们将描述如何在 Google Maps UI 中指定控制的位置。

准备工作

这个菜谱基于上一个菜谱的代码;因此,拥有那个代码将完成我们的大部分工作。

如何做到这一点...

你可以使用以下步骤来刷新控制的位置:

  1. 完全更新mapOptions对象如下:

    var mapOptions = {
      center: new google.maps.LatLng(43.771094,11.25033),
      zoom: 13,
      mapTypeId: google.maps.MapTypeId.ROADMAP,
      panControl: true,
      panControlOptions: {
        position:google.maps.ControlPosition.TOP_RIGHT
      },
      zoomControl: true,
      zoomControlOptions: {
        style: google.maps.ZoomControlStyle.SMALL,
        position: google.maps.ControlPosition.BOTTOM_CENTER
      },
      mapTypeControl: true,
      mapTypeControlOptions: {
        position: google.maps.ControlPosition.LEFT_TOP
      },
      streetViewControlOptions: {
        position: google.maps.ControlPosition.LEFT_CENTER
      }
    };
    

    如何做到这一点…

你可以根据自己的喜好在地图 UI 中更改控制的位置,就像你在前面的截图中所观察到的。

它是如何工作的...

mapOptions对象中,我们通过每个控制在其自己的选项中指定的position属性来指定每个控制的位置,就像你在以下代码片段中看到的那样:

mapTypeControlOptions: {
  position: google.maps.ControlPosition.LEFT_TOP
}

以下代码将mapTypeControl放置在地图div元素的左上角,而panControl放置在右上角:

panControlOptions:{
  position: google.maps.ControlPosition.TOP_RIGHT
},

注意

控制位置的完整列表

控制位置的完整列表可以在 Google Maps JavaScript API 参考文档的 URL 中找到(developers.google.com/maps/documentation/javascript/reference#ControlPosition)。

创建并添加地理位置控制

Google Maps UI 在之前的菜谱中引入了许多内置控制。这些控制满足了许多需求,如平移、缩放和更改地图类型。然而,用户的需求是无限的,用户可能非常具有创造性。不可能为每个需求提供内置控制。

相反,Google Maps JavaScript API 为满足每个特定需求提供了创建自定义控制的路径。自定义控制基本上是简单 HTML 元素包裹在一个单一元素中,通常是 <div> 元素。

在本菜谱中,我们将介绍创建自定义控制、将其放置在 Google Maps UI 上以及通过事件处理程序使用它的基础知识。

准备工作

本菜谱将基于在 第一章 中介绍的 从 Web 移动到移动设备 菜谱,Google Maps JavaScript API 基础。我们的菜谱将利用此菜谱中的地理位置代码片段;因此,回顾此菜谱将很有帮助。

如何做到这一点…

如果你执行以下步骤,你将拥有一个全新的自定义地理位置控制:

  1. 首先,创建一个 JavaScript 对象,它将成为我们最终的自定义控制(构造函数将接受两个参数,将在后续步骤中解释):

    function GeoLocationControl(geoLocControlDiv, map){
    }
    
  2. GeoLocationControl 类内部,将 class 属性设置为包含构造函数中作为第一个参数引用的 div 元素:

    geoLocControlDiv.className = 'controlContainer';
    
  3. GeoLocationControl 类内部,设置内部 HTML div 元素的详细信息,包括其 class 属性,以便此元素看起来像一个按钮:

    var controlButton = document.createElement('div');
    controlButton.className = 'controlButton';
    controlButton.innerHTML = 'Geolocate';
    
  4. 按如下方式将此内部 div 元素(controlButton)添加到容器 div 元素中:

    geoLocControlDiv.appendChild(controlButton);
    
  5. GeoLocationControl 类内部为 controlButton 添加 click 事件监听器:

    google.maps.event.addDomListener(controlButton, 'click', function() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(position) {
            var lat = position.coords.latitude;
            var lng = position.coords.longitude;
            var devCenter = new google.maps.LatLng(lat, lng);
            map.setCenter(devCenter);
            map.setZoom(15);
    
            var marker = new google.maps.Marker({
              position: devCenter,
              map: map,
            });
    
        });
      }
    });
    
  6. 现在,在所有菜谱中使用的普通 initMap() 函数中,除了标准的 mapmapOptions 对象的定义外,还需要添加容器 HTML div 元素:

    var geoLocationControlDiv = document.createElement('div');
    
  7. initMap() 中实例化自定义控制类,即 GeoLocationControl 类,提供两个参数:前一步骤中创建的容器 div 元素和 map 对象本身:

    var geoLocationControl = new GeoLocationControl(geoLocationControlDiv, map);
    
  8. 将自定义控制放置在地图 UI 中的其他控制之间:

    map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(geoLocationControlDiv);
    

如何做到这一点…

你应该有一个像前面截图所示的功能齐全的自定义地理位置控制。

它是如何工作的…

与之前的菜谱相比,这个菜谱可能看起来有些复杂,但本质上,在 Google Maps JavaScript API 中创建自定义控制只有一个重要点;你可以利用任何 HTML 元素作为自定义控制。实际上,以下简单的代码片段就足以创建一个自定义控制:

var controlDiv = document.createElement('div');
map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(controlDiv);

此代码创建了一个 HTML div元素,并将其添加到map对象的控件数组中。控件数组是一个二维数组,第一维是google.maps.ControlPosition类中定义的可用位置,第二维是控件。这个没有标签的透明控件将执行任何操作,因为没有为div元素编写事件处理代码;然而,这一现实并不改变这是一个自定义控件的事实。

其他细节,如 CSS 样式、填充属性和事件处理,对于专业自定义控件在地图 UI 用户中的应用是必要的。

在我们的示例中,我们选择创建一个 JavaScript 类来封装所有这些细节,以便于结构化:

function GeoLocationControl(geoLocControlDiv, map)
{
}

我们的类构造函数使用了两个元素:容器div元素和map对象。它需要容器div元素的引用,以便将子元素controlButton添加到其中:

geoLocControlDiv.appendChild(controlButton);

controlButton对象(一个 HTML div元素)必须响应用户发起的一些事件,以便自定义控件有用且有意义:

google.maps.event.addDomListener(controlButton, 'click', 
function() {

});

google.maps.event.addDomListener方法充当事件处理程序注册,它在每个浏览器上都以相同的方式工作。这些方法和事件相关主题将在第五章理解 Google Maps JavaScript API 事件中介绍。现在,了解click事件,该事件将由controlButton对象监听,就足够了。

从第一章中提取的地理位置代码,Google Maps JavaScript API 基础知识,位于addDomListener方法内部,利用了浏览器的地理位置 API。如果支持地理位置 API 并且成功获取了位置,就会在地图上为该位置添加一个标记:

var marker = new google.maps.Marker({
  position: devCenter,
  map: map,
});

整个创建子元素和事件处理逻辑的过程都被封装在一个 JavaScript 类构造函数中,该构造函数如下所示:

var geoLocationControl = new GeoLocationControl(geoLocationControlDiv, map);

以下是需要完成此任务所需的唯一其他代码片段:

map.controls[google.maps.ControlPosition.RIGHT_CENTER].push(geoLocationControlDiv);

值得注意的是,controls数组将容器div元素作为自定义控件。同时,请记住,controls[google.maps.ControlPosition.RIGHT_CENTER]在其他场景中可能已经包含其他控件。我们使用push是为了不替换现有的控件。

为图层创建目录控件

在桌面 GIS 软件中,如 ArcGIS 桌面、Mapinfo 和 Geomedia,表目录(ToC)控件以及 UI 元素非常常见。同样,它们的 Web 对应版本也在 UI 中大量使用 ToC,包括 ArcGIS 和.Net Web 组件。

目录的主要用途是打开和关闭各种栅格或矢量图层,以便叠加和查看数据的多个层次。对于矢量图层,选项可以通过允许用户根据目录更改矢量图层的符号来丰富。

Google Maps UI 没有内置的目录控制;然而,通过构建自定义控制的灵活性,实际上有无限的可能性。

Google Maps JavaScript API 允许开发者利用第三方基础地图,如 OpenStreetMaps,或在基础地图上显示覆盖的栅格图层(在第二章添加栅格图层中详细讨论)。此外,在第三章添加矢量图层中,各种类型的矢量数据已在相应的菜谱中叠加。

在这个菜谱中,我们只将基础地图添加到我们的目录中,以便了解结构,包括保持控制的状态和为多个 HTML 元素(这些元素被一个控制包裹)提供多个事件处理程序。当然,这种结构可以通过添加覆盖和矢量图层来丰富。

准备工作

本菜谱将使用第二章中使用不同的瓦片源作为基础地图的菜谱,添加栅格图层。在开始当前菜谱之前回顾这个菜谱将非常有帮助。此外,为了理解如何创建一个简单的自定义控制,前一个菜谱将是关键。

如何操作…

以下是在 Google Maps UI 中创建一个工作目录控制的步骤:

  1. 创建一个 JavaScript 类,该类将包含所有我们的子控件和事件处理程序(直到步骤 12,所有代码都将嵌入到这个类构造函数中):

    function TableOfContentsControl(tocControlDiv, map){
    }
    
  2. this作为一个变量,因为它在事件处理程序中将超出作用域:

    var tocControl = this;
    
  3. 在类构造函数中设置容器div元素的 CSS 属性:

    tocControlDiv.className ='tocControl';
    
  4. 设置目录标题:

    var tocLabel = document.createElement('label');
    tocLabel.appendChild(document.createTextNode('Base Layers'));
    tocControlDiv.appendChild(tocLabel);
    
  5. 创建一个单选按钮用于OpenStreetMap 基础地图

    var osmStuffDiv = document.createElement('div');
    
    var osmRadioButton = document.createElement('input');
    osmRadioButton.type = 'radio';
    osmRadioButton.name = 'BaseMaps';
    osmRadioButton.id = 'OSM';
    osmRadioButton.checked = false;
    
    var osmLabel = document.createElement('label');
    osmLabel.htmlFor = osmRadioButton.id;
    osmLabel.appendChild(document.createTextNode('OpenStreetMap Base Map'));
    
    osmStuffDiv.appendChild(osmRadioButton);
    osmStuffDiv.appendChild(osmLabel);
    
  6. 创建一个单选按钮用于Google Roadmap基础地图:

    var roadmapStuffDiv = document.createElement('div');
    
    var roadmapRadioButton = document.createElement('input');
    roadmapRadioButton.type = 'radio';
    roadmapRadioButton.name = 'BaseMaps';
    roadmapRadioButton.id = 'Roadmap';
    roadmapRadioButton.checked = true;
    
    var roadmapLabel = document.createElement('label');
    roadmapLabel.htmlFor = roadmapRadioButton.id;
    roadmapLabel.appendChild(document.createTextNode('Google Roadmap'));
    
    roadmapStuffDiv.appendChild(roadmapRadioButton);
    roadmapStuffDiv.appendChild(roadmapLabel);
    
  7. 创建一个单选按钮用于Google 卫星地图基础地图:

    var satelliteStuffDiv = document.createElement('div');
    
    var satelliteRadioButton = document.createElement('input');
    satelliteRadioButton.type = 'radio';
    satelliteRadioButton.name = 'BaseMaps';
    satelliteRadioButton.id = 'Satellite';
    satelliteRadioButton.checked = false;
    
    var satelliteLabel = document.createElement('label');
    satelliteLabel.htmlFor = roadmapRadioButton.id;
    satelliteLabel.appendChild(document.createTextNode('Google Satellite'));
    
    satelliteStuffDiv.appendChild(satelliteRadioButton);
    satelliteStuffDiv.appendChild(satelliteLabel);
    
  8. 将所有单选按钮及其标签放入父div元素中:

    tocControlDiv.appendChild(osmStuffDiv);
    tocControlDiv.appendChild(roadmapStuffDiv);
    tocControlDiv.appendChild(satelliteStuffDiv);
    
  9. 创建osmRadioButtonclick事件处理程序(setActiveBasemapgetActiveBasemap方法将在以下代码中阐明):

    google.maps.event.addDomListener(osmRadioButton, 'click', 
    function() {
         if (osmRadioButton.checked) {
             tocControl.setActiveBasemap('OSM');
             map.setMapTypeId(tocControl.getActiveBasemap());
          }
    });
    
  10. 创建roadmapRadioButtonclick事件处理程序如下:

    google.maps.event.addDomListener(roadmapRadioButton, 
    'click', function() {
        if (roadmapRadioButton.checked){
            tocControl.setActiveBasemap
    (google.maps.MapTypeId.ROADMAP);
            map.setMapTypeId(tocControl.getActiveBasemap());
         }
    });
    
  11. 创建satelliteRadioButtonclick事件处理程序:

    google.maps.event.addDomListener(satelliteRadioButton, 
    'click', function() {
         if (satelliteRadioButton.checked) {
              tocControl.setActiveBasemap
    (google.maps.MapTypeId.SATELLITE);
              map.setMapTypeId(tocControl.getActiveBasemap());
           }
    });
    
  12. TableOfContentsControl类构造函数外部,定义一个用于保持活动基础地图的属性:

    TableOfContentsControl.prototype._activeBasemap = null;
    
  13. 定义_activeBasemap属性的获取器和设置器方法:

    TableOfContentsControl.prototype.getActiveBasemap = 
    function() {
      return this._activeBasemap;
    };
    
    TableOfContentsControl.prototype.setActiveBasemap = 
    function(basemap) {
      this._activeBasemap = basemap;
    };
    
  14. initMap()函数中,定义mapOptions对象如下:

    var mapOptions = {
      center: new google.maps.LatLng(39.9078, 32.8252),
      zoom: 10,
      mapTypeControlOptions: {
        mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.SATELLITE, 'OSM']
      },
      mapTypeControl: false
    };
    
  15. osmMapType对象定义为ImageMapType

    var osmMapType = new google.maps.ImageMapType({
      getTileUrl: function(coord, zoom) {
        return 'http://tile.openstreetmap.org/' + zoom + '/' + coord.x + '/' + coord.y + '.png';
      },
      tileSize: new google.maps.Size(256, 256),
      name: 'OpenStreetMap',
      maxZoom: 18
    });
    
  16. 'OSM' mapTypeId对象关联到osmMapType对象:

    map.mapTypes.set('OSM', osmMapType);
    
  17. 设置启动时的mapTypeId

    map.setMapTypeId(google.maps.MapTypeId.ROADMAP);
    
  18. 创建容器 div 元素,实例化 TableOfContentsControl 类,并将容器 div 元素定位为自定义控件:

    var tableOfContentsControlDiv = document.createElement('div');
    
    var tableOfContentsControl = new TableOfContentsControl(tableOfContentsControlDiv, map);
    
    map.controls[google.maps.ControlPosition.TOP_RIGHT].push(tableOfContentsControlDiv);
    

    如何操作…

你应该在地图的 UI 中拥有自己的目录控件作为自定义控件,如前一个屏幕截图所示。

它是如何工作的...

这个配方实际上与之前的配方具有相同的结构;然而,自定义控件中有 HTML 元素使其看起来更复杂。我们将逐步查看细节,以便事情变得清晰。与之前的配方一样,我们首先创建了一个 JavaScript 类构造函数,它嵌入所有细节,包括必要的单选按钮及其事件处理程序:

function TableOfContentsControl(tocControlDiv, map){
}

嵌入在 TableOfContentsControl 中的 osmRadioButton 单选按钮部分如下:

var osmRadioButton = document.createElement('input');
osmRadioButton.type = 'radio';
osmRadioButton.name = 'BaseMaps';
osmRadioButton.id = 'OSM';
osmRadioButton.checked = false;

var osmLabel = document.createElement('label');
osmLabel.htmlFor = osmRadioButton.id;
osmLabel.appendChild(document.createTextNode('OpenStreetMap Base Map'));

tocControlDiv.appendChild(osmRadioButton);
tocControlDiv.appendChild(osmLabel);

google.maps.event.addDomListener(osmRadioButton, 'click', 
function() {
    if (osmRadioButton.checked) {
        tocControl.setActiveBasemap('OSM');
        map.setMapTypeId(tocControl.getActiveBasemap());
    }
});

之前为 osmRadioButton 的代码与 roadmapRadioButtonsatelliteRadioButton 相同。该代码创建单选按钮及其关联的标签,将其添加到容器 div 元素(这是构造函数的第一个参数引用),然后为单选按钮注册 click 事件。

click 事件检查单选按钮是否被选中,然后——如果被选中——它将活动基础地图设置为 OSM 基础地图。然后,它使用活动基础地图信息来设置地图的 mapTypeId;这作为构造函数的第二个参数引用。

要设置和获取活动基础地图信息,使用了两种方法:

setActiveBasemap('OSM')
getActiveBasemap()

这些方法在构造函数外部定义如下:

TableOfContentsControl.prototype.getActiveBasemap = function() {
  return this._activeBasemap;
};

TableOfContentsControl.prototype.setActiveBasemap = 
function(basemap) {
     this._activeBasemap = basemap;
};

在这里,定义了 _activeBasemap 局部变量:

TableOfContentsControl.prototype._activeBasemap = null;

这里有一个微小但重要的细节。为了使 click 事件处理程序能够看到 TableOfContentsControl 对象的获取器和设置器方法,我们添加了一行代码:

var tocControl = this;

在这里,this 在事件处理程序内部将超出作用域。

OpenStreetMap 基础地图部分位于 initMap() 函数中。如何显示外部基础地图的详细信息在 第二章 添加栅格图层 中有介绍,因此无需详细说明。

最后一件工作实际上是在 UI 中运行控件。因为我们没有调用 TableOfContentsControl 的构造函数,所以不会显示为自定义目录控件。但在拥有目录控件之前,我们必须在 mapOptions 对象中预留一些空间:

mapTypeControlOptions: {
  mapTypeIds: [google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.SATELLITE, 'OSM']
},
mapTypeControl:false

mapTypeControlOptions 中,我们在 mapTypeIds 属性中列出地图的可能地图类型 ID。

然而,我们不再需要 maptypeControl,因为我们会有目录控件;因此,我们将 mapTypeControl 属性设置为 false

然后是最后一个阶段:放置自定义的目录控件:

var tableOfContentsControlDiv = document.createElement('div');

var tableOfContentsControl = new TableOfContentsControl(tableOfContentsControlDiv, map);

map.controls[google.maps.ControlPosition.TOP_RIGHT].push(tableOfContentsControlDiv);

首先,我们创建一个任意的div,它将作为自定义控件的容器div元素。然后,我们调用TableOfContentsControl类的构造函数,将容器div元素和map对象作为参数传递。之后,通过将容器div元素添加到控制map对象的二维控件数组中,即mapTypeControl中的默认位置,帷幕落下;也就是说,google.maps.ControlPosition.TOP_RIGHT

添加自定义标志作为控件

Google Maps JavaScript API 以非常灵活的方式设计了自定义控件的添加,这样你可以在一个 HTML div元素中拥有不同类型的 HTML 元素。

添加你选择的标志,例如在你的应用程序的地图 UI 上添加你公司的标志,是自定义化的好迹象,也展示了你的工作。

在本食谱中,我们将使用 Google Maps JavaScript API 在地图 UI 中显示一个标志作为控件。

准备工作

本食谱将使用第一章的第一个食谱,即Google Maps JavaScript API 基础,因为我们只需要基础知识来开发这个食谱。

如何操作…

以下是在 Google Maps UI 中显示标志作为自定义控件的步骤:

  1. initMap()函数中创建map对象后,创建容器div元素:

    var logoDiv = document.createElement("div");
    
  2. 然后,创建包含你首选标志的 HTML img元素:

    var logoPic = document.createElement("img");
    logoPic.src = "ch04_logo.PNG";
    logoPic.id = "CompanyLogo";
    
  3. img元素插入到容器div元素中:

    logoDiv.appendChild(logoPic);
    
  4. 将容器div元素添加到map对象的controls数组中:

    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(logoDiv);
    

    如何操作…

你可以在地图的 UI 中以自定义控件的形式拥有你喜欢的标志,如前一个截图所示。

工作原理...

本食谱的代码实际上是 Google Maps JavaScript API 中自定义控件的最简单形式。没有为控件设置事件处理器,也没有与控件相关的状态信息。唯一存在的是控件本身,即容器 div logoDiv元素。

logoPic元素和img元素保持对标志文件的引用,并嵌入到logoDiv中:

var logoPic = document.createElement("img");
logoPic.src = "ch04_logo.PNG";
logoDiv.appendChild(logoPic);

最后,将logoDiv添加到controls数组中的LEFT_BOTTOM位置。当你打开应用程序时,你可以在地图 UI 中指定位置看到你的标志。

第五章。理解 Google Maps JavaScript API 事件

在本章中,我们将介绍:

  • 创建两个并排同步的地图

  • 获取鼠标点击的坐标

  • 在地图上创建上下文菜单

  • 限制地图范围

  • 创建一个显示坐标的控制面板

  • 创建您自己的事件

简介

如果您曾经从事 JavaScript 编程工作,您应该知道事件的重要性。事件是 JavaScript 的核心。网页交互背后都有事件。可能有用户交互或浏览器操作可以通过事件来处理。

例如,在这本书的开头每个代码中,我们都写了一些像以下这样的代码行:

google.maps.event.addDomListener(window, 'load', initMap);

这行是一个简单的事件定义形式。这行代码告诉浏览器在所有内容加载后调用initMap()函数。此事件在加载所有 DOM 元素后启动映射函数是必需的。

本章介绍如何使用 Google Maps JavaScript API 中的事件以不同方式与地图交互。Google Maps JavaScript API 具有google.maps.event命名空间来处理事件。此命名空间具有静态方法来监听 API 中定义的事件。您应通过 Google Maps API 参考文档检查 API 中对象的受支持事件类型。

创建两个并排同步的地图

地图对人类很有用。借助地图,人们可以探索或比较他们周围的环境。有时他们需要并排比较两个地图以实时查看差异。例如,您可能想并排检查卫星图像和地形图,以查看山脉的位置。

本食谱向您展示如何在同一页面上添加两个地图并将它们同步,以显示相同区域,并通过 Google Maps JavaScript API 事件进行比较。

准备工作

您已经知道如何从前面的章节创建地图。因此,只需编写额外的代码行。

您可以在Chapter 5/ch05_sync_maps.html中找到源代码。

如何操作…

如果您想创建两个同步的地图,您应该执行以下步骤:

  1. 首先,在标题中添加div对象的 CSS 样式以显示它们并排:

    .mapClass { width: 500px; height: 500px; display: inline-block; }
    
  2. 然后定义两个全局地图变量以在事件回调中访问它们:

    var map1, map2;
    
  3. 接下来创建初始化左侧地图的函数:

    function initMapOne() {
      //Setting starting options of map
      var mapOptions = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        maxZoom: 15,
        mapTypeId: google.maps.MapTypeId.ROADMAP
      };
    
      //Getting map DOM element
      var mapElement = document.getElementById('mapDiv');
    
      //Creating a map with DOM element which is just obtained
      map1 = new google.maps.Map(mapElement, mapOptions);
    
      //Listening center_changed event of map 1 to
      //change center of map 2
      google.maps.event.addListener(map1, 'center_changed', function() { 
         map2.setCenter(map1.getCenter());
      });
    
      //Listening zoom_changed event of map 1 to change
      //zoom level of map 2
      google.maps.event.addListener(map1, 'zoom_changed', function() {
          map2.setZoom(map1.getZoom());
      });
    }
    
  4. 现在,添加第二个初始化右侧地图的函数。之前创建的函数内容几乎相同,只是变量名、div ID、地图类型和事件处理程序中的计时器不同:

    function initMapTwo() {
      //Setting starting options of map
      var mapOptions2 = {
        center: new google.maps.LatLng(39.9078, 32.8252),
        zoom: 10,
        maxZoom: 15,
        mapTypeId: google.maps.MapTypeId.TERRAIN
      };
    
      //Getting map DOM element
      var mapElement2 = document.getElementById('mapDiv2');
    
      //Creating a map with DOM element which is just //obtained
      map2 = new google.maps.Map(mapElement2, mapOptions2);
    
      //Listening center_changed event of map 2 to 
      //change center of map 1
      google.maps.event.addListener(map2, 'center_changed', function() {
          setTimeout(function() { map1.setCenter(map2.getCenter());
          }, 10);
      });
    
      //Listening zoom_changed event of map 2 to change
      //zoom level of map 1
      google.maps.event.addListener(map2, 'zoom_changed', function() {
          setTimeout(function() {
            map1.setZoom(map2.getZoom());
          }, 10);
      });
    }
    
  5. 我们现在有两个地图,我们必须在开始时初始化它们,因此我们需要一个调用之前函数的单个函数:

    //Starting two maps
    function initMaps() {
      initMapOne();
      initMapTwo();
    }
    
  6. window元素的load事件上所有内容都已加载时,调用initMaps()函数:

    google.maps.event.addDomListener(window, 'load', initMaps);
    
  7. 不要忘记在 HTML 标签中添加两个div对象:

    <div id="mapDiv" class="mapClass"></div>
    <div id="mapDiv2" class="mapClass"></div>
    
  8. 前往你最喜欢的浏览器中存储你的 HTML 文件的本地区址,查看结果。你会看到两个并排的地图。当你拖动或缩放一个地图时,另一个地图也会根据变化而改变。最终的地图将看起来像以下截图:如何操作…

由于这个菜谱的结果,我们可以创建两个同步显示不同地图类型相同区域的地图。

它是如何工作的...

这个菜谱中的重要点是保持两个地图在相同的位置和缩放级别上同步。为了实现这个目标,我们需要知道地图何时移动以及缩放级别何时发生变化。

map 对象有不同的触发事件。center_changedzoom_changed 事件是其中两个事件。center_changed 事件在地图中心每次改变时都会触发。还有 bounds_changeddragdragstartdragend 事件,这些事件也可以用来实现我们的目标,但 center_changed 是处理起来最简单的一个。zoom_changed 事件在缩放级别发生变化时触发。

要监听事件,我们需要将这些事件注册到 google.maps.event 命名空间中,如下所示:

google.maps.event.addListener(map1, 'center_changed', function() {
  map2.setCenter(map1.getCenter());
});

addListener() 方法接收三个参数;一个要监听的对象、一个事件类型和一个事件处理器。事件处理器可以是一个之前定义过的函数或者仅在此处使用的匿名函数。在这个例子中,我们监听左地图对象——map1——的 center_changed 事件,并将右地图的中心设置为左地图的中心。缩放部分也是以同样的方式工作。当左地图的缩放级别发生变化时,zoom_changed 事件的处理器将右地图的缩放级别设置为左地图的缩放级别。

这对于右地图也应该是一样的,但由于无限事件循环,用于右地图的事件处理代码与左地图的代码略有不同。如果我们使用相同的事件处理代码,会在两个地图之间创建一个无限循环。这个循环会导致你的浏览器在大多数情况下崩溃。为了避免这个无限循环,我们在事件之间创建一个小的中断(10 毫秒)。这个中断将解决所有问题,用户不会注意到差异。这个中断是通过 JavaScript 的 setTimeout() 函数创建的。此外,还有更好的版本可以使用,这在本菜谱的 还有更多… 部分中解释。这个菜谱涵盖了在这些情况下使用地图事件的方法:

google.maps.event.addListener(map2, 'zoom_changed', function() {
  setTimeout(function() {
    map1.setZoom(map2.getZoom());
  }, 10);
});

还有更多…

在这个菜谱中,我们的事件正在持续监听,这是预期的。但如果你需要监听一个有限时间的事件呢?有两种选择。一种选择是存储 addListener() 函数返回的 google.maps.MapEventListener 对象,并在需要时移除它,如下所示:

var eventObj = google.maps.event.addListener(map1, 'center_changed',function() {
    map2.setCenter(map1.getCenter());
});
function removeListener() {
    google.maps.event.removeListener(eventObj);
}

另一种移除事件监听器的方法是使用google.maps.event命名空间中的clearInstanceListeners()clearListeners()函数,分别用于移除给定实例的所有事件监听器或移除给定实例的指定事件监听器。以下代码示例了如何使用:

var eventObj = google.maps.event.addListener(map1, 'center_changed',function() {
    map2.setCenter(map1.getCenter());
});
//This one removes all the listeners of map1 object
google.maps.event.clearInstanceListeners(map1); 
//This one removes all center_changed listeners of map1 object
google.maps.event.clearListeners(map1, 'center_changed');

Google Maps JavaScript API 还提供了其他方法来监听 DOM 事件,在google.maps.event命名空间下命名为addDomListener()。还有一个addListenerOnce()方法用于监听一次事件。

同步地图还有另一种方法。以下代码块仅同步两个地图的事件:

map2.bindTo('center', map1, 'center');
map2.bindTo('zoom', map1, 'zoom');

参见

  • 在第一章的在自定义 DIV 元素中创建简单地图食谱中,Google Maps JavaScript API 基础知识

获取鼠标点击的坐标

鼠标长期以来一直是计算机最有效的输入设备。如今,有人试图用触摸屏来改变它,但没有任何东西能比得上鼠标提供的易用性。

鼠标在地图上有不同的交互方式,如点击、双击、右键点击、移动和拖动。这些事件可以通过不同的方式处理,以与用户交互。

在这个食谱中,我们将获取地图上任何点的鼠标点击坐标。用户在鼠标点击时将看到一个信息窗口,如下面的截图所示:

获取鼠标点击的坐标

这就是我们实现创建一个地图,该地图可以监听每个鼠标点击以获取坐标的方法。

准备工作

我们假设您已经知道如何创建一个简单的地图。我们只会介绍在鼠标点击时显示信息窗口所需的代码。

您可以在第五章/ch05_getting_coordinates.html中找到源代码。

如何做到这一点...

如果您执行以下步骤,您就可以获取地图上每个鼠标点击的坐标:

  1. 首先,我们必须在 JavaScript 代码的开头添加一个infowindow变量,用作全局对象:

    var infowindow = null;
    
  2. 然后,在map对象初始化之后,将以下行添加到initMap()函数中:

    google.maps.event.addListener(map, 'click', function(e) {
      if (infowindow != null)
        infowindow.close();
    
      infowindow = new google.maps.InfoWindow({
        content: '<b>Mouse Coordinates : </b><br><b>Latitude : </b>' + e.latLng.lat() + '<br><b>Longitude: </b>' + e.latLng.lng(),
        position: e.latLng
      });
      infowindow.open(map);
    });
    
  3. 在您最喜欢的浏览器中访问本地 URL,其中存储着您的 HTML 文件,并点击地图以查看结果。每次鼠标点击都会打开一个包含坐标信息的信息窗口。

它是如何工作的...

在初始化地图后,我们创建一个事件监听器来处理地图上的鼠标点击事件:

google.maps.event.addListener(map, 'click', function(e) {

});

click事件类型与其他事件有不同的处理程序。处理程序有一个输入参数,是一个从google.maps.MouseEvent类派生的对象。该对象有一个名为LatLng的属性,它是google.maps.latLng类的实例。该属性给出了鼠标点击的坐标。

我们希望在每次鼠标点击时显示一个信息窗口,并且只想看到一个信息窗口。为了实现这一点,我们在 JavaScript 代码的开头创建一个全局变量infowindow,并检查它是否仍然被定义。如果之前点击有一个infowindow对象,那么我们将按照以下方式关闭它:

if (infowindow != null)
  infowindow.close();

在每次鼠标点击时,我们将从e.latLng对象创建一个新的infowindow对象,包含新的内容、坐标和位置。创建infowindow后,我们只需在之前定义的地图上打开它:

infowindow = new google.maps.InfoWindow({
  content: '<b>Mouse Coordinates : </b><br><b>Latitude : </b>' + e.latLng.lat() + '<br><b>Longitude: </b>' + e.latLng.lng(),
  position: e.latLng
});
infowindow.open(map);

更多内容…

如前所述,某些事件类型有不同的事件处理程序,可以获取一个参数。这个参数是从google.maps.MouseEvent类派生出的一个对象。google.maps.Map类有以下事件,它们将MouseEvent对象返回给处理程序:clickdblclickmousemovemouseoutmouseoverrightclick

参见

  • 第一章中“在自定义 DIV 元素中创建简单地图”的配方,Google Maps JavaScript API 基础知识

在地图上创建上下文菜单

在用户界面中使用菜单是与用户沟通的一种方式。用户选择菜单项以与 Web 应用程序交互。某些菜单类型可以从可见位置访问,但某些菜单类型需要一些额外操作才能访问,例如上下文菜单。上下文菜单通常在鼠标右键单击的应用程序中显示。

在这个配方中,我们将在地图上创建一个上下文菜单,当我们右键单击地图时,该菜单将打开。此菜单包括放大、缩小和添加标记功能。您还将获得右键单击的位置,以便在例如添加标记的某些地理方法中使用它。

准备工作

这个配方与其他配方类似,我们假设您已经知道如何创建一个简单的地图。因此,我们只会显示添加上下文菜单的额外代码行。

您可以在Chapter 5/ch05_context_menu.html中找到源代码。

如何操作…

以下是我们需要创建一个带有上下文菜单以显示额外命令的地图的步骤:

  1. 让我们从向 HTML 的标题中添加上下文菜单的 CSS 样式开始:

    .contextmenu{
      visibility: hidden;
      background: #ffffff;
      border: 1px solid #8888FF;
      z-index: 10;
      position: relative;
      width: 100px;
      height: 50px;
      padding: 5px;
    }
    
  2. 下一步是定义上下文菜单和坐标的全局变量:

    var contextMenu, lastCoordinate;
    
  3. 然后,我们将添加定义上下文菜单类的行。详细信息将在稍后解释:

    //Defining the context menu class.
    function ContextMenuClass(map) {
      this.setMap(map);
      this.map = map;
      this.mapDiv = map.getDiv();
      this.menuDiv = null;
    };
    
    ContextMenuClass.prototype = new google.maps.OverlayView();
    ContextMenuClass.prototype.draw = function() {};
    ContextMenuClass.prototype.onAdd = function() {
      var that = this;
      this.menuDiv = document.createElement('div');
      this.menuDiv.className = 'contextmenu';
      this.menuDiv.innerHTML = '<a href="javascript:createMarker()">Create Marker Here</a><br><a href="javascript:zoomIn()">Zoom In</a><br><a href="javascript:zoomOut()">Zoom Out</a><br>';
      this.getPanes().floatPane.appendChild(this.menuDiv);
    
      //This event listener below will close the context menu //on map click
      google.maps.event.addListener(this.map, 'click', function(mouseEvent) {
          that.hide();
      });
    };
    
    ContextMenuClass.prototype.onRemove = function() {
      this.menuDiv.parentNode.removeChild(this.menuDiv);
    };
    
    ContextMenuClass.prototype.show = function(coord) {
      var proj = this.getProjection();
      var mouseCoords = proj.fromLatLngToDivPixel(coord);
      var left = Math.floor(mouseCoords.x);
      var top =  Math.floor(mouseCoords.y);
      this.menuDiv.style.display = 'block';
      this.menuDiv.style.left = left + 'px';
      this.menuDiv.style.top = top + 'px';
      this.menuDiv.style.visibility = 'visible';
    };
    
    ContextMenuClass.prototype.hide = function(x,y) {
      this.menuDiv.style.visibility= 'hidden';
    }
    
  4. 现在,添加上下文菜单中要使用的函数:

    //Defining context menu functions
    function zoomIn() {
      map.setZoom(map.getZoom() + 1);
    }
    
    function zoomOut() {
      map.setZoom(map.getZoom() - 1);
    }
    
    function createMarker() {
      var marker = new google.maps.Marker({
        position: lastCoordinate,
        map: map,
        title: 'Random Marker'
      });
    }
    
  5. 在初始化map对象后添加以下代码块。此块将从ContextMenuClass类创建一个对象,并在map对象中监听右键单击以显示创建的contextMenu对象:

    //Creating a context menu to use it in event handler
    contextMenu = new ContextMenuClass(map);
    
    //Listening the map object for mouse right click.
    google.maps.event.addListener(map, 'rightclick', function(e) {
        lastCoordinate = e.latLng;
        contextMenu.show(e.latLng);
    });
    
  6. 前往您在最喜欢的浏览器中存储 HTML 的本地 URL,并右键单击以查看上下文菜单。如何操作…

如前述截图所示,我们已创建带有上下文菜单的简单地图。

它是如何工作的…

JavaScript 是一种基于原型的脚本语言,它以不同于经典服务器端编程语言的方式支持面向对象编程。没有经典的类定义,但你可以使用原型来创建类或继承其他类。这本书不是一本 JavaScript 书。如果你对 JavaScript 的这些概念有任何疑问,你应该通过 google 搜索来学习详细信息。

Google Maps JavaScript API 有一个 google.maps.OverlayView 类,用于在地图上创建你自己的自定义类型的覆盖对象。我们将继承这个类来创建我们自己的上下文菜单类。首先,我们将定义 ContextMenu 类及其构造函数如下:

function ContextMenuClass (map) {
  this.setMap(map);
  this.map = map;
  this.mapDiv = map.getDiv();
  this.menuDiv = null;
};

然后,我们将 ContextMenu 类的原型设置为从 google.maps.OverlayView 类创建的对象:

ContextMenuClass.prototype = new google.maps.OverlayView();

google.maps.OverlayView 类在我们新创建的类中需要实现三个方法:onAdd()draw()onRemove()。除此之外,我们添加了两个方法来显示或隐藏上下文菜单。每个方法的任务如下所述:

  • onAdd():在这个方法中完成 DOM 对象的创建并将它们作为 panes 的子元素添加。我们将创建一个具有在 HTML 顶部定义的 CSS 类的 div 对象。菜单项也通过 innerHTML 属性添加到这个 div 对象中。我们还将创建一个地图点击的事件监听器,以从其他操作中移除上下文菜单:

    ContextMenuClass.prototype.onAdd = function () {
      var that = this;
      this.menuDiv = document.createElement('div');
      this.menuDiv.className = 'contextmenu';
      this.menuDiv.innerHTML = '<a href="javascript:createMarker()">Create Marker Here</a><br><a href="javascript:zoomIn()">Zoom In</a><br><a href="javascript:zoomOut()">Zoom Out</a><br>';
      this.getPanes().floatPane.appendChild(this.menuDiv);
    
      //This event listener below will close the context menu // on map click
      google.maps.event.addListener(this.map, 'click', function(mouseEvent){
          that.hide();
      });
    };
    
  • draw():通过这个方法完成创建的元素的位置设置,但我们跳过填充这个方法的步骤。我们创建 show()hide() 方法,而不是每次添加或移除上下文菜单:

    ContextMenuClass.prototype.draw = function() {};
    
  • onRemove():在这个方法中完成创建的元素的移除:

    ContextMenuClass.prototype.onRemove = function() {
      this.menuDiv.parentNode.removeChild(this.menuDiv);
    };
    
  • show(coord):在这个方法中完成在鼠标右键点击时显示上下文菜单。输入参数是一个 latLng 对象,因此我们必须将其转换为 div 元素中的像素坐标。为了实现这一点,我们需要从 google.maps.MapCanvasProjection 类创建额外的对象。这个类有一个名为 fromLatLngToDivPixel 的方法,可以将 latLng 对象转换为简单的 google.maps.Point 对象。这个对象用于设置从地图左上角开始的上下文菜单的 xy 坐标。我们还将 div 的可见性样式更改为在地图上显示:

    ContextMenuClass.prototype.show = function(coord) {
      var proj = this.getProjection();
      var mouseCoords = proj.fromLatLngToDivPixel(coord);
      var left = Math.floor(mouseCoords.x);
      var top =  Math.floor(mouseCoords.y);
      this.menuDiv.style.display = 'block';
      this.menuDiv.style.left = left + 'px';
      this.menuDiv.style.top = top + 'px';
      this.menuDiv.style.visibility = 'visible';
    };
    
  • hide():在这个方法中完成上下文菜单的隐藏。我们只需将上下文菜单 div 的可见性属性更改为 hidden 以隐藏它:

    ContextMenuClass.prototype.hide = function(x,y) {
      this.menuDiv.style.visibility = 'hidden';
    }
    

ContextMenuClass 类已经定义,但还没有从这个类创建任何对象。我们根据新类创建了一个 contextMenu 对象,如下所示:

contextMenu = new ContextMenuClass(map);

为了使用这个 contextMenu 对象,我们应该监听 map 对象的 rightclick 事件并在其处理程序中显示上下文菜单。我们还将更新全局变量 lastCoordinate 以保持最后一次右键点击的坐标,以便在 createMarker() 函数中使用:

google.maps.event.addListener(map, 'rightclick', function(e) {
  lastCoordinate = e.latLng;
  contextMenu.show(e.latLng);
});

上下文菜单功能在之前的章节中已有介绍,因此这里不再解释。你还可以借助google.maps.OverlayView类创建其他类型的覆盖层,就像本食谱中这样。

注意

更多关于 JavaScript 基于原型的继承

如果你对 JavaScript 基于原型的继承细节感兴趣,请从以下页面获取更多详细信息:javascript.crockford.com/prototypal.html

本文由道格拉斯·克罗克福德撰写,他是 JavaScript 的大师和 JSON 格式的创始人。我建议你阅读他的流行 JavaScript 书籍 JavaScript: The Good Parts,以深入了解 JavaScript。

参见

  • 第一章 Google Maps JavaScript API 基础知识在自定义 DIV 元素中创建简单地图 食谱

  • 第一章 Google Maps JavaScript API 基础知识通过编程更改地图属性 食谱

  • 第三章 添加矢量图层向地图添加标记 食谱

限制地图范围

Google Maps 拥有全球范围,几乎显示了地球上每一条街道。你可以使用 Google Maps JavaScript API 覆盖整个地球,但有时你需要在地图应用程序中仅显示相关区域。你可以放大到固定位置,但这并不能阻止用户移动到应用程序范围之外的其他地方。

在本食谱中,我们将监听地图事件以检查我们是否在允许的范围内。如果我们不在允许的范围内,那么我们将地图移动到该范围内的允许中心。本食谱中我们使用了土耳其的地理范围。

准备工作

本食谱仍在使用第一章中定义的相同地图创建过程,Google Maps JavaScript API 基础知识,但有一些额外的代码块用于监听地图事件和检查受限范围。

你可以在Chapter 5/ch05_restrict_extent.html找到源代码。

如何操作…

如果你执行以下步骤,限制地图范围相当简单:

  1. 首先,我们必须在定义map变量后将allowedMapBoundsallowedZoomLevel变量作为全局变量添加。这是土耳其的地理边界:

    var allowedMapBounds = new google.maps.LatLngBounds(
      new google.maps.LatLng(35.817813, 26.147461),
      new google.maps.LatLng(42.049293, 44.274902)
    );
    var allowedZoomLevel = 6;
    
  2. 下一步是在初始化地图后监听地图的dragzoom_changed事件:

    google.maps.event.addListener(map, 'drag', checkBounds);
    google.maps.event.addListener(map, 'zoom_changed', checkBounds);
    
  3. 然后,我们创建一个checkBounds()函数来处理事件触发时的情况。函数的第一部分是检查缩放级别。我们选择6以最小化本食谱中地图的缩放级别:

    function checkBounds() {
      if (map.getZoom() < allowedZoomLevel) 
        map.setZoom(allowedZoomLevel);
    }
    
  4. 以下代码行将添加到checkBounds()函数中,以获取允许的范围、最近的范围和地图的最近中心:

    if (allowedMapBounds) {
      var allowedNELng = allowedMapBounds.getNorthEast().lng();
      var allowedNELat = allowedMapBounds.getNorthEast().lat();
      var allowedSWLng = allowedMapBounds.getSouthWest().lng();
      var allowedSWLat = allowedMapBounds.getSouthWest().lat();
    
      var recentBounds = map.getBounds();
      var recentNELng = recentBounds.getNorthEast().lng();
      var recentNELat = recentBounds.getNorthEast().lat();
      var recentSWLng = recentBounds.getSouthWest().lng();
      var recentSWLat = recentBounds.getSouthWest().lat();
    
      var recentCenter = map.getCenter();
      var centerX = recentCenter.lng();
      var centerY = recentCenter.lat();
    
      var nCenterX = centerX;
      var nCenterY = centerY;
    }
    
  5. checkBounds() 函数的重要部分是允许边界与最近边界的比较。如果 centerXcenterYnCenterXnCenterY 变量之间存在差异,则我们将地图移动到允许边界内的中心位置:

    if (recentNELng > allowedNELng) centerX = centerX- (recentNELng - allowedNELng);
    if (recentNELat > allowedNELat) centerY = centerY- (recentNELat - allowedNELat);
    if (recentSWLng < allowedSWLng) centerX = centerX+ (allowedSWLng - recentSWLng);
    if (recentSWLat < allowedSWLat) centerY = centerY+ (allowedSWLat - recentSWLat);
    
    if (nCenterX != centerX || nCenterY != centerY) {
      map.panTo(new google.maps.LatLng(centerY,centerX));
    }
    else {
      return;
    }
    
  6. 前往您在最喜欢的浏览器中存储 HTML 的本地 URL,并尝试移动土耳其附近的其他国家地图。您会看到地图会回到顶部定义的边界内允许的先前位置。如何操作…

如前述截图所示,您可以通过 Google Maps JavaScript API 提供的事件轻松限制地图范围。

它是如何工作的...

如前述事件配方所述,Google Maps JavaScript API 为开发者提供了许多与地图活动相关的事件。我们在这个配方中使用 dragzoom_changed 事件来监听。这次,我们并没有创建匿名事件处理器,因为我们为两个事件监听器使用相同的事件处理器,名为 checkBounds()

google.maps.event.addListener(map, 'drag', checkBounds);
google.maps.event.addListener(map, 'zoom_changed', checkBounds);

Google Maps JavaScript API 有 google.maps.LatLngBounds 类用于定义地理边界。该类的构造函数接收两个参数,这两个参数是由 google.maps.LatLng 类创建的对象。参数分别是西南角和东北角的地理坐标。这为我们应用程序创建了一个地理边界。西南角具有最低的纬度和经度,而东北角则具有最高的纬度和经度:

var allowedMapBounds = new google.maps.LatLngBounds(
  new google.maps.LatLng(35.817813, 26.147461),
  new google.maps.LatLng(42.049293, 44.274902)
);

这个配方中的主要技巧在于 checkBounds() 函数。首先,我们获取允许边界和最近边界的最小和最大纬度和经度。NE 标签是纬度和经度的最大值,而 SW 标签是纬度和经度的最小值:

var allowedNELng = allowedMapBounds.getNorthEast().lng();
var allowedNELat = allowedMapBounds.getNorthEast().lat();
var allowedSWLng = allowedMapBounds.getSouthWest().lng();
var allowedSWLat = allowedMapBounds.getSouthWest().lat();

var recentBounds = map.getBounds();
var recentNELng = recentBounds.getNorthEast().lng();
var recentNELat = recentBounds.getNorthEast().lat();
var recentSWLng = recentBounds.getSouthWest().lng();
var recentSWLat = recentBounds.getSouthWest().lat();

地图的中心用于检查差异并根据此值定位地图。nCenterXnCenterY 值用于检查 centerXcenterY 值是否有变化。if 语句检查最近值和允许值。如果地图超出允许的范围,它将改变 centerXcenterY 值:

var recentCenter = map.getCenter();
var centerX = recentCenter.lng();
var centerY = recentCenter.lat();

var nCenterX = centerX;
var nCenterY = centerY;

if (recentNELng > allowedNELng) centerX = centerX - (recentNELng - allowedNELng);
if (recentNELat > allowedNELat) centerY = centerY - (recentNELat - allowedNELat);
if (recentSWLng < allowedSWLng) centerX = centerX + (allowedSWLng - recentSWLng);
if (recentSWLat < allowedSWLat) centerY = centerY + (allowedSWLat - recentSWLat);

如果 centerXcenterY 值发生变化,则我们必须使用 panTo() 方法将地图保持在边界内;否则,使用 return 不做任何操作:

if (nCenterX != centerX || nCenterY != centerY) {
  map.panTo(new google.maps.LatLng(centerY,centerX));
}
else {
  return;
}

检查允许范围可能有不同的方法,例如只检查地图的中心,但这种方法不会限制你想要的精确范围。

参见

  • 第一章中 “在自定义 DIV 元素中创建简单地图” 的配方,Google Maps JavaScript API 基础

  • 第一章中 “通过编程更改地图属性” 的配方,Google Maps JavaScript API 基础

创建显示坐标的控制

地理坐标对于显示你在地球上的位置非常重要。纬度和经度结合在一起形成一个二维网格,模拟地球表面。当你移动鼠标时,在地图上的控件中显示纬度和经度可以是一个很好的控件和事件结合的用法。

在第四章,使用控件中,我们看到了如将自定义标志作为控件添加的配方,并且我们也在本章中看到了如何使用地图事件。在这个配方中,我们将利用地图的mousemove事件创建一个显示实时坐标的控件。

准备工作

在这个配方中,我们将使用第一章,Google Maps JavaScript API 基础知识中定义的第一个配方作为模板,以跳过地图创建。

您可以在第五章/ch05_coordinate_control.html中找到源代码。

如何做到这一点...

您可以通过以下步骤轻松创建一个简单的控件,在鼠标移动时显示坐标:

  1. 首先,我们将在 head 部分的样式部分添加一个 CSS 类,这将装饰坐标控件:

    .mapControl { 
      width: 165px; 
      height: 16px; 
      background-color: #FFFFFF; 
      border-style: solid; 
      border-width: 1px; 
      padding: 2px 5px; 
    }
    
  2. 在初始化地图后,我们将定义控件参数:

    //Defining control parameters
    var controlDiv = document.createElement('div');
    controlDiv.className = 'mapControl';
    controlDiv.id = 'mapCoordinates';
    controlDiv.innerHTML = 'Lat/Lng: 0.00 / 0.00';
    
  3. 然后,使用以下行向地图添加控件:

    //Creating a control and adding it to the map.
    map.controls[google.maps.ControlPosition.LEFT_BOTTOM].push(controlDiv);
    
  4. 现在,我们向地图添加一个事件监听器来处理mousemove事件并在每次mousemove事件中更新坐标:

    //Listening the map for mousemove event to show it in control.
    google.maps.event.addListener(map, 'mousemove', function(e) {
      var coordinateText = 'Lat/Lng: ' + e.latLng.lat().toFixed(6) + ' / ' + e.latLng.lng().toFixed(6);
      controlDiv.innerHTML = coordinateText;
    });
    
  5. 前往您在最喜欢的浏览器中存储 HTML 的本地 URL,并尝试移动鼠标。您将看到坐标控件在地图的左下角发生变化。如何做到这一点…

我们已经成功创建了一个简单的控件,可以在鼠标移动时显示坐标。

它是如何工作的...

这个配方是两个章节的结合:第四章,使用控件,以及本章。更多关于控件的信息可以从第四章,使用控件中获得。

如前所述,Google Maps JavaScript API 为我们提供了不同的事件来监听,用于不同的目的。在这个配方中,我们将使用地图的mousemove事件来获取鼠标的坐标。mousemove事件处理程序有一个输入参数来获取坐标。我们将使用lat()lng()函数分别从e.latLng对象中获取纬度和经度。然后,我们将使用 Math 函数toFixed()将它们的十进制数固定为6位,以便在坐标控件中有一个有序的视图:

//Listening the map for mousemove event to show it in control.
google.maps.event.addListener(map, 'mousemove', function(e) {
  var coordinateText = 'Lat/Lng: ' + e.latLng.lat().toFixed(6) + ' / ' + e.latLng.lng().toFixed(6);
  controlDiv.innerHTML = coordinateText;
});

代码的剩余部分与简单地图创建和创建自定义控件有关,但这不是本章的范围。

参考信息

  • 在自定义 DIV 元素中创建简单地图的配方,Google Maps JavaScript API 基础知识中的创建简单地图配方

  • 第四章,“使用控件”中的添加您的自定义徽标作为控件菜谱

创建您自己的事件

事件对于 JavaScript 编程非常重要,所有的 JavaScript 框架和 API 都为开发者提供了访问与其类相关的预定义事件类型的权限。Google Maps JavaScript API 也在做同样的事情,它为我们提供了最常用的与类相关的事件类型。但如果你需要一个自定义事件类型呢?

Google Maps JavaScript API 有一个名为 google.maps.MVCObject 的基类,这是大多数类继承的顶级类。该类已准备好在 google.maps.event 命名空间中用于自定义事件。

在这个菜谱中,我们将使用 google.maps.MVCObject 类创建一个自定义对象,并将其绑定到自定义事件以创建您自己的事件。自定义事件的用法可能不是一个真实世界的案例,但它会给你一个关于监听和触发您自己的事件的思路。

准备工作

这个菜谱仍然使用在第一章,“Google Maps JavaScript API 基础知识”中定义的相同的地图创建过程,但有一些额外的代码块用于创建目录(ToC)控件和自定义事件。

你可以在 Chapter 5/ch05_custom_events.html 找到源代码。

如何操作…

如果你执行以下步骤,你可以添加和创建你自己的事件类型:

  1. 首先,我们添加我们自定义控件的 CSS 类:

    .mapControl { 
      width: 165px; 
      height: 55px; 
      background-color: #FFFFFF; 
      border-style: solid; 
      border-width: 1px; 
      padding: 2px 5px; 
    }
    
  2. 现在,我们在 map 变量之后创建一个 customObject 变量作为全局变量:

    var customObject;
    
  3. 然后,我们创建 createTOCControl() 来创建我们的目录控件,如下所示:

    function createTOCControl () {
      var controlDiv = document.createElement('div');
      controlDiv.className = 'mapControl';
      controlDiv.id = 'layerTable';
      map.controls[google.maps.ControlPosition.RIGHT_TOP].push(controlDiv);
      var html = '<b>Map Layers</b><br/>';
      html = html + '<input type="checkbox" onclick="checkLayers(this)" value="geojson">GeoJSON Layer<br/>';
      html = html + '<input type="checkbox" onclick="checkLayers(this)" value="marker">MarkerLayer';
      controlDiv.innerHTML = html;
    }
    
  4. 下一步是添加另一个名为 checkLayers() 的函数,该函数是从复选框的 onclick 事件中调用的函数:

    function checkLayers(cb) {
      //Firing customEvent with trigger function.
      google.maps.event.trigger(customObject, 'customEvent', {layerName: cb.value, isChecked: cb.checked});
    }
    
  5. 所有函数都准备好添加到 initMap() 函数中。在初始化地图后添加以下行:

    //Creating Table of Contents Control.
    createTOCControl();
    
    //Creating custom object from Google Maps JS Base Class
    customObject = new google.maps.MVCObject();
    
    //Start listening custom object's custom event
    google.maps.event.addListener(customObject, 'customEvent', function (e) {
        var txt = '';
        if(e.isChecked) {
          txt = e.layerName + ' layer is added to the map';
        }
        else {
          txt = e.layerName + ' layer is removed from the map';
        }
        alert(txt);
    });
    
  6. 前往您在最喜欢的浏览器中存储 HTML 的本地 URL,查看地图。当你点击内容表中的任何一个复选框时,你会看到一个包含图层名称及其状态的警告框,显示该图层是否已被添加或删除。如何操作…

这是菜谱的结果,展示了触发和监听您自己定义的自定义事件。

它是如何工作的…

JavaScript 有一个继承,这是面向对象编程的核心概念之一。这使得你的生活更轻松,以便不必一次又一次地编写相同的方法。

Google Maps JavaScript API 既用于自身也用于 API 开发者,它使用了 JavaScript 的继承。有一些核心类是其他类的基类。Google Maps JavaScript API 有一个名为 google.maps.MVCObject 的基类,所有其他类都是从它产生的。

如果您想创建一个像之前食谱中那样的自定义类,您应该从google.maps.MVCObject类创建一个类。在本食谱中,我们只是从MVCObject类创建一个对象,而不是创建一个新的类。然后,我们将像监听其他事件一样监听这个创建的对象的customEvent

//Creating custom object from Google Maps JS Base Class
customObject = new google.maps.MVCObject();

//Start listening custom object's custom event
google.maps.event.addListener(customObject, 'customEvent', function (e) {
    var txt = '';
    if(e.isChecked) {
      txt = e.layerName + ' layer is added to the map';
    }
    else {
      txt = e.layerName + ' layer is removed from the map';
    }
    alert(txt);
});

触发自定义事件比监听它要容易得多。我们使用google.maps.event.trigger()函数来触发事件,并附带额外的参数。参数应以 JSON 对象格式发送到事件处理器:

//Firing customEvent with trigger function.
google.maps.event.trigger(customObject, 'customEvent', {layerName: cb.value, isChecked: cb.checked});

在本食谱中创建自定义事件不能直接用于实际案例,但这应该能给你一个关于如何使用它们的想法。为了高效使用内存,事件应该谨慎使用。

参见

  • 第一章的在自定义 DIV 元素中创建简单地图食谱,Google Maps JavaScript API 基础知识

  • 第四章的将您的自有标志作为控件添加食谱,使用控件

第六章. Google Maps JavaScript 库

在本章中,我们将涵盖:

  • 在地图上绘制形状

  • 计算多边形和多边形的长度/面积

  • 编码坐标

  • 搜索并显示附近的地点

  • 使用自动完成选项查找地点

  • 向地图添加拖动缩放

  • 创建自定义弹出窗口/信息框

简介

本章深入探讨了 Google Maps JavaScript API 的一部分附加 JavaScript 库。当你引用 Google Maps API 时,这些库默认不会添加到你的应用程序中;然而,这些库可以手动添加。

这些库被分为以下六个类别:

  • drawing

  • geometry

  • places

  • panoramio

  • visualization

  • weather

在前面列表中的最后三个库——panoramiovisualizationweather——在第二章中已经详细讨论过,添加栅格图层,涉及它们的相关主题和用法。在本章中,我们将详细了解DrawingGeometry库。我们还将使用两个外部库。

这些库的目的是作为核心 API 的扩展,确保 Google Maps JavaScript API 能够自给自足,以提供它所提供的所有任务。这意味着,没有这些额外库,你也可以使用 API 进行开发而不会遇到任何问题。

此外,这些库在某种程度上是自主的。它们有非常明确和精心设计的目标和边界,因此添加它们将提供额外的功能,但移除它们不会从核心 API 中移除任何功能。

这额外的库的可选性确实解释了 API 加载速度的加快。除非你明确请求这些库,否则它们不会被加载。这种组件化结构让你可以选择是否包含加载这些库的成本。

本章将首先处理drawing库,这将使你能够在基础地图上绘制矢量覆盖。然后,它将处理geometry库,获取矢量覆盖的属性,如长度和面积。最后,places库将详细解释如何在 Google Maps JavaScript API 中搜索地点并显示这些地点的详细信息。

在地图上绘制形状

你可能已经探索了第三章,添加矢量图层中的矢量覆盖。不深入细节,你可以使用 Google Maps JavaScript API 编程添加标记、线和多边形。但如果你想要绘制这些矢量覆盖——不是通过编程,而是通过鼠标点击或触摸手势,就像在 AutoCAD 或 ArcGIS for Desktop 中一样——你会怎么做?

drawing库处理这项工作,使你能够根据你的偏好绘制矢量形状,并在基础地图上显示它们。

在本配方中,我们将详细介绍如何绘制形状、处理它们广泛的选项以及如何处理它们特定的事件。

准备工作

第一章 第一部分的配方 Google Maps JavaScript API 基础 将完成我们的工作。我们将修改 Google Maps API 引导 URL 以包含此配方。

如何操作...

以下步骤显示了您如何获得绘图控制并使用该控制绘制一些形状:

  1. 通过添加 libraries 参数修改 Google Maps API 引导 URL:

    <script type="text/javascript"
        src="img/js? libraries=drawing&sensor=false">
    </script>
    
  2. 创建 drawingManager 对象:

    var drawingManager = new google.maps.drawing.DrawingManager();
    
  3. 启用绘图功能:

    drawingManager.setMap(map);
    

    如何操作...

在前面的截图中,您可以看到通过点击左上角按钮可以绘制的各种形状。

它是如何工作的...

使用 Google Maps JavaScript API 将绘图功能添加到您的应用程序中非常简单。首先,您必须将 libraries 参数包含在 Google Maps JavaScript API URL 中,并在其中包含 drawing 值以将 drawing 库包含到您的应用程序中:

&libraries=drawing

接下来,您可以使用 drawing 库支持的函数和对象,以及标准的 Google Maps JavaScript API。

要绘制矢量形状,您必须有一个 DrawingManager 对象:

var drawingManager = new google.maps.drawing.DrawingManager();

拥有一个 DrawingManager 对象,您就拥有了所有的绘图功能,但您必须将其附加到当前地图实例上才能使用它:

drawingManager.setMap(map);

在此之后,您将看到一个包含标记、折线、矩形、圆形和多边形绘图按钮的绘图控制。通过使用这些按钮,您可以绘制任何想要的矢量叠加。在工具集中,您还可以看到一个平移工具,用于退出绘图模式以使用平移和缩放控制。如果您想再次绘制矢量形状,请按相关按钮(标记、折线等)并在地图上绘制。

还有更多...

到目前为止,拥有绘图功能非常简单,您可以通过添加两行代码来实现它。然而,有一系列广泛的选项可供使用,这些选项与 DrawingManager 对象和您绘制的矢量形状相关。了解它们是值得的,因为它们可以丰富您在应用程序中的绘图体验。

可以在 DrawingManager 的初始化过程中或通过其 setOptions 方法修改 DrawingManager 的设置。所有与 DrawingManager 类相关的设置都是 DrawingManagerOptions 类的属性。

让我们修改我们的配方以包含 DrawingManager 选项:

var drawingManager = new google.maps.drawing.DrawingManager({
    drawingControl: true,
});

drawingControl 属性可以启用或禁用在地图 UI 中看到的绘图控制:

还有更多...

drawingControl 属性设置为 false 将会隐藏绘图控制。其默认值为 true;因此,尽管它没有包含在我们的原始配方代码中,但它显示在地图上。

重要的是要注意,绘图功能是通过将 DrawingManager 类附加到地图上实现的。

drawingManager.setMap(map);

因此,隐藏绘图控制与绘图功能无关。实际上,您可以使用自己的用户控件来使用DrawingManager而不是标准绘图控件。

绘图控制在其drawingControlOptions属性中嵌入了自己的选项。记住从第四章,使用控件,您可以在 Google Maps UI 中预定义的位置放置您的控件,无论是默认控件还是您实际开发的控件。

drawingControl属性也不例外。您可以使用以下代码片段来定位drawingControl

var drawingManager = new google.maps.drawing.DrawingManager({
    drawingControl: true,
    drawingControlOptions: {
        position: google.maps.ControlPosition.BOTTOM_CENTER
    }
});

上述代码在地图 UI 中的反映方式如下:

还有更多...

注意,drawingControl属性被放置在底部中央,正如我们在drawingControlOptions属性的position属性中提到的。

注意

google.maps.ControlPosition 的完整列表

控制位置的完整列表可以在以下链接的 Google Maps JavaScript API 参考文档中找到:

developers.google.com/maps/documentation/javascript/reference

除了position属性之外,您还可以选择您想要绘制的形状类型,换句话说,您想要包含在drawingControl中的按钮:

var drawingManager = new google.maps.drawing.DrawingManager({
    drawingControl: true,
    drawingControlOptions: {
        position: google.maps.ControlPosition.BOTTOM_CENTER,
        drawingModes: [
            google.maps.drawing.OverlayType.MARKER,
            google.maps.drawing.OverlayType.POLYGON,
            google.maps.drawing.OverlayType.POLYLINE
        ]
    }
});

我们显然选择了在drawingModes属性中列出的数组中的三种绘图形状类型:

  • 标记

  • 多边形

  • 多段线

这些在drawingControl属性中体现出来:

还有更多...

默认情况下,所有矢量形状按钮都在drawingControl中可用。这意味着,除了我们示例中列出的三种类型之外,以下形状也是可用的:

  • 矩形

  • 圆形

如果您已经按照这个步骤做到这里,您可能已经意识到,在您的应用程序开始时,您可以像往常一样缩放和拖动地图。然后,您必须点击drawingControl属性中的矢量覆盖按钮来开始绘制形状。

然而,您可以通过设置来更改这一点。例如:

var drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    ...
});

drawingMode属性采用矢量形状类型google.maps.drawing.OverlayType作为其数据类型,并设置该矢量形状类型以便可以绘制。在我们的示例中,当用户点击地图时,他们立即开始绘制POLYGON矢量覆盖。

但是,如果程序流程中需要程序化地更改drawingMode,会发生什么呢?幸运的是,有一个解决方案:

drawingManager.setDrawingMode(null);

将属性设置为null会使drawingMode属性变为默认值,允许最终用户像往常一样使用 Google Maps JavaScript UI。这意味着点击地图不会绘制任何矢量形状覆盖。

您也可以使用drawingManagersetOptions方法达到相同的目的:

drawingManager.setOptions({
   drawingMode: google.maps.drawing.OverlayType.POLYGON,
});

到目前为止,我们已经处理了drawingManagerdrawingControl属性选项。但我们将要绘制的形状及其样式怎么办?正如您所预期的,您可以在google.maps.drawing.DrawingManagerOptions中设置您绘制的矢量形状的属性:

var drawingManager = new google.maps.drawing.DrawingManager({
   ...
   polylineOptions: {
       strokeColor: 'red',
       strokeWeight: 3
   },
   polygonOptions: {
        strokeColor: 'blue',
        strokeWeight: 3,
        fillColor: 'yellow',
        fillOpacity: 0.2
   }
...
});

我们现在可以绘制如下所示的形状:

更多内容...

您可能已经观察到多边形和折线形状的样式已经完全改变。折线变成了红色,因为它们的strokeColor属性被设置为red,而多边形的strokeColor属性被设置为blue,它们的fillColoryellow,透明度接近透明——0.2——这样您就可以通过它们看到基础地图。

对于每种矢量覆盖类型,drawingManager都有一个options属性:

  • markerOptions

  • polylineOptions

  • polygonOptions

  • circleOptions

  • rectangleOptions

对于矢量覆盖,有许多有趣的属性,其中大多数对所有覆盖类型都是通用的。我们已涉及根据您的喜好自定义样式的线条和填充属性。

例如,您可以尝试editabledraggable属性,这些属性值得注意:

polygonOptions: {
            strokeColor: 'blue',
            strokeWeight: 3,
            fillColor: 'yellow',
            fillOpacity: 0.2,
            editable: true,
            draggable: true
}

前面的代码片段使得在谷歌地图 UI 上绘制的多边形可编辑,如下面的截图所示(您必须通过在绘图控制中点击平移按钮退出多边形绘图模式):

更多内容...

观察代表构成整个多边形的节点(LatLng对象)的白点。您可以通过点击和拖动这些点来改变这些点的位置;这将允许您改变多边形的形状,如下面的截图所示:

更多内容...

您可能已经注意到位于南边缘中间的白点已被向下拖动,因此多边形的形状已经改变。

除了改变原始形状外,您还可以拖动形状,如下面的截图所示:

更多内容...

如您所见,在第二张截图中,形状已经向东移动。

更多内容...

draggable属性设置为true并且鼠标位于形状上时,您可以将形状拖动到地图上的任何位置。

注意

google.maps.drawing.DrawingManager 属性完整列表

DrawingManager属性和相关选项的完整列表可以在以下链接的谷歌地图 JavaScript API 参考文档中找到:

developers.google.com/maps/documentation/javascript/reference#DrawingManager

DrawingManager不仅限于其属性和选项;它还有一些与之相关的事件。这些事件在您完成绘制形状时触发:

google.maps.event.addListener(drawingManager, '    polygoncomplete', function(polygon) {
    polygon.setEditable(true);
    polygon.setDraggable(true);
});

你可能会注意到事件的类型是polygoncomplete,并且有一个回调函数接受一个参数,即已经完成的多边形。

每种形状类型都有一个事件:

  • markercomplete

  • linestringcomplete

  • polygoncomplete

  • rectanglecomplete

  • circlecomplete

还有一种额外的事件类型涵盖了所有这些形状类型:

google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
     if (event.type == google.maps.drawing.OverlayType.POLYGON) {
          event.overlay.setEditable(true);
          event.overlay.setDraggable(true);
     }
});

前一个事件的行为与上一个示例相同。而不是shapecomplete模式,事件有一个overlaycomplete参数。这个事件对于所有形状类型特别有用。然而,作为一个针对所有形状的通用事件,你也可以从event.type获取形状类型,并从event.overlay获取绘制形状的引用。利用这些,你可以在一个事件处理器中为不同的形状类型设置条件语句。

计算折线和多边形的长度/面积

如本章第一道菜谱所述——在地图上绘制形状——你可以根据你的喜好绘制形状。但关于这些形状的信息,例如,它们的长度和面积信息,你了解多少呢?

Google Maps JavaScript API 在geometry库中提供了收集这些信息的机会。从这个库中,你可以访问静态实用函数,这些函数提供了长度/面积计算等信息。

本菜谱将展示如何获取绘制形状的长度和面积信息。

准备工作

在查看在地图上绘制形状的菜谱之前,可以让你更容易地工作,因为需要很多关于绘制形状及其背景的细节。

如何操作...

你可以通过以下步骤查看你形状的面积和长度信息:

  1. drawinggeometry库添加到 bootstrap URL 中:

    <script type="text/javascript"
        src="img/js? libraries=drawing,geometry&sensor=false">
    </script>
    
  2. 使用以下设置创建一个drawingManager对象:

    var drawingManager = new google.maps.drawing.DrawingManager({
        drawingMode: null,
        drawingControl: true,
        drawingControlOptions: {
           position:
                google.maps.ControlPosition.BOTTOM_CENTER,drawingModes: [
            google.maps.drawing.OverlayType.POLYGON,
            google.maps.drawing.OverlayType.POLYLINE
            ]
        },
        polylineOptions: {
            strokeColor: 'red',
            strokeWeight: 3
        },
        polygonOptions: {
            strokeColor: 'blue',
            strokeWeight: 3,
            fillColor: 'yellow',
            fillOpacity: 0.2
      }
    });
    
  3. 启用绘图功能:

    drawingManager.setMap(map);
    
  4. 为你的多边形完成添加事件监听器:

    google.maps.event.addListener(drawingManager, 'polygoncomplete', function(polygon) {
        var path = polygon.getPath();
        var area =     google.maps.geometry.spherical.computeArea(path);
        var length = google.maps.geometry.spherical.computeLength(path);
        console.log('Polygon Area: ' +  area/1000000 + ' km sqs');
        console.log('Polygon Length: ' +  length/1000 + ' kms');
    });
    
  5. 为你的折线添加事件监听器:

    google.maps.event.addListener(drawingManager, 'polylinecomplete', function(polyline) {
        var path = polyline.getPath();
        var length = google.maps.geometry.spherical.computeLength(path);
        console.log('Polyline Length: ' +  length/1000 + ' kms');
    });
    

    如何操作...

如前一张截图所示,你可以在控制台窗口中查看面积和长度信息。

工作原理...

要使用 Google Maps JavaScript API 中的drawinggeometry实用工具,我们在代码顶部的 Google Maps JavaScript API bootstrap URL 中添加了两个库——drawinggeometry

libraries=drawing,geometry

需要注意的是,你可以通过逗号分隔每个列表来添加多个库,就像在这个例子中一样。

我们在initMap()函数本身中添加了drawingManager对象,在常规地图细节之后。在这个drawingManager对象中,我们设置了属性,以便我们只能绘制折线和多边形:

drawingModes: 
[
    google.maps.drawing.OverlayType.POLYGON,
    google.maps.drawing.OverlayType.POLYLINE
]

我们不需要任何标记绘制,因为没有与标记相关的长度和面积信息。

在应用程序开始时,我们暗示用户可以使用标准地图控件(缩放、平移等)而不是绘制形状:

drawingMode:null,

这种对用户输入的控制特别适用于专业应用程序,因为即使应用程序是唯一的绘图应用程序,用户也可能需要首先使用平移和缩放控件来指定他们的绘图区域。

我们已经将 drawingControl 对象放置在地图 UI 的底部中央:

position: google.maps.ControlPosition.BOTTOM_CENTER,

您可以自行决定将 drawingControl 放置在哪里;我们只是选择了 BOTTOM_CENTER 作为示例。

我们最终将 drawingManager 对象附加到地图实例上以启用功能:

drawingManager.setMap(map);

在完成所有这些设置后,用户可以打开他们的应用程序,并根据他们的意愿绘制多边形和多段线。但是,我们如何获取他们形状的长度和面积信息呢?

我们必须添加事件处理器来确保他们已经完成了形状的绘制。对于每个多边形和多段线,都必须执行长度和面积的计算。因此,我们使用了 polygoncompletepolylinecomplete 事件。首先,让我们对多边形进行计算:

google.maps.event.addListener(drawingManager, 'polygoncomplete', function(polygon) {
    var path = polygon.getPath();
    var area = google.maps.geometry.spherical.computeArea(path);
    var length = google.maps.geometry.spherical.computeLength(path);
    console.log('Polygon Area: ' +  area/1000000 + ' km sqs');
    console.log('Polygon Length: ' +  length/1000 + ' kms');
});

在用户完成绘制每个多边形时触发的 polygoncomplete 事件处理器中,我们首先获取他们所绘制的多边形的路径:

var path = polygon.getPath();

getPath() 方法返回一个 MVCArray,该对象为 LatLng 类型,包含构成多边形的经纬度对。例如,对于我们所绘制的想象中的多边形,调用 polygon.getPath().getArray().toString(); 会得到以下结果:

"(39.92132255884663, 32.7337646484375),(39.75048953595117, 32.754364013671875),(39.78110197709871, 33.061981201171875),(39.98132938627213, 33.0084228515625)"

现在已经很清楚,所绘制的想象中的多边形由四个经纬度对组成。

为什么我们需要多边形的路径?我们需要它是因为我们使用的 computeArea() 函数不接受多边形,而是接受其路径作为参数:

var area = google.maps.geometry.spherical.computeArea(path);

这个 spherical 命名空间代表什么?

正如您所观察到的,地图是二维表面。然而,地球的表面不是。为了在二维画布上反映地球表面,需要使用投影。然而,这种反映并不像最初看起来那么平滑。它伴随着代价;地球的形状和属性会发生扭曲。为了处理这些副作用,需要球面几何计算,而 google.maps.geometry.spherical 正好就是为了这个目的而存在的。

当您调用 computeArea()computeLength() 方法时,面积计算会像将形状扭曲到地球表面一样进行,考虑到地球的曲率。

这两个方法的返回值单位是米。我们将它们转换为平方千米和千米,以便在控制台窗口打印时具有更有意义的值:

console.log('Polygon Area: ' +  area/1000000 + ' km sqs');
console.log('Polygon Length: ' +  length/1000 + ' kms');

polygoncompletepolylinecomplete 事件的处理器在 polylinecomplete 中是相同的,除了在 polylinecomplete 中没有面积计算。

还有更多...

有很大的可能性,将长度和面积信息附加到形状上会很方便。你可以扩展 PolygonPolyline JavaScript 类来实现这一点。但请记住,扩展 JavaScript 对象可能会导致意外的错误;你可能会覆盖不同库的对象扩展。因此,在扩展 JavaScript 类之前请三思:

google.maps.Polygon.prototype.getArea = function()
{
    return google.maps.geometry.spherical.computeArea(this.getPath());
};

google.maps.Polygon.prototype.getLength = function(){
    return google.maps.geometry.spherical.computeLength(this.getPath());
};

google.maps.Polyline.prototype.getLength=function(){
    return google.maps.geometry.spherical.computeLength(this.getPath());
};

在扩展了 PolygonPolyline 类之后,你可以直接从它们的对象中调用 getArea()getLength() 方法:

polygon.getArea();
polyline.getLength();

参见

  • 本章中的 在地图上绘制形状 菜单

编码坐标

你使用 Google Maps JavaScript API 绘制的折线和多边形由经纬度对的 LatLng 对象数组组成。

这些数组的长度显著增加,尤其是在你有太多节点的形状,例如长折线或具有过多细节的多边形的情况下。

处理这些数组(可以通过折线和多边形的 getPath() 方法检索)是一个主要问题,尤其是在你必须将形状保存到数据库中时。序列化和反序列化长数组通常是笨重的。

然而,你可以使用 Google 的 polyline 编码算法压缩形状的路径。

注意

关于 Google polyline 编码算法的详细信息

你可以在以下链接中找到有关 polyline 编码算法的详细信息:

developers.google.com/maps/documentation/utilities/polylinealgorithm

通过使用 geometry 库,你可以编码和解码折线和多边形的路径。

本菜谱将向你展示如何编码和解码折线和多边形的路径。

准备工作

快速查看本章的第一个菜谱——在地图上绘制形状——将很有帮助,因为它涵盖了使用 Google Maps JavaScript API 绘制形状的每一个细节。

如何操作...

这里有一些你可以使用的步骤来查看你路径的编码和解码版本:

  1. geometrydrawing 库添加到 bootstrap URL:

    <script type="text/javascript"
       src="img/js? libraries=drawing,geometry&sensor=false">
    </script>
    
  2. 组织你的 HTML,以便你可以在一个 div 元素中查看形状的原始、编码和解码坐标:

    <div>
        <H3>Original, Encoded and Decoded Coordinate Pairs:<H3>
        <div id="loggingDiv"></div>
    </div>
    
  3. 在你的 initMap() 函数中保留对 loggingDiv div 元素的引用:

    loggingDiv = document.getElementById('loggingDiv');
    
  4. 在创建 drawingManager 并将其附加到地图实例之后,在你的 initMap() 函数中创建一个 polylinecomplete 事件处理程序:

    google.maps.event.addListener(drawingManager, 'polylinecomplete', function(polyline){
        var path = polyline.getPath();
    
        var coords = path.getArray();
    
        var text = '<b>Original Coordinates:</b> ' + coords;
    
        var encodedCoords = google.maps.geometry.encoding.encodePath(path);
    
        text += '<br/><b>Encoded Coordinates:</b> ' + encodedCoords;
    
        var decodedCoords = google.maps.geometry.encoding.decodePath(encodedCoords);
    
        text += '<br/><b>Decoded Coordinates:</b> ' + decodedCoords;
    
        loggingDiv.innerHTML = text;
    });
    

如何操作...

你可以查看你路径的原始、编码和解码版本,如前一个屏幕截图所示。

它是如何工作的...

当你完成绘制折线时,会触发 polylinecomplete 事件。你可以以下方式获取包含你的折线的 LatLng 对象类型的 MVCArray

var path = polyline.getPath();

拥有路径对象后,你可以通过使用 encodePath() 方法轻松地进行编码:

var encodedCoords = google.maps.geometry.encoding.encodePath(path);

encodePath()方法接受LatLng对象类型的对象MVCArrayLatLng对象的数组。因此,在我们的食谱中,这也将是可能的:

var encodedCoords = google.maps.geometry.encoding.encodePath(coords);

encodePath()方法返回一个适合保存到数据库的字符串,并且可以节省大量用于序列化和反序列化操作的时间:

op_rFitihE|}Q|pCpyLo`GzmMq|HneEg}Wim@ghNwiIapJidD~zKmiIwXuiC_tHm`Gy{Ds|Ij}AqxE~zKqf@pxUngAfhNxdEvfFfaH_fBwvCg}WbqDc~E~`Nr_N

不进行编码,coords数组将看起来像这样:

(39.81592026602056, 32.9864501953125),(39.71880717209066, 32.963104248046875),(39.64799732373418, 33.004302978515625),(39.573939343591896, 33.05511474609375),(39.54217596171196, 33.182830810546875),(39.54958871848275, 33.2611083984375),(39.6025139495577, 33.320159912109375),(39.62896140981413, 33.254241943359375),(39.681826010893644, 33.25836181640625),(39.70401708565211, 33.30780029296875),(39.74521015328692, 33.3380126953125),(39.80115102364286, 33.322906494140625),(39.83595916247957, 33.256988525390625),(39.842286020743394, 33.1402587890625),(39.83068633533497, 33.061981201171875),(39.79904087286648, 33.02490234375),(39.7526011757416, 33.0413818359375),(39.776880380637024, 33.169097900390625),(39.74837783143156, 33.204803466796875),(39.67125632523973, 33.127899169921875)

对折线和多边形进行编码不是单向操作。你可以按照以下方式解码编码的坐标对:

var decodedCoords = google.maps.geometry.encoding.decodePath(encodedCoords);

decodePath()方法接受以字符串形式的编码坐标,并返回一个LatLng对象的数组。

搜索并显示附近的地点

Google Maps 不仅仅是具有巨大制图质量的美丽基础地图或定期更新的卫星图像。在日常生活中,你作为 Google Maps 的普通用户,无疑已经使用 Google Maps 搜索过地点;无论是纽约的大都会艺术博物馆,还是罗马的普通药房

这些信息在 Google Maps 中,但如何通过 Google Maps JavaScript API 访问并提供服务呢?

places库正是为此目的而存在的,它使你能够使用某些搜索参数来查找地点。

你可以进行附近的搜索,地点结果将靠近你提供的位置,最常见的是用户的位置。你可以在一定范围内搜索,或者只需指定一个搜索字符串。你甚至可以请求额外的详细信息,例如相关照片、评论评分、电话号码和特定地点的营业时间。

本食谱将专注于使用 Google Maps JavaScript API 的places库进行附近的搜索。

准备工作

本食谱将使用drawing库,因此建议回顾本章的第一食谱——在地图上绘制形状——并刷新对该主题的理解。

如何操作...

你可以绘制一个圆,使用关键词在这个圆内搜索地点,并按照以下步骤获取每个地点的详细信息:

  1. drawingplaces库添加到 bootstrap URL:

    <script type="text/javascript"
       src="img/js? libraries=drawing,places&sensor=false">
    </script>
    
  2. circlesmarkers全局变量添加到在initMap()函数外部推送和弹出相应的覆盖物:

    var circles;
    var markers;
    
  3. 添加一个popup全局变量来保存infoWindow对象:

    var popup;
    
  4. initMap()函数中初始化circlesmarkers数组以及infoWindow对象:

    circles = new Array();
    markers = new Array();
    popup = new google.maps.InfoWindow();
    
  5. 在创建drawingManager对象并将其附加到地图实例后,在initMap()函数中创建一个circlecomplete事件处理器(从第 6 项到第 12 项的内容将在这个事件处理器中):

    google.maps.event.addListener(drawingManager, 'circlecomplete', function(circle){
    
    });
    
  6. circlecomplete事件处理器内部,将drawingMode设置为null

    drawingManager.setDrawingMode(null);
    
  7. 将最新绘制的圆添加到circles数组中,然后反转数组内的顺序:

    circles.push(circle);
    circles.reverse();
    
  8. 弹出之前的圆圈并将它的地图句柄设置为 null,以便只显示最后绘制的圆圈:

    while(circles[1]){
        circles.pop().setMap(null);
    }
    
  9. 清除之前绘制的标记:

    while(markers[0]){
        markers.pop().setMap(null);
    }
    
  10. 创建附近的搜索设置,将位置设置为圆心,将半径设置为圆的半径。此外,添加一个 keyword 属性以返回包含该关键字的地点:

    var nearbyPlacesRequest = {
        location: circle.getCenter(),
        radius: circle.radius,
        keyword: 'pizza'
    };
    
  11. 获取 PlacesService 服务对象的句柄:

    var placesService = new google.maps.places.PlacesService(map);
    
  12. 使用回调函数发送请求:

    placesService.nearbySearch(nearbyPlacesRequest, resultsCallback);
    
  13. initMap() 函数外部,为 nearbySearch 请求创建一个回调函数,使用以下代码片段:

    function resultsCallback(results, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            for (var i = 0, l=results.length; i < l; i++) {
                pinpointResult(results[i]);
            }
        }
    }
    
  14. 创建一个函数来根据地点结果创建标记(从第 15 步到第 17 步将在该函数中执行):

    function pinpointResult(result) {
    
    }
    
  15. pinpointResult() 函数内部创建标记:

    var marker = new google.maps.Marker({
        map: map,
        position: result.geometry.location
    });
    
  16. 向标记添加点击事件处理程序,以便当它被点击时,infoWindow 对象弹出:

    google.maps.event.addListener(marker, 'click', function() {
        var popupContent = '<b>Name: </b> ' + result.name + '<br/>' + '<b>Vicinity: </b>' + result.vicinity + '<br/><b>Rating: </b>' + result.rating; 
        popup.setContent(popupContent);
        popup.open(map, this);
    });
    
  17. 将标记推送到 markers 数组:

    markers.push(marker);
    

    如何操作...

如前图所示,您可以绘制一个圆圈,使用关键字搜索此圆圈内的地点,并获取每个找到的地点的详细信息。

它是如何工作的...

此配方的步骤需要您多花一点时间;然而,本质是简单的。暂时忘记 circlesmarkers 数组以及相关的逻辑细节;只需专注于附近的搜索:

var nearbyPlacesRequest = {
	location: circle.getCenter(),
   radius: circle.radius,
   keyword: 'pizza'
};

circlecomplete 事件处理程序(在我们完成绘制圆圈后触发)中,我们放置一个 nearbyPlacesRequest 对象。此对象应该是 google.maps.places.PlaceSearchRequest 类型。

location 属性设置应该作为地点搜索中心的 LatLng 对象。通常,在附近的搜索中,此属性根据用户的位置设置。但在此配方中,我们将其绑定到绘制的圆圈中心,因为您可以按照需要绘制和搜索多次。

通过 radius 属性设置 location 的距离,以便返回位于圆心一定距离内的地点。在我们的配方中,我们设置了绘制的圆的半径。

最后,keyword 属性过滤地点,以便返回包含关键字的地点。请注意,所有信息不仅包括地点的名称或类型,还包括地址和评论,这些将与关键字匹配。因此,请准备好返回一个包含关键字 "pizza" 的自助餐厅的评论。

准备好请求参数后,下一步是发送请求。首先,我们创建一个 PlacesService 对象,将当前的地图实例作为参数:

var placesService = new google.maps.places.PlacesService(map);

通过使用 placesService 对象,我们可以发送我们的请求:

placesService.nearbySearch(nearbyPlacesRequest, resultsCallback);

nearbySearch 方法接受两个参数,第一个参数是我们嵌入在 nearbyPlacesRequest 对象中的旧请求参数,第二个参数是返回结果的回调函数。在我们的配方中,第二个参数是 resultsCallback 函数:

function resultsCallback(results, status) {
    if (status == google.maps.places.PlacesServiceStatus.OK) {
        for (var i = 0, l=results.length; i < l; i++) {
            pinpointResult(results[i]);
        }
    }
}

这个回调函数在这里接受两个参数(实际上,它还有一个与搜索分页相关的第三个参数):搜索中找到的地点数组和服务状态。在回调函数中,我们首先检查服务状态是否正常。然后我们遍历 results,它是一个 PlaceResult 类型的数组,为每个返回的地点创建标记并填充 infoWindow 对象。

我们可以为每个地点创建一个关联的标记,如下面的代码片段所示:

var marker = new google.maps.Marker({
    map: map,
    position: result.geometry.location
});

result 对象的 geometry 属性嵌入了一个 location 属性,它属于 LatLng 类类型。这非常适合 Marker 类的 position 属性。

我们可以通过在标记的 click 事件处理器中附加的 popup 对象来获取地点的详细信息:

google.maps.event.addListener(marker, 'click', function() {
    var popupContent = '<b>Name: </b> ' + result.name + '<br/>' + '<b>Vicinity: </b>' + result.vicinity + '<br/><b>Rating: </b>' + result.rating; 

    popup.setContent(popupContent);
   popup.open(map, this);
});

你可能已经注意到,我们正在使用地点的 namevicinityrating 属性作为弹出窗口的内容。name 表示地点名称,vicinity 返回地址信息的一部分,而 rating 值是地点的评论评分,0.0 是最低分,5.0 是最高分。

更多内容...

搜索附近地点的详细信息和选项不仅限于本食谱中展示的选项。我们在这里将进一步探讨。首先,是 nearbyPlacesRequest 对象。该对象内部包含的属性有:locationradiuskeyword

然而,我们的对象所属的 PlaceSearchRequest 类比本食谱中展示的还要多。例如,你可以提供一个 LatLngBounds 对象而不是位置和半径:

var requestBounds = new google.maps.LatLngBounds(
    new google.maps.LatLng(39.85, 32.74),
    new google.maps.LatLng(40.05, 32.84)
);

var nearbyPlacesRequest = {
    bounds: requestBounds,
    keyword: 'pizza'
};

请记住,一个选项是使用 bounds,另一个选项是使用 locationradius 配对。对于 PlaceSearchRequest 类,使用其中一个选项是强制性的。

要过滤地点结果,使用 keyword 并非唯一解决方案。你可以尝试使用 name 属性直接匹配地点名称。例如,以下代码给出了名称中包含 Buckingham 的地点:

var nearbyPlacesRequest = {
    location: circle.getCenter(),
    radius: circle.radius,
    name: 'Buckingham'
};

如果你绘制的圆圈位于伦敦,它可能会同时显示白金汉宫以及附近的一批酒店。

你可以使用 types 属性来选择返回的地点类型。这个属性接受一个类型数组,例如:

var nearbyPlacesRequest = {
    location: circle.getCenter(),
    radius: circle.radius,
    types: ['stadium', 'car_rental', 'library','university','administrative_area_level_3']
};

Google Maps 已经覆盖了极其广泛的地点类型。你可以插入你想要的任何地点类型,从汽车租赁到大学,就像我们做的那样。

注意

地点类型完整列表

你可以在以下位置找到完整的地点类型列表:

Google 地点 API 支持的地点类型

除了 typesnamebounds 之外,PlaceSearchRequest 类中还有许多其他属性,例如 openNow,这是一个非常实用的属性,可以显示在搜索时开放的地点。

注意

PlaceSearchRequest 类的完整属性列表

您可以在以下位置找到 PlaceSearchRequest 类的完整属性列表:

developers.google.com/maps/documentation/javascript/reference#PlaceSearchRequest

除了在请求附近搜索时出现的选项池之外,还有另一组属性在返回结果中;换句话说,由 PlaceResult 类表示的地点。

例如,PlaceResult 类的 icon 属性,我们可以在以下代码块中在 pinpointResult() 函数内部使用:

var placeIcon = {
    url: result.icon,
    scaledSize: new google.maps.Size(30, 30)
};

var marker = new google.maps.Marker({
    map: map,position: result.geometry.location,
    icon: placeIcon
});

此代码块将返回带有各自图标的地点:

还有更多...

注意前一个屏幕截图中的画家调色板图标,它与返回的地点相结合,即纽约大都会艺术博物馆。

您还可以访问该地点所属的类型。PlaceResult 类的 types 属性返回一个字符串数组中的类型。因此,Result.types 属性返回以下参数,用于纽约大都会艺术博物馆:

["art_gallery", "museum", "establishment"]

如果您更改标记的点击处理程序,您还可以获取有关地点在搜索时是否开放的搜索信息,如下面的代码片段所示:

google.maps.event.addListener(marker, 'click', function() {
    var popupContent = '<b>Name: </b> ' + result.name + '<br/>' + '<b>Vicinity: </b>' + result.vicinity; 
    if (result.opening_hours){
        if (result.opening_hours.open_now){
            popupContent += '<br/><b>Is Open Now: </b> '
            + 'YES'
        }
        else {
            popupContent += '<br/><b>Is Open Now: </b> '
            + 'NO'
        }
    }
    popup.setContent(popupContent);
    popup.open(map, this);
});

使用前面的代码,您可能会得到以下信息:

还有更多...

注意

PlaceResult 类的完整属性列表

您可以在以下位置找到 PlaceResult 类的完整属性列表:

developers.google.com/maps/documentation/javascript/reference#PlaceResult

使用自动完成选项查找地点

Google Maps JavaScript API 提供了多种搜索地点和获取额外信息的方法。您可以使用附近搜索,并获取有关地点的详细信息,包括它们的几何形状,正如您在本章中名为 搜索并显示附近地点 的配方中所观察到的。

有没有想过使用具有自动完成功能的文本字段来控制搜索地点?您可以硬编码它,但这样做没有必要,因为 Google 已经有了一个专门为此目的的功能。

在本配方中,我们将介绍 Google Maps JavaScript API 的 places 库的自动完成功能。

准备工作

本配方将使用本章中 搜索并显示附近地点 配方中引入的与 places 库相关的概念。建议您阅读此配方,以便对地点及其属性有一个整体的理解。

如何操作...

您可以通过以下步骤添加文本字段并使用自动完成功能搜索地点:

  1. 插入一个用作自动完成字段的输入 HTML 元素:

    <div id="searchDiv">
    <input id="autocomplete_searchField" type="text" size="40"  placeholder="Search for Places">
    </div>
    
  2. initMap()函数外部定义标记和弹出变量为全局变量:

    var markers;
    var popup;
    
  3. initMap()中初始化全局变量:

    markers = new Array();
    popup = new google.maps.InfoWindow();
    
  4. 获取 ID 为searchDivdiv标签,并在使用initMap()选项创建地图后将其作为自定义控件推入:

    var searchDiv = document.getElementById('autocomplete_searchField');
    map.controls[google.maps.ControlPosition.TOP_CENTER].push(searchDiv);
    
  5. 获取输入元素的句柄:

    var searchField = document.getElementById('autocomplete_searchField');
    
  6. 为自动完成搜索请求提供属性:

    var searchOptions = {
        bounds: new google.maps.LatLngBounds(
           new google.maps.LatLng(8.54, 17.33),
            new google.maps.LatLng(39.67, 43.77)
        ),
       types: new Array()
    };
    
  7. 通过提供要使用的输入 HTML 元素,即searchFieldsearchOptions,来获取autocomplete对象:

    var autocompleteSearch = new google.maps.places.Autocomplete(searchField, searchOptions);
    
  8. autocomplete对象创建一个place_changed事件处理器(步骤 9 到 11 将在这个事件处理器中):

    google.maps.event.addListener(autocompleteSearch, 'place_changed', function() {
    });
    
  9. 在事件处理器中,首先清除之前的标记:

    while(markers[0]) {
        markers.pop().setMap(null);
    }
    
  10. 在自动完成搜索的响应中获取PlaceResult对象:

    var place = autocompleteSearch.getPlace();
    
  11. 如果地点有几何形状,调用一个函数来创建相关的标记:

    if (place.geometry) {
        pinpointResult(place);
    }
    
  12. 创建一个用于创建标记并为标记添加点击事件处理器的函数:

    function pinpointResult(result) {
        var placeIcon = {
            url: result.icon,
            scaledSize: new google.maps.Size(30, 30)
        };
    
        var marker = new google.maps.Marker({
            map: map,
            position: result.geometry.location,
            icon: placeIcon
        });
    
        map.setCenter(result.geometry.location);
        map.setZoom(16);
    
        google.maps.event.addListener(marker, 'click', function() {
           var popupContent = '<b>Name: </b> ' + result.name + '<br/>' + '<b>Vicinity: </b>' + result.vicinity;
    
            popup.setContent(popupContent);
            popup.open(map, this);
        });
        markers.push(marker);
    }
    

    如何操作...

你可以使用前面截图所示的自定义搜索功能:

它是如何工作的...

在这个食谱中,我们首先创建了一个带有一些占位文本的输入元素(注意,这在不支持旧浏览器的浏览器中不受支持),它将作为我们的搜索地点的文本字段:

<input id="autocomplete_searchField" type="text" size="40"  placeholder="Search for Places"></input>

然后,我们在 Google Maps JavaScript API 中添加了div容器标签作为自定义控件,以便在 Google Maps UI 中包含文本字段:

var searchDiv = document.getElementById('searchDiv');
map.controls[google.maps.ControlPosition.TOP_CENTER].push(searchDiv);

我们在名为searchOptionsAutocompleteOptions对象中设置自动完成搜索的属性:

var searchOptions = {
   bounds: new google.maps.LatLngBounds(
   new google.maps.LatLng(8.54, 17.33),
        new google.maps.LatLng(39.67, 43.77)
    ),
   types: new Array()
};

在前面的代码片段中,bounds用于定义要找到的地点的边界。在这里,我们将其设置为一个大预定义的边界;你可以将其设置为另一个你喜欢的LatLngBounds对象。

对于这个食谱,types数组是空的;实际上,这个数组用于限制要找到的地点的类型,无论是商业、城市还是地区。在我们的例子中,它是空的,所以我们的搜索将返回每种类型的PlaceResult对象。

我们使用两个成分创建了我们的自动完成对象:searchField是输入元素,searchOptions具有boundstypes属性:

var autocompleteSearch = new google.maps.places.Autocomplete(searchField, searchOptions);

然后,我们为我们的Autocomplete对象创建了一个place_changed事件处理器,当用户选择提供的PlaceResult时会被触发:

google.maps.event.addListener(autocompleteSearch, 'place_changed', function() {
    while(markers[0]) {
        markers.pop().setMap(null);
    }

    var place = autocompleteSearch.getPlace();
    if (place) {
        if (place.geometry) {
            pinpointResult(place);
        }
    }
});

在事件处理器中,我们取消映射在地图上的标记;然后,我们调用getPlace()方法来获取此上下文中的PlaceResult类型的Place对象。如果该地点存在并且具有几何形状(意味着找到了合适的PlaceResult实例),我们调用pinpoint()函数从PlaceResult创建标记,并为标记附加一个点击事件处理器以弹出相关的InfoWindow对象:

更多内容...

在我们的食谱中,我们在searchOptions对象中设置bounds属性为一个预定义的边界:

bounds: new google.maps.LatLngBounds(
    new google.maps.LatLng(8.54, 17.33),
    new google.maps.LatLng(39.67, 43.77)
),

这行代码将自动完成操作设置为在主要但不仅限于特定LatLngBounds对象内查找搜索地点。因此,如果您意外地给出一个小边界并发现结果在边界之外,请不要感到惊讶。

我们将边界设置为LatLngBounds对象,例如地图的边界,之后您可以更改它:

autocompleteSearch.setBounds(map.getBounds());

但如果您需要将边界设置为当前视口,该视口在您平移和缩放地图时更新怎么办?有一种方法,如下所示:

autocompleteSearch.bindTo('bounds', map)

通过使用此bindTo()函数,bounds属性被绑定到当前视口边界,并在它更改时更新。

除了bounds属性外,还有一个types属性,我们将其设置为空数组,但它不需要为空以过滤掉由我们的autocompleteSearch对象完成的预测:

types: ['(regions)']

这将渲染autocompleteSearch对象,仅搜索行政区域而不是所有地点。因此,当您输入colos时,罗马的斗兽场不会出现,因为只有允许在autocompleteSearch对象中显示的行政区域;您可以在以下屏幕截图中观察到这一点:

还有更多...

注意

google.maps.places.AutocompleteOptions 类中 types 属性的完整条目列表

您可以在以下位置找到AutocompleteOptions类中types属性的完整条目列表:

developers.google.com/maps/documentation/javascript/reference#AutocompleteOptions

将拖动缩放添加到地图

Google Maps 有一个缩放控件,JavaScript API 利用这个控件为程序员提供各种选项。这是一个非常实用且易于使用的控件。但还有其他缩放的方法;例如,通过拖动框绘制感兴趣的区域,这样地图就会缩放到该区域。

这种功能在标准的 Google Maps JavaScript API 及其任何库中都不存在;您必须自己编写代码。或者,您可以使用以下链接中由好人开发的实用库:

code.google.com/p/google-maps-utility-library-v3/wiki/Libraries

他们的一个库,KeyDragZoom,正是为此缩放功能而设计的,我们将在本食谱中使用这个库。

准备工作

您必须从以下链接下载keydragzoom.js JavaScript 源文件(截至本书编写时,最新版本为 2.0.9),并将其放置在与我们的食谱源代码相同的目录中:

google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/

如何做到这一点...

下面是执行通过拖动框进行缩放并放大框内区域的步骤:

  1. 使用对keydragzoom.js文件的引用:

    <script type="text/javascript" src="img/keydragzoom.js">
    </script>
    
  2. initMap() 函数中设置所有与地图相关的选项后启用此功能:

    map.enableKeyDragZoom({
        visualEnabled: true,
        visualPosition: google.maps.ControlPosition.LEFT,
        visualPositionOffset: new google.maps.Size(35, 0),
        visualPositionIndex: null,
        visualSprite: 'http://maps.gstatic.com/mapfiles/ftr/controls/dragzoom_btn.png',
        visualSize: new google.maps.Size(20, 20),
        visualTips: {
            off: "Turn on",
            on: "Turn off"
        }
    });
    

    如何操作...

您可以通过拖动一个框来使用缩放功能,如图中所示的前一个截图。

如何工作...

您可以通过按下控制按钮并绘制一个框来执行拖动缩放,或者更简单的方法是,按住 Shift 键并绘制一个框来缩放框内的区域。

要执行此操作,我们首先在我们的配方中添加了拖动缩放库的 JavaScript 源文件。在设置地图选项并使用地图实例后,我们可以通过使用地图实例的 enableKeyDragZoom() 方法来启用拖动缩放功能。

此扩展方法不是 Google Maps JavaScript API 的一部分,而是随 keydragzoom 库一起提供。有几个相关的选项嵌入在 KeyDragZoomOptions 类中。

请记住,在其最简单形式中,您可以通过启用它来使用键拖动缩放功能:

map.enableKeyDragZoom();

唯一的区别是您必须使用 Shift 键作为您唯一的操作方式,因为没有拖动缩放控制按钮。

KeyDragZoomOptions 类中嵌入的属性都是关于放置在标准缩放控制按钮下方的控制按钮:

如何工作...

visualEnabled 属性设置控制是否可见,因此如果此属性为 false,则不需要其他属性。visualPosition 属性设置控制位置;我们将其放置在左侧。有关控制位置的详细描述,请参阅第四章中的更改控件位置配方,使用控件

注意

KeyDragZoomOptions 类中的属性完整列表

您可以在以下链接中找到 KeyDragZoomOptions 类中属性的完整列表:

google-maps-utility-library-v3.googlecode.com/svn/tags/keydragzoom/2.0.9/docs/reference.html

参见

  • 您可以在第四章中查看 Google Maps JavaScript API 控件及其使用,使用控件

创建自定义弹出窗口/信息框

我们已经在第三章中创建了弹出窗口或信息框,添加矢量图层。正如那里所述,几乎每个地图应用都有显示与其上显示的特征相关的信息的能力。这些信息可以与标记或地图相关。而不是在地图上显示所有信息,弹出窗口或信息框仅在需要时使用。

Google Maps JavaScript API 有一个 google.maps.InfoWindow 类来为开发者创建默认的信息框。在某些情况下,您需要自定义信息框来显示信息。有两种方法可以做到这一点:

  • 第一种方法是通过创建一个继承自google.maps.OverlayView类的自定义类,并填充显示/隐藏信息框的方法,使用自定义 CSS 样式。

  • 另一种更简单的方法是使用为你创建的库。在 Google Code 上有一个名为google-maps-utility-library-v3的项目,它包含扩展 Google Maps JavaScript API 的多个库。以下是链接:

    Google Maps Utility Library v3 库

    此项目有一个名为InfoBox的库,它使得创建自定义信息框或地图标签成为可能。

在这个菜谱中,我们将使用之前提到的库来创建可以绑定到标记和地图的自定义信息框。同一个信息框根据其绑定显示不同的信息。如果需要向地图添加更多信息,我们还将添加一个简单的地图标签。

准备工作

第一章的第一道菜谱,Google Maps JavaScript API 基础,将完成我们的工作。我们将在此菜谱中添加内容。

如何操作...

通过完成以下步骤,您可以获取自定义信息框:

  1. 首先,访问以下地址以获取最新的InfoBox源代码,并将其保存到lib目录下的infobox.js文件中。我们使用了以下 URL 下的/1.1.9/src/infobox_packed.js文件:

    Google Maps Utility Library v3 代码库

  2. 然后,我们通过创建一个简单的地图配方来获取代码,并将以下代码添加到页面中,以添加我们的库:

    <script type="text/javascript" src='lib/infobox.js'></script>
    
  3. 下一步是使用div元素的帮助来创建信息框的内容:

    //Creating the contents for info box
    var boxText = document.createElement('div');
    boxtext.className = 'infoContent';
    boxText.innerHTML = '<b>Marker Info Box</b> <br> Gives   information about marker';
    
  4. 现在我们创建一个对象来定义信息框的选项:

    //Creating the info box options.
    var customInfoBoxOptions = {
        content: boxText,
        pixelOffset: new google.maps.Size(-100, 0),
        boxStyle: {
            background: "url('img/tipbox2.gif') no-repeat",
            opacity: 0.75,
            width: '200px'
        },
        closeBoxMargin: '10px 2px 2px 2px',
        closeBoxURL: 'img/close.gif',
        pane: 'floatPane'
    };
    
  5. 我们可以以下述方式初始化我们的自定义信息框:

    //Initializing the info box
    var customInfoBox = new InfoBox(customInfoBoxOptions);
    
  6. 此外,我们创建一个 JSON 对象来定义地图标签的选项:

    //Creating the map label options.
    var customMapLabelOptions = {
        content: 'Custom Map Label',
        closeBoxURL: "",
        boxStyle: {
            border: '1px solid black',
            width: '110px'
        },
        position: new google.maps.LatLng(40.0678,   33.1252),
        pane: 'mapPane',
        enableEventPropagation: true
    };
    
  7. 然后,我们以以下方式初始化地图标签并将其添加到地图中:

    //Initializing the map label
    var customMapLabel = new InfoBox(customMapLabelOptions);
    //Showing the map label
    customMapLabel.open(map);
    
  8. 创建一个简单的标记,该标记将被绑定到信息框:

    //Initializing the marker for showing info box
    var marker = new google.maps.Marker({
        map: map,
        draggable: true,
        position: new google.maps.LatLng(39.9078,    32.8252),
        visible: true
    });
    
  9. 当地图准备就绪时,我们将打开与标记绑定的信息框:

    //Opening the info box attached to marker
    customInfoBox.open(map, marker);
    
  10. 我们应该为标记和地图创建事件监听器,以便在它们的点击事件中显示信息框。当点击标记或在某些点上点击地图时,信息框将出现在标记的底部:

    //Listening marker to open info box again with contents //related to marker
    google.maps.event.addListener(marker, 'click', function (e) {
        boxText.innerHTML = '<b>Marker Info Box</b> <br> Gives information about marker';
        customInfoBox.open(map, this);
    });
    
    //Listening map click to open info box again with //contents related to map coordinates
    google.maps.event.addListener(map,'click', function (e) {
        boxText.innerHTML = '<b>Map Info Box</b> <br> Gives information about coordinates <br> Lat: ' + e.latLng.lat().toFixed(6) + " -   Lng: ' + e.latLng.lng().toFixed(6);
        customInfoBox.setPosition(e.latLng);
        customInfoBox.open(map);
    });
    
  11. 您还可以监听信息框的事件。我们将向信息框关闭按钮的点击事件添加一个监听器:

    //Listening info box for clicking close button
    google.maps.event.addListener(customInfoBox, 'closeclick', function () {
            console.log('Info Box Closed!!!');
    });
    
  12. 前往您在最喜欢的浏览器中存储 HTML 文件的本地 URL;您将看到一个带有信息框的弹出窗口。如果您点击地图,您将在信息框内看到鼠标点击的坐标,或者如果您点击标记,您将看到与标记相关的内容的信息框。地图的右上角还有一个固定的地图标签,其中包含一些内容;它说自定义地图标签如何操作...

您可以获取您自定义的信息框,如图中所示的前一个屏幕截图。

它是如何工作的...

在您的 Web 应用程序中使用库是常见的。库的使用为开发者节省了开发和调试时间。与您有限的案例相比,库在不同的环境中针对不同的案例进行了测试。

如前所述,您也可以编写自己的自定义类来显示自定义信息框或地图标签,但这并不是一开始就发现美洲大陆的建议方法。我们使用了名为InfoBox的库,它是为此目的编写的。该库的文档类似于 Google Maps JavaScript API 文档(可在google-maps-utility-library-v3.googlecode.com/svn/tags/infobox/1.1.9/docs/reference.html找到)。在本书编写时,该库的最新版本是 1.1.9。如果您使用的是新版本,请更新库。

InfoBox库建立在 Google Maps JavaScript API 的基类google.maps.OverlayView之上,用于向地图添加额外的图层或视图。

如预期的那样,需要内容,这些内容在div元素中定义。

    //Creating the contents for info box
    var boxText = document.createElement('div');
    boxtext.className = 'infoContent';
    boxText.innerHTML = '<b>Marker Info Box</b> <br> Gives information about marker';

InfoBox库可以通过其构造函数初始化,使用从InfoBoxOptions类创建的参数显示信息框,如下所示:

    //Creating the info box options.
    var customInfoBoxOptions = {
        content: boxText,
        pixelOffset: new google.maps.Size(-100, 0),
        boxStyle: {
            background: "url('img/tipbox2.gif') no-repeat",
            opacity: 0.75,
            width: '200px'
        },
        closeBoxMargin: '10px 2px 2px 2px',
        closeBoxURL: "img/close.gif",
        pane: 'floatPane'
    };

InfoBox库可以通过其构造函数初始化,使用从InfoBoxOptions类创建的参数创建地图标签,如下所示:

    //Creating the map label options.
    var customMapLabelOptions = {
        content: 'Custom Map Label',
        closeBoxURL: '',
        boxStyle: {
            border: '1px solid black',
            width: '110px'
        },
        position: new google.maps.LatLng(40.0678, 33.1252),
        pane: 'mapPane',
        enableEventPropagation: true
    };

InfoBoxOption类的参数在以下列表中解释:

  • content:这可以是一个字符串或一个 HTML 元素。在我们的例子中,我们使用了 HTML div元素来创建一个漂亮的装饰信息框。您可以使用CSS样式元素来创建您自定义的信息框。

  • pixelOffset:这是信息框从左上角起像素偏移量。在这个食谱中,我们想要使信息框居中,所以我们使用了信息框宽度的一半。

  • boxStyle:这定义了用于信息框的 CSS 样式。在这个食谱中使用的background样式属性显示了上箭头图像。这是一个放置在信息框中间的定制图像。widthopacity样式属性的名称暗示了它们的使用方式。

  • closeBoxMargin:用于定义关闭框在 CSS 边距样式值中的位置。在本食谱中,我们使用了信息框顶部的向上箭头,因此我们必须将关闭框移动到箭头图像下方。

  • closeBoxURL:这是关闭框的图像 URL。这里使用的是 Google 的标准关闭框图像。如果您不想添加关闭框,请将此属性设置为空。

  • pane:这是信息框将出现的面板。如果您将其用作信息框,则使用floatPane。如果您将其用作地图标签,则使用mapPane

  • position:这是由google.maps.LatLng类创建的对象中定义的信息框或地图标签的地理位置。

  • enableEventPropagation:用于传播事件。如果您使用InfoBox类作为地图标签,则不需要获取标签的事件。在这种情况下,地图的事件更为重要。

不论是信息框还是地图标签,您都可以使用open()方法显示InfoBox对象。如果没有锚点,例如标记,它只接受一个地图参数;否则,您应该添加第二个参数作为锚点对象。以下有两个用法示例:

    //Showing the map label    
    customMapLabel.open(map);
    //Opening the info box attached to marker
    customInfoBox.open(map, marker);

如果您需要更改信息框的位置,如事件处理程序中所示,您应使用类的setPosition()方法。此方法获取由google.maps.LatLng类创建的对象。

    //Changing the position of info box    
    customInfoBox.setPosition(e.latLng);

在本食谱中使用的活动是第五章理解 Google Maps JavaScript API 事件的主题。我们没有深入探讨,但对于某些目的,InfoBox类的活动也需要处理。下面的代码块将监听关闭按钮的点击,这将导致信息框的关闭。监听器的事件处理程序将只向控制台记录一条消息以供演示:

    //Listening info box for clicking close button
    google.maps.event.addListener(customInfoBox, 'closeclick', function () {
        console.log('Info Box Closed!!!');
    });

如您所见,在前面的代码中,Google Maps JavaScript API 具有许多潜力,可以通过额外的库来提取。Google Maps JavaScript API 为您提供了基础,您可以在其上构建任何您想要的东西。

参见

  • 第一章Google Maps JavaScript API 基础知识中的在自定义 DIV 元素中创建简单地图食谱

  • 第五章理解 Google Maps JavaScript API 事件中的获取鼠标点击坐标食谱

第七章。使用服务

在本章中,我们将涵盖:

  • 查找地址的坐标

  • 通过点击在地图上查找地址

  • 通过点击在地图上获取海拔

  • 为给定位置创建距离矩阵

  • 获取给定位置的路线

  • 将街景添加到您的地图中

简介

本章重点介绍 Google Maps JavaScript API 提供的各种服务。这些服务增加了显著的功能,在很大程度上将 Google Maps 与其竞争对手区分开来。底层数据的可靠性和质量使得这些服务更加受到重视,因为这允许使用 Google Maps 的应用程序提供额外的功能。

这些服务通常遵循异步模式,其中请求发送到外部服务器,并提供一个回调方法来处理响应。

注意

这些服务并非在全世界范围内都可用;存在限制或配额——即使可用——以防止滥用这些服务。在相关食谱中将提供关于这些服务的详细信息。

这些服务的优点在于,作为 Google Maps JavaScript API 的一部分,它们与 API 的类和对象完全兼容。

例如,您可以使用 Google Maps API 路线服务查找两个地址之间的路线。首先,您提供必要的参数进行请求。然后,通过使用您的回调函数,如果一切顺利,您将获得路线。但是,对于时间延迟,您可能需要考虑将路线叠加到基础地图上的方法。幸运的是,API 提供了这样的基础设施,因此只需一行额外的代码,您就可以在基础地图上观察您请求的路线。

本章将详细描述每种服务类型,包括地理编码、路线、海拔、距离矩阵和街景,每个食谱都包含一个相关场景。

查找地址的坐标

在地图上定位地址或地点一直是一项繁琐的任务,Google Maps JavaScript API 通过地理编码服务简化了这项任务。地理编码在其最简单的定义中,是将地理坐标与地址信息相关联,无论是仅一个街道名称,详细的建筑编号和邮政编码,还是仅一个地区名称。

通过拥有您各自地址的坐标,您可以在地图应用程序中轻松地将它们叠加。

在本食谱中,您将成功输入您的假日地点和地址,然后在您的应用程序中将它们作为标记映射到基础地图上。

准备工作

本食谱将利用在第三章中“向地图添加标记”食谱中引入的与添加矢量图层相关概念,特别是标记。建议您阅读此食谱,以便对矢量图层及其属性有一个整体的理解。

如何做到这一点...

您可以通过以下步骤定位您的地址:

  1. 创建 HTML 标记,以便您可以输入您的地址并搜索它们:

    <input id="addressField" type="text" size="30"  placeholder="Enter your Address" />
    <input type="button" id="listAddressBtn" value="Pin Address On Map" />
    <p id="placesText"></p>
    <ul id="addressList" class="addressList"></ul>
    
  2. 定义全局geocoder对象:

    var geocoder;
    
  3. 在您的initMap()函数中初始化geocoder对象:

    geocoder = new google.maps.Geocoder();
    
  4. 获取listAddressBtn按钮元素并添加一个click事件监听器:

    var listAddressBtn = document.getElementById('listAdressBtn');
    listAddressBtn.addEventListener('click', function(){
        listAddresses();
    });
    
  5. 创建一个函数,用于在addressList元素上列出地址并发送地理编码请求:

    function listAddresses() {
        //get text input handler
        var addressField = document.getElementById('addressField');
        //get addressList <ul> element handle
        var addressList = document.getElementById('addressList');
        if (addressList.children.length === 0) {
            var placesText = document.getElementById('placesText');
            placesText.innerHTML = 'Places You Have Visited (Click on the place name to see on map):';
        }
        //create a list item
        var listItem = document.createElement('li');
        //get the text in the input element and make it a //list item
        listItem.innerHTML = addressField.value;
        listItem.addEventListener('click', function() {
            geocodeAddress (listItem.innerHTML);
        });
        //append it to the <ul> element
        addressList.appendChild(listItem);
        //call the geocoding function
        geocodeAddress(addressField.value);
    }
    
  6. 创建一个用于地理编码输入地址的函数:

    function geocodeAddress(addressText) {
        //real essence, send the geocoding request
        geocoder.geocode( {'address': addressText}, function(results, status) {
            //if the service is working properly...
            if (status == google.maps.GeocoderStatus.OK) {
                //show the first result on map
                pinpointResult(results[0]);
            } else {
                alert('Cannot geocode because: ' + status);
            }
        });
    }
    
  7. 在地图上放置一个标记,并将其InfoWindow对象附加以显示其详细信息:

    function pinpointResult(result) {
        var marker = new google.maps.Marker({
            map: map,
            position: result.geometry.location
        });
    
        map.setCenter(result.geometry.location);
        map.setZoom(16);
    
        //infowindow stuff
        google.maps.event.addListener(marker, 'click', function() {
            var popupContent = '<b>Address: </b> ' + result.formatted_address;
            popup.setContent(popupContent);
            popup.open(map, this);
        });
    }
    
  8. 您的地址将如以下截图所示在地图上标记:如何做到这一点…

它是如何工作的...

发起地理编码请求实际上非常简单。首先,您创建一个Geocoder对象:

geocoder = new google.maps.Geocoder();

然后,您从geocoder对象调用geocode()方法,向其地址参数提供一个地址、地点或地区名称:

geocoder.geocode( {'address': addressText}, function(results, status) {…});

此方法接收地址,将其发送到谷歌服务器进行地理编码,并通过回调函数以GeocoderResult对象数组的形式返回结果。

响应以数组形式按最相关匹配的顺序返回。例如,当您搜索Colosseum时,第一个GeocoderResult对象的formatted_address属性是:

Colosseum, Piazza del Colosseo, 1, 00184 Rome, Italy

第二个是:

Colosseum, Enschede, The Netherlands

您可以迅速理解,古罗马的著名且高度旅游的斗兽场比第二个结果更受欢迎。当然,您可以通过限制地图边界和国家代码来偏置结果(我们将在接下来的章节中详细回顾)。然而,如果没有任何干预,您将通过各种国家和大陆看到高人气地理编码结果位于顶部。

GeocoderResult对象具有geometry属性,这样您就可以通过在基础地图上叠加标记来查看它。在我们的配方中,pinpointResult()函数利用了这一点,其中它将名为resultGeocoderResult对象作为其唯一参数:

    function pinpointResult(result) {
        var marker = new google.maps.Marker({
            map: map,
            position: result.geometry.location
        });
    ...
    }

还有更多...

地理编码服务请求和响应有一系列广泛的选择和属性。让我们首先从请求开始。除了是GeocodeRequest对象的主要和必需参数的address参数(作为Geocoder对象的geocode()方法的第一个参数提供)之外,还有一个bounds属性,您可以使用它来指定返回的地理编码结果,如下面的代码所示:

    geocoder.geocode({
        'address': addressText,
        'bounds': new google.maps.LatLngBounds(
        new google.maps.LatLng(25.952910068468075, -15.93734749374994),
        new google.maps.LatLng(57.607047845370246, 54.37515250625006)
        )
        },
        function(results, status) {...}
    );

当您提供bounds属性时,例如前面代码中用于覆盖欧洲的属性,然后当您搜索 Sun Street 时,第一个结果是英国。这是因为bounds属性偏置了在提供的LatLngBounds对象内部存在的地理编码结果。当您删除bounds属性时,相同搜索的第一个结果来自美国。

此外,您还可以通过使用region参数来偏置结果,其中接受 IANA 语言区域子标签。

注意

IANA 语言区域子标签的完整列表可以在www.iana.org/assignments/language-subtag-registry/language-subtag-registry找到。

关于GeocodeRequest对象的详细信息可以在developers.google.com/maps/documentation/javascript/reference#GeocoderRequest找到。

例如,在以下代码中,将region参数设置为've'(如委内瑞拉所示),并搜索'Valencia',将首先返回委内瑞尔的'Valencia'市:

    geocoder.geocode({
        'address': addressText,
        'region':'ve'},
        function(results, status) {...}
    );

如果没有region参数,将首先返回西班牙的'Valencia'市。

将返回的结果及其属性传递给GeocoderResult对象,该对象携带一个精度指示器,因为某些地理编码过程涉及插值和匹配,而不是一对一的相等。

结果的值存储在GeocoderResult对象的geometry属性中,该属性包含location_type属性。这些值按照从高到低的精度顺序排列:

  • google.maps.GeocoderLocationType.ROOFTOP

  • google.maps.GeocoderLocationType.RANGE_INTERPOLATED

  • google.maps.GeocoderLocationType.GEOMETRIC CENTER

  • google.maps.GeocoderLocationType.APPROXIMATE

在前面的代码中,ROOFTOP值表示确切的地址,RANGE_INTERPOLATED表示道路的某些部分之间存在插值,GEOMETRIC_CENTER表示道路或区域的几何中心,最后APPROXIMATE告诉我们返回的结果位置是一个近似值。

例如,当我们搜索“William Village”时,第一个结果的formatted_address是:

    "Bremerton, WA, USA"

结果的几何形状的location_type属性是APPROXIMATE。这通常发生在搜索短语与返回结果之间没有直接联系的情况下,正如我们的例子一样。

除了地理编码过程的精度之外,我们还可以通过GeocoderResult对象的types属性获取该对象类型。types属性是一个数组,表示返回结果所属的类别。

例如,对于罗马的斗兽场,types属性是:

    ["point_of_interest", "establishment"]

而对于罗马的 Via del Corso,它是:

    ["route"]

对于佛罗伦萨的乌菲齐美术馆,它是:

    ["museum", "point_of_interest", "establishment"]

注意

GeocoderResult对象的types属性的可能值的完整列表可以在[developers.google.com/maps/documentation/javascript/geocoding #GeocodingAddressTypes](https://developers.google.com/maps/documentation/javascript/geocoding #GeocodingAddressTypes)找到。

需要注意的是,通过回调函数获取的地理编码请求结果需要另一个参数,这个参数是关于请求状态的。这个参数最显著的可能值包括:

  • google.maps.GeocoderStatus.OK

  • google.maps.GeocoderStatus.ZERO_RESULTS

  • google.maps.GeocoderStatus.OVER_QUERY_LIMIT

  • google.maps.GeocoderStatus.REQUEST_DENIED

  • google.maps.GeocoderStatus.INVALID_REQUEST

除了GeocoderStatus.OK之外的所有值都指向一个问题。在所有这些问题中,GeocoderStatus.OVER_QUERY_LIMIT需要特别注意。在本章的介绍中,我们已经提到,所有这些 Google Maps 服务在地理和请求速率方面都受到限制。当你的地理编码服务使用超出限制时,就会触发此状态码。

注意

可以在developers.google.com/maps/documentation/business/articles/usage_limits#limitexceeded找到关于OVER_QUERY_LIMIT状态码的详细解释。

可以在developers.google.com/maps/documentation/javascript/geocoding#GeocodingStatusCodes找到GeocoderStatus对象可能值的完整列表。

参考以下内容

  • 第三章中的在地图上添加标记配方,添加矢量图层

通过点击地图查找地址

在前面的配方中,我们手头有地址,我们的目标是找到地图位置;换句话说,地球上的地址坐标。但是,如果我们有确切的坐标并尝试找到与这些确切坐标匹配的地址会发生什么呢?

这个过程被称为逆向地理编码,它是将坐标转换为可读地址的过程。

在本配方中,我们将利用 Google Maps JavaScript API 的逆向地理编码功能。当用户点击地图时,我们将找到用户点击的地址并立即显示给他/她。

准备工作

查阅第六章中的“在地图上绘制形状”配方 Drawing shapes on the map,这将使你的工作更加轻松,因为该配方需要更详细地了解绘制形状及其背景。

如何操作…

下面是允许用户点击地图并找到他/她点击的地方的地址的步骤:

  1. geocoder对象定义为全局变量:

    var geocoder;
    
  2. popup对象定义为全局变量:

    var popup;
    
  3. initMap()函数中初始化geocoderpopup对象:

    geocoder = new google.maps.Geocoder();
    popup = new google.maps.InfoWindow();
    
  4. initMap()函数内部创建drawingManager对象:

    var drawingManager = new google.maps.drawing.DrawingManager(
    {
        //initial drawing tool to be enabled, we want to be in //no drawing mode at start
        drawingMode:null,
        //enable the drawingControl to be seen in the UI
        drawingControl:true,
        //select which drawing modes to be seen in the //drawingControl and position the drawingControl itself
        drawingControlOptions: {
            //select a control position in the UI
            position: google.maps.ControlPosition.TOP_CENTER,
            //selected drawing modes to be seen in the control
            drawingModes:[
            google.maps.drawing.OverlayType.MARKER	
            ]
        }
    });
    
  5. 启用绘图功能:

    drawingManager.setMap(map);
    
  6. 为用户绘制的标记完成添加事件监听器,执行逆向地理编码任务并找到地址:

    google.maps.event.addListener(drawingManager, 'markercomplete', function(marker) {
        //get the LatLng object of the marker, it is necessary //for the geocoder
        var markerPosition = marker.getPosition();
        //reverse geocode the LatLng object to return the //addresses
        geocoder.geocode({'latLng': markerPosition}, function(results, status) {
            //if the service is working properly...
            if (status == google.maps.GeocoderStatus.OK) {
                //Array of results will return if everything //is OK
                if (results) {
                    //infowindow stuff
                    showAddressOfResult(results[0],marker);
                }
            }
            //if the service is not working, deal with it
            else {
                alert('Reverse Geocoding failed because: ' + status);
            }
        });
    });
    
  7. 创建一个函数,用于在标记绘制的InfoWindow对象上显示地址:

    function showAddressOfResult(result, marker) {
        //set the center of the map the marker position
        map.setCenter(marker.getPosition());
        map.setZoom(13);
    
        //create the InfoWindow content
        var popupContent = '<b>Address: </b> ' + result.formatted_address;
    
        //set the InfoWindow content and open it
        popup.setContent(popupContent);
        popup.open(map, marker);
    }
    
  8. 现在,你可以点击并获取以下截图所示的信息窗口中的地址信息:如何操作…

它是如何工作的...

如果你已经查看本章的“根据地址查找坐标”菜谱,你可能已经意识到我们再次使用了前面展示的相同的geocoder对象:

    geocoder = new google.maps.Geocoder();

然而,这次我们是以LatLng对象的形式提供坐标对,而不是地址文本,用于熟悉的geocoder对象的geocode()方法:

    geocoder.geocode({'latLng': markerPosition}, function(results, status) {
    ...
    });

实际上,geocode()方法还有一个我们没有在前一个菜谱中讨论的属性;那就是接受LatLng对象的latlng属性。

因此,geocoder对象的geocode()方法可以双向使用,既可以用于地理编码,也可以用于反向地理编码。对于地理编码,我们必须使用address属性来填写我们想要获取位置的地址。对于反向地理编码,我们必须使用latlng属性来填写我们想要获取地址信息的LatLng对象。

我们通过使用标记的getPosition()方法来获取用户绘制的标记的LatLng对象:

    var markerPosition = marker.getPosition();

在我们的回调函数中,我们必须为我们的反向地理编码请求提供,当我们的请求回复时,我们有两个参数会获取它们的值:

    function(results, status) {
        ...
    }

第一个参数是一个GeocoderResult对象数组,第二个参数是一个GeocoderStatus对象数组。

注意

你可以在本章的“根据地址查找坐标”菜谱中查看GeocoderStatus对象的可用的详细分解,该菜谱在GeocoderResult对象中。

在测试服务状态后,如果一切正常,我们可以与我们的GeocoderResult对象数组一起工作:

    if (status == google.maps.GeocoderStatus.OK) {
        //Array of results will return if everything //is //OK
        if (results) {
            //infowindow stuff
            showAddressOfResult(results[0], marker);
        }
    }

我们选择了第一个对象,因为它是最精确的。例如,对于我们的菜谱中的标记位置,完整的地址信息数组是:

    results[0].formatted_address: "764-768 5th Avenue, New York, NY 10019, USA"
    results[1].formatted_address: "5 Av/West 60 - 59 St, New York, NY 10019, USA"
    results[2].formatted_address: "New York, NY 10153, USA"
    results[3].formatted_address: "5 Av/59 St, New York, NY 10022, USA"
    results[4].formatted_address: "New York, NY 10019, USA"
    results[5].formatted_address: "Midtown, New York, NY, USA"
    results[6].formatted_address: "Manhattan, New York, NY, USA"
    results[7].formatted_address: "New York, NY, USA"
    results[8].formatted_address: "New York, NY, USA"
    ...
    results[10].formatted_address: "New York, USA"
    results[11].formatted_address: "United States"

你可以观察到,从数组的开始迭代到结束,我们最终到达了"United States",这是我们反向地理编码请求中最不精确的地址信息。

参见

  • 本章的“根据地址查找坐标”菜谱

  • 第六章“在地图上绘制形状”菜谱

通过点击地图获取高程

Google Maps JavaScript API 提供了关于高程数据的信息,相对于海平面返回正值。它还以负值提供关于海底深度的信息。

使用ElevationService对象,我们可以获取单个位置以及路径上的高程信息。

在这个菜谱中,我们首先将展示如何从用户选择的单个点获取高程数据,然后我们将使用路径重复相同的场景。

准备中

建议快速浏览第六章中的在地图上绘制形状配方,因为该配方涵盖了使用 Google Maps JavaScript API 绘制形状的每个细节。

如何做到这一点...

如果你遵循给定的步骤,你可以查看你选择的地点的海拔数据:

  1. elevator对象定义为全局变量:

    var elevator;
    
  2. popup对象定义为全局变量:

    var popup;
    
  3. initMap()函数内部初始化elevatorpopup对象:

    elevator = new google.maps.ElevationService();
    popup = new google.maps.InfoWindow();
    
  4. initMap()函数内部创建drawingManager对象:

    var drawingManager = new google.maps.drawing.DrawingManager(
    {
        //initial drawing tool to be enabled, we want to be in //no drawing mode at start
        drawingMode:null,
        //enable the drawingControl to be seen in the UI
        drawingControl:true,
        //select which drawing modes to be seen in the //drawingControl and position the drawingControl itself
        drawingControlOptions: {
            //select a control position in the UI
            position: google.maps.ControlPosition.TOP_CENTER,
            //selected drawing modes to be seen in the control
            drawingModes: [
            google.maps.drawing.OverlayType.MARKER	
            ]
        }
    });
    
  5. 启用绘制功能:

    drawingManager.setMap(map);
    
  6. 为用户绘制的标记完成添加事件监听器,使用elevator对象发送请求,并找到标记位置的海拔数据:

    google.maps.event.addListener(drawingManager, 'markercomplete', function(marker) {
        //get the LatLng object of the marker, it is necessary //for the geocoder
        var markerPosition = marker.getPosition();
        //embed the marker position in an array
        var markerPositions = [markerPosition];
        //send the elevation request and get the results in the //callback function
        elevator.getElevationForLocations({'locations': markerPositions}, function(results, status) {
            //if the service is working properly...
            if (status == google.maps.ElevationStatus.OK) {
                //Array of results will return if everything //is OK
                if (results) {
                    //infowindow stuff
                    showElevationOfResult(results[0], marker);
                }
            }
            //if the service is not working, deal with it
            else {
                alert('Elevation request failed because: ' + status);
            }
        });
    });
    
  7. 为在标记的InfoWindow对象上显示海拔数据创建一个函数:

    function showElevationOfResult(result, marker) {
        //set the center of the map the marker position
        map.setCenter(marker.getPosition());
        map.setZoom(13);
    
        //create the InfoWindow content
        var popupContent = '<b>Elevation: </b> ' + result.elevation;
    
        //set the InfoWindow content and open it
        popup.setContent(popupContent);
        popup.open(map, marker);
    }
    
  8. 现在,你将得到你点击的点的海拔,如下面的截图所示:如何做到这一点…

它是如何工作的...

我们使用ElevationService对象获取海拔数据:

elevator = new google.maps.ElevationService();

elevator对象有一个getElevationForLocations()方法,它接受一个LatLng对象数组,以返回特定LatLng对象所代表每个位置的海拔数据。换句话说,如果你在数组中分配了三个LatLng对象,你将在回调函数中获得一个包含三个ElevationResult对象的数组:

elevator.getElevationForLocations({'locations': markerPositions}, function(results, status) {
...});

然而,请注意,当LatLng对象的数量嵌入在数组中时,海拔的准确性会降低。因此,如果你想获得高精度,你必须选择只包含单个元素的LatLng数组,正如我们案例中所示。

getElevationForLocations()方法的locations属性提供了LatLng对象数组。然而,我们手头有一个marker对象来处理当用户绘制标记时触发的markercomplete事件:

    google.maps.event.addListener(drawingManager, 'markercomplete',   function(marker)
    {...});

因此,我们将单个标记位置转换为只包含一个元素的数组:

    var markerPosition = marker.getPosition();
    var markerPositions = [markerPosition];

在回调函数中,我们获取服务状态以及ElevationResult对象数组:

    function(results, status) {
        //if the service is working properly...
        if (status == google.maps.ElevationStatus.OK) {
            //Array of results will return if everything //is OK
            if (results) {
                //infowindow stuff
                showElevationOfResult(results[0],marker);
            }
        }
        //if the service is not working, deal with it
        else {
            alert('Elevation request failed because: ' + status);
        }
    }

status参数的类型为ElevationStatus,其常量与GeocoderStatus对象非常相似,如下列所示:

  • google.maps.ElevationStatus.OK

  • google.maps.ElevationStatus.UNKNOWN_ERROR

  • google.maps.ElevationStatus.OVER_QUERY_LIMIT

  • google.maps.ElevationStatus.REQUEST_DENIED

  • google.maps.ElevationStatus.INVALID_REQUEST

除了ElevationStatus.OK之外,所有状态值都指向一个问题。其他值在其名称中都有明确的解释。

注意

ElevationStatus对象的可能值完整列表和详细信息可以在[developers.google.com/maps/documentation/javascript/reference# ElevationStatus](https://developers.google.com/maps/documentation/javascript/reference# ElevationStatus)找到。

results参数的类型是ElevationResultElevationResult对象有三个属性,称为elevationlocationresolution。我们在showElevationOfResult()函数中使用了elevation属性:

    var popupContent = '<b>Elevation: </b> ' + result.elevation;

高程数据是地形为正数,海洋底部为负数。

位置属性是ElevationResultLatLng对象,而分辨率属性是用于生成/插值此高程数据的样本点之间的距离(以米为单位)。分辨率越高,高程数据越不准确。

相关内容

  • 第六章中的在地图上绘制形状配方

为给定位置创建距离矩阵

Google Maps JavaScript API 包含一些有趣且特别有用的属性,其中之一就是距离矩阵服务。使用此服务,您可以计算多个起点和目的地之间的旅行时间和距离。

这特别有用,当您想要对您的旅行节点有一个一对一的报告时,无论是配送业务还是仅夏季节日。此服务为您提供了您选择的旅行方式(驾驶、步行和骑行)的旅行时间和距离;您可以看到针对每个起点和终点的结果。

值得注意的是,此服务的输出不能映射到基础地图上;您可以获取关于旅行时间和持续时间的详细信息,但对于路线,您必须使用本章后面详细解释的路线服务获取给定位置的路线

在本配方中,我们将定位起点和目的地,并获取我们位置的距离矩阵结果。

准备工作

此配方将使用绘图库;因此,建议您阅读第六章中的在地图上绘制形状配方,Google Maps JavaScript 库,并了解相关主题。

如何操作…

您可以绘制起点和终点,然后通过点击按钮请求距离矩阵。您可以通过遵循以下步骤了解如何操作:

  1. 添加 HTML input元素,类型为button,以启动距离矩阵请求:

    <input type="button" id ="generateDistanceMatrix" value="Generate Distance Matrix" />
    
  2. 定义全局变量:

    //define an array that includes all origin LatLng objects
    var originLatLngs;
    //define an array that includes all destination LatLng objects
    var destinationLatLngs;
    //define a global DistanceMatrixService object
    var distanceMatrixService;
    //define a global markerCount variable
    var markerCount;
    //define a global matrixResultDiv variable
    var matrixResultDiv;
    
  3. initMap()函数中初始化全局变量:

    //initialize originLatLngs array
    originLatLngs = [];
    //initialize destinationLatLngs array
    destinationLatLngs = [];
    //initialize markerCount - the count of markers to be drawn
    markerCount = 0;
    //assign matrixResultDiv to the specific div element
    matrixResultDiv = document.getElementById('matrixResultDiv');
    
  4. 获取button元素并添加一个click事件处理器:

    var generateDistanceMatrixBtn = document.getElementById('generateDistanceMatrix');
    generateDistanceMatrixBtn.addEventListener('click', function(){
        makeDistanceMatrixRequest();
    });
    
  5. initMap()函数中初始化distanceMatrixService对象:

    distanceMatrixService = new google.maps.DistanceMatrixService();
    
  6. initMap()函数内创建drawingManager对象:

    var drawingManager = new google.maps.drawing.DrawingManager(
    {
        //initial drawing tool to be enabled, we want to be in //no drawing mode at start
        drawingMode: null,
        //enable the drawingControl to be seen in the UI
        drawingControl: true,
        //select which drawing modes to be seen in the //drawingControl and position the drawingControl itself
        drawingControlOptions: {
            //select a control position in the UI
            position: google.maps.ControlPosition.TOP_CENTER,
            //selected drawing modes to be seen in the control
            drawingModes: [
            google.maps.drawing.OverlayType.MARKER
            ]
        }
    });
    
  7. 启用绘制功能:

    drawingManager.setMap(map);
    
  8. 为用户绘制的标记完成添加事件监听器,根据标记指向的位置设置标记图标,是起点还是目的地,并限制标记的总数:

    google.maps.event.addListener(drawingManager, 'markercomplete', function(marker) {
        //count the number of markers drawn
        markerCount++;
    
        //limit the number of markers to 10
        if (markerCount > 10) {
            alert('No more origins or destinations allowed');
            drawingManager.setMap(null);
            marker.setMap(null);
            return;
        }
    
        //distinguish the markers, make the blue ones be //destinations and red ones origins
        if (markerCount % 2 === 0) {
            destinationLatLngs.push(marker.getPosition());
            marker.setIcon('icons/b' + destinationLatLngs.length + '.png');
        }
        else {
            originLatLngs.push(marker.getPosition());
            marker.setIcon('icons/r' + originLatLngs.length + '.png');
        }
    });
    
  9. 创建一个函数,通过使用 getDistanceMatrix() 方法为 distanceMatrixService 对象准备请求属性并发送请求:

    function makeDistanceMatrixRequest() {
        distanceMatrixService.getDistanceMatrix(
            {
                origins: originLatLngs,
                destinations: destinationLatLngs,
                travelMode: google.maps.TravelMode.DRIVING,
            },
            getDistanceMatrixResult
        );
    }
    
  10. distanceMatrixService 对象的 getDistanceMatrix() 方法调用创建一个名为 getDistanceMatrixResult 的回调函数:

    function getDistanceMatrixResult(result, status) {
        //clear the div contents where matrix results will be //written
        matrixResultDiv.innerHTML = '';
    
        //if everything is OK
        if (status == google.maps.DistanceMatrixStatus.OK) {
            //get the array of originAddresses
            var originAddresses = result.originAddresses;
            //get the array of destinationAddresses
            var destinationAddresses = result.destinationAddresses;
    
            //there are two loops, the outer is for origins, //the inner will be for destinations,
            //their intersection will be the element object //itself
            for (var i = 0, l= originAddresses.length; i < l; i++) {
                //get the elements array
                var elements = result.rows[i].elements;
                for (var j = 0, m= elements.length;  j < m; j++) {
                    var originAddress = originAddresses[i];
                    var destinationAddress = destinationAddresses[j];
                    //get the element object
                    var element = elements[j];
    
                    //get distance and duration properties for //the element object
                    var distance =  element.distance.text;
                    var duration = element.duration.text;
                    //write the results to the div for each //element object
    
                    writeDistanceMatrixResultOnDiv(originAddress, destinationAddress, distance, duration, i, j);
                }
            }
        }
    else {
            alert('Cannot find distance matrix because: ' + status);
        }
    }
    
  11. 创建一个函数,由前面列出的回调函数调用,将结果写入 matrixResultDiv 对象:

    function writeDistanceMatrixResultOnDiv(originAddress, destinationAddress, distance, duration, originAddressCount, destinationAddressCount) {
        //get the existing content
        var existingContent = matrixResultDiv.innerHTML;
    
        var newContent;
        //write the Origin Address and Destination Address //together with travel distance and duration
        newContent = '<b>Origin ' + letterForCount(originAddressCount) + ' :</b><br />';
        newContent += originAddress + '<br />';
        newContent += '<b>Destination ' + letterForCount(destinationAddressCount) + ' :</b><br />';
        newContent += destinationAddress + '<br />';
        newContent += '<b>Distance: </b> ' + distance + '<br />';
        newContent += '<b>Duration: </b> ' + duration + '<br />';
        newContent += '<br />';
    
        //add the newContent to the existingContent of the //matrixResultDiv
        matrixResultDiv.innerHTML = existingContent + newContent;
    }
    
  12. 创建一个将计数转换为字母的函数;目的是将计数与标记图标匹配:

    function letterForCount(count)
    {
        switch (count)
        {
            case 0:
            return 'A';
            case 1:
            return 'B';
            case 2:
            return 'C';
            case 3:
            return 'D';
            case 4:
            return 'E';
            default:
            return null;
        }
    }
    
  13. 你现在将拥有你选择点之间的距离矩阵,如下截图所示:如何操作…

工作原理...

在我们的菜谱中,我们允许用户将标记指向他们选择的地点下方。然而,我们遵循的方案是,第一个标记将指向第一个起点,第二个将指向第一个目的地,第三个将指向第二个起点,第四个将指向第二个目的地位置,依此类推。此外,我们限制绘制的标记数量为 10 个。

这就是绘制标记的过程。然后,我们将准备要提供给 distanceMatrixService 对象的起点和目的地位置。对象初始化如下代码所示:

    distanceMatrixService = new google.maps.DistanceMatrixService();

用户按下输入按钮元素,我们通过 getDistanceMatrix() 方法触发请求:

    function makeDistanceMatrixRequest() {
        distanceMatrixService.getDistanceMatrix(
        {
            origins: originLatLngs,
            destinations: destinationLatLngs,
            travelMode: google.maps.TravelMode.DRIVING,
        },
        getDistanceMatrixResult
    );
}

在这里,我们将 originLatLngs 传递给 origins 属性,其中 originLatLngs 是从用户绘制的标记中收集的 LatLng 对象数组——在 drawingManager 对象的 markercomplete 事件监听器中的奇数标记:

    if (markerCount % 2 === 0) {
        destinationLatLngs.push(marker.getPosition());

    }
    else {
        originLatLngs.push(marker.getPosition());

  }

destinations 属性在 destimationLatLngs 数组中按照相同的逻辑设置。

作为快速提醒,destinationsorigins 属性可以接受地址字符串数组以及 LatLng 对象数组,就像在我们的例子中一样。

我们在请求中使用的第三个属性是 travelMode 属性,它用于设置旅行模式。除了 TravelMode.DRIVING 之外,此属性还有其他选项:

  • TravelMode.WALKING

  • TravelMode.BICYCLING

除了携带 originsdestinationstravelMode 属性的 DistanceMatrixRequest 对象外,我们还为 getDistanceMatrix() 方法调用提供了一个名为 getDistanceMatrixResult 的回调函数。getDistanceMatrixResult 函数有两个参数:一个是服务的响应,另一个是服务状态。如下代码所示:

    function getDistanceMatrixResult(result, status)
    {...}

在这个函数中,首先我们检查服务是否正常工作:

    if (status == google.maps.DistanceMatrixStatus.OK)
    {...}

注意

DistanceMatrixStatus对象的完整列表和可能值的详细信息可以在[developers.google.com/maps/documentation/javascript/reference# DistanceMatrixStatus](https://developers.google.com/maps/documentation/javascript/reference# DistanceMatrixStatus)找到。

然后,我们处理类型为DistanceMatrixResponse对象的结果,该对象携带字符串数组originAddressesdestinationAddresses以及名为rowsDistanceMatrixResponseRow数组。首先,我们获取originAddressesdestinationAddresses数组:

    var originAddresses = result.originAddresses;
    var destinationAddresses = result.destinationAddresses;

rows数组由另一个名为elements的数组组成,其中其子元素是DistanceMatrixResponseElement类型。因此,我们必须有两个循环来遍历DistanceMatrixResponseElement对象:

    for (var i = 0, l=originAddresses.length; i < l; i++) {
        //get the elements array
        var elements = result.rows[i].elements;
        for (var j = 0, m=elements.length;j < m; j++) {
            ...
            var element = elements[j];
                    ...
        }
    }

DistanceMatrixResponseElement 对象有两个突出的属性,我们在我们的配方中使用了它们:一个是距离,另一个是持续时间。它们在以下代码中进行了详细说明:

    var distance =  element.distance.text;
    var duration = element.duration.text;

通过使用这些属性,我们达到了相应起始地址和目标地址的特定距离和持续时间属性。

参见

  • 在第六章的在地图上绘制形状配方中,Google Maps JavaScript 库

为给定位置获取路线

在两个或更多位置之间拥有路线图一直是用户、驾驶员、游客等人的最爱。对于驾驶、步行或任何其他交通方式,导航产品的需求通过这些产品的销售来衡量。

一个好的路线服务需要包含综合道路数据,并填充多个属性,如交通流向、转弯限制、桥梁和地下隧道。希望 Google Maps 在后台有这些数据;因此,对于 Google 来说,在 Google Maps 中包含此功能是非常自然的。

在 Google Maps 中,路线可能是最常用的功能之一。它也包含在 Google Maps JavaScript API 中,为开发者提供了在所选位置之间以广泛选项生成路线的编程能力。

在这个配方中,首先我们将让用户输入一个地址或任何地点的位置,使用 Geocoder 服务将其标记出来,然后按照它们进入的顺序提供它们之间的路线。

准备工作

本配方将使用与本章开头为地址查找坐标配方中引入的 Geocoder 服务相关的概念。强烈建议您阅读此配方,以便对 Geocoder 及其用法有一个全面的了解。

如何做到这一点...

您可以通过执行以下步骤输入您的地址并获取它们之间的路线:

  1. 插入一个ContainerDiv HTML 元素,该元素将被放置在地图的div元素的右侧:

    <div id="DirectionsContainerDiv">
        <div id="PlacesContainerDiv">
            <b>Get Directions Between your Places</b></br>
            <input id="addressField" type="text" size="30"  placeholder="Enter your Address" />
            <input type="button" id ="pinAddressOnMapBtn" value="Pin Address On Map" onclick="listAddresses()" />
            <input type="button" id = "getDirectionsBtn" disabled value="Get Directions" onclick="getDirections()" />
            <p id="placesText"></p>
            <ul id="addressList" class="addressList">
            </ul>
        </div>
        <div id="DirectionsListContainerDiv">
            <div id="DirectionsListDiv">
            </div>
        </div>
    </div>
    
  2. 定义全局变量:

    //define global marker popup variable
    var popup;
    //define global geocoder object
    var geocoder;
    //define global markers array
    var markers;
    //define global DirectionsService object
    var directionsService;
    //define global DirectionsRenderer object
    var directionsRenderer;
    
  3. initMap()函数中初始化全局变量:

    //initialize geocoder object
    geocoder = new google.maps.Geocoder();
    //initialize markers array
    markers = [];
    //initialize directionsService object
    directionsService = new google.maps.DirectionsService();
    //initialize directionsRenderer object
    directionsRenderer = new google.maps.DirectionsRenderer();
    
  4. 给出directionsRenderer的指令,以便它在地图上绘制路线,并在地图的右侧列出路线:

    //directionsRenderer will draw the directions on current //map
    directionsRenderer.setMap(map);
    //directionsRenderer will list the textual description of //the directions
    //on directionsDiv HTML element
    directionsRenderer.setPanel(document.getElementById('DirectionsListDiv'));
    
  5. 创建一个函数,用于列出用户输入的地址并调用执行地理编码的函数:

    function listAddresses() {
        //get text input handler
        var addressField = document.getElementById('addressField');
        //get addressList <ul> element handle
        var addressList = document.getElementById('addressList');
        if (addressList.children.length == 0) {
            var placesText = document.getElementById('placesText');
            placesText.innerHTML = 'Places You Have Visited (Click on the place name to see on map):';
        }
        //create a list item
        var listItem = document.createElement('li');
        //get the text in the input element and make it a list //item
        listItem.innerHTML = addressField.value;
        listItem.addEventListener('click', function() {
            pinAddressOnMap(listItem.innerHTML);
        });
        //append it to the <ul> element
        addressList.appendChild(listItem);
        //call the geocoding function
        pinAddressOnMap(addressField.value);
        if (addressList.children.length > 1) {
            //get getDirectionsBtn button handler
            var getDirectionsBtn = document.getElementById('getDirectionsBtn');
            //enable the getDirectionsBtn
            getDirectionsBtn.disabled = false;
        }
        addressField.value = '';
    }
    
  6. 创建一个执行实际地理编码任务的函数:

    function pinAddressOnMap(addressText) {
        //real essence, send the geocoding request
        geocoder.geocode({'address': addressText}, function(results, status) {
            //if the service is working properly...
            if (status == google.maps.GeocoderStatus.OK) {
                //show the first result on map
                pinpointResult(results[0]);
            } else {
                alert('Cannot geocode because: ' + status);
            }
        });
    }
    
  7. 创建一个函数,用于放置用户输入地址信息的地理编码结果标记,并将InfoWindow对象附加到其上以显示其详细信息:

    function pinpointResult(result) {
        var marker = new google.maps.Marker({
            map: map,
            position: result.geometry.location,
            zIndex: -10
        });
    
        map.setCenter(result.geometry.location);
        map.setZoom(16);
    
        //infowindow stuff
        google.maps.event.addListener(marker, 'click', function() {
            var popupContent = '<b>Address: </b> ' + result.formatted_address;
            popup.setContent(popupContent);
            popup.open(map, this);
        });
        markers.push(marker);
    }
    
  8. 最后,可以通过使用getDirectionsBtn按钮处理程序来调用实际路线。创建一个函数,用于向directionsService对象发送请求,确保结果在地图上绘制并列出:

    function getDirections() {
        //define an array that will hold all the waypoints
        var waypnts = [];
        //define a directionsRequest object
        var directionRequest;
    
        //if there are stops other than the origin and the //final destination
        if (markers.length > 2) {
            for (i=1;i<=markers.length-2;i++) {
                //add them to the waypnts array
                waypnts.push({
                    location: markers[i].getPosition(),
                    stopover: true
                });
            }
    
            //prepare the directionsRequest by including //the waypoints property
            directionRequest = {
               origin:markers[0].getPosition(),
               destination: markers[markers.length-1].getPosition(),
              waypoints: waypnts,
              travelMode: google.maps.TravelMode.DRIVING
            };
        }
        else {
            //this time, do not include the waypoints property as //there are no waypoints
            directionRequest = {
                origin:markers[0].getPosition(),
                destination:markers[markers.length-1].getPosition(),
                travelMode: google.maps.TravelMode.DRIVING
            };
        }
    
        //send the request to the directionsService
        directionsService.route(directionRequest, function(result, status) {
            if (status == google.maps.DirectionsStatus.OK) {
                directionsRenderer.setDirections(result);
            }
            else
            {
                alert('Cannot find directions because: ' + status);
            }
        });
    }
    
  9. 现在,您将看到您选择点之间的路线,如下面的截图所示:如何操作…

它是如何工作的...

在这个菜谱中,我们同时使用了GeocoderServiceDirectionsService。然而,为了避免冗余(强烈建议阅读本章的查找地址坐标菜谱),我们将主要关注DirectionsService,准备请求属性,发送和获取绘制到地图上的结果,以及它的逐步文本描述。

首先,我们正在等待用户输入要地理编码并在地图上显示的地址。这些是我们将生成路线的地点。我们正在收集用户地理编码请求的结果标记,以便我们可以使用它们来生成路线:

    function pinpointResult(result) {
        ...
        markers.push(marker);
    }

一旦地理编码地址的数量超过 1,标有获取路线的按钮就会启用,以便用户可以请求在他们的地理编码地址之间获取路线:

    function listAddresses()
    {
        ...
        if (addressList.children.length > 1) {
            //get getDirectionsBtn button handler
            var getDirectionsBtn = document.getElementById('getDirectionsBtn');
            //enable the getDirectionsBtn
            getDirectionsBtn.disabled = false;
        }
        ...
    }

在此之后,只要我们准备好了基础设施,就可以生成路线,所以使用以下代码:

    directionsService = new google.maps.DirectionsService();
    directionsRenderer = new google.maps.DirectionsRenderer();

DirectionsService对象负责将DirectionsRequest对象发送到谷歌的服务器,而DirectionsRenderer对象,正如其名称所暗示的,将DirectionsResult对象渲染到地图上及其文本描述。

对于DirectionsRequest来说,起点和终点是逻辑上的必需项;然而,在起点和终点之间可能有途经点。如果用户地理编码了两个地址并点击了获取路线按钮,就没有途经点的位置,第一个地址成为起点,而第二个地址成为终点。

如果地理编码地址列表中有超过两个地址,第一个将是起点,最后一个将再次是终点。此外,在地址之间将存在途经点。我们正在考虑这些因素来准备DirectionsRequest参数,如下面的代码所示:

function getDirections() {
    ...
    //if there are stops other than the origin and the //final destination
    if (markers.length > 2)
    {
        for (var i=1, markers.length;i<=l-2;i++)
        {
            //add them to the waypnts array
            waypnts.push({
                location: markers[i].getPosition(),
                stopover: true
            });
        }

        //prepare the directionsRequest by including //the waypoints property
        directionRequest = {
            origin:markers[0].getPosition(),
            destination:markers[markers.length-1].getPosition(),
            waypoints: waypnts,
            travelMode: google.maps.TravelMode.DRIVING
        };
    }
    else
    {
        //this time, do not include the waypoints property as //there are no waypoints
        directionRequest = {
            origin:markers[0].getPosition(),
            destination:markers[markers.length-1].getPosition(),
            travelMode: google.maps.TravelMode.DRIVING
        };
    }
    ...
}

您可能已经意识到,我们为 directionsRequest 对象的 origindestination 属性提供了 LatLng 对象。这并不一定非得如此:您也可以为 origindestination 属性,以及我们添加到 waypnts 数组中的 DirectionsWaypoint 对象的 location 属性提供字符串形式的地址。此外,DirectionsWaypoint 对象还有一个 stopover 属性。它指定了航点实际上是一个停靠点,并分割了路线。DirectionsRequest 对象的另一个属性是 travelMode,我们选择了 DRIVING

注意

TravelMode 对象可能值的完整列表和详细信息可以在developers.google.com/maps/documentation/javascript/reference#TravelMode找到。

我们包含了一些大多数情况下都需要用到的属性;然而,DirectionsRequest 对象还有更多。

注意

DirectionsRequest 对象属性的完整列表可以在developers.google.com/maps/documentation/javascript/reference#DirectionsRequest找到。

在准备好我们的 directionsRequest 对象后,我们可以通过 directionsService 对象的 route() 方法发送请求:

    function getDirections() {
        ...
        //send the request to the directionsService
        directionsService.route(directionRequest, function(result, status) {
            if (status == google.maps.DirectionsStatus.OK) {
                directionsRenderer.setDirections(result);
            }
            else
            {
                alert('Cannot find directions because: ' + status);
            }
        });
    }

route() 方法接受两个参数:一个是 DirectionsRequest 对象,另一个是返回 DirectionsResultDirectionsStatus 对象的回调函数。

我们使用 status 对象测试一切是否按计划进行,该对象是 DirectionsStatus 类型。

注意

DirectionsStatus 对象的常量完整列表可以在developers.google.com/maps/documentation/javascript/reference#DirectionsStatus找到。

然后,我们使用旧的 directionsRenderer 对象将结果映射到 div 元素上,并添加文本描述:

    directionsRenderer.setDirections(result);

但是,directionsRenderer 对象是如何知道在哪里映射结果,或者写入哪个 div 元素上的?希望我们已经在 initMap() 函数中提前向 DirectionsRenderer 对象提供了指令:

    directionsRenderer.setMap(map);
    directionsRenderer.setPanel(document.getElementById('DirectionsListDiv'));

DirectionsRenderer 对象的 setMap() 方法将 DirectionsResult 对象映射到选定的地图对象。同样,setPanel() 方法用于选择一个 HTML div 元素,以便在它上面写入逐步说明。这样我们就可以在我们的地图实例中同时显示我们的路线。地图立即放大以显示整个路线,我们可以借助带有字母的额外标记来查看我们的行程顺序。

参见

  • 本章中关于查找地址坐标的配方

将街景添加到您的地图中

Google Maps 已经拥有高质量的地图数据,并且持续更新,同时还有最新的卫星图像。尽管这些已经足够让 Google Maps 变得如此受欢迎和成功,但还有一种观点引起了极大的兴趣——街景。

街景是该服务覆盖的道路的 360 度全景视图。

注意

可以在maps.google.com/intl/ALL/maps/about/behind-the-scenes/streetview/找到提供街景的国家和城市的完整列表。

在这个配方中,我们将介绍如何向当前视图添加街景全景,在地图视图和街景之间切换,并设置全景属性。

准备工作

在这个配方中,我们将利用本章中“寻找地址坐标配方”中介绍的与地理编码服务相关的概念。强烈建议阅读这个配方,以便对 Geocoder 及其用法有一个大致的了解。

如何操作…

以下步骤将使您的地理编码地址在街景中可见:

  1. 首先,使用 HTML 标记:

    <div id="addressDiv">
        <b>Map your Holiday Places</b><br />
        <input id="addressField" type="text" size="30"placeholder="Enter your Address" />
        <input type="button" id="pinAddress" value="Pin Address On Map" onclick="listAddresses()">
        <input type="button" value="Show Map" onclick="showMap()">
        <input type="button" value="Show StreetView" onclick="showStreetView()">
        <p id="placesText"></p>
        <ul id="addressList" class="addressList">
        </ul>
    </div>
    
  2. 定义全局对象:

    var geocoder;
    var streetView;
    
  3. initMap() 函数中初始化全局对象:

    geocoder = new google.maps.Geocoder();
    //initialize streetView object of type StreetViewPanorama
    streetView = map.getStreetView();
    
  4. 创建一个用于在 addressList 元素上列出地址并调用地理编码函数的函数:

    function listAddresses() {
        //get text input handler
        var addressField = document.getElementById('addressField');
        //get addressList <ul> element handle
        var addressList = document.getElementById('addressList');
        if (addressList.children.length == 0) {
            var placesText = document.getElementById('placesText');
            placesText.innerHTML = 'Places You Have Visited (Click on the place name to see on map):';
        }
        //create a list item
        var listItem = document.createElement('li');
        //get the text in the input element and make it// a list item
        listItem.innerHTML = addressField.value;
        listItem.addEventListener('click', function() {
            pinAddressOnMapOrStreetView(listItem.innerHTML);
        });
        //append it to the <ul> element
        addressList.appendChild(listItem);
        //call the geocoding function
        pinAddressOnMapOrStreetView(addressField.value);
    }
    
  5. 创建一个用于地理编码地址的函数:

    function pinAddressOnMapOrStreetView(addressText) {
        //send the geocoding request
        geocoder.geocode({'address': addressText}, function(results, status) {
            //if the service is working properly...
            if (status == google.maps.GeocoderStatus.OK) {
                //show the first result on map, either on
                showAddressMarkerOnMapOrStreetView(results[0]);
                if (streetView.getVisible())
                {
                    //set the streetView properties, its //location and "Point Of View"
                    setStreetViewOptions(results[0].geometry.location);
                }
            } else {
                alert('Cannot geocode because: ' + status);
            }
        });
    }
    
  6. 创建一个用于在地图上放置标记的函数,用于地理编码的地址:

    function showAddressMarkerOnMapOrStreetView(result) {
        var marker = new google.maps.Marker({
            map:map,
            position: result.geometry.location
        });
        map.setCenter(result.geometry.location);
        map.setZoom(16);
    }
    
  7. 创建一个用于设置街景全景属性的函数:

    function setStreetViewOptions(location)
    {
        //set the location of the streetView object
        streetView.setPosition(location);
        //set the "Point Of View" of streetView object
        streetView.setPov({
            heading: 0,
            pitch: 10
        });
    }
    
  8. 创建一个用于显示熟悉地图视图的函数,该函数通过带有“显示地图”标签的 HTML 点击按钮调用:

    function showMap()
    {
        var pinAddressBtn = document.getElementById('pinAddress');
        pinAddressBtn.value = 'Pin Address On Map';
        streetView.setVisible(false);
    }
    
  9. 创建一个用于显示以地图中心位置为位置的街景全景的函数,该函数通过带有“显示街景”标签的 HTML 点击按钮调用:

    function showStreetView() {
        var pinAddressBtn = document.getElementById('pinAddress');
        pinAddressBtn.value = 'Pin Address On StreetView';
        setStreetViewOptions(map.getCenter());
        streetView.setVisible(true);
    }
    
  10. 现在,您将拥有带有街景的地理编码地址,如下面的截图所示:如何操作…

它是如何工作的...

在我们的配方中,我们的目标是执行地理编码地址的普通任务,同时在同一地图的 div 元素中提供 Google Maps JavaScript API 的街景功能。为此,我们需要可用的 StreetViewPanorama 对象:

    streetView = map.getStreetView();

此对象使我们能够在我们的地图 div 元素内或在我们自己的单独 div 元素内显示街景。

注意

StreetViewPanorama 类的属性和方法完整描述可以在developers.google.com/maps/documentation/javascript/reference#StreetViewPanorama找到。

然后,当点击带有“显示街景”标签的按钮时,我们可以显示街景,提供 map 对象的中心位置作为 LatLng 对象:

    setStreetViewOptions(map.getCenter());

然后,我们通过指定位置和设置 streetView 对象 的视角来设置 StreetViewPanorama 对象的属性:

    function setStreetViewOptions(location) {
        //set the location of the streetView object
        streetView.setPosition(location);
        //set the "Point Of View" of streetView object
        streetView.setPov({
            heading: 0,
            pitch: 10
        });
    }

setPosition() 方法接受 LatLng 对象作为其参数,我们提供地图中心或地理编码地址的位置。通过使用 setPov() 方法,我们安排了街景的相机视角。要有一个相机视角,对象必须对准真实北方和街景的起点——通常是街道上的车辆。

StreetViewPov 对象的 heading 属性是相对于真实北方的角,其中 0 度是真实北方,90 度是东方,180 度是南方,270 度是西方。在我们的配方中,我们将 heading 属性设置为 0 度。

pitch 属性是相对于街景车辆的角。这意味着 90 度是完全向上,观看天空或云朵,而-90 度通常是完全向下,观看道路地面。

第八章.通过高级教程精通 Google Maps JavaScript API

在本章中,我们将介绍以下内容:

  • 将 WMS 图层添加到地图中

  • 将 Fusion Tables 图层添加到地图中

  • 将 CartoDB 图层添加到地图中

  • 使用 Google Maps JavaScript API 访问 ArcGIS Server

  • 使用 Google Maps JavaScript API 访问 GeoServer

简介

Google Maps JavaScript API 可能看起来像是一个仅显示基本地理相关功能的简单库,但实际上有很多功能可以探索。Google Maps JavaScript API 为开发者提供了许多基础类,用于构建针对不同情况的复杂解决方案,特别是对于地理信息系统(GIS)。

Google Maps JavaScript API 与 GIS 服务和工具具有很大的潜力。大多数 GIS 解决方案都需要基础地图和服务来支持工具本身,而 Google Maps JavaScript API 凭借其基础地图和服务是最佳解决方案。

有不同的 GIS 解决方案,从专有软件和服务到开源解决方案,如 Google Fusion Tables、CartoDB、ArcGIS Server 或 GeoServer。在本章中,我们将将这些服务器或服务与 Google Maps JavaScript API 集成。由于篇幅限制,省略了一些 GIS 服务创建过程。如果您需要更多信息,请查阅 Packt Publishing 的其他书籍,深入了解细节。

将 WMS 图层添加到地图中

Web Map Service(WMS)是开放地理空间联盟(OGC)的一个标准,用于通过互联网发布由地图服务器使用来自各种地理空间源(如 shapefiles 或地理空间数据库)生成的地理参照地图图像。WMS 服务中使用了各种版本,但最常用的版本是 1.1.1 或 1.3.0。WMS 有两个必需的请求类型:GetCapabilitiesGetMap

本教程展示了如何通过扩展google.maps.OverlayView类将 WMS 图层添加到 Google Maps JavaScript API 中。

准备工作

到目前为止,您应该已经知道如何创建地图,所以本教程中只解释了额外的代码行。

您可以在Chapter 8/ch08_wms_map.html中找到源代码。

如何操作…

如果您按照以下步骤操作,将 WMS 图层添加到地图上相当简单:

  1. 首先,创建一个wms.js文件,稍后将其包含在 HTML 中。此 JavaScript 文件包含一个WMSUntiled类,编写方式如下:

    function WMSUntiled (map, wmsUntiledOptions) {
      this.map_ = map;
      this.options = wmsUntiledOptions;
      this.div_ = null;
      this.image_ = null;
      this.setMap(map);
    }
    
  2. 然后,通过继承google.maps.OverlayView类扩展我们的基类:

    WMSUntiled.prototype = new google.maps.OverlayView();
    
  3. 下一步是实现OverlayView类的三种方法。

    WMSUntiled.prototype.draw = function() {
      var overlayProjection = this.getProjection();
      var sw = overlayProjection.fromLatLngToDivPixel (this.map_.getBounds ().getSouthWest());
      var ne = overlayProjection.fromLatLngToDivPixel (this.map_.getBounds().getNorthEast());
      var div = this.div_;
      if (this.image_ != null)
        div.removeChild(this.image_);
    
      // Create an IMG element and attach it to the DIV.
      var img = document.createElement('img');
      img.src = this.prepareWMSUrl();
      img.style.width = '100%';
      img.style.height = '100%';
      img.style.position = 'absolute';
      img.style.opacity = 0.6;
      this.image_ = img;
      div.appendChild(this.image_);
    
      div.style.left = sw.x + 'px';
      div.style.top = ne.y + 'px';
      div.style.width = (ne.x - sw.x) + 'px';
      div.style.height = (sw.y - ne.y) + 'px';
    };
    
    WMSUntiled.prototype.onAdd = function() {
      var that = this;
      var div = document.createElement('div');
      div.style.borderStyle = 'none';
      div.style.borderWidth = '0px';
      div.style.position = 'absolute';
    
      this.div_ = div;
      this.getPanes().floatPane.appendChild(this.div_);
    
      google.maps.event.addListener(this.map_, 'dragend', function() {
          that.draw();
      });
    };
    
    WMSUntiled.prototype.onRemove = function() {
      this.menuDiv.parentNode.removeChild(this.div_);
      this.div_ = null;
    };
    
  4. 最后,添加以下方法来完成WMSUntiled类:

    WMSUntiled.prototype.prepareWMSUrl = function() {  
      var baseUrl = this.options.baseUrl;
      baseUrl += 'Service=WMS&request=GetMap&CRS=EPSG:3857&';
      baseUrl += 'version=' + this.options.version;
      baseUrl += '&layers=' + this.options.layers;
      baseUrl += '&styles=' + this.options.styles;
      baseUrl += '&format=' + this.options.format;
    
      var bounds = this.map_.getBounds();
      var sw = this.toMercator(bounds.getSouthWest());
      var ne = this.toMercator(bounds.getNorthEast());
    
      var mapDiv = this.map_.getDiv(); 
      baseUrl += '&BBOX=' + sw.x + ',' + sw.y + ',' + ne.x + ',' + ne.y;  
      baseUrl += '&width=' + mapDiv.clientWidth + '&height=' + mapDiv.clientHeight;    
      return baseUrl;
    };
    
    WMSUntiled.prototype.toMercator = function(coord) {
      var lat = coord.lat();
      var lng = coord.lng();
      if ((Math.abs(lng) > 180 || Math.abs(lat) > 90))
        return;
    
      var num = lng * 0.017453292519943295;
      var x = 6378137.0 * num;
      var a = lat * 0.017453292519943295;
    
      var merc_lon = x;
      var merc_lat = 3189068.5 * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
    
      return { x: merc_lon, y: merc_lat };
    };
    
    WMSUntiled.prototype.changeOpacity = function(opacity) {
      if (opacity >= 0 && opacity <= 1){
        this.image_.style.opacity = opacity;
      }
    };
    
  5. 现在必须将此 JavaScript 类文件添加到 HTML 中,在添加 Google Maps JavaScript API 之后:

    <script type="text/javascript" src="img/wms.js"> 
    </script>
    
  6. 在初始化地图后,我们创建以下 WMS 选项:

    var wmsOptions = {
      baseUrl: 'http://demo.cubewerx.com/cubewerx/cubeserv.cgi?',
      layers: 'Foundation.gtopo30',
      version: '1.1.1',
      styles: 'default',
      format: 'image/png'
    };
    
  7. 最后,我们使用步骤 1 到 4 中创建的 WMS 选项初始化 WMS 图层:

    var wmsLayer = new WMSUntiled(map, wmsOptions);
    
  8. 前往您在浏览器中存储 HTML 文件的本地 URL,并查看结果。以下来自 WMS 的拓扑地图显示在谷歌地图的卫星基础地图上:如何操作…

如前一个截图所示,我们向我们的地图添加了一个 WMS 层。

它是如何工作的...

WMS 是一个用于提供地理参考图像的标准。WMS 的主要思想是根据地图的宽度/高度和边界框以及额外的参数(如投影类型、图层名称和返回格式)来提供图像。

网上大多数针对谷歌地图 JavaScript API 的 WMS 类都是基于瓦片结构的,这是大多数地图 API 的基础。这种瓦片结构获取每个瓦片的边界框并将其发送到服务器。这在用户交互方面可能是一个好的用法,因为用户只有在拖动地图时才会得到缺失的瓦片,但地图服务器存在一个问题。如果没有缓存机制,大量瓦片而不是单个图像会导致地图服务器负载很大。

在这个菜谱中,我们使用了无瓦片结构从服务器获取 WMS 图像。这种方法在用户交互中每次都从服务器获取一个图像,在某些情况下可能很有用。关于这种方法的信息不多,所以我们鼓励您阅读并实现这两种方法,用于您的地理网络应用程序。

命名为 WMSUntiled 的 JavaScript 类是在不同的文件中创建的,以便使 HTML 文件易于阅读。这个类以函数式风格创建,并将方法添加到构造函数的原型上:

function WMSUntiled (map, wmsUntiledOptions) {
  this.map_ = map;
  this.options = wmsUntiledOptions;
  this.div_ = null;
  this.image_ = null;
  this.setMap(map);
};

谷歌地图 JavaScript API 有一个基类可以扩展,称为 google.maps.OverlayViewWMSUntiled 类扩展了这个类,在地图上创建 WMS 叠加层:

WMSUntiled.prototype = new google.maps.OverlayView();

OverlayView 类需要实现三个方法来显示叠加层,分别是 draw()onAdd()onRemove()onAdd()onRemove() 方法分别在初始化和移除时被调用。在 onAdd() 方法中,通过 appendChild 函数创建并添加 div 元素到地图上。同时,在这个方法中启动监听地图的 drag 事件,以便在用户拖动时绘制 WMS 层。onRemove() 方法移除之前创建的 div 元素:

WMSUntiled.prototype.onAdd = function() {
  var that = this;
  var div = document.createElement('div');
  div.style.borderStyle = 'none';
  div.style.borderWidth = '0px';
  div.style.position = 'absolute';

  this.div_ = div;
  this.getPanes().floatPane.appendChild(this.div_);

  google.maps.event.addListener(this.map_, 'dragend', function() {
    that.draw();
  });
};

WMSUntiled.prototype.onRemove = function() {
  this.menuDiv.parentNode.removeChild(this.div_);
  this.div_ = null;
};

类中最重要的部分是 draw() 方法。这个方法创建一个 img 元素,并在 onAdd() 方法中将这个元素附加到创建的 div 元素上。如果之前创建了 img 元素,它将被从 div 元素中移除。img 的源是从类的另一个方法 prepareWMSUrl() 获取的:

var div = this.div_;
if (this.image_ != null)
  div.removeChild(this.image_);
var img = document.createElement('img');
img.src = this.prepareWMSUrl();
img.style.width = '100%';
img.style.height = '100%';
img.style.position = 'absolute';
img.style.opacity = 0.6;
this.image_ = img;
div.appendChild(this.image_);

我们需要像素坐标来放置div元素。我们获取图层的投影,以便在地图上正确定位divimg元素。fromLatLngToDivPixel()方法将LatLng坐标转换为屏幕像素,这些像素用于将div元素放置在正确的位置:

var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel (this.map_.getBounds().getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel (this.map_.getBounds().getNorthEast());

WMS 有一个边界框参数(BBOX),它定义了地理参照图像的边界。BBOX参数必须与 CRS 参数中定义的单位相同。Google Maps 基于 Web Mercator 投影,定义为EPSG:900913EPSG:3857。Google Maps JavaScript API 使用 Web Mercator 作为基础投影,但给我们的是定义为EPSG:4326的地理投影的LatLng对象。为了在 Google Maps 上获取正确的 WMS 图像,需要将坐标从EPSG:4326转换为EPSG:3857。这种转换可以通过类的toMercator()方法完成。

prepareWMSUrl()方法从wmsoptions对象获取大多数参数并创建一个 WMS URL 来获取地理参照图像。BBOXwidth/height参数从地图函数中收集:

WMSUntiled.prototype.prepareWMSUrl = function() {
  var baseUrl = this.options.baseUrl;
  baseUrl += 'Service=WMS&request=GetMap&CRS=EPSG:3857&';
  baseUrl += 'version=' + this.options.version;
  baseUrl += '&layers=' + this.options.layers;
  baseUrl += '&styles=' + this.options.styles;
  baseUrl += '&format=' + this.options.format;

  var bounds = this.map_.getBounds();
  var sw = this.toMercator(bounds.getSouthWest());
  var ne = this.toMercator(bounds.getNorthEast());

  var mapDiv = this.map_.getDiv();
  baseUrl += '&BBOX=' + sw.x + ',' + sw.y + ',' + ne.x + ',' + ne.y;
  baseUrl += '&width=' + mapDiv.clientWidth + '&height=' + mapDiv.clientHeight;
  return baseUrl;
};

WMSUntiled类处理几乎所有事情。为了将 WMS 图层添加到 Google Maps JavaScript API 中,你需要定义 WMS 图层的参数并从WMSUntiled类创建一个对象。由于我们提供了map作为参数,因此不需要将 WMS 图层添加到map对象中:

var wmsOptions = {
  baseUrl: 'http://demo.cubewerx.com/cubewerx/cubeserv.cgi?',
  layers: 'Foundation.gtopo30',
  version: '1.1.1', 
  styles: 'default',
  format: 'image/png'
};
var wmsLayer = new WMSUntiled(map, wmsOptions);

从服务器获取 WMS 有很多参数,但这本书的范围之外。本例中使用的示例 WMS 服务器在你想要使用时可能不可用,因此请使用你自己的 WMS 服务器以确保服务的可用性。

还有更多…

如食谱开头所述,我们创建一个叠加类来将 WMS 图层添加到 Google Maps JavaScript API 中,而不使用瓦片结构。这只是一个针对开发者的用例。你应该检查你的情况中的瓦片和非瓦片结构。本章中有一个使用瓦片结构的示例,即使用 Google Maps JavaScript API 访问 GeoServer食谱。

参见

  • 第一章中在自定义 DIV 元素中创建简单地图的食谱,Google Maps JavaScript API 基础知识

  • 使用 Google Maps JavaScript API 访问 GeoServer食谱

将 Fusion Tables 图层添加到地图中

Fusion Tables(tables.googlelabs.com/)是 Google 提供的一个实验性工具,用于存储不同类型的表格数据。对于地理开发者来说,Fusion Tables 非常重要,因为它支持点、折线和多边形等要素类型。它还支持地址、地点名称或国家的地理编码,使 Fusion Tables 成为您要素的强大数据库。Fusion Tables 还有一个 API,允许开发人员将其连接到不同的应用程序。Fusion Tables 有一些限制,但这些限制对于大多数开发者来说已经足够了。

OpenStreetMap POI 数据库可以通过不同的来源下载。我们以 KML 格式下载了瑞士的餐厅 POI 数据库并将其导入到 Fusion Tables 中。这个表中共有 7967 个点。在这个菜谱中,我们将使用这个表作为示例来帮助我们可视化。

如以下截图所示,可以使用 Fusion Tables 查看瑞士 POI 数据库的餐厅地图视图:

将 Fusion Tables 层添加到地图中

这就是我们实现将 Fusion Tables 层添加到显示数千个点的地图中而不会出现问题的方法。

准备工作

我们假设您已经知道如何创建一个简单的地图。我们只会介绍添加 Fusion Tables 层所需的代码。

您可以在第八章/ch08_fusion_tables.html中找到源代码。

如何操作…

如果您想将 Fusion Tables 层添加到地图中,应执行以下步骤:

  1. 首先,在添加 Google Maps JavaScript API 之后,添加以下行以简化我们的工作:

    <script src="img/jquery-1.10.1.min.js"></script>
    
  2. 然后,在地图的 DIV 元素之前添加以下 HTML 代码,以便与 Fusion Tables 层进行交互:

    <input type="checkbox" id="status"/>HeatMap Enabled <input type="text" id="query"/> <input type="button" id="search" value="Search"/><br/>
    
  3. 现在,在map对象初始化后创建 Fusion Tables 层并将其添加到地图中,如下所示:

    var layer = new google.maps.FusionTablesLayer({
      query: {
        select: 'geometry',
        from: '1_1TjGKCfamzW46TfqEBS7rXppOejpa6NK-FsXOg'
      },
      heatmap: {
        enabled: false
      }
    });
    layer.setMap(map);
    
  4. 下一步是监听复选框的click事件,在普通视图和热力图视图之间切换:

    $('#status').click(function() {
      if (this.checked) {
        layer.setOptions({ 
          heatmap: { enabled: true } });
      }
      else {
        layer.setOptions({
          heatmap: { enabled: false } });
      }
    });
    
  5. 添加以下行以监听搜索按钮的click事件,根据文本框中输入的值过滤 Fusion Tables 层:

    $('#search').click(function() {
      var txtValue = $('#query').val();
      layer.setOptions({query: { 
        select: 'geometry', 
        from: 
        '1_1TjGKCfamzW46TfqEBS7rXppOejpa6NK-FsXOg', 
        where: 'name contains "' + txtValue + '"' } });
    });
    
  6. 前往您本地存储 HTML 的 URL,并在您喜欢的浏览器中点击地图以查看结果。如果您点击热力图复选框,Fusion Tables 层将转换为热力图。您还可以使用搜索按钮搜索餐厅名称:如何操作…

前面的截图还显示了添加到地图中的过滤后的 Fusion Tables 层。

它是如何工作的...

如前所述,Fusion Tables 是一个实验性工具,根据文档,Google Maps JavaScript API 下与 Fusion Tables 相关的类也是实验性的。就我们所知,Fusion Tables 和 Google Maps JavaScript API 下的类都是稳定的,可以用于生产环境,但使用它们在您的地理 Web 应用程序中存在风险。

顺便说一句,请确保您的表不超过 100,000 行,以便正确使用,因为 API 中有写入的限制。

Fusion Tables 支持导入各种数据类型,如 CSV、TSV、TXT 或带有几何坐标的 KML。Fusion Tables 的几何列可以有不同的格式,例如 KML 格式的几何列、地址列,或单列或两列单独的纬度/经度坐标。如果您有地址或城市名称,这些列也可以进行地理编码,以便在您的应用程序中使用。我们上传了一个从 OpenStreetMap 收集的 KML 文件到 Fusion Tables,其中包含许多带有名称的餐厅点。

Fusion Tables 还有一个 REST API,用于访问和操作表格中的数据,无论是否使用 OAuth,都可以与 Google Maps JavaScript API 无关。

在 Google Maps JavaScript API 中有一个 google.maps.FusionTablesLayer 类,用于访问和可视化来自 Fusion Tables 的数据。我们需要表 ID 和几何列的名称来访问 Google Maps JavaScript API 中的 Fusion Tables 层。请记住,您的表必须共享为公开或未列出,才能通过 Google Maps JavaScript API 访问。开发者可以通过在 Fusion Tables 网页界面中导航到 文件 | 关于 来获取表 ID。以下代码块是添加 Fusion Tables 到 Google Maps JavaScript API 所必需的:

var layer = new google.maps.FusionTablesLayer({
  query: {
    select: 'geometry',
    from: '1_1TjGKCfamzW46TfqEBS7rXppOejpa6NK-FsXOg'
  },
  heatmap: {
    enabled: false
  }
});
layer.setMap(map);

如果您想在 API 开启时启用热图选项,应在 heatmap 参数下将启用选项设置为 true。我们将在我们的菜谱中用 checkbox 选项切换这些参数,如下所示:

$('#status').click(function(){
  if (this.checked) {
    layer.setOptions({heatmap: { enabled: true } });
  }
  else {
    layer.setOptions({heatmap: { enabled: false } });
  }
});

使用热图是总结您拥有的数据并显示大多数点聚集位置的好方法。热图主要在各种领域中用于显示重要地点,例如犯罪地图中最密集的犯罪地点。如果用户启用了热图,您将在应用程序中看到以下结果。以下地图用红色显示了餐厅人口密集的区域:

如何工作...

Fusion Tables 还支持使用类似 SQL 的查询过滤行。可以将 SQL 查询添加到 query 参数的 where 字段。这可以是一个起始值,也可以稍后添加以过滤可视化的数据。在这个菜谱中,我们根据文本框中输入的值过滤我们的数据。以下代码监听 搜索 按钮,当点击发生时,它获取文本框的值,并根据文本框的值设置 Fusion Tables 层的选项。过滤后的数据立即显示在地图上:

$('#search').click(function() {
  var txtValue = $('#query').val();
  layer.setOptions({ 
  query: { 
    select: 'geometry', 
    from: '1_1TjGKCfamzW46TfqEBS7rXppOejpa6NK-FsXOg', where: 'name contains "' + txtValue + '"' } });
  });

google.maps.FusionTablesLayer 类还具有根据过滤器更改地图样式的能力。您可以根据列的值更改点的标记类型、多边形的线颜色或填充颜色。

如果开发者了解限制,Fusion Tables 可以成为存储、分析和可视化您数据的良好候选。开发者也不要忘记 Fusion Tables 仍然处于实验阶段,因此 Google 未来可能会在 Fusion Tables 中做出一些改变,这可能导致您的应用程序停止。

注意

关于数据更多信息

本应用程序使用的数据可以从 poi-osm.tucristal.es/ 下载,该数据使用 OpenStreetMap 作为来源。本食谱中使用的数据与代码一起提供。数据也可以从 Fusion Tables 作为公开共享获取。

相关内容

  • 在第一章 Google Maps JavaScript API 基础在自定义 DIV 元素中创建简单地图 食谱中,Google Maps JavaScript API 基础

  • 在第二章 添加栅格图层创建热图 食谱中,添加栅格图层

将 CartoDB 图层添加到地图中

CartoDB 是一个云上的地理空间数据库,允许在网络上存储和可视化数据。使用 CartoDB 将允许您快速创建基于地图的可视化。根据 CartoDB 网站 (www.cartodb.com),您可以使用 CartoDB 以以下方式:

  • 使用 CartoDB 控制台上传、可视化和管理您的数据

  • 使用地图嵌入工具快速创建和自定义您可以通过公共 URL 嵌入或共享的地图

  • 使用 SQL API 分析和整合您存储在 CartoDB 上的数据到您的应用程序中

  • 对于您网站或应用程序上 CartoDB 地图的更高级集成,请使用 CartoDB.js

CartoDB 是一个开源项目,您可以从 GitHub 分叉代码并开始在您自己的硬件上启动自己的 CartoDB 实例,但 CartoDB 的力量在于云后端。CartoDB 基于 PostgreSQL、PostGIS 和 Mapnik,这些是目前最受欢迎和功能强大的开源地理工具。

开发者有一个免费层来探索 CartoDB 的功能,存储限制最高为 5 MB,并且有五个表。

在本食谱中,从 CartoDB 控制台导入简化版的世界边界数据以进行操作。以下截图显示了世界边界的表格视图和地图视图。这些数据将在 Google Maps JavaScript API 的帮助下通过 CartoDB.js 库发布:

将 CartoDB 图层添加到地图中

现在以下截图显示了世界边界的地图视图:

将 CartoDB 图层添加到地图中

准备工作

在本食谱中,我们假设您已经知道如何创建一个简单的地图。因此,我们只展示添加 CartoDB 图层到 Google Maps 基础地图上的额外代码行。

您可以在 Chapter 8/ch08_cartodb_layer.html 找到源代码。

如何做…

如果您执行以下步骤,您可以将 CartoDB 图层添加到地图中:

  1. 首先,将 CartoDB 相关文件添加到 HTML 文档中:

    <link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/themes/css/cartodb.css" />
    <!--[if lte IE 8]>
    <link rel="stylesheet" href="http://libs.cartocdn.com/cartodb.js/v3/themes/css/cartodb.ie.css" />
    <![endif]-->
    <script src="img/cartodb.js"></script>
    
  2. 然后,在 CartoDB 文件之后添加 jQuery 文件:

    <script src="img/jquery-1.10.1.min.js"></script>
    
  3. 下一步是在map变量之后添加一个全局变量,以便从任何地方访问:

    var cartoLayer;
    
  4. 地图初始化后,添加以下行以定义层的制图。这可以是一个单行字符串,但为了提高可读性,将其分为多行:

    var cartoStyle = '#world_borders { ' + 
      'polygon-fill: #1a9850; ' +
      'polygon-opacity:0.7; ' +
      '} ' +
      '#world_borders [pop2005 > 10000000] { ' +
      'polygon-fill: #8cce8a ' +
      '} ' +
      '#world_borders [pop2005 > 40000000] { ' +
      'polygon-fill: #fed6b0 ' +
      '} ' +
      '#world_borders [pop2005 > 70000000] { ' +
      'polygon-fill: #d73027 ' +
    '} ';
    
  5. 代码中的重要部分是以下 CartoDB 层的初始化:

    //Creating CartoDB layer and add it to map.
    cartodb.createLayer(map, {
      user_name: 'gmapcookbook',
      type: 'cartodb',
      sublayers: [{
        sql: 'SELECT * FROM world_borders',
        cartocss: cartoStyle,
        interactivity: 'cartodb_id, name, pop2005, area',
      }]
    })
    .addTo(map)
    .done(function(layer) {
      cartoLayer = layer;
    
      //Enabling popup info window
      cartodb.vis.Vis.addInfowindow(map, layer.getSubLayer(0), ['name', 'pop2005', 'area']);
    
      //Enabling UTFGrid layer to add interactivity.
      layer.setInteraction(true);
      layer.on('featureOver', function(e, latlng, pos, data) {
        $('#infoDiv').html('<b>Info : </b>' + data.name + ' (Population : ' + data.pop2005 + ')');
      });
    });
    
  6. 现在,添加以下部分以监听搜索按钮的click事件,以便根据文本框的值更新地图内容:

    //Listening click event of the search button to filter the //data of map
    $('#search').click(function() {
      var txtValue = $('#query').val();
      cartoLayer.setQuery('SELECT * FROM world_borders WHERE name LIKE \'%' + txtValue + '%\'');
      if (txtValue == '') {
       cartoLayer.setCartoCSS(cartoStyle);
      }
      else { cartoLayer.setCartoCSS('#world_borders { polygon-fill: #00000d; polygon-opacity:0.7; }');
      }
    });
    
  7. 不要忘记在地图的div元素前后添加以下行:

    <input type="text" id="query"/> <input type="button" id="search" value="Search"/><br/>
    <div id="mapDiv"></div>
    <div id="infoDiv">--</div>
    
  8. 前往您在最喜欢的浏览器中存储 HTML 的本地 URL,并查看位于 Google Maps 基础地图之上的 CartoDB 层。当您在地图上移动时,地图的底部行会根据鼠标放置的位置而改变。当您点击地图时,您还会看到有关该国家的信息窗口,如图中所示:如何操作…

通过这个菜谱的结果,我们可以将 CartoDB 层添加到地图中,该地图从您的数据源获取实时数据。

它是如何工作的...

如前所述,CartoDB 基于 PostGIS 和 Mapnik 技术,因此您可以使用 CartoCSS 作为层的样式语言。CartoCSS 类似于 CSS,但有一些额外的标签来定义制图。在本菜谱中,我们将根据人口值定义一个面状制图。使用人口值对于制图来说似乎很简单,但这是最容易理解 CartoCSS 的方法:

var cartoStyle = '#world_borders { ' +
    'polygon-fill: #1a9850; ' +
    'polygon-opacity:0.7; ' +
    '} ' +
  '#world_borders [pop2005 > 10000000] { ' +
    'polygon-fill: #8cce8a ' +
    '} ' +
  '#world_borders [pop2005 > 40000000] { ' +
    'polygon-fill: #fed6b0 ' +
    '} ' +
  '#world_borders [pop2005 > 70000000] { ' +
    'polygon-fill: #d73027 ' +
  '} ';

#world_borders层是 CartoDB 仪表板中定义的层的名称。第一个括号包括具有多边形填充和多边形不透明度的层中所有要素。第二个括号针对人口超过 1000 万的要素,使用不同的颜色。第三个括号和第四个括号分别针对人口超过 4000 万和 7000 万的要素,使用不同的颜色。因此,我们在这个 CartoCSS 标签中根据国家的人口定义了四个不同的类别。每个 CartoCSS 规则都会覆盖之前写入的规则。

现在我们有了层的制图,是时候创建层并将其添加到地图中了:

cartodb.createLayer(map, {
  user_name: 'gmapcookbook',
  type: 'cartodb',
  sublayers: [{
    sql: 'SELECT * FROM world_borders',
    cartocss: cartoStyle,
    interactivity: 'cartodb_id, name, pop2005, area'
  }]
})
.addTo(map)

我们已经使用了链式方法来创建层并将其添加到地图中。以下部分将在稍后解释。有一个user_name字段来定义您的 CartoDB 账户。定义层的重点在于sublayers字段。您可以定义多个层,但我们将在此处只添加一个层。sublayers字段中的 JavaScript 对象非常重要。sql字段定义了要在地图上显示哪些要素。您甚至可以在这里编写非常复杂的 SQL 查询,就像您的 PostGIS 数据库一样。cartocss字段是您定义层制图的部分。这已经在之前定义了,所以只需将那个变量传递到这个字段即可。

下一个字段是 interactivity 字段。这很重要,因为它背后是称为 UTFGrid 的技术。UTFGrid 是一个用于栅格化交互数据的规范。根据引入此标准的 MapBox,UTFGrid 解决这个问题的方法是将多边形和点在 JSON 中栅格化为文本字符的网格。每个特征由一个独特的字符引用,并通过其字符代码与 JSON 数据相关联。结果是便宜、快速的查找,即使是 Internet Explorer 7 也能立即完成。

使用 UTFGrid,你可以在加载层瓦片图像的同时将一些属性数据加载到客户端,并且可以在鼠标移动时显示这些属性数据,而无需向服务器发送任何请求。这是与用户交互最快的方式,并从服务器卸载负载。当真正需要时,你仍然可以从服务器获取详细信息。大多数时候,用户对这种快速的数据交互非常满意,无需从服务器获取更多信息。

注意

关于 UTFGrid 的更多信息

如果你对 UTFGrid 的更多技术细节感兴趣,以下网址建议进一步参考:

正如我们之前所讨论的,有一个名为 interactivity 的字段。这个字段应该填写将要用于交互的列名;确保交互对用户来说快速是很重要的。因此,不建议添加复杂的文本列以显示在交互中,以增加 UTFGrid 的加载。然后我们使用链式方法将此层添加到地图中。

我们已将 CartoDB 层添加到地图中,但仍有缺失的部分来激活交互。我们添加另一个链式方法来添加必要的功能,如下所示:

.done(function(layer) {
  cartoLayer = layer;
  //Enabling popup info window
  cartodb.vis.Vis.addInfowindow(map, layer.getSubLayer(0), ['name', 'pop2005', 'area']);
});

当层被创建并添加到地图中时,会调用 done() 方法。首先,我们将局部变量 layer 赋值给全局变量 cartoLayer,以便稍后操作 layer 变量的 SQL 查询。然后,我们使用 cartodb.vis.Vis.addInfoWindow() 方法激活信息窗口。但激活 UTFGrid 还需要一些代码部分,如下所示:

layer.setInteraction(true);
layer.on('featureOver', function(e, latlng, pos, data) {
  $('#infoDiv').html('<b>Info : </b>' + data.name + ' (Population : ' + data.pop2005 + ')');
});

第一行激活了 UTFGrid 交互,但我们仍然需要知道在哪里以及何时显示数据。通过层的 featureOver 事件,我们捕捉到每个鼠标移动,从 UTFGrid 获取相关数据,并在定义的 div 元素上显示。我们只在鼠标移动时显示层的 namepop2005 字段。

菜谱的最后部分是通过输入国家名称来搜索国家。这部分就像编写 SQL 查询。在 搜索 按钮的每个 click 事件中,我们获取文本框的值并将其赋值给一个名为 txtValue 的局部变量:

$('#search').click(function() {
  var txtValue = $('#query').val();
});

当我们拥有 txtValue 变量时,我们通过使用 setQuery() 方法设置 CartoDB 图层的查询:

cartoLayer.setQuery('SELECT * FROM world_borders WHERE name LIKE \'%' + txtValue + '%\'');

如果 txtValue 变量为空,我们将恢复定义的制图;否则,我们将使用 setCartoCSS() 方法将图层的制图颜色更改为黑色,以查看哪些国家被选中:

if (txtValue == '') {
  cartoLayer.setCartoCSS(cartoStyle);
}
else {
  cartoLayer.setCartoCSS('#world_borders { polygon-fill: #00000d; polygon-opacity:0.7; }');
}

以下截图是在搜索包含 Turk 的国家名称后获取的:

工作原理...

正如我们在本配方中看到的,CartoDB 是一个面向所有人的完整解决方案,从基本的地图可视化到复杂的 GIS 分析。您可以使用 PostGIS 的全部功能在您的地理网络应用程序后面。

参见

  • 第一章中 Google Maps JavaScript API 基础在自定义 DIV 元素中创建简单地图 配方

使用 Google Maps JavaScript API 访问 ArcGIS Server

ArcGIS Server 是由 ESRI 开发的地图和空间服务器。它是一款专有软件,但它的功能和与桌面软件的集成使 ArcGIS Server 比其他空间服务器产品更出色。ArcGIS Server 是面向企业公司或机构的完整空间服务器解决方案。ArcGIS Server 用于创建和管理 GIS 网络服务、应用程序和数据。ArcGIS Server 通常在组织内部署在 面向服务的架构SOA)或云计算环境中。

ESRI 为 ArcGIS Server 发布了适用于多个平台的 API,但 ESRI 不支持 Google Maps JavaScript API v3。曾经有一个 Google Maps JavaScript API v2 的扩展,但它不与新 API 一起工作。有一个开源库可以将 Google Maps JavaScript API v3 扩展以与 ArcGIS Server 一起工作。

在本配方中,我们将使用开源库与 ArcGIS Server 一起工作。我们将向 Google Maps JavaScript API 添加瓦片和动态图层。我们还通过鼠标点击识别动态图层,并显示底层信息。

注意

关于开源 ArcGIS Server 库的更多信息

Google Maps JavaScript API v3 的 ArcGIS 服务器链接是一个开源库,可以在以下网址找到。建议下载并检查该库:google-maps-utility-library-v3.googlecode.com/svn/trunk/arcgislink/docs/reference.html

准备工作

此配方仍在使用第一章中定义的相同地图创建过程,即 Google Maps JavaScript API 基础,但有一些额外的代码块用于添加 ArcGIS 瓦片/动态图层,并监听鼠标点击以识别动态图层。

您可以在 Chapter 8/ch08_arcgis_layer.html 找到源代码。

如何操作…

以下是我们需要使用 Google Maps JavaScript API 访问 ArcGIS Server 的步骤:

  1. 首先,从以下地址下载 Google Maps JavaScript API v3 的 ArcGIS Server 链接:google-maps-utility-library-v3.googlecode.com/svn/trunk/arcgislink/docs/reference.html

  2. 下一步是将下载的库添加到你的 HTML 文件中:

    <script src="img/arcgis.js"></script>
    
  3. 在这个菜谱中还需要 jQuery 库:

    <script src="img/jquery1.10.1.min.js"></script>
    
  4. 我们还需要一些全局变量,如下所示:

    var overlays = [];
    var infowindow = null;
    
  5. 现在,创建一个名为tiledMap的瓦片地图图层,其不透明度为0.6

    //Creating a tiled map layer
    var topoMapURL = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer';
    var tiledMap = new gmaps.ags.MapType(topoMapURL, {
      name: 'TopoMap',
      opacity: 0.6
    });
    
  6. 然后,创建一个名为dynamicMap的动态地图图层,其不透明度为0.8。同时,添加版权控制到地图中:

    //Creating a dynamic map layer
    var dynamicMapURL = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer';
    var copyrightControl = new gmaps.ags.CopyrightControl(map);
    var dynamicMap = new gmaps.ags.MapOverlay(dynamicMapURL, {
      opacity: 0.8
    });
    
  7. 我们还需要一个与动态地图图层使用的相同 URL 进行识别的地图服务:

    //Creating a map service layer
    var mapService = new gmaps.ags.MapService(dynamicMapURL);
    
  8. 现在,开始监听map对象上的每个鼠标点击事件:

    //Listening map click event for identifying
    google.maps.event.addListener(map, 'click', identify);
    
  9. 让我们创建一个在每个鼠标点击事件上被调用的函数:

    //Function that is called on each mouse click
    function identify(evt) {
      mapService.identify({
        'geometry': evt.latLng,
        'tolerance': 3,
        'layerIds': [5],
        'layerOption': 'all',
        'bounds': map.getBounds(),
        'width': map.getDiv().offsetWidth,
        'height': map.getDiv().offsetHeight
      }, function(results, err) {
        if (err) {
          alert(err.message + err.details.join('\n'));
        } else {
          showResult(results.results, evt.latLng);
        }
      });
    }
    
  10. 之后,我们将在信息窗口中显示结果:

    //Function that is showing the result of identify
    function showResult(results, position) {
      if (infowindow != null) {
        infowindow.close();
      }
    
      var info = '<b>State Name : </b>' + results[0].feature.attributes.STATE_NAME + '<br><b>2007 Population : </b>' + results[0].feature.attributes.POP2007;
      infowindow = new google.maps.InfoWindow({
        content: info,
        position: position
      });
    
      infowindow.open(map);
    
      removePolygons();
    
      for (var j=0; j < results[0].geometry.rings.length; j++){
        addPolygon(results[0].geometry.rings[j]);
      }
    }
    
  11. 接下来,我们添加用于显示多边形的函数。这个函数在之前的菜谱中使用:

    //Function that is used for adding polygons to map.
    function addPolygon(areaCoordinates) {
      //First we iterate over the coordinates array to create 
      // new array which includes objects of LatLng class.
      var pointCount = areaCoordinates.length;
    
      var areaPath = [];
      for (var i=0; i < pointCount; i++) {
        var tempLatLng = new google.maps.LatLng(areaCoordinates[i][1], areaCoordinates[i][0]);
        areaPath.push(tempLatLng);
      }
      //Polygon properties are defined below
      var polygonOptions = {
        paths: areaPath,
        strokeColor: '#FF0000',
        strokeOpacity: 0.9,
        strokeWeight: 3,
        fillColor: '#FFFF00',
        fillOpacity: 0.25
      };
    
      var polygon = new google.maps.Polygon(polygonOptions);
    
      //Polygon is set to current map.
      polygon.setMap(map);
    
      overlays.push(polygon);
    }
    
  12. 现在,添加以下用于删除所有多边形的函数:

    //Function that is used for removing all polygons
    function removePolygons() {
      if (overlays) {
        for (var i = 0; i < overlays.length; i++) {
          overlays[i].setMap(null);
        }
        overlays.length = 0;
      }
    }
    
  13. 以下代码块监听复选框并切换瓦片和动态图层的可见性:

    //Start listening for click event to add/remove tiled map layer
    $('#statusTile').click(function(){
      if (this.checked) {
        map.overlayMapTypes.insertAt(0, tiledMap);
      }
      else {
        map.overlayMapTypes.removeAt(0);
      }
    });
    
    //Start listening for click event to add/remove dynamic map layer
    $('#statusDynamic').click(function(){
      if (this.checked) {
        dynamicMap.setMap(map);
      }
      else {
        dynamicMap.setMap(null);
      }
    });
    
  14. 最后一步是添加必要的复选框 HTML 标签:

    <input type="checkbox" id="statusTile"/>Add Topo Map Overlay <br/>
    <input type="checkbox" id="statusDynamic"/>Add Dynamic Map <br/>
    
  15. 前往你最喜欢的浏览器中存储 HTML 的本地 URL,并通过点击附近的复选框启用添加拓扑地图覆盖。以下拓扑地图显示在卫星基础地图上:如何操作…

因此,我们已经成功创建了一个使用 Google Maps JavaScript API 访问 ArcGIS Server 图层的地图。

它是如何工作的...

ArcGIS Server 具有与 Google Maps JavaScript API 一起使用的不同功能。在这个菜谱中用于访问 ArcGIS Server 的库几乎包含了 ArcGIS Server REST API 的所有方法。该库基于 Google Maps JavaScript API 的基类创建,因此使用它并不像预期的那样困难。

在这个菜谱中使用的服务 URL 由 ESRI 提供服务,因此你可以无任何问题地用于开发目的。如果你想在生产环境中使用它们,请联系 ESRI 获取有效许可证。

在第一步中,我们将添加一个在卫星基础地图上显示世界拓扑的瓦片地图。借助gmaps.ags.MapType类,你可以轻松地创建一个带有地图服务 URL 的瓦片地图:

var topoMapURL = 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer';
var tiledMap = new gmaps.ags.MapType(topoMapURL, {
  name: 'TopoMap',
  opacity: 0.6
});

添加和删除瓦片地图的方式与我们在第二章中做的一样,添加栅格图层。请获取图层的索引。这个索引在从地图中移除图层时使用:

// Adding layer to the map.
map.overlayMapTypes.insertAt(0, tiledMap);
// Removing layer from the map
map.overlayMapTypes.removeAt(0);

由于库的存在,创建动态图层也非常简单。库处理绘制动态图层所需的所有代码。在这个菜谱中使用的示例动态图层是 CENSUS 数据层,它包含有关美国各州、县或人口普查区的人口统计信息:

var dynamicMapURL = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer';
var dynamicMap = new gmaps.ags.MapOverlay(dynamicMapURL, {
  opacity: 0.8
});

以下为 CENSUS 层的截图:

工作原理...

添加和删除动态图层与覆盖层相同,因为 gmaps.ags.MapOverlay 类是从 google.maps.OverlayView 基类扩展而来的:

// Adding layer to the map.
dynamicMap.setMap(map);
// Removing layer from the map
dynamicMap.setMap(null);

识别地图图层对于大多数地理网络应用来说是一项非常重要的任务。这为用户提供了关于已知点的图层信息。为了实现这一点,我们需要定义一个地图服务,如下所示。gmaps.ags.MapService 类仅获取在动态图层之前定义的 URL 参数:

var mapService = new gmaps.ags.MapService(dynamicMapURL);

当地图发生 click 事件时,我们需要使用名为 identify 的函数来处理它。这个函数获取 latLng 对象并触发 gmaps.ags.MapService 类的 identify 方法:

function identify(evt) {
  mapService.identify({
    'geometry': evt.latLng,
    'tolerance': 3,
    'layerIds': [5],
    'layerOption': 'all',
    'bounds': map.getBounds(),
    'width': map.getDiv().offsetWidth,
    'height': map.getDiv().offsetHeight
  }, function(results, err) {
    if (err) {
        alert(err.message + err.details.join('\n'));
    } else {
        showResult(results.results, evt.latLng);
    }
  });
}

identify 方法获取以下参数:

  • geometry: 这将获取 LatLngPolylinePolygon 对象。

  • tolerance: 这是鼠标点击处的屏幕像素距离。

  • layerIds: 这个数组包含图层 ID。在这个菜谱中,值 5 定义了该州图层。

  • layerOption: 这些选项可以是 topvisibleall

  • bounds: 这将获取由 LatLngBounds 类创建的对象。这定义了地图的当前边界。

  • width: 这是指地图 div 元素的宽度。

  • height: 这是指地图 div 元素的高度。

函数的返回值包含一个包含属性和几何数据的特征数组。结果函数可以遍历这个数组,并在信息窗口中显示属性数据。每个特征的几何也可以在地图上显示。identify 操作的结果如下截图所示:

工作原理...

如果你有许可证,ArcGIS Server 是与 Google Maps JavaScript API 一起使用的强大工具。还有一些其他 GIS 功能,如地理处理、地理编码或几何服务,由于范围限制,这些功能不包括在这个菜谱中,但它们的用法与 identify 操作没有不同。

Google Maps JavaScript API 是一种完美的地图工具,并且与这类服务和库结合使用非常强大。

更多内容...

在这个菜谱中,我们专注于 ArcGIS Server,但 ESRI 还有一个名为 ArcGIS Online 的替代云解决方案(www.arcgis.com)。它是 ArcGIS Server 的云版本,其服务的使用几乎与 ArcGIS Server 的服务相同。

参见

  • 在 第一章 的 在自定义 DIV 元素中创建简单地图 菜谱中,Google Maps JavaScript API 基础

  • 在 第三章 的 向标记或地图添加弹出窗口 菜谱中,添加矢量图层

  • 在 第三章 的 向地图添加多边形 菜谱中,添加矢量图层

  • 第五章中理解 Google Maps JavaScript API 事件获取鼠标点击坐标食谱

使用 Google Maps JavaScript API 访问 GeoServer

GeoServer 是一个用 Java 编写的开源地图服务器,允许用户共享和编辑地理空间数据。它是可以发布 OGC 兼容服务(如 WMS 和 WFS)的流行开源地图服务器之一。Web 地图服务WMS)用于发布地理参考图像和简单查询。另一方面,Web 要素服务WFS)用于将矢量数据发布到任何类型的 GIS 客户端。WFS 主要用于数据共享目的。

在本食谱中,我们将使用 GeoServer 的一个标准已发布服务,名为topp:states,格式为 WMS。正如本章中将 WMS 图层添加到地图食谱中所述,WMS 有不同的请求类型,如GetMapGetCapabilities。我们还将使用GetFeatureInfoGetMap请求的附加功能。这个新请求获取地图上点的信息。此外,在本食谱中,我们使用瓦片结构来获取 WMS 图像,以便将本章中将 WMS 图层添加到地图食谱中的无瓦片结构与本食谱中的瓦片结构进行比较。

准备工作

在本食谱中,我们将使用第一章中定义的第一个食谱,即Google Maps JavaScript API 基础,作为模板以跳过地图创建。

你可以在Chapter 8/ch08_geoserver.html找到源代码。

如何操作…

在执行以下步骤后,你可以轻松地使用 Google Maps JavaScript API 访问 GeoServer:

  1. 首先,我们创建一个wms-tiled.js文件,稍后将其包含在 HTML 中。这个 JavaScript 文件包含了WMSTiledWMSFeatureInfo类。让我们按照以下方式添加WMSTiled类:

    function WMSTiled(wmsTiledOptions) {
    
      var options = {
        getTileUrl: function(coord, zoom) {
          var proj = map.getProjection();
          var zfactor = Math.pow(2, zoom);
    
          // get Long Lat coordinates
          var top = proj.fromPointToLatLng(new google.maps.Point(coord.x * 256 / zfactor, coord.y * 256 / zfactor));
          var bot = proj.fromPointToLatLng(new google.maps.Point((coord.x + 1) * 256 / zfactor, (coord.y + 1) * 256 / zfactor));
    
          //create the Bounding box string
          var ne = toMercator(top);
          var sw = toMercator(bot);
          var bbox = ne.x + ',' + sw.y + ',' + sw.x + ','+ ne.y;
    
          //base WMS URL
          var url = wmsTiledOptions.url;
          url += '&version=' + wmsTiledOptions.version;
          url += '&request=GetMap';
          url += '&layers=' + wmsTiledOptions.layers;
          url += '&styles=' + wmsTiledOptions.styles;
          url += '&TRANSPARENT=TRUE';
          url += '&SRS=EPSG:3857';
          url += '&BBOX='+ bbox;
          url += '&WIDTH=256';
          url += '&HEIGHT=256';
          url += '&FORMAT=image/png';
          return url;
        },
        tileSize: new google.maps.Size(256, 256),
        isPng: true
      };
    
      return new google.maps.ImageMapType (options);
    }
    
  2. 然后,创建WMSFeatureInfo类及其getUrl方法:

    function WMSFeatureInfo(mapObj, options) {
      this.map = mapObj;
      this.url = options.url;
      this.version = options.version;
      this.layers = options.layers;
      this.callback = options.callback;    
      this.fixedParams = 'REQUEST=GetFeatureInfo&EXCEPTIONS=application%2Fvnd.ogc.se_xml&SERVICE=WMS&FEATURE_COUNT=50&styles=&srs=EPSG:3857&INFO_FORMAT=text/javascript&format=image%2Fpng';
    
      this.overlay = new google.maps.OverlayView();
      this.overlay.draw = function() {};
      this.overlay.setMap(this.map);
    }
    
    WMSFeatureInfo.prototype.getUrl = function(coord) {
      var pnt = this.overlay.getProjection(). fromLatLngToContainerPixel (coord);
      var mapBounds = this.map.getBounds();
      var ne = mapBounds.getNorthEast();
      var sw = mapBounds.getSouthWest();
    
      var neMerc = toMercator(ne);
      var swMerc = toMercator(sw);
      var bbox = swMerc.x + ',' + swMerc.y + ',' + neMerc.x + ',' + neMerc.y;
    
      var rUrl = this.url + this.fixedParams;
      rUrl += '&version=' + this.version;
      rUrl += '&QUERY_LAYERS=' + this.layers + '&Layers=' + this.layers;
      rUrl += '&BBOX=' + bbox;
      rUrl += '&WIDTH=' + this.map.getDiv().clientWidth + '&HEIGHT=' + this.map.getDiv().clientHeight;
      rUrl += '&x=' + Math.round(pnt.x) + '&y=' + Math.round(pnt.y);
      rUrl += '&format_options=callback:' + this.callback;
      return rUrl;
    };
    
  3. wms-tiled.js文件的最后一步是添加toMercator()方法:

    function toMercator(coord) {
      var lat = coord.lat();
      var lng = coord.lng();
      if ((Math.abs(lng) > 180 || Math.abs(lat) > 90))
        return;
    
      var num = lng * 0.017453292519943295;
      var x = 6378137.0 * num;
      var a = lat * 0.017453292519943295;
    
      var merc_lon = x;
      var merc_lat = 3189068.5 * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
    
      return { x: merc_lon, y: merc_lat };
    }
    
  4. 现在,我们有了我们的 JavaScript 类文件;在添加 Google Maps JavaScript API 后,添加以下行:

    <script src="img/wms-tiled.js"></script>
    
  5. 我们还需要将 jQuery 库添加到 HTML 文件中:

    <script src="img/jquery-1.10.1.min.js"></script>
    
  6. 现在,从wms-tiled.js文件中创建一个瓦片 WMS:

    //Creating a tiled WMS Service and adding it to the map
    var tiledWMS = new WMSTiled({
      url: 'http://localhost:8080/geoserver/topp/wms?service=WMS',
      version: '1.1.1',
      layers: 'topp:states',
      styles: ''
    });
    map.overlayMapTypes.push(tiledWMS);
    
  7. 下一步是从WMSFeatureInfo类创建一个对象,以便稍后在事件监听器中使用:

    //Creating a WMSFeatureInfo class to get info from map.
    var WMSInfoObj = new WMSFeatureInfo(map, {
      url: 'http://localhost:8080/geoserver/topp/wms?',
      version: '1.1.1',
      layers: 'topp:states',
      callback: 'getLayerFeatures'
    });
    
  8. 最后一步是监听地图的click事件以从地图获取信息:

    google.maps.event.addListener(map, 'click', function(e){
      //WMS Feature Info URL is prepared by the help of
      //getUrl method of WMSFeatureInfo object created before
      var url = WMSInfoObj.getUrl(e.latLng);
      $.ajax({
        url: url,
        dataType: 'jsonp',
        jsonp: false,
        jsonpCallback: 'getLayerFeatures'
      }).done(function(data) {
        if (infowindow != null) {
          infowindow.close();
        }
    
        var info = '<b>State Name : </b>' + data.features[0].properties.STATE_NAME + '<br><b>Population : </b>' + data.features[0].properties.SAMP_POP;
        infowindow = new google.maps.InfoWindow({
          content: info,
          position: e.latLng
        });
    
        infowindow.open(map);
      });
    });
    
  9. 前往你喜欢的浏览器中存储 HTML 的本地 URL,并尝试点击你想要获取信息的地图位置。如何操作…

上一张截图是本食谱的结果,显示了在 Google Maps JavaScript API 上由 GeoServer 创建的 WMS 图层。

作用原理...

访问 GeoServer 与访问 WMS 服务器没有太大区别,因为它们共享相同的标准。使用 GeoServer,你可以根据你的安全标准在自己的服务器上发布你的数据。

在这个食谱中,我们在 Mac OS X 上安装了一个新的 GeoServer,其示例数据已准备好提供 WMS 和 WFS 服务。我们使用美国示例州数据在 WMS 中显示交互。

在我们的案例中,我们从localhost的 80 端口提供 HTML 文件,但 GeoServer 在localhost的 8080 端口运行。这在我们案例中是一个问题,因为我们无法在获取信息时访问 GeoServer,这是由于 HTML 的跨站脚本安全限制。解决方案是使用 JSONP 格式来跨越限制。GeoServer 可以提供 JSONP 格式,但您应该从选项中激活它。

在本章的将 WMS 层添加到地图食谱中,我们使用了无瓦片结构来获取 WMS 图像,但这次我们使用瓦片结构来获取 WMS 图像。在无瓦片和瓦片 WMS 使用的截图中的差异可以观察到,瓦片 WMS 中州名的缩写出现多次,因为同一州的几何形状可以在瓦片 WMS 的不同图像中看到。正如所说,选择权在你,根据你的地理 Web 应用需求,选择瓦片或无瓦片。

在 WMS 中创建瓦片结构的方式与我们在第二章的将瓦片覆盖添加到地图食谱中做的方式完全相同,添加栅格层。这里的重要部分是创建每个瓦片的 URL。每个瓦片的BBOX参数计算如下:

var proj = map.getProjection();
var zfactor = Math.pow(2, zoom);
// get Long Lat coordinates
var top = proj.fromPointToLatLng(new google.maps.Point(coord.x * 256 / zfactor, coord.y * 256 / zfactor) );
var bot = proj.fromPointToLatLng(new google.maps.Point((coord.x + 1) * 256 / zfactor, (coord.y + 1) * 256 / zfactor));
//create the Bounding box string
var ne = toMercator(top);
var sw = toMercator(bot);
var bbox = ne.x + ',' + sw.y + ',' + sw.x + ',' + ne.y;

需要进行投影变换以获取恰好适合 Google Maps 基础地图的瓦片。Google Maps 具有 Web Mercator 投影,因此覆盖层需要使用此投影。

URL 需要的另一个参数是 WMS 标准参数,但请确保根据 WMS 版本了解参数的差异。

在本食谱中使用的SRS参数是EPSG:3857,它与EPSG:900913ESRI:102113ESRI:102100等价。这里提到的所有SRS参数都定义了 Web Mercator 投影系统。

WMSFeatureInfo类是为创建 WMS 获取信息请求而编写的。URL 的参数很重要,如下所示:

  • x:这是鼠标的像素 x 坐标。

  • y:这是鼠标的像素 y 坐标。

  • width:这是地图div元素的宽度。

  • height:这是地图div元素的高度。

  • info_format:这是一个字符串,描述了信息的返回格式。在这种情况下,使用Text/JavaScript以 JSONP 格式获取信息。

  • query_layers:这是要查询的层的逗号分隔列表。

  • layers:这是要显示的层的逗号分隔列表(来自GetMap请求)。

  • bbox:这是显示的地图的边界框。

  • format_options:对于 JSONP 是必需的,用于定义callback函数的名称。callback函数的名称必须与 jQuery AJAX 请求中获取信息时相同,以避免任何错误。

getUrl方法接受LatLng对象作为输入,但在GetFeatureInfo请求中需要屏幕坐标。我们想出了一个技巧,以便在getUrl方法中将LatLng转换为屏幕坐标。在构造函数中,我们使用google.maps.OverlayView类创建一个覆盖层,并使用其功能将LatLng转换为屏幕坐标:

var pnt = this.overlay.getProjection ().fromLatLngToContainerPixel(coord);
rUrl += '&x=' + Math.round(pnt.x) + '&y=' + Math.round(pnt.y);

google.maps.Projection类有一个名为fromLatLngToPoint()的方法,用于将LatLng对象转换为屏幕坐标,但这个方法并不像预期的那样工作。这个方法将LatLng坐标转换为世界比例的屏幕坐标,但我们需要获取地图div引用中的屏幕坐标。为了实现这一点,我们使用google.maps.MapCanvasProjection类的方法fromLatLngToContainerPixel()

我们没有详细讲解如何监听地图click事件并显示弹出窗口。此外,我们使用了 jQuery 的ajax方法来获取 JSONP 请求,这也不在本书的范围之内。如果您想了解这些主题的详细信息,请参考相关章节的先前菜谱。

参见

  • 在第一章的“在自定义 DIV 元素中创建简单地图”菜谱中,Google Maps JavaScript API 基础知识

  • 在第三章的“向标记或地图添加弹出窗口”菜谱中,添加矢量图层

  • 在第五章的“获取鼠标点击坐标”菜谱中,理解 Google Maps JavaScript API 事件

  • 向地图添加 WMS 图层的菜谱

posted @ 2025-10-26 08:50  绝不原创的飞龙  阅读(1)  评论(0)    收藏  举报