GWTとGAEとAndroidとGooglMapsの連携

AndroidGPSを使ったアプリとAndroidの位置情報をデータストアに保存しGoogleMapに表示するWebアプリを作成する

今回はサーバ側を作成します。
サーバはGAE/Jを使います
サーバ側は二つのサーブレットを用意します


■プロジェクト構成

  • sample.googlemap.server
    • GreetingServiceImpl
    • PMF
    • GPSPoint
  • sample.googlemap.myservlet
    • MyServlet
  • sample.googlemap.client
    • GwtSampleGoogleMap
    • GreetingService
    • GreetingServiceAsync
    • GPSBeans

■作成手順
前回作成したGoogleMapsアプリに以下の修正をします
概ね以下の2つの処理に分けて作成します
サーバ側:データ保存

  • サーブレットクラスを追加
    • 位置情報保存サーブレットの追加
    • 新規入力フォーム作成
    • データストア保存処理
    • データストアに保存するデータクラスの作成

サーバ側:地図出力

  • 座標テキストボックスを削除
  • データストアより最新の位置情報を取得する
  • マップ表示

サーバ側プログラム概要

GPSデータを保存するサーブレットを追加します

■アプリ概要

  • 入力フォームより、緯度、経度を入力する
  • 送信ボタンでサーブレットにリクエストを投げる
  • 受け取った座標情報をデータストアに格納する
  • 完了画面を表示する

クラス名:MyServlet
パッケージ名:sample.googlemap.myservlet

サーバ側:位置情報保存サーブレット

■作成・変更クラス

  • PMFクラスの作成:PersistenceManagerのヘルパークラス的なもの
    • パッケージ名:sample.googlemap.server
    • クラス名:PMF
  • データストアに保存するデータクラスの作成
    • パッケージ名:sample.googlemap.myservlet
    • クラス名:GpsPoint
  • サーブレットクラスの作成
    • パッケージ名:sample.googlemap.myservlet
    • クラス名:MyServlet
  • web.xmlの修正
  • inex.htmlの作成:座標入力フォーム

PMF

public class PMF {

    private static final PersistenceManagerFactory pmfInstance = JDOHelper
        .getPersistenceManagerFactory("transactions-optional");

    private PMF() {
    }

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

■GpsPoint

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class GpsPoint implements IsSerializable{
	 @PrimaryKey
	    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	    private Long id;
	  
	    @Persistent
	    private double x;

	    @Persistent
	    private double y;

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public double getX() {
			return x;
		}

		public void setX(double x) {
			this.x = x;
		}

		public double getY() {
			return y;
		}

		public void setY(double y) {
			this.y = y;
		}

		public GpsPoint( double x, double y) {
			this.x = x;
			this.y = y;
		}

		public GpsPoint() {
		}

		@Override
		public String toString() {
			return "id" + this.id + "x:" + this.x + " y:" + this.y;
		}
}

■MyServlet
リクエストより座標情報を取得するdoGetメソッドの実装

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		double point_x, point_y;
		
		//リクエスト情報から座標を取得
		try{
			point_x = Double.parseDouble(request.getParameter("point_x"));
			point_y = Double.parseDouble(request.getParameter("point_y"));
			store(point_x, point_y);
		}catch(NumberFormatException e){
			return ;
		}

		//レスポンス情報を作成する
		response.setContentType("text/html; charset=\"utf-8\"");

		PrintWriter out = response.getWriter();
		out.write("<html>");
		out.write("<head><title>AndroindGPS</title></head>");
		out.write("<body>");
		out.write("<h2> x:" + point_x + " y:" + point_y + "</h2>");
		out.write("</body>");
		out.write("</html>");	
	}

データ保存をするstoreメソッドの実装
>|java|
	public void store(double x, double y) {
        GpsPoint gpsPoint = new GpsPoint(x, y);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(gpsPoint);
        } finally {
            pm.close();
        }
    }

■web.xml
xmlサーブレットを追加する

  <servlet>
    <description></description>
    <display-name>MyServlet</display-name>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>sample.googlemap.myservlet.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>
||

<span style="font-weight:bold;">■index.html</span>
入力フォームを作成
>|html|
<form action="MyServlet" method="GET" >
	<div>AndroidGPS:
		x:<input type="text" name="point_x" size=6 value="35.69964" />
		y:<input type="text" name="point_y" size=6 value="139.77089" />
	</div>
	<input type="submit" value="送信" />
	<input type="reset" value="取り消し" />
</form>

ここまででひとまず実行する
プロジェクト右クリック→Run As→Web Application→index.html

実行結果


送信ボタンをクリック

サーバ側:Map表示サーブレット

データストア内のデータを読み込み、地図を表示する
座標データはGpsBeansを使ってやり取りする。

