精通 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. <geonamesstyle="FULL">
  2. <totalResultsCount>1</totalResultsCount>
  3. <geoname>
  4. <name>DenverInternationalAirport</name>
  5. <lat>39.8583188</lat>
  6. <lng>-104.6674674</lng>
  7. <geonameId>5419401</geonameId>
  8. <countryCode>US</countryCode>
  9. <countryName>UnitedStates</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>DenverCounty</adminName2>
  22. <alternateNamelang="iata">DEN</alternateName>
  23. <alternateNamelang="icao">KDEN</alternateName>
  24. <timezonedstOffset="-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. classGeocoderService{
  2. booleantransactional=false
  3. //http://ws.geonames.org/search?name_equals=den&fcode=airp&style=full
  4. defgeocodeAirport(Stringiata){
  5. defbase="http://ws.geonames.org/search?"
  6. defqs=[]
  7. qs<<"name_equals="+URLEncoder.encode(iata)
  8. qs<<"fcode=airp"
  9. qs<<"style=full"
  10. defurl=newURL(base+qs.join("&"))
  11. defconnection=url.openConnection()
  12. defresult=[:]
  13. if(connection.responseCode==200){
  14. defxml=connection.content.text
  15. defgeonames=newXmlSlurper().parseText(xml)
  16. result.name=geonames.geoname.nameasString
  17. result.lat=geonames.geoname.latasString
  18. result.lng=geonames.geoname.lngasString
  19. result.state=geonames.geoname.adminCode1asString
  20. result.country=geonames.geoname.countryCodeasString
  21. }
  22. else{
  23. log.error("GeocoderService.geocodeAirportFAILED")
  24. log.error(url)
  25. log.error(connection.responseCode)
  26. log.error(connection.responseMessage)
  27. }
  28. returnresult
  29. }
  30. }

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

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


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

  1. importgrails.converters.*
  2. classAirportController{
  3. defgeocoderService
  4. defscaffold=Airport
  5. defgeocode={
  6. defresult=geocoderService.geocodeAirport(params.iata)
  7. renderresultasJSON
  8. }
  9. ...
  10. }

如果您定义一个与服务同名的成员变量,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. classAirport{
  2. staticconstraints={
  3. name()
  4. iata(maxSize:3)
  5. city()
  6. state(maxSize:2)
  7. country()
  8. }
  9. Stringname
  10. Stringiata
  11. Stringcity
  12. Stringstate
  13. Stringcountry="US"
  14. Stringlat
  15. Stringlng
  16. StringtoString(){
  17. "${iata}-${name}"
  18. }
  19. }

在命令提示处输入 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:formaction="save"method="post">
  2. <divclass="dialog">
  3. <table>
  4. <tbody>
  5. <trclass="prop">
  6. <tdvalign="top"class="name"><labelfor="iata">Iata:</label></td>
  7. <tdvalign="top"
  8. class="value${hasErrors(bean:airport,field:'iata','errors')}">
  9. <inputtype="text"
  10. maxlength="3"
  11. id="iata"
  12. name="iata"
  13. value="${fieldValue(bean:airport,field:'iata')}"/>
  14. </td>
  15. </tr>
  16. <trclass="prop">
  17. <tdvalign="top"class="name"><labelfor="city">City:</label></td>
  18. <tdvalign="top"
  19. class="value${hasErrors(bean:airport,field:'city','errors')}">
  20. <inputtype="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. <divclass="buttons">
  30. <spanclass="button"><inputclass="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:formmethod="post">
  2. <inputtype="hidden"name="id"value="${airport?.id}"/>
  3. <divclass="dialog">
  4. <table>
  5. <tbody>
  6. <trclass="prop">
  7. <tdvalign="top"class="name"><labelfor="iata">Iata:</label></td>
  8. <tdvalign="top"
  9. class="value${hasErrors(bean:airport,field:'iata','errors')}">
  10. <inputtype="text"
  11. maxlength="3"
  12. id="iata"
  13. name="iata"
  14. value="${fieldValue(bean:airport,field:'iata')}"/>
  15. </td>
  16. </tr>
  17. <trclass="prop">
  18. <tdvalign="top"class="name"><labelfor="city">City:</label></td>
  19. <tdvalign="top"
  20. class="value${hasErrors(bean:airport,field:'city','errors')}">
  21. <inputtype="text"
  22. id="city"
  23. name="city"
  24. value="${fieldValue(bean:airport,field:'city')}"/>
  25. </td>
  26. </tr>
  27. <trclass="prop">
  28. <tdvalign="top"class="name">Name:</td>
  29. <tdvalign="top"class="value">${airport.name}</td>
  30. </tr>
  31. <trclass="prop">
  32. <tdvalign="top"class="name">State:</td>
  33. <tdvalign="top"class="value">${airport.state}</td>
  34. </tr>
  35. <trclass="prop">
  36. <tdvalign="top"class="name">Country:</td>
  37. <tdvalign="top"class="value">${airport.country}</td>
  38. </tr>
  39. <trclass="prop">
  40. <tdvalign="top"class="name">Lat:</td>
  41. <tdvalign="top"class="value">${airport.lat}</td>
  42. </tr>
  43. <trclass="prop">
  44. <tdvalign="top"class="name">Lng:</td>
  45. <tdvalign="top"class="value">${airport.lng}</td>
  46. </tr>
  47. </tbody>
  48. </table>
  49. </div>
  50. <divclass="buttons">
  51. <spanclass="button"><g:actionSubmitclass="save"value="Update"/></span>
  52. <spanclass="button">
  53. <g:actionSubmitclass="delete"
  54. onclick="returnconfirm('Areyousure?');"
  55. value="Delete"/>
  56. </span>
  57. </div>
  58. </g:form>

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


