jQueryとJerseyを使ってGoogle Maps APIをいじる

前回作った無理矢理拡張Jerseyをサーバサイドで、クライアント側はjQueryを利用してGoogle Maps APIをちょっと弄ってみたいと思います。まずはDBに、マップに表示させるコンテンツとカテゴリを保持させます。

CREATE DATABASE mapsample DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;
USE mapsample;

CREATE TABLE map_categories (
	id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID'
	,name VARCHAR(255) NOT NULL COMMENT '名前'
	,order_by INT NOT NULL COMMENT '順序'
	,lock_version INT UNSIGNED NOT NULL COMMENT 'バージョン'
	,created_at DATETIME NOT NULL COMMENT '作成日時'
	,updated_at DATETIME NOT NULL COMMENT '更新日時'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='地図カテゴリー';

CREATE TABLE area_categories (
	id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID'
	,name VARCHAR(255) NOT NULL COMMENT '名前'
	,order_by INT NOT NULL COMMENT '順序'
	,lock_version INT UNSIGNED NOT NULL COMMENT 'バージョン'
	,created_at DATETIME NOT NULL COMMENT '作成日時'
	,updated_at DATETIME NOT NULL COMMENT '更新日時'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='エリアカテゴリー';

CREATE TABLE map_contents (
	id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID'
	,map_category_id INT UNSIGNED NOT NULL COMMENT '地図カテゴリーID'
	,area_category_id INT UNSIGNED NOT NULL COMMENT 'エリアカテゴリーID'
	,title VARCHAR(50) NOT NULL COMMENT 'タイトル'
	,lat DECIMAL(18,15) NOT NULL COMMENT '緯度'
	,lng DECIMAL(18,15) NOT NULL COMMENT '経度'
	,comment VARCHAR(500) COMMENT 'コメント'
	,order_by INT NOT NULL COMMENT '順序'
	,visible BOOLEAN NOT NULL COMMENT '表示フラグ'
	,lock_version INT UNSIGNED NOT NULL COMMENT 'バージョン'
	,created_at DATETIME NOT NULL COMMENT '作成日時'
	,updated_at DATETIME NOT NULL COMMENT '更新日時'
	,FOREIGN KEY (map_category_id) REFERENCES map_categories(id)
	,FOREIGN KEY (area_category_id) REFERENCES area_categories(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='地図コンテンツ';

カテゴリに緯度・経度を保持させ、この情報を元にGoogle Mapに表示させるマーカーを作成します。ちなみに緯度経度情報はGoogle Maps APIのGClientGeocoder#getLatLngメソッド等で取得できます。
次は、Google Mapを表示させるHTMLを作成

<!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" />
<link rel="stylesheet" href="css/map.css" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/util.js"></script>
<script type="text/javascript"
	src="http://maps.google.com/maps?file=api&amp;v=2&amp;key={ここにGoogle Maps APIのKEYを記入}"></script>
<script type="text/javascript" src="index.js"></script>
<title>Google Maps サンプル</title>
</head>
<body>
<h1>Google Maps サンプル</h1>
<div id="map"></div>
</body>
</html>

利用するJavaScriptファイルの読み込みと、マップを表示させるDIVの定義をしているだけです。マップの大きさはCSSファイルで定義しています。
次は、このHTMLの挙動を設定するJavaScriptファイル

$(function() {
	if (GBrowserIsCompatible()) {
		var map = new GMap2(document.getElementById("map"));
		map.setCenter(new GLatLng({地図中心の緯度を記入}, {地図中心の経度を記入}), 16);
		map.addControl(new GLargeMapControl());
		map.addControl(new GMapTypeControl());
		map.addControl(new GOverviewMapControl());
		
		$.getJSON(
			"resources/mapContents",
			null,
			function(data) {
			
				var addMarker = function(mc) {
					var latLng = new GLatLng(mc.lat, mc.lng);
					var marker = new GMarker(latLng);
					var html = "<h4>" + xmlEscape(mc.title) + "</h4>";
					if (mc.comment) {
						html += "<p>" + xmlEscape(mc.comment) + "</p>";
					}
					GEvent.addListener(marker, "click", function() {
						marker.openInfoWindowHtml(html);
					});
					map.addOverlay(marker);
				};
				for (var i = 0; i < data.length; i++) {
					addMarker(data[i]);
				}
			}
		);
		
	}
	$(window).unload(GUnload);
 });
 

xmlEscapeメソッドは、自作のXMLエスケープメソッドです。
HTMLファイルがロードされたときにAjaxでコンテンツ一覧を取得し、コンテンツの緯度・経度を使ってマーカーを地図に設定します。マーカーをクリックしたときには、タイトルとコメントが表示されるようにイベントを追加します。
最後に、このコンテンツデータを取得するJPAのEntityとJerseyのリソースクラスを作成

package mapsample.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Version;

import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

@Entity
public class Map_contents implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 838950060562483992L;

	@Id
	@GeneratedValue
	private Integer id;

	private String title;

	private BigDecimal lat;

	private BigDecimal lng;

	private int order_by;

	private String comment;

	@ManyToOne(fetch = FetchType.LAZY)
	private Area_categories area_category;

	@ManyToOne(fetch = FetchType.LAZY)
	private Map_categories map_category;

	private boolean visible;

	@Version
	private Integer lock_version;

	private Timestamp created_at;

	private Timestamp updated_at;

(setter、getter省略)
	
	public JSONObject getJson() throws JSONException {
		return new JSONObject()
			.put("id", id)
			.put("title", title)
			.put("lat", lat)
			.put("lng", lng)
			.put("order_by", order_by)
			.put("comment", comment)
			.put("visible", visible)
			.put("lock_version", lock_version)
			.put("created_at", created_at)
			.put("updated_at", updated_at);
	}


}
package mapsample.resource;

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

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.WebApplicationException;

import mapsample.entity.Map_contents;

import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;

@Path("/mapContents")
public class MapContentsRecource {
	
	@PersistenceContext(unitName = "mapsample")
	private EntityManager em;
	
	
	@SuppressWarnings("unchecked")
	@GET 
	@ProduceMime("application/json")
	public JSONArray getList() throws JSONException {
		
		List<Map_contents> list = em.createNamedQuery("Map_contents.getMapContents")
			.getResultList();
		
		List<JSONObject> jsons = new ArrayList<JSONObject>();
		for (Map_contents mc : list) {
			jsons.add(mc.getJson());
		}
		JSONArray array = new JSONArray(jsons);
		return array;
	}	

}

NamedQueryは単純にSELECTしているだけなので省略。Jerseyが利用しているJSONライブラリのjettisonは、どうもBeanからJSONに自動変換するような関数を用意してないみたいなので、Entity側で個別に対応しています。今回は関連Entityについての定義は省略しています。
jQuery側で定義していたURLをJAX-RSの@Pathに定義し、戻り値をJSONArrayにして@ProduceMineでapplication/jsonを定義すれば準備完了です。後は適当にデータを入れて、GlassFish上で実行してみると


こんな感じで画面が表示されます。簡単ですね。