■作成・変更手順
対象パッケージ:sample.googlemap.client

  • GwtSampleGoogleMap.html
    • ID:error_messageの追加:エラーメッセージ表示用
  • Beansの作成:データストア保存データクラスBeans
    • クラス名:GpsBeans
  • サービスインタフェースの引数・戻り値をBeansにする
    • GreetingService
    • GreetingServiceAsync
  • GreetingServiceImplの修正
    • 非同期通信Beans対応
    • データストアからデータの読み込み処理
  • GwtSampleGoogleMap
    • テキストボックス削除
    • Beans対応

■GwtSampleGoogleMap.html
エラーメッセージ表示領域の追加

    <table align="center">
      <tr>
        <td id="error_message"></td>
      </tr>
      <tr>
        <td id="dockpanel"></td>
      </tr>
    </table>

■GpsBeans
データクラスを非同期通信するため、同じ構造のBeansクラスをわざわざ作成。
※GPSPointクラスをそのまま非同期通信しようとしたがなぜかエラーがおきるので同じ構成のBeansクラスを作成した。
メンバ変数、getter/setter と コンストラクタを追加

	    private Long id;
	  
	    private double x;

	    private double y;

■GreetingService

  • 引数なし
  • 戻り値:List
List<GpsBeans> greetServer();

■GreetingServiceAsync

  • 引数:AsyncCallback>
  • 戻り値なし
	void greetServer(AsyncCallback<List<GpsBeans>> callback);

■GreetingServiceImpl
greetServerメソッドの実装
データストアからデータを取り出し、Listに格納して戻す

	@SuppressWarnings("unchecked")
	@Override
	public List<GpsBeans> greetServer() {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        String query = "select from " + GpsPoint.class.getName();
        List<GpsPoint> list = (List<GpsPoint>) pm.newQuery(query).execute();
        List<GpsBeans> result = new ArrayList<GpsBeans>();
        for(GpsPoint point : list){
        	result.add(new GpsBeans(point.getId(), point.getX(), point.getY()));
        }
        
        return result;
	}

■GwtSampleGoogleMap
GreetingServiceAsync変数の追加
HTML型 error_message変数の追加:サーバエラーなどのエラーメッセージ表示領域
初期化し、RootPanelに追加する

private HTML error_message;
private final GreetingServiceAsync greetingService = GWT.create(GreetingService.class);

public void onModuleLoad() {
		error_message = new HTML();
		〜略〜
RootPanel.get("error_message").add(error_message);
}

OnClickイベントの非同期通信処理追加
結果データより、最新のものの座標を取得し地図表示

			@Override
			public void onClick(ClickEvent event) {

				AsyncCallback<List<GpsBeans>> callback = new AsyncCallback<List<GpsBeans>>(){
					@Override
					public void onFailure(Throwable caught) {
						error_message.setHTML(caught.getMessage());
					}

					@Override
					public void onSuccess(List<GpsBeans> result) {
						//debug
						StringBuilder str = new StringBuilder();
						if( result.size() == 0){
							str.append("nodata");
						}
						for(GpsBeans gps: result){
							str.append(gps.toString());
							str.append("<br>");
						}
						
						if( result.size() != 0){
							GpsBeans gps_data = result.get(result.size() - 1);
							setMap(gps_data.getX(), gps_data.getY());
						}
						error_message.setHTML(str.toString());
					}
				};
				greetingService.greetServer(callback);
			}

マップ表示をルーチン化

	public void setMap(double x, double y){
		LatLng here = LatLng.newInstance(x, y);
		
		//マップサイズ
		map.setSize("500px", "300px");
	    
	    // ズームコントローラ
	    map.addControl(new LargeMapControl());
	    map.setScrollWheelZoomEnabled(true);
	    
	    // マーカー
	    map.addOverlay(new Marker(here));

	    //真ん中と縮尺
	    map.setCenter(here, 13);
		panelMap.add(map, DockPanel.CENTER);
		
	}

実行結果

入力フォームより、座標の記入

GwtSampleGoogleMap.htmlを表示

ボタンクリックするとindexで指定した座標を表示する

ソース

■GpsBeans.java

package sample.googlemap.client;

import com.google.gwt.user.client.rpc.IsSerializable;

public class GpsBeans implements IsSerializable {
	    private Long id;
	  
	    private double x;

	    private double y;

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public double getX() {
			return x;
		}

		public void setX(double x) {
			this.x = x;
		}

		public double getY() {
			return y;
		}

		public void setY(double y) {
			this.y = y;
		}

		public GpsBeans( double x, double y) {
			this.x = x;
			this.y = y;
		}

		public GpsBeans(Long id, double x, double y) {
			super();
			this.id = id;
			this.x = x;
			this.y = y;
		}

		public GpsBeans() {
		}

		@Override
		public String toString() {
			return "id" + this.id + "x:" + this.x + " y:" + this.y;
		}

}

