Grails をマスターする: Grails サービスと Google Maps

外部の技術を Grails アプリケーションに統合する

連載「Grails をマスターする」では今回、Scott Davis が無料で入手可能な API とWeb サービスを利用して Grails アプリケーションにマップを追加する方法を紹介します。また、この連載で今までビルドしてきた Trip Planner アプリケーションを、ジオコード、Google Maps、そして Grails サービスを使ってさらに上のレベルに改良します。

Scott Davis , Editor in Chief, AboutGroovy.com

Scott DavisScott Davis は国際的に知られた著者、講演者、そしてソフトウェア開発者です。彼の著書には、『Groovy Recipes: Greasing the Wheels of Java』、『GIS for Web Developers: Adding Where to Your Application』、『The Google Maps API』、『JBoss At Work』などがあります。



2008年 5月 20日

Trip Planner アプリケーションはこの連載の第 1 回目からビルドしてきました。基本的な MVC (Model-View-Controller) フレームワークが整った今、いよいよ外部技術を取り込む段階にきました。具体的に言うと、今回の記事ではマップを追加します。例えば、「デンバーからローリーに旅行する途中でサンノゼとシアトルにも立ち寄る」と言うことはできますが、マップを使えば、この旅行の説明がもっとわかりやすくなるはずです。シアトルとローリーは合衆国の両端に位置していることはご存知かもしれませんが、マップによってこの2 つの都市の距離を視覚的に把握することができます。

この記事の終わりまでにこのアプリケーションで何をするのかを大まかに把握するには、http://maps.google.com にアクセスして検索ボックスに IATA コード、DEN を入力してください。すると、図 1 のようにデンバー国際空港が表示されます (IATA コードについての詳細は、先月の記事を参照してください)。

図 1. デンバー国際空港 (Google Maps による)
デンバー国際空港 (Google Maps による)

Trip Planner は、合衆国の空港を作成して HTML の表に表示するだけでなく、マップ上に空港を表示するようにもなります。この記事では無料の Google Maps API を使用しますが、同じく無料の Yahoo! Maps API など、他にも使用できる API はいろいろとあります (「参考文献」を参照)。いったんオンライン Web マッピングの基本を理解すれば、他のさまざまな API にも無理なく置き換えられることがわかるはずです。このソリューションのマッピングの部分に取り掛かる前に、まずは DEN のような単純な 3 文字がどのようにマップ上の 1 つの地点に変換されるかを理解する必要があります。

ジオコーディング

Google Maps に DEN を入力したときに、アプリケーションは裏でちょっとした変換を行っています。位置というと、通常は 123 Main Street などといった番地で考えますが、Google Maps がマップ上に特定の位置を表示するために必要なのは、緯度と経度による地点です。しかしユーザーに緯度と経度を入力させる代わりに、Google Maps は人間が読める住所を緯度と経度に自動変換します。この変換は、ジオコーディングと呼ばれます (「参考文献」を参照)。

この連載について

Grails は、Spring や Hibernate などのよく知られた Java™ 技術に「Convention over Configuration」といった現代のプラクティスを盛り込んだ最新の Web 開発フレームワークです。Groovy で作成された Grails は既存の Java コードをシームレスに統合するだけでなく、スクリプト言語ならではの柔軟性と動的機能を与えてくれます。Grails を学んだら、Web 開発に対する今までの見方がまったく違ってくるはずです。

同様の変換は、Web 上でネット・サーフィンするときにも行われています。厳密に言うと、リモートWeb サーバーに接続する唯一の方法はサーバーの IP アドレスを指定することですが、幸いなことに、IP アドレスを自分で入力する必要はありません。わかりやすい URL を Web ブラウザーに入力すると、ブラウザーが DNS (Domain Name System) サーバーを呼び出し、呼び出された DNS サーバーが URL を適切な IP アドレスに変換し、そしてブラウザーがリモート・サーバーに対して HTTP 接続を行います。この一連の動作はユーザーには見えません。このように Web を極めて使いやすくするために DNS が行っているのと同じことを、ジオコーダーは Web ベースのマッピング・アプリケーションに対して行います。

