首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Grails 服务和 Google 地图(1)

Grails 服务和 Google 地图(1)

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




您在 URL 中输入的 name_equals 参数是该机场的 IATA 代码。这只是在每个查询中需要更改的 URL 的一部分。fcode=airp 表明您正在搜索的特征代码是一个机场。style 参数 —short、medium、long 或 full— 指定了 XML 响应的详细程度。
现在已经准备好地理编码器,下一步就是将它与 Grails 应用程序集成在一起。为此,您需要一个服务
Grails 服务到目前为止,通过学习  系列文章,您应该已经明白域类、控制器和 Groovy 服务器页面(Groovy Server Pages,GSP 是如何协调工作的。它们简化了在单一数据类型上执行基本的创建/检索/更新/删除(Create/Retrieve/Update/Delete,CRUD)操作。这个地理编码服务似乎略微超出了简单 Grails Object Relational Mapping(GORM)转换(从关系数据库记录到普通的旧 Groovy 对象(plain old Groovy objects,POGO))的范围。同样,这个服务很可能由多种方法使用。稍后您将看到,对 IATA 代码进行地理编码需要用到 save 和 update。Grails 为您提供了保存常用方法的位置,并且超越了任何单个的域类:即服务。
要创建 Grails 服务,请在命令行输入 grails create-service Geocoder。在文本编辑器中查看 grails-app/services/GeocoderService.groovy,如清单 2 所示:
清单 2. 一个无存根(stubbed-out)Grails 服务
1
2
3
4
5
6
class GeocoderService {
    boolean transactional = true
    def serviceMethod() {

    }
}




如果使用同一个方法进行多个数据库查询,那么将涉及到 transactional 字段。它将所有内容都包装在一个单个数据库事务中,如果任何一个查询失败,该数据库事务将回滚到原来的状态。因为在本示例中您远程地调用 Web 服务,所以可以安全地将它设置为 false。
名称 serviceMethod 是一个占位符(placeholder),可以将其改为更具描述性的内容(服务可以包含任意多种方法)。在清单 3 中, 我把名称改为 geocodeAirport:
清单 3. geocodeAirport() 地理编码器服务方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class GeocoderService {
    boolean transactional = false

    // http://ws.geonames.org/search?name_equals=den&amp;fcode=airp&style=full
    def geocodeAirport(String iata) {
      def base = "http://ws.geonames.org/search?"
      def qs = []
      qs << "name_equals=" + URLEncoder.encode(iata)
      qs << "fcode=airp"
      qs << "style=full"
      def url = new URL(base + qs.join("&"))
      def connection = url.openConnection()

      def result = [:]
      if(connection.responseCode == 200){
        def xml = connection.content.text
        def geonames = new XmlSlurper().parseText(xml)
        result.name = geonames.geoname.name as String
        result.lat = geonames.geoname.lat as String
        result.lng = geonames.geoname.lng as String
        result.state = geonames.geoname.adminCode1 as String
        result.country = geonames.geoname.countryCode as String
      }
      else{
        log.error("GeocoderService.geocodeAirport FAILED")
        log.error(url)
        log.error(connection.responseCode)
        log.error(connection.responseMessage)
      }      
      return result
    }
}




geocodeAirport 方法的第一部分构建 URL 并进行连接。查询字符串元素先集中在一个 ArrayList 里,然后和一个 & 符号连接起来。方法的最后部分使用 Groovy XmlSlurper 解析 XML 结果并将结果存储在 hashmap 里。
Groovy 服务不可以直接从 URL 访问。如果您想在 Web 浏览器中测试这个新的服务方法,请将一个简单的闭包添加到 AirportController,如清单 4 所示:
清单 4. 在控制器中向服务提供一个 URL
1
2
3
4
5
6
7
8
9
10
11
12
13
import grails.converters.*

class AirportController {
  def geocoderService
  def scaffold = Airport
   
  def geocode = {
    def result = geocoderService.geocodeAirport(params.iata)
    render result as JSON
  }  
   
  ...
}




如果您定义一个与服务同名的成员变量,Spring 会自动地将服务注入控制器(要想让这种方法奏效,您必须把服务名的第一个字母由大写改为小写,使它遵循 Java 风格的变量命名约定)。
要测试服务,请在 Web 浏览器中输入 URL http://localhost:9090/trip/airport/geocode?iata=den。您将看到如清单 5 所示的结果:
清单 5. 地理编码器请求的结果
1
2
3
4
5
{"name":"Denver International Airport",
"lat":"39.8583188",
"lng":"-104.6674674",
"state":"CO",
"country":"US"}




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