■GreetingService

package sample.googlemap.client;

import java.util.List;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

/**
 * The client side stub for the RPC service.
 */
@RemoteServiceRelativePath("greet")
public interface GreetingService extends RemoteService {
	List<GpsBeans> greetServer();
}

■GreetingServiceAsync

package sample.googlemap.client;

import java.util.List;

import com.google.gwt.user.client.rpc.AsyncCallback;

/**
 * The async counterpart of <code>GreetingService</code>.
 */
public interface GreetingServiceAsync {
	void greetServer(AsyncCallback<List<GpsBeans>> callback);
}

■GwtSampleGoogleMap.java

package sample.googlemap.client;

import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class GwtSampleGoogleMap implements EntryPoint {

	private Button btMap;
	private DockPanel panelMap;
	private Panel panelForm;
	private MapWidget map;
	private HTML error_message;
	
	
	private final GreetingServiceAsync greetingService = GWT
	.create(GreetingService.class);
	
	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		error_message = new HTML();
		this.btMap = new Button("表示");
		this.panelMap = new DockPanel();
		this.panelForm = new HorizontalPanel();
		this.map = new MapWidget();
		this.panelForm.add(btMap);
		this.panelMap.add(panelForm, DockPanel.NORTH);
		
		this.btMap.addClickHandler(new ClickHandler(){

			@Override
			public void onClick(ClickEvent event) {

				AsyncCallback<List<GpsBeans>> callback = new AsyncCallback<List<GpsBeans>>(){
					@Override
					public void onFailure(Throwable caught) {
						error_message.setHTML(caught.getMessage());
					}

					@Override
					public void onSuccess(List<GpsBeans> result) {
						//debug
						StringBuilder str = new StringBuilder();
						if( result.size() == 0){
							str.append("nodata");
						}
						for(GpsBeans gps: result){
							str.append(gps.toString());
							str.append("<br>");
						}
						
						if( result.size() != 0){
							GpsBeans gps_data = result.get(result.size() - 1);
							setMap(gps_data.getX(), gps_data.getY());
						}
						error_message.setHTML(str.toString());
					}
				};
				greetingService.greetServer(callback);
			}
		});
		RootPanel.get("error_message").add(error_message);
		RootPanel.get("dockpanel").add(panelMap);
	}
	
	public void setMap(double x, double y){
		LatLng here = LatLng.newInstance(x, y);
		
		//マップサイズ
		map.setSize("500px", "300px");
	    
	    // ズームコントローラ
	    map.addControl(new LargeMapControl());
	    map.setScrollWheelZoomEnabled(true);
	    
	    // マーカー
	    map.addOverlay(new Marker(here));

	    //真ん中と縮尺
	    map.setCenter(here, 13);
		panelMap.add(map, DockPanel.CENTER);
		
	}
}

■MyServlet.java

package sample.googlemap.myservlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.jdo.PersistenceManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import sample.googlemap.server.GpsPoint;
import sample.googlemap.server.PMF;

/**
 * Servlet implementation class MyServlet
 */
public class MyServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public MyServlet() {
        super();
    }

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	}
	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		double point_x, point_y;
		
		//リクエスト情報から座標を取得
		try{
			point_x = Double.parseDouble(request.getParameter("point_x"));
			point_y = Double.parseDouble(request.getParameter("point_y"));
			store(point_x, point_y);
		}catch(NumberFormatException e){
			return ;
		}

		//レスポンス情報を作成する
		response.setContentType("text/html; charset=\"utf-8\"");

		PrintWriter out = response.getWriter();
		out.write("<html>");
		out.write("<head><title>AndroindGPS</title></head>");
		out.write("<body>");
		out.write("<h2> x:" + point_x + " y:" + point_y + "</h2>");
		out.write("</body>");
		out.write("</html>");	
	}

	public void store(double x, double y) {
        GpsPoint gpsPoint = new GpsPoint(x, y);
        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(gpsPoint);
        } finally {
            pm.close();
        }
    }
}

■GpsPoint.java

package sample.googlemap.server;

import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import com.google.gwt.user.client.rpc.IsSerializable;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class GpsPoint implements IsSerializable{
	 @PrimaryKey
	    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	    private Long id;
	  
	    @Persistent
	    private double x;

	    @Persistent
	    private double y;

		public Long getId() {
			return id;
		}

		public void setId(Long id) {
			this.id = id;
		}

		public double getX() {
			return x;
		}

		public void setX(double x) {
			this.x = x;
		}

		public double getY() {
			return y;
		}

		public void setY(double y) {
			this.y = y;
		}

		public GpsPoint( double x, double y) {
			this.x = x;
			this.y = y;
		}

		public GpsPoint() {
		}

		@Override
		public String toString() {
			return "id" + this.id + "x:" + this.x + " y:" + this.y;
		}
}