無料のジオコーダーで素早い Web 検索を可能にするとなると、Trip Planner でのジオコーディングにはさまざまな候補が浮上してきます。Google と Yahoo! の API にはいずれも標準でジオコーディング・サービスが提供されていますが、このアプリケーションには geonames.org が提供する無料のジオコーディング・サービスを使うことにします (「参考文献」を参照)。この RESTful なAPI では、一般のテキスト検索語ではなく、IATA コードを指定しているということを示すことができます。ネブラスカ州 Ord の住人には何の反感も持っていませんが、私が興味を持っている ORD はシカゴのオヘア国際空港のほうだからです。

Web ブラウザーに URL http://ws.geonames.org/search?name_equals=den&fcode=airp&style=full と入力してください。すると、リスト 1 に記載するXML レスポンスが表示されます。

リスト 1. ジオコーディング・リクエストによる XML 結果
<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 パラメーター (shortmediumlong、または full) は、XML レスポンスの詳細さを指定します。

ジオコーダーが用意できたところで、次はこれを Grails アプリケーションに統合します。そのために必要なのは、サービスです。


Grails サービス

連載「Grails をマスターする」をこれまで読んできて、読者の皆さんはもう、ドメイン・クラス、コントローラー、GSP (Groovy Server Page) のすべてが協調して機能する仕組みを十分に理解しているはずです。この 3 つは単一のデータ型での基本的な CRUD (Create/Retrieve/Update/Delete) 操作を容易にします。しかしこのジオコーディング・サービスに関しては、リレーショナル・データベースのレコードから POGO (Plain Old Groovy Object) への単純な GORM (Grails Object Relational Mapping) 変換では間に合わないでしょう。さらに、このサービスは複数のメソッドが使用することになるはずです。この後すぐにわかるように、saveupdate は両方ともIATA コードをジオコーディングしなければなりません。そこで Grails が提供するのは、単一のドメイン・クラスの枠を超えて一般に使用されるメソッドを保存する場所、すなわちサービスです。

Grails サービスを作成するには、コマンドラインに grails create-service Geocoder と入力します。grails-app/services/GeocoderService.groovy (リスト 2 に記載) をテキスト・エディターで確認してください。

リスト 2. Grails サービスからの抜粋
class GeocoderService {
    boolean transactional = true
    def serviceMethod() {

    }
}

同じメソッドで複数のデータベース・クエリーを行う場合に興味深いフィールドは、transactional です。このフィールドはすべてを単一のデータベース・トランザクションにラップし、クエリーが 1 つでも失敗すると、このトランザクションはロールバックします。このサンプルではリモート Web サービス呼び出しを行うため、このフィールドは安全に false に設定することができます。

serviceMethod という名前のプレースホルダーは、もっとわかりやすい名前に変更しても構いません (サービスには好きなだけメソッドを含められます)。リスト 3 では、この名前を geocodeAirport に変更してあります。

リスト 3. ジオコーダーのサービス・メソッド、geocodeAirport()
class GeocoderService {
    boolean transactional = false

    // http://ws.geonames.org/search?name_equals=den&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 結果の構文解析を行い、解析結果をハッシュマップに保存します。

Groovy サービスには URL からは直接アクセスできません。この新しいサービス・メソッドを Web ブラウザーでテストするには、AirportController に単純なクロージャーを追加します (リスト 4 を参照)。

リスト 4. コントローラー内サービスに対する URL の指定
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. ジオコーダー・リクエストの結果
{"name":"Denver International Airport",
"lat":"39.8583188",
"lng":"-104.6674674",
"state":"CO",
"country":"US"}

AirportControllergeocode クロージャーは、サービスの正常性をチェックするためだけのものなので、テストが終わったこの時点で削除しても、その後の Ajax 呼び出しに備えて残しておいても構いません。次のステップでは、この新規ジオコーディング・サービスを活用するように Airport のインフラストラウチャーをリファクタリングします。


サービスの統合

まず始めに、grails-app/domain/Airport.groovy に lat フィールドと lng フィールドを新しく追加します (リスト 6 を参照)。

リスト 6. Airport POGO への lat および lng フィールドの追加
class Airport{
  static constraints = {
    name()
    iata(maxSize:3)
    city()
    state(maxSize:2)
    country()
  }
  
