Asial Blog

Recruit! Asialで一緒に働きませんか?

Google MapsとMySQLの連携

カテゴリ :
バックエンド(プログラミング)
タグ :
Tech
GoogleMap
MySQL
こんにちは、アシアルの中川です。
Google Mapsに表示する情報をMySQLのgeometry型で扱う方法を試してみました。

Google Mapsのある地点にマーカーを表示したりすることがある場合、
緯度・経度の情報で地点に表示させることができますが、
データベースにこの情報を保存する場合、lat(float), lng(float)とそれぞれカラムに保存する方法もあるのですが、この方法では各データの位置の関係が非常に扱いにくくなります。
Google Mapsで表示されている範囲内のデータのみ取得したい場合や、
ある地点から近い順に取得する場合などに面倒なことになります。

そこで、MySQL4.1以降で用意されている空間情報を扱う機能の「geometry」型を使用します。
MySQL 4.1 リファレンスマニュアル :: 10 MySQL における空間情報の機能(日本語)」
MySQL :: MySQL 5.0 Reference Manual :: 11.12 Spatial Extensions(英語)」
MySQL :: MySQL 5.1 リファレンスマニュアル :: 16 Spatial Extensions(英語)」

※今回紹介する方法は、「鉄道模型のコミュニティ『トレイン・トレイン』」にて
先日リリースしたコンテンツ「鉄マップ」にも使用してみました。

テーブルの作成ですが、空間情報を扱うカラムの型を「geometry」と指定します。
  1. CREATE TABLE `spot` (
  2.   `id` int(10) unsigned NOT NULL auto_increment,
  3.   `name` varchar(255) NOT NULL,
  4.   `latlng` geometry NOT NULL,
  5.   `zoom` tinyint(2) unsigned default NULL,
  6.   PRIMARY KEY  (`id`),
  7.   SPATIAL KEY `spot_latlng_index` (`latlng`)
  8. ) ENGINE=MyISAM;

空間情報用のカラムにインデックスを作成するためには、「InnoDB」でなく「MyISAM」を使用し、
「SPATIAL」キーワードを使いインデックスを作成します。
また、対象のカラムが「NOT NULL」宣言がされている必要があるようです。

これで、準備ができました。次にデータを登録してみましょう。

