地図を表示したい!ym4r_gmをちゃんと使いたい
前回の続き。目的地を中心に置いてマーカと吹き出しウィンドウを表示する最も基本的な地図は表示できるようになったが、未だGoogle Maps APIやym4r_gmの使い方はちゃんと理解できていない...。そんな状態で作業しながら考えたこと。
ym4r_gmのgmaps_api_keyを動的に設定する方法
- ym4r_gmではdevelopment、test、production環境ごとにAPIキーを設定できるようになっている。
- そして、デフォルトのgmaps_api_key.ymlを見ていると、気になる書き方がある。production環境の設定のところ。
# ---------- config/gmaps_api_key.yml ---------- #Fill here the Google Maps API keys for your application #In this sample: #For development and test, we have only one possible host (localhost:3000), so there is only a single key associated with the mode. #In production, the app can be accessed through 2 different hosts: thepochisuperstarmegashow.com and exmaple.com. There then needs a 2-key hash. If you deployed to one host, only the API key would be needed (as in development and test). development: ABQIAAAAzMUFFnT9uH0xq39J0Y4kbhTJQa0g3IQ9GZqIMmInSLzwtGDKaBR6j135zrztfTGVOm2QlWnkaidDIQ test: ABQIAAAAzMUFFnT9uH0xq39J0Y4kbhTJQa0g3IQ9GZqIMmInSLzwtGDKaBR6j135zrztfTGVOm2QlWnkaidDIQ production: thepochisuperstarmegashow.com: ABQIAAAAzMUFFnT9uH0Sfg76Y4kbhTJQa0g3IQ9GZqIMmInSLzwtGDmlRT6e90j135zat56yhJKQlWnkaidDIQ example.com: ABQIAAAAzMUFFnT9uH0Sfg98Y4kbhGFJQa0g3IQ9GZqIMmInSLrthJKGDmlRT98f4j135zat56yjRKQlWnkmod3TB
- コメントの英語の説明を読んでみると、どうやら、さらにホスト名ごとにAPIキーを複数設定しておいて、使い分けることができるようだ。やってみた。
<%# ---------- app/views/layouts/maps.html.erb ---------- %> ...(中略)... <%= GMap.header(:host => 'example.com') %> <%= @map.to_html %> ...(中略)...
- 上記のように指定すれば、example.com: に設定したAPIキーを利用してくれる。
- そして、Railsではrequest.hostでホスト名は簡単に取得できるので、以下のようにしておけばとっても便利。
<%# ---------- app/views/layouts/maps.html.erb ---------- %> ...(中略)... <%= GMap.header(:host => request.host) %> <%= @map.to_html %> ...(中略)...
- production環境に限らず、development、test環境でも同じように設定できる。
- localhost以外で開発している時にとても幸せを感じる。
いろいろなマーカーアイコンを利用する
- 地図は目的の場所と情報が表示されて、初めて立派な地図としての機能を果たす。
- その場所を指し示すのがマーカーなのだが、現在は地図中心を示すマーカーが一つだけ。そのマーカーはデフォルトで水滴が逆立ちした形状で、赤く、中心に黒い点があるアイコンになっている。
- 目的の場所が一カ所だけならこれで問題ないのだが、目的の場所が複数箇所の場合、マーカーアイコンも区別したくなる。
- Google Mapをお手本にすれば、マーカの中にA、B、C...とアルファベットが表示されている。それは以下のようにすれば利用できた。
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController def index @map = GMap.new("map_div") @map.center_zoom_init([37.4419, -122.1419], 13) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # マーカーAと吹き出しウィンドウを表示する icon = GIcon.new(:image => "http://www.google.com/mapfiles/markerA.png", :copy_base => GIcon::DEFAULT) @map.overlay_init(GMarker.new([37.4419, -122.1419], :title => "Hello", :info_window => "Info! Info!", :icon => icon)) end end
- 複数のマーカーを区別したい時は以下のようにしてみた。
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController POINTS = [ {:point => [35.678355, 139.715109], :name => "国立競技場"}, {:point => [35.67452 , 139.717083], :name => "神宮球場" }, {:point => [35.676472, 139.699316], :name => "明治神宮" } ] def index @map = GMap.new("map_div") @map.center_zoom_init([35.678982,139.710131], 14) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # マーカーA,B,Cと吹き出しウィンドウを表示する POINTS.each do |item| @letter = (@letter.succ rescue 'A') icon = GIcon.new(:image => "http://www.google.com/mapfiles/marker#{@letter}.png", :copy_base => GIcon::DEFAULT) marker = GMarker.new(item[:point], :title => "Hello #{@letter}", :info_window => item[:name], :icon => icon) @map.overlay_init(marker) end end end
- Google Mapで利用できそうなアイコンの一覧がこちらで紹介されていた。(素晴らしい!)
- オリジナル画像をアイコンにすることだって出来る。(以下、デフォルトアイコンをベースにした簡略的な方法)
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController def index @map = GMap.new("map_div") @map.center_zoom_init([37.4419, -122.1419], 13) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # オリジナルマーカーと吹き出しウィンドウを表示する icon = GIcon.new(:image => "../images/my_marker.png", :copy_base => GIcon::DEFAULT) @map.overlay_init(GMarker.new([37.4419, -122.1419], :title => "Hello", :info_window => "Info! Info!", :icon => icon)) end end
- オリジナルなアイコン画像public/images/my_marker.pngを準備した。(marcador_amarillo.png 20px-34pxをベースにさせて頂きました。感謝です!)
- デフォルトアイコンをベースにしているので、同じ画像サイズの20x34にした。
- アイコン画像のurlは、:image => "../images/my_marker.png"のように指定する。
- 何故、../imagesで始まるのか?
- Railsではhttp://localhost:3000/test.htmlとすると、public/test.htmlを表示しようとする。
- 現在表示中のページがhttp://localhost:3000/maps/index.html.erbで、http://localhost:3000/images/my_marker.pngを指定するには...
- この場合、一つ階層を遡って"../images/my_marker.png"とすれば良さそう。以下でも同等の結果になる。
:image => @template.image_path("my_marker.png") :image => "http://localhost:3000/images/my_marker.png"
-
- @template.image_pathで指定するのがベストかもしれない。
マーカーリストとマーカーを連動させる
- 地図の横にマーカーリストをリンク表示して、そのリンクをクリックしても吹き出しウィンドウを表示するようにしてみた。
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController POINTS = [ {:point => [35.678355, 139.715109], :name => "国立競技場"}, {:point => [35.67452 , 139.717083], :name => "神宮球場" }, {:point => [35.676472, 139.699316], :name => "明治神宮" } ] def index @map = GMap.new("map_div") @map.center_zoom_init([35.678982,139.710131], 14) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # マーカーリストと連動させて、マーカーA,B,Cと吹き出しウィンドウを表示する @map.record_global_init("var markers = [];") POINTS.each_with_index do |item, index| @letter = (@letter.succ rescue 'A') icon = GIcon.new(:image => "http://www.google.com/mapfiles/marker#{@letter}.png", :copy_base => GIcon::DEFAULT) marker = GMarker.new(item[:point], :title => "Hello #{@letter}", :info_window => item[:name], :icon => icon) @map.declare_init(marker, 'marker') @map.overlay_init(marker) @map.record_init("markers[#{index}] = marker;") end end end
<%# ---------- app/views/maps/index.html.erb ---------- %> <div style="float:right"> <ul> <% MapsController::POINTS.each_with_index do |item, index| %> <li><%= link_to_function "#{('A'[0] + index).chr}. #{item[:name]}", "GEvent.trigger(markers[#{index}], 'click')" %></li> <% end %> </ul> </div> <%= @map.div(:width => 500, :height => 300) %>
Rubyコードはjavascriptコードを生成するだけ
今まで、まるでRubyコードがGoogle Mapを制御しているような錯覚に陥っていたが、上記でマーカーリストのリンクを作成してみて、javascriptコードが生成されている事実を改めて理解する必要があった。Rubyコードはjavascriptコードを生成し、そのjavascriptコードがGoogle Mapを制御している。生成されたjavascriptコードは以下のようになっていた。
// ---------- app/controllers/maps_controller.rbで設定したjavascriptコード ---------- var markers = [];//<------[1] var map; window.onload = addCodeToFunction(window.onload,function() { if (GBrowserIsCompatible()) { map = new GMap2(document.getElementById("map_div")); map.setCenter(new GLatLng(35.678982,139.710131),14); var marker = addInfoWindowToMarker(new GMarker(new GLatLng(35.678355,139.715109),{icon : addOptionsToIcon(new GIcon(G_DEFAULT_ICON),{image : "http://www.google.com/mapfiles/markerA.png"}),title : "Hello A"}),"国立競技場",{});//<------[2] map.addOverlay(marker);//<------[3] markers[0] = marker;//<------[4] var marker = addInfoWindowToMarker(new GMarker(new GLatLng(35.67452,139.717083),{icon : addOptionsToIcon(new GIcon(G_DEFAULT_ICON),{image : "http://www.google.com/mapfiles/markerB.png"}),title : "Hello B"}),"神宮球場",{});//<------[2] map.addOverlay(marker);//<------[3] markers[1] = marker;//<------[4] var marker = addInfoWindowToMarker(new GMarker(new GLatLng(35.676472,139.699316),{icon : addOptionsToIcon(new GIcon(G_DEFAULT_ICON),{image : "http://www.google.com/mapfiles/markerC.png"}),title : "Hello C"}),"明治神宮",{});//<------[2] map.addOverlay(marker);//<------[3] markers[2] = marker;//<------[4] map.addControl(new GLargeMapControl()); map.addControl(new GMapTypeControl()); } });
- マーカーリストと連動させて、マーカーA,B,Cと吹き出しウィンドウを表示する部分に限定して確認してみると、以下のような対応関係になっていた。
No. | Rubyコード | 対応するjavascriptコード |
---|---|---|
[1] | @map.record_global_init("var markers = [];") | var markers = []; |
[2] | @letter = (@letter.succ rescue 'A') ...(中略)... @map.declare_init(marker, 'marker') |
var marker = addInfoWindowToMarker(...); |
[3] | @map.overlay_init(marker) | map.addOverlay(marker); |
[4] | @map.record_init("markers[#{index}] = marker;") | markers[0] = marker; |
- @map.record_global_initまたは@map.record_initは、引数の文字列をそのままjavascriptコードとして出力する。
- つまり、ym4r_gmまたはGoogle Maps APIのサポート外のことをやろうとしたら、@map.record_...メソッドに頼るしか無いのだ。
- ..._initで終わるメソッドは<%= @map.to_html %>で出力するjavascriptコードとして@mapオブジェクトに溜め込まれていく。
- ..._global_initで終わるメソッドとの違いは...
- @map.declare_init(marker, 'marker')も、理解しようとすると頭が混乱してくるメソッドだ。
- Rubyコードとしての変数markerには、直前の行でGMarkerオブジェクトが代入されている。
- そのGMarkerオブジェクトをjavascriptコードの変数markerに代入するコードを生成している。
- 分かりにくいので、@map.declare_initあり・無しの場合を比較してみると...
# @map.declare_initありの場合 marker = GMarker.new([37.4419, -122.1419], :title => "Hello", :info_window => "Info! Info!") @map.declare_init(marker, 'marker') @map.overlay_init(marker) # 一旦var markerで変数に代入してから、その変数markerをmap.addOverlayの引数に設定している => var marker = addInfoWindowToMarker(new GMarker(new GLatLng(35.678355,139.715109),{,title : "Hello"}),"Info! Info!",{}); => map.addOverlay(marker);
# @map.declare_init無しの場合 marker = GMarker.new([37.4419, -122.1419], :title => "Hello", :info_window => "Info! Info!") @map.overlay_init(marker) # map.addOverlayの引数に直接コードが代入される => map.addOverlay(addInfoWindowToMarker(new GMarker(new GLatLng(35.678355,139.715109),{title : "Hello"}),"Info! Info!",{}));
当初、ym4r_gmを全く理解できない状態だったが、上記の要点をイメージできるようになって、一気に理解が深まった。
- RubyはMapを制御しているんじゃない、Mapを制御するjavascriptコードを生成しているだけだ。
- init、global_initの違い。
- declare_initは何をしているのか?
吹き出しウィンドウの中身を設定
吹き出しウィンドウに画像や拡大縮小の操作リンクを表示するようにしてみた。
- :info_window => @template.balloon(item)として、吹き出しウィンドウの中身をヘルパメソッドでデザインした。
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController POINTS = [ {:index => 0, :point => [35.678355, 139.715109], :name => "国立競技場", :image_url => "http://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/National_Stadium_of_Japan_Kasumigaoka.jpg/800px-National_Stadium_of_Japan_Kasumigaoka.jpg"}, {:index => 1, :point => [35.67452 , 139.717083], :name => "神宮球場" , :image_url => "http://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Meiji_Jingu_Stadium-4.jpg/800px-Meiji_Jingu_Stadium-4.jpg"}, {:index => 2, :point => [35.676472, 139.699316], :name => "明治神宮" , :image_url => "http://upload.wikimedia.org/wikipedia/ja/5/53/Meiji-jingu_naihaiden.jpg"} ] def index @map = GMap.new("map_div") @map.center_zoom_init([35.678982,139.710131], 14) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # マーカーA,B,Cと画像や操作リンク付きの吹き出しウィンドウを表示する @map.record_global_init("var markers = [];\n") POINTS.each_with_index do |item, index| @letter = (@letter.succ rescue 'A') icon = GIcon.new(:image => "http://www.google.com/mapfiles/marker#{@letter}.png", :copy_base => GIcon::DEFAULT) marker = GMarker.new(item[:point], :title => "Hello #{@letter}", :info_window => @template.balloon(item), :icon => icon) @map.declare_init(marker, 'marker') @map.overlay_init(marker) @map.record_init("markers[#{index}] = marker;") end end end
- 吹き出しウィンドウの中身はballoonメソッドに定義した。
# ---------- app/helpers/maps_helper.rb ---------- module MapsHelper def balloon(item) link_to( image_tag(item[:image_url], :border => 1, :size => '80x80', :alt => '画像なし', :align => 'left'), item[:image_url], :target => '_blank') + item[:name] + content_tag(:div, "[" + link_to_function('+地図拡大', "zoom_in(#{item[:index]})") + "]" + "[" + link_to_function('−地図戻す', "zoom_out(#{item[:index]})") + "]", :style => 'clear:both' ) end end
- 地図拡大zoom_in、地図戻すzoom_outのjavascript関数を定義した。
// ---------- public/javascripts/application.js ---------- // Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults function zoom_in(i) { map.setZoom(17); map.panTo(markers[i].getPoint()); } function zoom_out(i) { map.setZoom(14); GEvent.trigger(markers[i], "click"); }
- 吹き出しウィンドウを表示するとこんな感じ。
中央に不動のマーカーを表示したい
地図をスクロールしても常に中央に位置するマーカーを表示したい。以下のようにやってみた。
- [1]で、地図が動くとmoveイベントが発生するので、drawCenterMarker関数を呼び出して中央マーカーの位置を修正する。
- [2]で、中央マーカーを作成して、初期表示する。
- [3]で、簡潔にするため中央マーカーを作成するプライベートメソッドcenter_markerを定義した。
# ---------- app/controllers/maps_controller.rb ---------- class MapsController < ApplicationController POINTS = [ {:index => 0, :point => [35.678355, 139.715109], :name => "国立競技場", :image_url => "http://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/National_Stadium_of_Japan_Kasumigaoka.jpg/800px-National_Stadium_of_Japan_Kasumigaoka.jpg"}, {:index => 1, :point => [35.67452 , 139.717083], :name => "神宮球場" , :image_url => "http://upload.wikimedia.org/wikipedia/commons/thumb/3/34/Meiji_Jingu_Stadium-4.jpg/800px-Meiji_Jingu_Stadium-4.jpg"}, {:index => 2, :point => [35.676472, 139.699316], :name => "明治神宮" , :image_url => "http://upload.wikimedia.org/wikipedia/ja/5/53/Meiji-jingu_naihaiden.jpg"} ] def index @map = GMap.new("map_div") @map.center_zoom_init([35.678982,139.710131], 14) # 地図をコントロールする部品を設定(拡大縮小スライダーとボタン、地図と航空写真の切替ボタン) @map.control_init(:large_map => true,:map_type => true) # マーカーA,B,Cと吹き出しウィンドウを表示する @map.record_global_init("var markers = [];") POINTS.each_with_index do |item, index| @letter = (@letter.succ rescue 'A') icon = GIcon.new(:image => "http://www.google.com/mapfiles/marker#{@letter}.png", :copy_base => GIcon::DEFAULT) marker = GMarker.new(item[:point], :title => "Hello #{@letter}", :info_window => @template.balloon(item), :icon => icon) @map.declare_init(marker, 'marker') @map.overlay_init(marker) @map.record_init("markers[#{index}] = marker;") end # 地図が移動すると発生するmoveイベントで、drawCenterMarkerを実行する @map.event_init(@map, 'move', 'function(){drawCenterMarker(map);}')#<------[1] # 中央マーカーをグローバル変数center_markerとして定義して、表示する @map.overlay_global_init(center_marker, "center_marker", :local_construction => true)#<------[2] end private def center_marker#<------[3] icon = GIcon.new(:image => "http://www.google.com/mapfiles/dd-start.png", :copy_base => GIcon::DEFAULT) GMarker.new(@center, :clickable => false, :icon => icon) end end
- @map.overlay_global_initでlocal_construction => trueは重要。指定した時と、指定しない時の違いは以下のようになる。
- この場合、local_construction => trueを指定しないと、map.getCenter()で中央の座標を取得できないので、正常に表示できなくなる。
# :local_construction => trueありの場合 @map.overlay_global_init(center_marker, "center_marker", :local_construction => true) # 関数外部ではグローバル変数center_markerの定義だけ。値の代入はwindow.onloadの関数内部で行う。 => var center_marker; window.onload = addCodeToFunction(window.onload,function() { if (GBrowserIsCompatible()) { ...(中略)... center_marker = new GMarker(map.getCenter(),{icon : addOptionsToIcon(new GIcon(G_DEFAULT_ICON),{image : "http://www.google.com/mapfiles/dd-start.png"}),clickable : false}); } });
# :local_construction => true無しの場合 @map.overlay_global_init(center_marker, "center_marker") # 関数外部でグローバル変数center_markerの定義と値の代入を行う。window.onloadの関数内部では何もしない。 => var center_marker = new GMarker(map.getCenter(),{icon : addOptionsToIcon(new GIcon(G_DEFAULT_ICON),{image : "http://www.google.com/mapfiles/dd-start.png"}),clickable : false}); window.onload = addCodeToFunction(window.onload,function() { if (GBrowserIsCompatible()) { ...(中略)... } });
- 地図が移動すると、drawCenterMarker(map) が呼び出されて、中央マーカーの位置を常に中央になるように修正する。
// ---------- public/javascripts/application.js ---------- // Place your application-specific JavaScript functions and classes here // This file is automatically included by javascript_include_tag :defaults ...(中略)... function drawCenterMarker(map) { var mapCenter = map.getCenter(); center_marker.setPoint(mapCenter); }
- 緑色のマーカーが常に中央を指し示すマーカー。グリグリ動かしても、常に中央。
少しずつ地図の使い方が分かって、面白くなってきた!しかし、まだ知らないことの方がきっと多いので、もっと便利に使う技がたくさんあるはず。試行錯誤はまだまだ続く...。