  String name
  String iata
  String city
  String state
  String country = "US"
  String lat
  String lng
  
  String toString(){
    "${iata} - ${name}"
  }
}

コマンド・プロンプトで grails generate-views Airport と入力し、GSP ファイルを作成します。これらのファイルはコマンド実行時から作成されるまでの間に、AirportController.groovy にある def scaffold = Airport 行のおかげで動的に scaffold されます。ここで、この表示内容を少し変更したいので、コードに手を加える必要があります

新規の Airport を作成するときには、ユーザーが編集可能なフィールドを iatacity だけに制限するつもりです。iata フィールドについては、これがなければジオコーディング・クエリーは機能しません。city を残しておく理由は、この情報は自分自身で指定したいからです。DEN はまさしくデンバー州にありますが、ORD (シカゴ・オヘア空港) はイリノイ州ローズモントにあります。CVG (オハイオ州シンシナティ空港) に至っては、実際の所在地はケンタッキー州フローレンスです。上記の 2 つのフィールドを抜かしたすべてのフィールドを削除した create.gsp は、リスト 7 のようになります。

リスト 7. create.gsp の変更
<g:form action="save" method="post" >
  <div class="dialog">
    <table>
      <tbody>                                         
        <tr class="prop">
          <td valign="top" class="name"><label for="iata">Iata:</label></td>
          <td valign="top" 
              class="value ${hasErrors(bean:airport,field:'iata','errors')}">
              <input type="text" 
                     maxlength="3" 
                     id="iata" 
                     name="iata" 
                     value="${fieldValue(bean:airport,field:'iata')}"/>
          </td>
        </tr> 
        <tr class="prop">
          <td valign="top" class="name"><label for="city">City:</label></td>
          <td valign="top" 
              class="value ${hasErrors(bean:airport,field:'city','errors')}">
              <input type="text" 
                     id="city" 
                     name="city" 
                     value="${fieldValue(bean:airport,field:'city')}"/>
          </td>
        </tr>                                                 
      </tbody>
    </table>
  </div>
  <div class="buttons">
    <span class="button"><input class="save" type="submit" value="Create" /></span>
  </div>
</g:form>

図 2 は、上記の変更が反映されたフォームです。

図2. Create Airport フォーム
図2. Create Airport フォーム

このフォームは、AirportControllersave クロージャーに送信されます。新しい Airport が保存される前に geocodeAirport を呼び出すため、リスト 8 のコードをコントローラーに追加してください。

リスト 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 と入力した場合に表示される内容と変わりません。デフォルトで生成されたクロージャーとの唯一の違いは、最初の 2 行です。最初の行はジオコーダー・サービスから HashMap を取得し、2 番目の行ではその取得した HashMap である results をもう 1 つの HashMap である params と合成します (Groovy で 2 つの HashMaps をマージするには、この2 つを一緒に追加すればよいだけの話です)。

データベースの保存が成功すると、show アクションにリダイレクトされます。ありがたいことに、show.gsp に必要な変更は何もありません (図3 を参照)。

図3. Show Airport フォーム
図3. Show Airport フォーム

Airport を編集できるように、iata フィールドと city フィールドは edit.gsp の所定の位置にそのまま残しておいてください。その他のフィールドは show.gsp からコピー・アンド・ペーストして読み取り専用にします (以前の記事で「コピー・アンド・ペーストはオブジェクト指向プログラミングの最低の形である」というような暴言を私が吐いたのに従うのであれば、共通フィールドを部分テンプレートに抽出し、show.gsp と edit.gsp の両方でこのテンプレートをレンダリングするのでも構いません)。変更後の edit.gsp はリスト 9 のとおりです。

リスト 9. edit.gsp の変更
<g:form method="post" >
  <input type="hidden" name="id" value="${airport?.id}" />
  <div class="dialog">
    <table>
      <tbody>                                              
        <tr class="prop">
          <td valign="top" class="name"><label for="iata">Iata:</label></td>
          <td valign="top" 
              class="value ${hasErrors(bean:airport,field:'iata','errors')}">
              <input type="text" 
                     maxlength="3" 
                     id="iata" 
                     name="iata" 
                     value="${fieldValue(bean:airport,field:'iata')}"/>
          </td>
        </tr>                         
        <tr class="prop">
          <td valign="top" class="name"><label for="city">City:</label></td>
          <td valign="top" 
              class="value ${hasErrors(bean:airport,field:'city','errors')}">
              <input type="text" 
                     id="city" 
                     name="city" 
                     value="${fieldValue(bean:airport,field:'city')}"/>
          </td>
        </tr> 
        <tr class="prop">
          <td valign="top" class="name">Name:</td>
          <td valign="top" class="value">${airport.name}</td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">State:</td>
          <td valign="top" class="value">${airport.state}</td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">Country:</td>
          <td valign="top" class="value">${airport.country}</td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">Lat:</td>
          <td valign="top" class="value">${airport.lat}</td>
        </tr>
        <tr class="prop">
          <td valign="top" class="name">Lng:</td>
          <td valign="top" class="value">${airport.lng}</td>
        </tr>
      </tbody>
    </table>
  </div>
  <div class="buttons">
    <span class="button"><g:actionSubmit class="save" value="Update" /></span>
    <span class="button">
      <g:actionSubmit class="delete" 
                      onclick="return confirm('Are you sure?');" 
                      value="Delete" />
    </span>
  </div>
</g:form>

図 4 は、上記の結果作成されたフォームです。

図4. Edit Airport フォーム
図4. Edit Airport フォーム

Update ボタンをクリックすると、フォームの値が update クロージャーに送信されます。デフォルト・コードには、サービス呼び出しとハッシュアップのマージを追加します (リスト 10 を参照)。

リスト 10. update クロージャーの変更
def update = {
    def airport = Airport.get( params.id )
    if(airport) {
        def results = geocoderService.geocodeAirport(params.iata)    
        airport.properties = params + results
        if(!airport.hasErrors() && airport.save()) {
            flash.message = "Airport ${params.id} updated"
            redirect(action:show,id:airport.id)
        }
        else {
            render(view:'edit',model:[airport:airport])
        }
    }
    else {
        flash.message = "Airport not found with id ${params.id}"
        redirect(action:edit,id:params.id)
    }
}

これで、Google Maps と同じようにジオコーディングをアプリケーションにシームレスに統合することができました。ここで、アプリケーションのなかで住所をキャプチャーするすべての場所、つまり顧客、従業員、地理的に分散したオフィス、倉庫、小売店の場所などについて考えてみてください。緯度と経度の座標を保存するためのフィールドを 2、3 追加してジオコーディング・サービスに統合するだけで、オブジェクトを表示する簡単なマップの準備ができることになります。それがまさに、次に行おうとしている作業です。


Google Maps

Web マッピングの使いやすさについては、Google Maps がその基準となることに大半の人が同意しています。しかし、この使いやすさは Google Maps を自分の Web ページに組み込む際にも当てはまることに気づいている人はほとんどいません。この演習で最も難しい部分はデータ・ポイントの緯度と経度の座標を取得することですが、この問題はすでに解決済みです。

Google Maps を Grails アプリケーションに組み込むために最初に必要となる作業は、無料の API キーを入手することです。その使用条件は、サインアップ・ページに詳しく説明されていますが、原則として、Google がその API を無料で提供するのはアプリケーションも同じく無料である場合に限られます。つまり、Google Maps を使用したアプリケーションをパスワードで保護することや、アプリケーションへのアクセスを有料にしたり、ファイアウォールで保護したりすることはできません (厚かましい宣伝ですが、私の書著『GIS for Web Developers』では無料のデータとオープンソース・ソフトウェアを使って Google Maps のようなアプリケーションをビルドする手順をステップバイステップで説明しています (「参考文献」を参照)。この手順に従ってアプリケーションをビルドすれば、Google がその API に設定しているほんのわずかな使用制限からも解放されます)。

API キーは、特定の URL およびディレクトリーに結び付けられます。フォームに http://localhost:9090/trip と入力して、Generate API Key ボタンをクリックすると、確認ページに新しく生成された API キー、キーに関連付けられた URL、そして俗に言う「マッピングを成功に導くための」サンプル Web ページが表示されます。

このサンプル・ページを Grails アプリケーションに組み込むため、grails-app/views/airport ディレクトリーに map.gsp という名前のファイルを作成し、Google のサンプル・ページをこのファイルにコピーしてください。 リスト 11 に、map.gsp の内容を記載します。

リスト 11. 単純な Google Maps Web ページ
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <title>Google Maps JavaScript API Example</title>
    <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABCDE"
            type="text/javascript"></script>
    <script type="text/javascript">
    //<![CDATA[
    function load() {
      if (GBrowserIsCompatible()) {
        var map = new GMap2(document.getElementById("map"));
        map.setCenter(new GLatLng(37.4419, -122.1419), 13);
      }
    }
    //]]>
    </script>
  </head>
  <body onload="load()" onunload="GUnload()">
    <div id="map" style="width: 500px; height: 300px"></div>
  </body>
</html>

API キーは、ページ先頭にあるスクリプト URL に組み込まれていることに注目してください。load メソッドでは、新しい GMap2 オブジェクトをインスタンス化しています。これが、ページの終わりにある、ID が map に設定された <div /> に現れるマップです。マップを大きくしたい場合は、CSS (Cascading Style Sheets) の style 属性で幅と高さを調整します。現在、マップはカリフォルニア州パロアルトが中心に設定されており、ズーム・レベルは 13 に設定されています (レベル 0 では最大限ズームアウトされます。この数字が増えるにつれ、ストリート・レベルの表示に近づいていきます)。これらの値はこの後すぐに調整することにして、とりあえずは空の map クロージャーを AirlineController に追加してください (リスト12 を参照)。

リスト 12. map クロージャーの追加
class AirportController {
  def map = {}

