精通 Grails: Grails 服务和 Google 地图,将外部技术融入到 Grails 应用程序

 自本系列的 第一篇文章 开始,我就一直在构建一个 trip-planner 应用程序。目前基本的模型-视图-控制器(Model-View-Controller,MVC)框架已经准备就绪,我将加入一些外部技术,具体来讲,我将加入地图功能。虽然我可以表示 “我的旅程是从丹佛到罗利,途经圣何塞和西雅图”,但地图将能更好地描述旅途路线。您可能知道西雅图和罗利是在美国的两端,但地图能够帮助您显示出两个城市之间的距离。

这个应用程序有什么用?本文的末尾为您提供一个大体的介绍。请访问 http://maps.google.com 并在搜索框内输入 IATA 代码 DEN。将出现丹佛国际机场(Denver International Airport),如图 1 所示

图 1. 丹佛机场(由 Google Map 友情提供)
丹佛机场(由 Google Map 友情提供)

除了能显示您在 HTML 表创建的美国机场以外,trip planner 还将在地图上把机场描绘出来。在本文中,我将使用免费的 Google Maps API。我还可以使用免费的 Yahoo! Maps API,等等(参见 参考资料)。一旦了解在线 Web 地图绘制的基本原理之后,您将发现不同的 API 之间能够合理地互换。在讨论该解决方案的地图绘制部分之前,您需要了解如何将一个简单的三个字母的字符串(如 DEN)转换为地图上的一点。

地理编码

当向 Google Map 输入 DEN 时,这个应用程序在幕后进行了一些转换。您可能用街道地址(如 123 Main Street)的方式想象地理位置,但 Google Map 需要一个纬度/经度点,以便在地图上把它显示出来。这并不需要您自己设法提供纬度/经度点,应用程序会替您把人类能够识别的地址转换为纬度/经度点。这一转换过程称为地理编码(参见 参考资料)。

关于本系列

Grails 是一种新型 Web 开发框架,它将常见的 Spring 和 Hibernate 等 Java™ 技术与当前流行的约定优于配置等实践相结合。Grails 是用 Groovy 编写的,它可以提供与遗留 Java 代码的无缝集成,同时还可以加入脚本编制语言的灵活性和动态性。学习完 Grails 之后,您将彻底改变看待 Web 开发的方式。

浏览 Web 时,也会发生一个类似的转换。从技术角度来说,联系远程 Web 服务器的惟一方式是提供服务器的 IP 地址。幸运的是,您不需要自己输入 IP 地址。只要将友好的 URL 输入到 Web 浏览器,它将调用域名系统(DNS)服务器。DNS 服务器会将 URL 转换为对应的 IP 地址,然后浏览器与远程服务器建立 HTTP 连接。所有这些对用户而言都是透明的。DNS 使 Web 的使用容易了很多。同样,地理编码器也使基于 Web 的地图绘制应用程序更加容易使用。

在 Web 上快速搜索免费地理编码器 会产生许多符合 trip planner 地理编码需求的结果。Google 和 Yahoo! 都提供地理编码服务,并把它作为 API 的标准部分,但针对这个应用程序,我将使用由 geonames.org(参见 参考资料)提供的免费地理编码服务。它的 RESTful API 允许我指明我提供的是 IATA 代码,而不是通用的文本搜索术语。比如,ORD 并不是指内布拉斯加州 Ord. 市的居民,ORD 指的是 Chicago O'Hare International Airport。

在 Web 浏览器中输入 URL http://ws.geonames.org/search?name_equals=den&fcode=airp&style=full。您将看到 XML 响应,如清单 1 所示:


清单 1. 来自地理编码请求的 XML 结果

  1. <geonames style="FULL">
  2.   <totalResultsCount>1</totalResultsCount>
  3.   <geoname>
  4.     <name>Denver International Airport</name>
  5.     <lat>39.8583188</lat>
  6.     <lng>-104.6674674</lng>
  7.     <geonameId>5419401</geonameId>
  8.     <countryCode>US</countryCode>
  9.     <countryName>United States</countryName>
  10.     <fcl>S</fcl>
  11.     <fcode>AIRP</fcode>
  12.     <fclName>spot, building, farm</fclName>
  13.     <fcodeName>airport</fcodeName>
  14.     <population/>
  15.     <alternateNames>DEN,KDEN</alternateNames>
  16.     <elevation>1655</elevation>
  17.     <continentCode>NA</continentCode>
  18.     <adminCode1>CO</adminCode1>
  19.     <adminName1>Colorado</adminName1>
  20.     <adminCode2>031</adminCode2>
  21.     <adminName2>Denver County</adminName2>
  22.     <alternateName lang="iata">DEN</alternateName>
  23.     <alternateName lang="icao">KDEN</alternateName>
  24.     <timezone dstOffset="-6.0" gmtOffset="-7.0">America/Denver</timezone>
  25.   </geoname>
  26. </geonames>

您在 URL 中输入的 name_equals 参数是该机场的 IATA 代码。这只是在每个查询中需要更改的 URL 的一部分。fcode=airp 表明您正在搜索的特征代码是一个机场。style 参数 — shortmediumlongfull — 指定了 XML 响应的详细程度。

现在已经准备好地理编码器,下一步就是将它与 Grails 应用程序集成在一起。为此,您需要一个服务

Grails 服务