图 4. 编辑 Airport 表单
编辑 Airport

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


清单 10. 修改 update 闭包

  1. defupdate={
  2. defairport=Airport.get(params.id)
  3. if(airport){
  4. defresults=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="Airportnotfoundwithid${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. <!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Strict//EN"
  2. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <htmlxmlns="http://www.w3.org/1999/xhtml">
  4. <head>
  5. <metahttp-equiv="content-type"content="text/html;charset=utf-8"/>
  6. <title>GoogleMapsJavaScriptAPIExample</title>
  7. <scriptsrc="http://maps.google.com/maps?file=api&v=2&key=ABCDE"
  8. type="text/javascript"></script>
  9. <scripttype="text/javascript">
  10. //<![CDATA[
  11. functionload(){
  12. if(GBrowserIsCompatible()){
  13. varmap=newGMap2(document.getElementById("map"));
  14. map.setCenter(newGLatLng(37.4419,-122.1419),13);
  15. }
  16. }
  17. //]]>
  18. </script>
  19. </head>
  20. <bodyonload="load()"onunload="GUnload()">
  21. <divid="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. <scripttype="text/javascript">
  2. varusCenterPoint=newGLatLng(39.833333,-98.583333)
  3. varusZoom=4
  4. functionload(){
  5. if(GBrowserIsCompatible()){
  6. varmap=newGMap2(document.getElementById("map"))
  7. map.setCenter(usCenterPoint,usZoom)
  8. map.addControl(newGLargeMapControl());
  9. map.addControl(newGMapTypeControl());
  10. }
  11. }
  12. </script>
  13. </head>
  14. <bodyοnlοad="load()"οnunlοad="GUnload()">
  15. <divid="map"style="width:800px;height:400px"></div>
  16. </body>

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


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

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


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

  1. <scripttype="text/javascript">
  2. varusCenterPoint=newGLatLng(39.833333,-98.583333)
  3. varusZoom=4
  4. functionload(){
  5. if(GBrowserIsCompatible()){
  6. varmap=newGMap2(document.getElementById("map"))
  7. map.setCenter(usCenterPoint,usZoom)
  8. map.addControl(newGLargeMapControl());
  9. map.addControl(newGMapTypeControl());
  10. varmarker=newGMarker(newGLatLng(39.8583188,-104.6674674))
  11. marker.bindInfoWindowHtml("DEN<br/>DenverInternationalAirport")
  12. map.addOverlay(marker)
  13. }
  14. }
  15. </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. <scripttype="text/javascript">
  2. varusCenterPoint=newGLatLng(39.833333,-98.583333)
  3. varusZoom=4
  4. functionload(){
  5. if(GBrowserIsCompatible()){
  6. varmap=newGMap2(document.getElementById("map"))
  7. map.setCenter(usCenterPoint,usZoom)
  8. map.addControl(newGLargeMapControl());
  9. map.addControl(newGMapTypeControl());
  10. <g:eachin="${airportList}"status="i"var="airport">
  11. varpoint${airport.id}=newGLatLng(${airport.lat},${airport.lng})
  12. varmarker${airport.id}=newGMarker(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、付费专栏及课程。

余额充值