  ...
}

ここで、ブラウザーで http://localhost:9090/trip/airport/map にアクセスしてみてください。すると図 5 のように、組み込まれた Google Maps が表示されます。

図 5. 単純な Google Maps
単純な Google Maps

今度は map.gsp に戻って値を調整します (リスト13 を参照)。

リスト13. 基本マップの調整
  <script type="text/javascript">
  var usCenterPoint = new GLatLng(39.833333, -98.583333)
  var usZoom = 4

  function load() {
    if (GBrowserIsCompatible()) {
      var map = new GMap2(document.getElementById("map"))
      map.setCenter(usCenterPoint, usZoom)
      map.addControl(new GLargeMapControl());
      map.addControl(new GMapTypeControl());        
    }
  }
  </script>
</head>
<body onload="load()" onunload="GUnload()">
  <div id="map" style="width: 800px; height: 400px"></div>
</body>

800 x 400 ピクセルは、米国全体を表示するのにちょうどよいサイズです。リスト13 では中心点とズーム・レベルを調整して、マップ全体が見られるようにしています。追加できるマップ・コントロールは種類も数も豊富です。リスト 13 の GLargeMapControlGMapTypeControl の場合には、通常のコントロールをマップの左端および右上端に沿って表示します。いろいろ試してみて、その都度ブラウザーの更新ボタンをクリックして変更がどのように適用されるのかを確認してください。図 6 には、リスト 13 で行った調整が反映されています。

図 6. 調整後のマップ
調整後のマップ

基本のマップは用意できたので、次はマーカーを追加していきます。ここで言うマーカーとは、空港それぞれのプッシュ・ピンのことです。このプロセスを自動化する前に、リスト 14 にマーカーを 1 つ手動で追加する方法を示します。

リスト 14. マップへのマーカーの追加
<script type="text/javascript">
var usCenterPoint = new GLatLng(39.833333, -98.583333)
var usZoom = 4

function load() {
  if (GBrowserIsCompatible()) {
    var map = new GMap2(document.getElementById("map"))
    map.setCenter(usCenterPoint, usZoom)
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl()); 
    
    var marker = new GMarker(new GLatLng(39.8583188, -104.6674674))
    marker.bindInfoWindowHtml("DEN<br/>Denver International Airport")
      map.addOverlay(marker)                       
  }
}
</script>