到目前为止,通过学习 精通 Grails 系列文章,您应该已经明白域类、控制器和 Groovy 服务器页面(Groovy Server Pages,GSP 是如何协调工作的。它们简化了在单一数据类型上执行基本的创建/检索/更新/删除(Create/Retrieve/Update/Delete,CRUD)操作。这个地理编码服务似乎略微超出了简单 Grails Object Relational Mapping(GORM)转换(从关系数据库记录到普通的旧 Groovy 对象(plain old Groovy objects,POGO))的范围。同样,这个服务很可能由多种方法使用。稍后您将看到,对 IATA 代码进行地理编码需要用到 saveupdate。Grails 为您提供了保存常用方法的位置,并且超越了任何单个的域类:即服务。

要创建 Grails 服务,请在命令行输入 grails create-service Geocoder。在文本编辑器中查看 grails-app/services/GeocoderService.groovy,如清单 2 所示:


清单 2. 一个无存根(stubbed-out)Grails 服务

                
class GeocoderService {
    boolean transactional = true
    def serviceMethod() {

    }
}

如果使用同一个方法进行多个数据库查询,那么将涉及到 transactional 字段。它将所有内容都包装在一个单个数据库事务中,如果任何一个查询失败,该数据库事务将回滚到原来的状态。因为在本示例中您远程地调用 Web 服务,所以可以安全地将它设置为 false

名称 serviceMethod 是一个占位符(placeholder),可以将其改为更具描述性的内容(服务可以包含任意多种方法)。在清单 3 中, 我把名称改为 geocodeAirport


清单 3. geocodeAirport() 地理编码器服务方法

  1. class GeocoderService {
  2.     boolean transactional = false
  3.     // http://ws.geonames.org/search?name_equals=den&fcode=airp&style=full
  4.     def geocodeAirport(String iata) {
  5.       def base = "http://ws.geonames.org/search?"
  6.       def qs = []
  7.       qs << "name_equals=" + URLEncoder.encode(iata)
  8.       qs << "fcode=airp"
  9.       qs << "style=full"
  10.       def url = new URL(base + qs.join("&"))
  11.       def connection = url.openConnection()
  12.       def result = [:]
  13.       if(connection.responseCode == 200){
  14.         def xml = connection.content.text
  15.         def geonames = new XmlSlurper().parseText(xml)
  16.         result.name = geonames.geoname.name as String 
  17.         result.lat = geonames.geoname.lat as String
  18.         result.lng = geonames.geoname.lng as String
  19.         result.state = geonames.geoname.adminCode1 as String
  20.         result.country = geonames.geoname.countryCode as String
  21.       }
  22.       else{
  23.         log.error("GeocoderService.geocodeAirport FAILED")
  24.         log.error(url)
  25.         log.error(connection.responseCode)
  26.         log.error(connection.responseMessage)
  27.       }      
  28.       return result
  29.     }
  30. }

geocodeAirport 方法的第一部分构建 URL 并进行连接。查询字符串元素先集中在一个 ArrayList 里,然后和一个 & 符号连接起来。方法的最后部分使用 Groovy XmlSlurper 解析 XML 结果并将结果存储在 hashmap 里。

Groovy 服务不可以直接从 URL 访问。如果您想在 Web 浏览器中测试这个新的服务方法,请将一个简单的闭包添加到 AirportController,如清单 4 所示:


清单 4. 在控制器中向服务提供一个 URL

 

  1. import grails.converters.*
  2. class AirportController {
  3.   def geocoderService
  4.   def scaffold = Airport
  5.   
  6.   def geocode = {
  7.     def result = geocoderService.geocodeAirport(params.iata)
  8.     render result as JSON
  9.   }  
  10.   
  11.   ...
  12. }

如果您定义一个与服务同名的成员变量,Spring 会自动地将服务注入控制器(要想让这种方法奏效,您必须把服务名的第一个字母由大写改为小写,使它遵循 Java 风格的变量命名约定)。

要测试服务,请在 Web 浏览器中输入 URL http://localhost:9090/trip/airport/geocode?iata=den。您将看到如清单 5 所示的结果:


清单 5. 地理编码器请求的结果
                
{"name":"Denver International Airport",
"lat":"39.8583188",
"lng":"-104.6674674",
"state":"CO",
"country":"US"}

AirportController 中的 geocode 闭包只是用于对服务进行检查。因此,可以把它删除,或者保留下来供以后的 Ajax 调用使用。下一步是重新构造 Airport 基础设施,以利用这个新的地理编码服务。

加入服务

首先,把新的 latlng 字段添加到 grails-app/domain/Airport.groovy,如清单 6 所示:


清单 6. 把 latlng 字段添加到 Airport POGO

  1. class Airport{
  2.   static constraints = {
  3.     name()
  4.     iata(maxSize:3)
  5.     city()
  6.     state(maxSize:2)
  7.     country()
  8.   }
  9.   
  10.   String name
  11.   String iata
  12.   String city
  13.   String state
  14.   String country = "US"
  15.   String lat
  16.   String lng
  17.   
  18.   String toString(){
  19.     "${iata} - ${name}"
  20.   }
  21. }

在命令提示处输入 grails generate-views Airport 来创建 GSP 文件。借助 AirportController.groovy 的 def scaffold = Airport 行,从运行时开始就一直在动态搭建 GSP 文件。要想对这个视图进行更改,我必须先处理代码。

创建新的 Airport 时,我将把用户可编辑字段限制为 iatacity。要想让地理编码查询能够工作,必须具备 iata 字段。我没有更改 city,因为我喜欢由自己来提供这个信息。DEN 真的就在丹佛(Denver),但 ORD(Chicago O'Hare)却在伊里诺斯州的罗斯蒙特(Rosemont),而 CVG(俄亥俄州辛辛那提机场,Cincinnati,Ohio airport)则在肯塔基州的佛罗伦萨市(Florence)。将这两个字段留在 create.gsp 里,其余的删除。现在 create.gsp 如清单 7 所示:


清单 7. 修改 create.gsp

 

  1. <g:form action="save" method="post" >
  2.   <div class="dialog">
  3.     <table>
  4.       <tbody>                                         
  5.         <tr class="prop">
  6.           <td valign="top" class="name"><label for="iata">Iata:</label></td>
  7.           <td valign="top" 
  8.               class="value ${hasErrors(bean:airport,field:'iata','errors')}">
  9.               <input type="text" 
  10.                      maxlength="3" 
  11.                      id="iata" 
  12.                      name="iata" 
  13.                      value="${fieldValue(bean:airport,field:'iata')}"/>
  14.           </td>
  15.         </tr> 
  16.         <tr class="prop">
  17.           <td valign="top" class="name"><label for="city">City:</label></td>
  18.           <td valign="top" 
  19.               class="value ${hasErrors(bean:airport,field:'city','errors')}">
  20.               <input type="text" 
  21.                      id="city" 
  22.                      name="city" 
  23.                      value="${fieldValue(bean:airport,field:'city')}"/>
  24.           </td>
  25.         </tr>                                                 
  26.       </tbody>
  27.     </table>
  28.   </div>
  29.   <div class="buttons">
  30.     <span class="button"><input class="save" type="submit" value="Create" /></span>
  31.   </div>
  32. </g:form>

图 2 展示了所产生的表单:


图 2. 创建 Airport 表单
创建 Airport

该表提交到 AirportController 中的 save 闭包。将清单 8 中的代码添加到控制器,以在保存新的 Airport 之前调用 geocodeAirport


清单 8. 修改 save 闭包
                def save = {
    def results = geocoderService.geocodeAirport(params.iata)    
    def airport = new Airport(params + results)
    if(!airport.hasErrors() && airport.save()) {
        flash.message = "Airport ${airport.id} created"
        redirect(action:show,id:airport.id)
    }
    else {
        render(view:'create',model:[airport:airport])
    }
}

如果在命令提示处输入 grails generate-controller Airport,方法的主要部分将与您所看到的一样。仅仅是开始的两行与默认生成的闭包不同。第一行从 geocoder 服务获得一个 HashMap。第二行将 results HashMapparams HashMap 合并起来(当然,在 Groovy 中合并两个 HashMap 就像把它们添加到一起一样简单)。

如果数据库保存成功的话,将重定向到显示操作。幸运的是,不需要更改 show.gsp,如图 3 所示:


图 3. 显示 Airport 表单
显示 Airport

要编辑 Airport,必须保持 iatacity 字段在 edit.gsp 中不变。您可以从 show.gsp 复制和粘贴其余的字段,把它们变为只读字段(或者,如果您能从 前期文章 体会到 “复制和粘贴是面向对象编程的最低级形式” 的话,您可以把常用字段提取到一个局部模板并在 show.gsp 和 edit.gsp 中呈现它)。清单 9 展示了修改后的 edit.gsp:


清单 9. 修改 edit.gsp

 

  1. <g:form method="post" >
  2.   <input type="hidden" name="id" value="${airport?.id}" />
  3.   <div class="dialog">
  4.     <table>
  5.       <tbody>                                              
  6.         <tr class="prop">
  7.           <td valign="top" class="name"><label for="iata">Iata:</label></td>
  8.           <td valign="top" 
  9.               class="value ${hasErrors(bean:airport,field:'iata','errors')}">
  10.               <input type="text" 
  11.                      maxlength="3" 
  12.                      id="iata" 
  13.                      name="iata" 
  14.                      value="${fieldValue(bean:airport,field:'iata')}"/>
  15.           </td>
  16.         </tr>                         
  17.         <tr class="prop">
  18.           <td valign="top" class="name"><label for="city">City:</label></td>
  19.           <td valign="top" 
  20.               class="value ${hasErrors(bean:airport,field:'city','errors')}">
  21.               <input type="text" 
  22.                      id="city" 
  23.                      name="city" 
  24.                      value="${fieldValue(bean:airport,field:'city')}"/>
  25.           </td>
  26.         </tr> 
  27.         <tr class="prop">
  28.           <td valign="top" class="name">Name:</td>
  29.           <td valign="top" class="value">${airport.name}</td>
  30.         </tr>
  31.         <tr class="prop">
  32.           <td valign="top" class="name">State:</td>
  33.           <td valign="top" class="value">${airport.state}</td>
  34.         </tr>
  35.         <tr class="prop">
  36.           <td valign="top" class="name">Country:</td>
  37.           <td valign="top" class="value">${airport.country}</td>
  38.         </tr>
  39.         <tr class="prop">
  40.           <td valign="top" class="name">Lat:</td>
  41.           <td valign="top" class="value">${airport.lat}</td>
  42.         </tr>
  43.         <tr class="prop">
  44.           <td valign="top" class="name">Lng:</td>
  45.           <td valign="top" class="value">${airport.lng}</td>
  46.         </tr>
  47.       </tbody>
  48.     </table>
  49.   </div>
  50.   <div class="buttons">
  51.     <span class="button"><g:actionSubmit class="save" value="Update" /></span>
  52.     <span class="button">
  53.       <g:actionSubmit class="delete" 
  54.                       onclick="return confirm('Are you sure?');" 
  55.                       value="Delete" />
  56.     </span>
  57.   </div>
  58. </g:form>

所产生的表单如图 4 所示:


图 4. 编辑 Airport 表单
编辑 Airport

单击 Update 按钮将表单值发送到 update 闭包。将服务调用和 hashmap 合并添加到默认代码,如清单 10 所示:


清单 10. 修改 update 闭包

 

  1. def update = {
  2.     def airport = Airport.get( params.id )
  3.     if(airport) {
  4.         def results = geocoderService.geocodeAirport(params.iata)    
  5.         airport.properties = params + results
  6.         if(!airport.hasErrors() && airport.save()) {
  7.             flash.message = "Airport ${params.id} updated"
  8.             redirect(action:show,id:airport.id)
  9.         }
  10.         else {
  11.             render(view:'edit',model:[airport:airport])
  12.         }
  13.     }
  14.     else {
  15.         flash.message = "Airport not found with id ${params.id}"
  16.         redirect(action:edit,id:params.id)
  17.     }
  18. }

到目前为止,您已经像 Google Map 一样无缝地将地理编码集成到您的应用程序里。花点时间想一想在应用程序中捕获地址的所有位置 — 顾客、雇员、远程办公室、仓库和零售点等等。通过简单地添加几个字段以存储纬度/经度坐标和加入一个地理编码服务,就能够设置一些简易的地图来显示对象 — 这正是我下一步的工作。

Google Map

许多人都知道为了易于使用,Google Map 针对 Web 地图绘制设置了标准。但很少人知道到这个标准也适用于将 Google Map 嵌入到您自己的 Web 页面中。为数据点获取纬度/经度坐标是这个应用中最困难的部分,但我们已经解决了这个问题。

要将 Google Map 嵌入到 Grails 应用程序中,首先要做的是获得一个免费的 API 密匙。注册页面详细说明了使用条款。实际上,只要您的应用程序是免费的,Google 也将免费为您提供 API。这意味着您不能对 Google Map 应用程序进行密码保护、收取访问费用或把它托管在防火墙后面(做个广告:由我撰写的 GIS for Web Developers 一书将逐步指导您使用免费数据和开发源码软件构建类似于 Google Map 的应用程序;参见 参考资料。这使您不再受到 Google 的 API 使用限制的约束)。

API 密匙通常绑定到一个特定的 URL 和目录。在表单中输入 http://localhost:9090/trip 并单击 Generate API Key 按钮。确认页面将显示刚生成的 API 密匙、与密匙相关联的 URL 和一个 “get you started on your way to mapping glory” 的示例 Web 页面。

为了将这个示例页面并入到 Grails 应用程序,需要在 grails-app/views/airport 目录中创建一个名为 map.gsp 的文件。从 Google 将示例页面复制到 map.gsp。 清单 11 展示了 map.gsp 的内容:


清单 11. 一个简单的 Google Map Web 页面

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  2.   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4.   <head>
  5.     <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  6.     <title>Google Maps JavaScript API Example</title>
  7.     <script src="http://maps.google.com/maps?file=api&v=2&key=ABCDE"
  8.             type="text/javascript"></script>
  9.     <script type="text/javascript">
  10.     //<![CDATA[
  11.     function load() {
  12.       if (GBrowserIsCompatible()) {
  13.         var map = new GMap2(document.getElementById("map"));
  14.         map.setCenter(new GLatLng(37.4419, -122.1419), 13);
  15.       }
  16.     }
  17.     //]]>
  18.     </script>
  19.   </head>
  20.   <body onload="load()" onunload="GUnload()">
  21.     <div id="map" style="width: 500px; height: 300px"></div>
  22.   </body>
  23. </html>

注意,API 密匙嵌入在页面顶部的脚本 URL 里。在 load 方法中,您正在实例化一个新的 GMap2 对象。这就是出现在 <div /> 里的地图,同时 map 的 ID 出现在页面的底端。如果想让地图变大些,可以在层叠样式表(Cascading Style Sheets,CSS)的 style 属性中调整地图的宽度和高度。目前,这个地图以加利福尼亚州的帕洛阿图市为中心,缩放倍数为 13 级(0 级是最小的。级别越大越接近街道级别的视图)。您可以快速地调整这些值。同时,将一个空 map 闭包添加到 AirlineController,如清单 12 所示:


清单 12. 添加 map 闭包
                
class AirportController {
  def map = {}

  ...
}  

现在,浏览 http://localhost:9090/trip/airport/map,您将看到已嵌入的 Google Map,如图 5 所示:


图 5. 简单的 Google Map
简单的 Google Map

现在先回到 map.gsp 并调整值,如清单 13 所示:


清单 13. 调整基本的地图

 

  1. <script type="text/javascript">
  2.   var usCenterPoint = new GLatLng(39.833333, -98.583333)
  3.   var usZoom = 4
  4.   function load() {
  5.     if (GBrowserIsCompatible()) {
  6.       var map = new GMap2(document.getElementById("map"))
  7.       map.setCenter(usCenterPoint, usZoom)
  8.       map.addControl(new GLargeMapControl());
  9.       map.addControl(new GMapTypeControl());        
  10.     }
  11.   }
  12.   </script>
  13. </head>
  14. <body οnlοad="load()" οnunlοad="GUnload()">
  15.   <div id="map" style="width: 800px; height: 400px"></div>
  16. </body>

要查看整个美国,将尺寸设置为 800 x 400 像素是比较好的。清单 13 调整了中心点和缩放级别,使您能够看到完整的地图。您还可以添加许多不同的地图控制。清单 13 中的 GLargeMapControlGMapTypeControl 分别在地图左边和右上角提供了常用的控制。在您调试时不断点击浏览器的 Refresh 按钮,查看修改后的效果。图 6 反映了对清单 13 所做的调整:


图 6. 调整后的地图
调整后的地图

现在基本的地图已经做好, 接下来就可以添加标记了 — 为每个机场添加图钉。在将这一过程自动化之前,我在清单 14 中手工添加了一些简单的标记:


清单 14. 将标记添加到地图

 

  1. <script type="text/javascript">
  2. var usCenterPoint = new GLatLng(39.833333, -98.583333)
  3. var usZoom = 4
  4. function load() {
  5.   if (GBrowserIsCompatible()) {
  6.     var map = new GMap2(document.getElementById("map"))
  7.     map.setCenter(usCenterPoint, usZoom)
  8.     map.addControl(new GLargeMapControl());
  9.     map.addControl(new GMapTypeControl()); 
  10.     
  11.     var marker = new GMarker(new GLatLng(39.8583188, -104.6674674))
  12.     marker.bindInfoWindowHtml("DEN<br/>Denver International Airport")
  13.       map.addOverlay(marker)                       
  14.   }
  15. }
  16. </script>

GMarker 构造器采用了一个 GLatLng 点。bindInfoWindowHtml 方法提供了用户单击标记时在 Info 窗口内显示的 HTML 文件片段。最后,清单 14 还通过使用 addOverlay 方法将标记添加到地图。

图 7 展示了添加标记后的地图:


图 7. 带标记的地图
带标记的地图

现在您已经知道如何添加一个单一的点,但要自动地添加数据库里的所有点,还需要做两个小的更改。第一个更改是在 AirportController 中生成 map 闭包,这会返回一个 Airport 列表,如清单 15 所示:


清单 15. 返回一个 Airport 列表
                
def map = {
  [airportList: Airport.list()]
}

接下来,需要遍历 Airport 列表并为每个 Airport 创建标记。在本系列的早期文章中,曾经介绍使用 <g:each> 标记向 HTML 表添加行。清单 16 使用这个标记创建必要的 JavaScript 行,这样才能在地图上显示 Airport


清单 16. 动态地为地图添加标记

 

  1. <script type="text/javascript">
  2. var usCenterPoint = new GLatLng(39.833333, -98.583333)
  3. var usZoom = 4
  4. function load() {
  5.   if (GBrowserIsCompatible()) {
  6.     var map = new GMap2(document.getElementById("map"))
  7.     map.setCenter(usCenterPoint, usZoom)
  8.     map.addControl(new GLargeMapControl());
  9.     map.addControl(new GMapTypeControl()); 
  10.       <g:each in="${airportList}" status="i" var="airport">
  11.          var point${airport.id} = new GLatLng(${airport.lat}, ${airport.lng})
  12.       var marker${airport.id} = new GMarker(point${airport.id})
  13.       marker${airport.id}.bindInfoWindowHtml("${airport.iata}<br/>${airport.name}")
  14.          map.addOverlay(marker${airport.id})
  15.       </g:each>
  16.   }
  17. }
  18. </script>

图 8 展示了自动添加了标记后的地图:


图 8. 带有多个标记的地图
带有多个标记的地图

这个针对 Google Maps API 的简单介绍只涉及到些皮毛,您可以进行的操作远不止这些。您可能已经决定利用事件模型来实现 Ajax 调用,当单击标记时就会返回 JavaScript Object Notation(JSON)数据。您可以使用 GPolyline 在地图上描绘一个旅途中的各段行程。可以实现无限的可能性。要获得更多的信息,可以参考 Google 的在线文档。您也可以从我的 PDF 书籍 Google Maps API 获得关于 API 的适当介绍(参见 参考资料)。

结束语

地图添加到 Grails 应用程序需要具备 3 个条件。

第一个条件是对数据进行地理编码。有许多免费的地理编码器,它们可以将人类能识别的地理位置转换为纬度/经度点。几乎能够对所有的内容进行地理编码:街道地址、城市、县城、国家、邮政区码、电话号码、IP 地址等,甚至包括机场的 IATA 代码。

在您找到合适的地理编码器之后,创建一个 Grails 服务以把远程 Web 服务调用封装在可重用方法调用中。服务是为在单一区域对象上超越简单 CRUD 操作的方法而准备的。在默认情况下,服务并不与 URL 相关联,但是您可以轻易地在控制器中创建一个闭包,使这些服务可以通过 Web 找到。

最后,利用免费的 Web 地图绘制 API(比如 Google Map)在地图上描绘纬度/经度点。这些免费的服务通常也要求您的应用程序可以免费访问。如果您希望地图是隐私的,请考虑使用开放源代码 API(比如 OpenLayers),它提供了和 Google Map 一样的用户体验,但没有 Google Map 那样的使用限制(参见 参考资料)。您将需要提供自己的地图绘制层,但可以将整个应用程序托管到自己的服务器上,从而保持它的隐私性。

在下一篇文章,我将讨论使 Grails 应用程序适用于移动电话的方法。您将看到如何优化 iPhone 的显示视图。我还将演示通过电子邮件从 Grails 发送信息,这个信息将在移动电话中显示为 SMS 消息。 那时,您将享受精通 Grails 的乐趣。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值