■GreetingServiceImpl.java

package sample.googlemap.server;

import java.util.ArrayList;
import java.util.List;

import javax.jdo.PersistenceManager;

import sample.googlemap.client.GpsBeans;
import sample.googlemap.client.GreetingService;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class GreetingServiceImpl extends RemoteServiceServlet implements
		GreetingService {

	@SuppressWarnings("unchecked")
	@Override
	public List<GpsBeans> greetServer() {
        PersistenceManager pm = PMF.get().getPersistenceManager();
        String query = "select from " + GpsPoint.class.getName();
        List<GpsPoint> list = (List<GpsPoint>) pm.newQuery(query).execute();
        List<GpsBeans> result = new ArrayList<GpsBeans>();
        for(GpsPoint point : list){
        	result.add(new GpsBeans(point.getId(), point.getX(), point.getY()));
        }
        
        return result;
	}

}

PMF.java

package sample.googlemap.server;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public class PMF {
	 private static final PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional");

	 private PMF() {
	 }
	
	 public static PersistenceManagerFactory get() {
	     return pmfInstance;
	 }
}


■GwtSampleGoogleMap.gwt.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.6.4//EN" "http://google-web-toolkit.googlecode.com/svn/tags/1.6.4/distro-source/core/src/gwt-module.dtd">
<module rename-to='gwtsamplegooglemap'>
  <!-- Inherit the core Web Toolkit stuff.                        -->
  <inherits name='com.google.gwt.user.User'/>

  <!-- Inherit the default GWT style sheet.  You can change       -->
  <!-- the theme of your GWT application by uncommenting          -->
  <!-- any one of the following lines.                            -->
  <inherits name='com.google.gwt.user.theme.standard.Standard'/>
  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

  <!-- Other module inherits                                      -->
  <inherits name='com.google.gwt.maps.GoogleMaps'/>
	<script src="http://maps.google.com/maps?gwt=1&amp;file=api&amp;v=2&amp;sensor=false" />
  <!-- Specify the app entry point class.                         -->
  <entry-point class='sample.googlemap.client.GwtSampleGoogleMap'/>
</module>

■web.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <!-- Default page to serve -->
  <welcome-file-list>
    <welcome-file>GwtSampleGoogleMap.html</welcome-file>
  </welcome-file-list>
  
  <!-- Servlets -->
  <servlet>
    <servlet-name>greetServlet</servlet-name>
    <servlet-class>sample.googlemap.server.GreetingServiceImpl</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>greetServlet</servlet-name>
    <url-pattern>/gwtsamplegooglemap/greet</url-pattern>
  </servlet-mapping>

  <servlet>
    <description></description>
    <display-name>MyServlet</display-name>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>sample.googlemap.myservlet.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>

</web-app>

■GwtSampleGoogleMap.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- The HTML 4.01 Transitional DOCTYPE declaration-->
<!-- above set at the top of the file will set     -->
<!-- the browser's rendering engine into           -->
<!-- "Quirks Mode". Replacing this declaration     -->
<!-- with a "Standards Mode" doctype is supported, -->
<!-- but may lead to some differences in layout.   -->

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <!--                                                               -->
    <!-- Consider inlining CSS to reduce the number of requested files -->
    <!--                                                               -->
    <link type="text/css" rel="stylesheet" href="GwtSampleGoogleMap.css">

    <!--                                           -->
    <!-- Any title is fine                         -->
    <!--                                           -->
    <title>Web Application Starter Project</title>
    
    <!--                                           -->
    <!-- This script loads your compiled module.   -->
    <!-- If you add any GWT meta tags, they must   -->
    <!-- be added before this line.                -->
    <!--                                           -->
    <script type="text/javascript" language="javascript" src="gwtsamplegooglemap/gwtsamplegooglemap.nocache.js"></script>
  </head>

  <!--                                           -->
  <!-- The body can have arbitrary html, or      -->
  <!-- you can leave the body empty if you want  -->
  <!-- to create a completely dynamic UI.        -->
  <!--                                           -->
  <body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

    <h1>GWT Google Map Sample</h1>

    <table align="center">
      <tr>
        <td id="error_message"></td>
      </tr>
      <tr>
        <td id="dockpanel"></td>
      </tr>
    </table>
  </body>
</html>

■index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>AndroidGPS</title>
</head>
<body>
<h1>AndroidGPS</h1>
<form action="MyServlet" method="GET" >
	<div>AndroidGPS:
		x:<input type="text" name="point_x" size=6 value="35.69964" />
		y:<input type="text" name="point_y" size=6 value="139.77089" />
	</div>
	<input type="submit" value="送信" />
	<input type="reset" value="取り消し" />
</form>
<br />
<br />
</body>
</html>||<