GMarker コンストラクターは GLatLng の地点を使用します。bindInfoWindowHtml メソッドが提供するのは、ユーザーがマーカーをクリックしたときに Info ウィンドウに表示するための HTML スニペットです。リスト14 では最後に、addOverlay メソッドを使ってマーカーをマップに追加しています。

図7 は、マーカーが追加された状態のマップです。

図 7. マーカーが追加されたマップ
マーカーが追加されたマップ

ある地点にマーカーを追加する方法がわかったところで、今度はすべての地点がデータベースから自動的に追加されるようにします。それにはちょっとした 2 つの変更が必要です。最初の変更では、AirportControllermap クロージャーが Airport のリストを返すようにします (リスト 15 を参照)。

リスト 15. Airport リストのリターン
def map = {
  [airportList: Airport.list()]
}

次に必要なのは、Airport のリストを繰り返し処理し、それぞれのマーカーを作成することです。この連載では以前、<g:each> タグを使用して HTML の表に行を追加する方法を説明しました。リスト 16 ではこの方法を使用して、マップ上 Airport を表示するために必要な JavaScript の行を作成します。

リスト 16. マップに対してのマーカーの動的な追加
<script type="text/javascript">
var usCenterPoint = new GLatLng(39.833333, -98.583333)
var usZoom = 4