通常のINSERT文の発行で行えるのですが、geometry型のカラムにデータを入れるためには、変換用に用意されている関数「GeomFromText」を使用します。
  1. mysql> INSERT INTO `spot` (`name`, `latlng`, `zoom`) VALUES ('アシアル株式会社', GeomFromText('POINT(139.762522 35.706752)'), 19);
  2. mysql> SELECT * FROM spot;
  3. +----+--------------------------+---------------------------+------+
  4. | id | name                     | latlng                    | zoom | +----+--------------------------+---------------------------+------+
  5. |  1 | アシアル株式会社         |        @`xa@              |   19 |
  6. +----+--------------------------+---------------------------+------+
上記にようにそのまま、取得しようとすると、geometry型がおかしな状態で表示されると思います。

緯度経度を取り出すときには関数(X, Y, AsTextなど)を使用し、以下のように変換して取得することができます。
  1. mysql> SELECT id, name, X(latlng) as lng, Y(latlng) as lat, zoom, AsText(latlng) as geomtext FROM spot;
  2. +----+--------------------------+------------+-----------+------+-----------------------------+
  3. | id | name                     | lng        | lat       | zoom | geomtext                    |
  4. +----+--------------------------+------------+-----------+------+-----------------------------+
  5. |  1 | アシアル株式会社         | 139.762522 | 35.706752 |   19 | POINT(139.762522 35.706752) | 
  6. +----+--------------------------+------------+-----------+------+-----------------------------+

データベースの準備ができたところで、次はGoogle Maps側です。

  1. var lat = 35.706752;
  2. var lng = 139.762522;
  3. var zoom = 19;
  4.  
  5. //マップを作成し、中央を指定位置にセットする。
  6. var map = new GMap2(document.getElementById("map-canvas"));
  7. map.setCenter(new GLatLng(lat, lng), zoom); 
  8. map.addControl(new GLargeMapControl());
  9. map.addControl(new GMapTypeControl());
  10. map.addMapType(G_SATELLITE_3D_MAP);
  11.  
  12. //指定位置にマーカーをセットする。
  13. var marker = new GMarker(new GLatLng(lat, lng));
  14. map.addOverlay(m);

Google Mapsで表示しているエリアの情報を以下のように取得できます。

  1. var options = {};
  2.  
  3. //センターとズーム情報取得
  4. var center = map.getCenter();
  5. options['lng'] = center.lng();
  6. options['lat'] = center.lat();
  7. options['zoom'] = map.getZoom();
  8.  
  9. //表示範囲情報
  10. var latlngbounds = map.getBounds();
  11. var sw = latlngbounds.getSouthWest();
  12. var ne = latlngbounds.getNorthEast();
  13. options['bounds[swlat]'] = sw.lat(); //南西の緯度
  14. options['bounds[swlng]'] = sw.lng(); //南西の経度
  15. options['bounds[nelat]'] = ne.lat(); //北東の緯度
  16. options['bounds[nelng]'] = ne.lng(); //北東の経度

JavaScriptで取得したデータをAjaxなどでサーバに投げてやります。
取得するタイミングは以下のようにイベント発生のタイミング等指定できます。

  1. GEvent.addListener(map, 'dragend', function(){
  2.   //マップのドラッグ移動終了後
  3.   // ここで位置・範囲取得やajax等の処理をする
  4. });
  5.  
  6. GEvent.addListener(map, 'zoomend', function(){
  7.   //マップのズーム変更終了後
  8.   // ここで位置・範囲取得やajax等の処理をする
  9. });
※詳細につきましては、「Google Maps APIリファレンス」を参照してください。

以上のようにGoogle MapsなどからAjaxでサーバ側に送信した地点や、範囲などの情報を使う際に、MySQLの「geometry」型の威力が発揮されます。

Ajax送信やサーバ側の受信のコードはここでは、触れませんが以下のSQLと組み合わせれば簡単に実装できるでしょう。

※サンプルデータは以下を使用
  1. mysql> SELECT id, name, X(latlng) as lng, Y(latlng) as lat, zoom, AsText(latlng) as geomtext FROM spot;
  2. +----+--------------------------+------------+-----------+------+-----------------------------+
  3. | id | name                     | lng        | lat       | zoom | geomtext                    |
  4. +----+--------------------------+------------+-----------+------+-----------------------------+
  5. |  1 | アシアル株式会社         | 139.762522 | 35.706752 |   19 | POINT(139.762522 35.706752) | 
  6. |  2 | 東京ドーム               | 139.752016 | 35.705567 |   18 | POINT(139.752016 35.705567) | 
  7. |  3 | 東京タワー               | 139.745423 | 35.658582 |   18 | POINT(139.745423 35.658582) | 
  8. |  4 | 大阪駅                   |     135.29 |     34.42 |   16 | POINT(135.29 34.42)         | 
  9. |  5 | 名古屋                   |     136.54 |     35.11 |   16 | POINT(136.54 35.11)         | 
  10. |  6 | 那覇市                   |     127.41 |     26.13 |   16 | POINT(127.41 26.13)         | 
  11. +----+--------------------------+------------+-----------+------+-----------------------------+
  12. 6 rows in set (0.00 sec)

■2点間の距離の近い順に5件取得する。(ここではアシアルの緯度経度から近い順)
  1. mysql> SELECT 
  2.     -> id, name, X(latlng) as lng, Y(latlng) as lat, zoom, 
  3.     -> GLength(GeomFromText(CONCAT('LineString(139.762522 35.706752,', X(latlng), ' ', Y(latlng),')'))) AS len 
  4.     -> FROM spot ORDER BY len;
  5. +----+--------------------------+------------+-----------+------+-------------------+
  6. | id | name                     | lng        | lat       | zoom | len               |
  7. +----+--------------------------+------------+-----------+------+-------------------+
  8. |  1 | アシアル株式会社         | 139.762522 | 35.706752 |   19 |                 0 | 
  9. |  2 | 東京ドーム               | 139.752016 | 35.705567 |   18 | 0.010572618455228 | 
  10. |  3 | 東京タワー               | 139.745423 | 35.658582 |   18 | 0.051114818800422 | 
  11. |  5 | 名古屋                   |     136.54 |     35.11 |   16 |   3.2773100234778 | 
  12. |  4 | 大阪駅                   |     135.29 |     34.42 |   16 |   4.6539428176534 | 
  13. |  6 | 那覇市                   |     127.41 |     26.13 |   16 |    15.63006649474 | 
  14. +----+--------------------------+------------+-----------+------+-------------------+
  15. 6 rows in set (0.00 sec)

■指定した範囲内のデータを取得する。(ここでは東京周辺の範囲のみ)
  1. mysql> SELECT id, name, X(latlng) as lng, Y(latlng) as lat, zoom 
  2.     -> FROM spot 
  3.     -> WHERE MBRContains(GeomFromText('LineString(140.05508422851562 36.00134056648952, 139.20089721679688 35.55345722493522)'), latlng);
  4. +----+--------------------------+------------+-----------+------+
  5. | id | name                     | lng        | lat       | zoom |
  6. +----+--------------------------+------------+-----------+------+
  7. |  1 | アシアル株式会社         | 139.762522 | 35.706752 |   19 | 
  8. |  2 | 東京ドーム               | 139.752016 | 35.705567 |   18 | 
  9. |  3 | 東京タワー               | 139.745423 | 35.658582 |   18 | 
  10. +----+--------------------------+------------+-----------+------+
  11. 3 rows in set (0.00 sec)

このように、通常のカラム型で保存した場合と違い、位置情報を有効に使用することができます。
まだ、2つのPOINT間の最短距離を返してくれる、Distanceなどの関数は使えないようですが、
マニュアルを見る限りでは、将来的にはサポートされるようです。

パフォーマンス等については、まだ私のほうでは未検証ですが、
位置情報を扱うには非常に簡単に扱えますので、ぜひ試してみてはいかがでしょう。

[補足]PostgreSQLでも位置情報を扱うことができ、PostGISやPostLBSなどのさらに便利な感じのものもあります。