function load() {
  if (GBrowserIsCompatible()) {
    var map = new GMap2(document.getElementById("map"))
    map.setCenter(usCenterPoint, usZoom)
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl()); 

      <g:each in="${airportList}" status="i" var="airport">
         var point${airport.id} = new GLatLng(${airport.lat}, ${airport.lng})
      var marker${airport.id} = new GMarker(point${airport.id})
      marker${airport.id}.bindInfoWindowHtml("${airport.iata}<br/>${airport.name}")
         map.addOverlay(marker${airport.id})
      </g:each>
  }
}
</script>

図 8 に、すべてのマーカーが自動的に追加された状態のマップを示します。

図 8. 複数のマーカーが追加されたマップ
複数のマーカーが追加されたマップ

この慌しい Google Maps API の説明では、この API で可能なことに関して表面をかじったに過ぎません。例えばマーカーがクリックされたときに Ajax 呼び出しで JSON (JavaScript Object Notation) データを返すイベンド・モデルを開発したり、GPolylines を使ってマップ上に旅行の各区間を描画するなど、可能性には限りがありません。詳しくは、Google の優れたオンライン・マニュアルを参照してください。私が書いた PDF 形式の本『Google Maps API』(「参考文献」を参照) でも初心者向けにわかりやすく説明しているので参考にしてください。


まとめ

マップを Grails アプリケーションに追加するために必要な作業は、以下の 3 つの部分で構成されます。

まず初めに必要となるのは、データのジオコーディングです。人間が読み取れる形で表した場所を緯度と経度による地点に変換する無料のジオコーダーは豊富に出回っています。番地、都市、郡、州、国、ZIP コード、電話番号、IP アドレス、さらには空港のIATA コードに至るまで、ほとんどすべてのものをジオコーディングすることができます。

この作業にふさわしいジオコーダーが見つかったら、次にリモートWeb サービス呼び出しを再利用可能なメソッド呼び出しにカプセル化する Grails サービスを作成してください。サービスの対象となるのは、単一のドメイン・オブジェクトでの単純な CRUD 操作では対応しきれないメソッドです。サービスは、デフォルトでは URL に関連付けられませんが、コントローラー内にクロージャーを作成すれば、簡単に Web でアドレス指定可能なサービスにすることができます。

最後に、Google Maps のような無料の Web マッピングAPI を利用してマップ上に緯度と経度による地点を描画します。通常、これらの無料サービスは、そのサービスを使用するアプリケーションも同じく無料でアクセス可能であることを条件とします。マップを非公開のままにしたいという場合には、OpenLayers などのオープンソースの API を検討してください。Goodle Maps の使用制限に縛られることなく、同じようなユーザー・エクスペリエンスを実現できます (「参考文献」を参照)。独自のマッピング・レイヤーが必要になることもありますが、アプリケーション全体を自分専用のサーバーだけでホストできるはずです。

次回の記事では、Grails アプリケーションを携帯電話で使いやすいアプリケーションにする方法について説明します。iPhone のディスプレイに合わせて表示を最適化する方法を学んでください。また、電話で SMS メッセージとして表示される E メールによって Grails の情報を送信する方法も紹介します。それまで、Grails を楽しみながらマスターしてください。

参考文献

学ぶために

  • 連載「Grails をマスターする」: この連載の他の記事を読んで、Grails とこのフレームワークを使って実現可能なすべての内容をよく理解してください。
  • Grails: Grails の Web サイトにアクセスしてください。
  • Grails Framework Reference Documentation: Grails を使い始める方法から、Grails フレームワークでWeb アプリケーションを構築する方法まで説明しています。
  • GeoNames: GeoNames ジオコーディング・サービスは、REST (Representational State Transfer) をベースとしたサービスです。
  • Google Maps API: Web ページに Google Maps を組み込んでください。
  • Yahoo! Maps API: Web アプリケーションおよびデスクトップ・アプリケーションに Yahoo! Maps を組み込んでください。
  • Geocoding: ジオコーディングに関するウィキペディアのエントリーです。
  • GIS for Web Developers』(Scott Davis 著、Pragmatic Programmers、2007年): この Scott Davis の著書では、GIS (Geographic Information System) について分かりやすく説明し、実用的な使用例を紹介しています。
  • The Google Maps API』(Scott Davis 著、Pragmatic Programmers、2006年): マップを描画する方法、アノテーションとルートを追加する方法、そしてデータをジオコーディングする方法を学んでください。
  • OpenLayers API: OpenLayers は、ほとんどの Web ブラウザーでマップ・データを表示するための純粋な JavaScript ライブラリーです。
  • Groovy Recipes』(Scott Davis 著、Pragmatic Programmers、2007年): Scott Davis の最新の著作で Groovy と Grails の詳細を学んでください。
  • 連載「実用的な Groovy」: developerWorks のこの連載記事では、実用的な Groovy の使用方法を探り、それぞれの方法をいつ、どんな場合に適用するかを解説しています。
  • Groovy: Groovy の Web サイトで、このプロジェクトの詳細を学んでください。
  • AboutGroovy.com: Groovy に関する最新のニュースと記事へのリンクが記載されています。
  • テクノロジー・ブックストア: この記事で紹介した技術やその他の技術に関する本を参照してください。
  • developerWorks Java technology ゾーン: Java プログラミングのあらゆる側面を網羅した記事が豊富に用意されています。

製品や技術を入手するために

  • Grails: Grails の最新リリースをダウンロードしてください。
  • OpenLayers: OpenLayers をダウンロードしてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=314157
ArticleTitle=Grails をマスターする: Grails サービスと Google Maps
publish-date=05202008