=YM4R/GM plugin for Rails This is the latest version of the YM4R/GM plugin for Rails (YM4RGMP4R?). Its aim is to facilitate the use of Google Maps from Rails application. It includes and enhances all the web application-related functionalities found in the YM4R gem as of version 0.4.1. ==Getting Started I present here the most common operations you would want to do with YM4R/GM. Read the rest of the documents if you want more details. In your controller, here is a typical initialization sequence in action +index+: def index @map = GMap.new("map_div") @map.control_init(:large_map => true,:map_type => true) @map.center_zoom_init([75.5,-42.56],4) @map.overlay_init(GMarker.new([75.6,-42.467],:title => "Hello", :info_window => "Info! Info!")) end Here I create a GMap (which will be placed inside a DIV of id +map_div+), add controls (large zoom slider and pan cross + map type selector), set the center and the zoom and add a marker. Of these 4 steps only the creation of the map and the setting of the center and the zoom are absolutely necessary. In your view, here is what you would have: <html><head><title>Test</title> <%= GMap.header %> <%= @map.to_html %> </head><body> <%= @map.div(:width => 600, :height => 400) %> </body></html> First you must output the header, used by all the maps of the page: It includes the Google Maps API JS code and the JS helper functions of YM4R/GM. Then we initialize the map by calling <tt>@map.to_html</tt>. In the body, we need a DIV whose +id+ is the same as the one passed to the GMap constructor in the controller. The call to <tt>@map.div</tt> outputs such a DIV. We pass to it options to set the width and height of the map in the style attribute of the DIV. <b>Note that you need to set a size for the map DIV element at some point or the map will not be displayed.</b> You have a few ways to do this: - You define it yourself, wherever you want. Usually as part of the layout definition in an external CSS. - In the head of the document, through a CSS instruction output by <tt>@map.header_width_height</tt>, to which you pass 2 arguments (width and height). - When outputting the DIV with <tt>@map.div</tt>, you can also pass an option hash, with keys <tt>:width</tt> and <tt>:height</tt> and a style attribute for the DIV element will be output. You can update the map trough RJS. Here is an action you can call from a <tt>link_remote_tag</tt> which would do this: def update @map = Variable.new("map") @marker = GMarker.new([75.89,-42.767],:title => "Update", :info_window => "I have been placed through RJS") end Here, I first bind the Ruby <tt>@map</tt> variable to the JS variable <tt>map</tt>, which already exists in the client browser. +map+ is by default the name given to a map created from YM4R/GM (this could be overriden by passing a second argument to the GMap constructor). Then I create a marker. And you would have inside the RJS template for the action: page << @map.clear_overlays page << @map.add_overlay(@marker) Here I first clear the map of all overlays. Then I add the marker. Note that the +overlay_init+ is not used anymore since, as its name indicates, this method is only for the initialization of the map. ==Relation between the YM4R gem and the YM4R/GM plugin They are completely independent from each other. With the plugin, you don't need the YM4R gem anymore, unless you want to use the tilers or the Ruby helpers for the Yahoo! Maps Building Block API's and the Google Maps geocoding API. Please refer to the documentation of the YM4R gem to know more about the functionalities in it. Conversely, the YM4R gem does not need the plugin to work. ==Installation Install like any other Rails plugin: ruby script/plugin install svn://rubyforge.org/var/svn/ym4r/Plugins/GM/trunk/ym4r_gm As part of the installation procedure, the JavaScript files found in the <tt>PLUGIN_ROOT/javascript</tt> directory will be copied to the <tt>RAILS_ROOT/public/javascripts/</tt> directory. Moreover a <tt>gmaps_api_key.yml</tt> file will also be created in the <tt>RAILS_ROOT/config/</tt> folder. If this file already exists (installed for example by a previous version of the plugin), nothing will be done to it, so you can keep your configuration data even after updating the plugin. This file is a YAML representation of a hash, similar to the <tt>database.yml</tt> file in that the primary keys in the file are the different environments (development, test, production), since you will probably need to have different Google Maps API keys depending on that criteria (for example: a key for localhost for development and test; a key for your host for production). If you don't have any special need, there will be only one key associated with each environment. If however, you need to have multiple keys (for example if your app will be accessible from multiple URLs, for which you need different keys), you can also associate a hash to the environment, whose keys will be the different hosts. In this case, you will need to pass a value to the <tt>:host</tt> key when calling the method <tt>GMap.header</tt> (usually <tt>@request.host</tt>). ==Migration from the YM4R gem versions <= 0.4.1 Apart from the installation of the plugin detailed above, you will also need to delete the instructions to require the file and include the Ym4r::GoogleMaps module in your controllers: require 'ym4r' include Ym4r::GoogleMaps This lines are now not needed since the plugin is loaded as part of the normal Rails loading procedure and the module is included when the plugin is loaded. ==Operations You can use the library to display Google maps easily from Rails. The version of the API used is v2. The library is engineered so updates to the map through RJS/Ajax are possible. I have made available 2 in-depth tutorials to show some of the functionalities of the library and how it can be integrated with GeoRuby and the Spatial Adapter for Rails: - http://thepochisuperstarmegashow.com/2006/06/02/ym4r-georuby-spatial-adapter-demo/ - http://thepochisuperstarmegashow.com/2006/06/03/google-maps-yahoo-traffic-mash-up/ Following is some notes about manipulating Google Maps with the library: ===Naming conventions The names of the Ruby class follow the ones in the JavaScript Google Maps API v2, except for GMap2, which in Ruby code is called simply GMap. To know what is possible to do with each class, you should refer to the documentation available on Google website. On top of that, you have some convenience methods for initializing the map (in the GMap class). Also, the constructors of some classes accept different types of arguments to be converted later in the correct JavaScript format. For example, the +GMarker+ aclass accepts an array of 2 floats as parameter, in addition of a GLatLng object, to indicate its position. It also facilitates the attribution of an HTML info window, displayed when the user clicks on it, since you can pass to the constructor an options hash with the <tt>:info_window</tt> key and the text to display as the value, instead of having to wire the response to a click event yourself. ===Binding JavaScript and Ruby Since the Google Maps API uses JavaScript to create and manipulate a map, most of what the library does is outputting JavaScript, although some convenience methods are provided to simplify some common operations at initialization time. When you create a YM4R mapping object (a Ruby object which includes the MappingObject module) and call methods on it, these calls are converted by the library into JavaScript code. At initialization time, you can pass arbitrary JavaScript code to the <tt>GMap#record_init</tt> and <tt>GMap#record_global_init</tt>.Then, at update time, if you use Ruby-on-Rails as your web framework, you can update your map through RJS by passing the result of the method calls to the <tt>page << </tt> method to have it then interpreted by the browser. For example, here is a typical initialization sequence for a map @map = GMap.new("map_div") @map.control_init(:large_map => true,:map_type => true) @map.center_zoom_init([35.12313,-110.567],12) @map.overlay_init GMarker.new([35.12878, -110.578],:title => "Hello!") @map.record_init @map.add_overlay(GMarker.new([35.12878, -110.578],:title => "Hello!")) While +center_zoom_init+, +control_init+ or +overlay_init+ (and generally all the GMap methods which end in +init+) are one of the rare convenience methods that do not output JavaScript, the +add_overlay+ does. Actually, if you look at the code of the GMap class, you won't find any +add_overlay+ method, although in the documentation of the GMap2 class from the Google Maps API documentation, you will find something about the +addOverlay+ JavaScript method. In fact, when you call on a mapping object an unknow method, it is converted to a javascriptified version of it, along with its arguments, and a string of JavaScript code is output. So the <tt>@map.add_overlay...</tt> above is converted to <tt>"map.addOverlay(new GMarker(GLatLng.new(35.12878, -110.578),{title:\"Hello!\"}))"</tt>, which is then passed to the +record_init+ method of a Ruby GMap object to be later output along with the rest of the initialization code. Any arbitrary JavaScript code can be passed to the +record_init+ method. Note that 2 last lines of the previous code sample are strictly equivalent and since the +overlay_init+ version is a bit simpler, it should be preferred. ===Initialization of the map The map is represented by a GMap object. You need to pass to the constructor the id of a DIV that will contain the map. You have to place this DIV yourself in your HTML template. You can also optionnally pass to the constructor the JavaScript name of the variable that will reference the map, which by default will be global in JavaScript. You have convenience methods to setup the controls, the center, the zoom, overlays, the interface configuration (continuous zoom, double click zoom, dragging), map types and the icons (which are also global). You can also pass arbitrary JavaScript to +record_init+ and +record_global_init+. Since, by default, the initialization of the map is performed in a callback function, if you want to have a globally accessible variable, you need to use the +global+ version. You can also have multiple maps. Just make sure you give them different DIV id's, as well as different global variable names, when constructing them: @map1 = GMap("map1_div","map1") @map2 = GMap("map2_div","map2") The other absolutely necessary initialization step in the controller is the setting of center and zoom: @map1.center_zoom_init([49.12,-56.453],3) Withouth this code the map will display an empty grey rectangle with Google's logo. Then in your template, you have 2 necessary calls: - The static <tt>GMap.header</tt>: Outputs the inclusion of the JavaScript file from Google to make use of the Google Maps API and by default a style declaration for VML objects, necessary to display polylines under IE. This default can be overriddent by passing <tt>:with_vml => false</tt> as option to the +header+ method. You can also pass to this method a <tt>:host</tt> option in order to select the correct key for the location where your app is currently deployed, in case the current environment has multiple possible keys. Usually, in this case, you should pass it <tt>@request.host</tt>. Finally you can override all the key settings in the configuration by passing a value to the <tt>:key</tt> key. - <tt>GMap#to_html</tt>: Outputs the initialization code of the map. By default, it outputs the +script+ tags and initializes the map in response to the onload event of the JavaScript window object. You can call +to_html+ on each one of your maps to have them all initialized. You can pass the option <tt>:full=>true</tt> to the method to setup a fullscreen map, which will also be resized when the window size changes. You can also use <tt>GMap#div</tt> to output the div element with the correct +id+. You can pass it options <tt>:height</Tt> and <tt>:width</tt> to set the size of the div (although, as indicated below, you have other ways to do that). So you should have something like the following: <html><head><title>Hello</title> <%= GMap.header %> <%= @map.to_html %> </head> <body><%= @map.div(:width => 600,:height => 400) %></body> </html> <b>Note that you need to set a size for the map DIV element at some point or the map will not be displayed.</b> You have a few ways to do this: - You define it yourself, wherever you want. Usually as part of the layout definition in an external CSS. - In the head of the document, through a CSS instruction output by <tt>@map.header_width_height</tt>, to which you pass 2 arguments (width and height). - When outputting the DIV with <tt>@map.div</tt>, you can also pass an option hash, with keys <tt>:width</tt> and <tt>:height</tt> and a style attribute for the DIV element will be output. ===GMarkers GMarkers are point of interests on a map. You can give a position to a GMarker constructor either by passing it a 2D-array of coordinates, a GLatLng object, an object of type Variable (which evaluates to a GLatLng when interpreted in the browser) or an address, which will be geocoded when the marker is initialized by the map. You can pass options to the GMarker to customize the info window (<tt>:info_window</tt> or <tt>:info_window_tabs</tt> options), the tooltip (<tt>:title</tt> option) or the icon used (<tt>:icon</tt> option). For example: GMarker.new([12.4,65.6],:info_window => "<b>I'm a Popup window</b>",:title => "Tooltip") GMarker.new(GLatLng.new([12.3,45.6])) GMarker.new("Rue Clovis Paris France", :title => "geocoded") ===Update of the map You are able to update the map through Ajax. In Ruby-on-Rails, you have something called RJS, which sends JavaScript created on the server as a response to an action, which is later interpreted by the browser. It is usually used for visual effects and replacing or adding elements. It can also accept arbitrary JavaScript and this is what YM4R uses. For example, if you want to add a marker to the map, you need to do a few things. First, you have to bind a Ruby mapping object to the global JavaScript map variable. By default its name is +map+, but you could have overriden that at initialization time. You need to do something like this: @map = Variable.new("map") +map+ in the Variable constructor is the name of the global JavaScript map variable. Then any method you call on <tt>@map</tt> will be converted in JavaScript to a method called on +map+. In your RJS code, you would do something like this to add a marker: page << @map.add_overlay(GMarker.new([12.1,12.76],:title => "Hello again!")) What is sent to the browser will be the fllowing JavaScript code: map.addOverlay(new GMarker(new GLatLng(123123.1,12313.76),{title:\"Hello again!\"})) ===GPolyline GPolylines are colored lines on the map. The constructor takes as argument a list of GLatLng or a list of 2-element arrays, which will be transformed into GLatLng for you. It can also take the color (in the <tt>#rrggbb</tt> form), the weight (an integer) and the opacity. These arguments are optional though. For example: GPolyline.new([[12.4,65.6],[4.5,61.2]],"#ff0000",3,1.0) Then you add it like any other overlay: @map.overlay_init(polyline) ===GPolygon GPolygons are colored areas on the map. As of 29/12, this feature is not documented in the official GMaps API, but thanks to Steven Talcott Smith (http://www.talcottsystems.com), it is possible to use it now in ym4r. The constructor takes as argument a list of GLatLng or a list of 2-element arrays, which will be transformed into GLatLng for you. Note that for polygons, the last point must be equal to the first, in order to have a closed loop. It can also take the color (in the <tt>#rrggbb</tt> form) of the stroke, the weight of the stroke, the opacity of the stroke, as well as the color of the fill and the opacity. These arguments are optional though. For example: GPolygon.new([[12.4,6.6],[4.5,1.2],[-5.6,-12.4],[12.4,6.6]],"#ff0000",3,1.0,"#00ff00",1.0) Then you add it like any other overlay: @map.overlay_init(polygon) ===GMarkerGroup A new type of GOverlay is available, called GMarkerGroup. <b>To use it you would have to include in your HTML template the JavaScript file <tt>markerGroup.js</tt> after the call to <tt>GMap.header</tt> (because it extends the GOverlay class).</b> You should have something like that in your template: <%= javascript_include_tag("markerGroup") %> It is useful in 2 situations: - Display and undisplay a group of markers without referencing all of them. You just declare your marker group globally and call +activate+ and +deactivate+ on this group in response, for example, to clicks on links on your page. - Keeping an index of markers, for example, in order to show one of these markers in reponse to a click on a link (the way Google Local does with the results of the search). Here is how you would use it from your Ruby code: @map = GMap.new("map_div") marker1 = GMarker.new([123.55,123.988],:info_window => "Hello from 1!") marker2 = GMarker.new([87.123,18.9],:info_window =>"Hello from 2!") @map.overlay_global_init(GMarkerGroup.new(true, 1 => marker1, 2 => marker2),"myGroup") Here I have created an active (ie which is going to be displayed when the map is created) marker group called +myGroup+ with 2 markers, which I want to reference later. If I did not want to reference them later (I just want to be able to display and undisplay the marker group), I could have passed an array instead of the hash. Then in your template, you could have that: <a href="#" onclick="myGroup.showMarker(1);return false;">Click here to display marker1</a> <a href="#" onclick="myGroup.showMarker(2);return false;">Click here to display marker2</a> <%= @map.div %> When you click on one of the links, the corresponding marker has its info window displayed. You can call +activate+ and +deactivate+ to display or undisplay a group of markers. You can add new markers with <tt>addMarker(marker,id)</tt>. Again if you don't care about referencing the marker, you don't need to pass an id. If the marker group is active, the newly added marker will be displayed immediately. Otherwise it will be displayed the next time the group is activated. Finally, since it is an overlay, the group will be removed when calling clearOverlays on the GMap object. You can center and zoom on all the markers in the group by calling <tt>GMarkerGroup#centerAndZoomOnMarkers()</tt> after the group has been added to a map. So for example, if you would want to do that at initalization time, you would do the following (assuming your marker group has been declared as +group+): @map.record_init group.center_and_zoom_on_markers ===GMarkerManager It is a recent (v2.67) GMaps API class that manages the efficient display of potentially thousands of markers. It is similar to the Clusterer (see below) since markers start appearing at specified zoom levels. The clustering behaviour has to be managed explicitly though by specifying the cluster for smaller zoom levels and specify the expanded cluster for larger zoom levels and so on. Note that it is not an overlay and is not added to the map through an overlay_init call. Here is an example of use: @map = GMap.new("map_div") @map.control_init(:large_map => true, :map_type => true) @map.center_zoom_init([59.91106, 10.72223],3) srand 1234 markers1 = [] 1.upto(20) do |i| markers1 << GMarker.new([59.91106 + 6 * rand - 3, 10.72223 + 6 * rand - 3],:title => "OY-20-#{i}") end managed_markers1 = ManagedMarker.new(markers1,0,7) markers2 = [] 1.upto(200) do |i| markers2 << GMarker.new([59.91106 + 6 * rand - 3, 10.72223 + 6 * rand - 3],:title => "OY-200-#{i}") end managed_markers2 = ManagedMarker.new(markers2,8,9) markers3 = [] 1.upto(1000) do |i| markers3 << GMarker.new([59.91106 + 6 * rand - 3, 10.72223 + 6 * rand - 3],:title => "OY-300-#{i}") end managed_markers3= ManagedMarker.new(markers3,10) mm = GMarkerManager.new(@map,:managed_markers => [managed_markers1,managed_markers2,managed_markers3]) @map.declare_init(mm,"mgr") ===Clusterer A Clusterer is a type of overlay that contains a potentially very large group of markers. It is engineered so markers close to each other and undistinguishable from each other at some level of zoom, appear as only one marker on the map at that level of zoom. Therefore it is possible to have a very large number of markers on the map at the same time and not having the map crawl to a halt in order to display them. It has been slightly modified from Jef Poskanzer's original Clusterer2 code (see http://www.acme.com/javascript/ for the original version). The major difference with that version is that, in YM4R, the clusterer is a GOverlay and can therefore be added to the map with <tt>map.addOverlay(clusterer)</tt> and is cleared along with the rest of overlays when <tt>map.clearOverlays()</tt> is called. <b>In order to use a clusterer, you should include in your template page the clusterer.js file after the call to <tt>GMap.header</tt> (because it extends the GOverlay class).</b> You should have something like that in your template: <%= javascript_include_tag("clusterer") %> To create a clusterer, you should first create an array of all the GMarkers that you want to include in the clusterer (you can still add more after the clusterer has been added to the map though). When GMarkers close together are grouped in one cluster (represented by another marker on the map) and the user clicks on the cluster marker, a list of markers in the cluster is displayed in the info windo with a description: By default it is equal to the +title+ of the markers (which is also displayed as a tooltip when hovering on the marker with the mouse). If you don't want that, you can also pass to the GMarker constructor a key-value pair with key <tt>:description</tt> to have a description different from the title. For example, here is how you would create a clusterer: markers = [GMarker.new([37.8,-90.4819],:info_window => "Hello",:title => "HOYOYO"), GMarker.new([37.844,-90.47819],:info_window => "Namaste",:description => "Chopoto" , :title => "Ay"), GMarker.new([37.83,-90.456619],:info_window => "Bonjour",:title => "Third"), ] clusterer = Clusterer.new(markers,:max_visible_markers => 2) Of course, with only 3 markers, the whole clusterer thing is totally useless. Usually, using the clusterer starts being interesting with hundreds of markers. The options to pass the clusterer are: - <tt>:max_visible_markers</tt>: Below this number of markers, no clustering is performed. Defaults to 150. - <tt>:grid_size</tt>: The clusterer divides the visible area into a grid of this size. Defaults to 5. - <tt>:min_markers_per_cluster</tt>: Below this number of markers a cluster is not formed. Defaults to 5. - <tt>:max_lines_per_info_box</tt>: Number of lines to display in the info window displayed when a cluster is clicked on by the user. Defaults to 10. - <tt>:icon</tt>: Icon to be used to mark a cluster. Defaults to G_DEFAULT_ICON (the classic red marker). Then to add the clusterer to the map at initialization time, you proceed as with a classic overlay: @map.overlay_init clusterer To add new markers in RJS (if the clusterer has been declared globally with overlay_global_init), you should do this: page << clusterer.add_marker(marker,description) In this case, the <tt>:description</tt> passed to the GMarker contructor will not be taken into account. Only the +description+ passed in the call to +add_marker+ will. ===GeoRss Overlay An group of markers taken from a Rss feed containing location information in the W3C basic geo (WGS83 lat/lon) vocabulary and in the Simple GeoRss format. See http://georss.org for more details. The support for GeoRss relies on the MGeoRSS library by Mikel Maron (http://brainoff.com/gmaps/mgeorss.html), although a bit modified, mostly to have the GeoRssOverlay respect the GOverlay API. It has also been enhanced by Andrew Turner who added support for the GeoRss Simple format. <b>Note that the <tt>geoRssOverlay.js</tt> file must be included in the HTML template in order to use this functionality.</b> You should have something like that in your template: <%= javascript_include_tag("geoRssOverlay") %> Here is how you would use it. First create the overlay at initialization: def index @map = GMap.new("map_div") @map.control_init(:large_map => true) @map.center_zoom_init([38.134557,-95.537109],0) @map.overlay_init(GeoRssOverlay.new(url_for(:action => "earthquake_rss")) end Since it is not possible to make requests outside the domain where the current page comes from, there is a need for a proxy. With the GeoRssOverlay initialization above, the request will be made by the <tt>earthquake_rss</tt> action, where the address to find the RSS feed will be hardwired: def earthquake_rss result = Net::HTTP.get_response("earthquake.usgs.gov","/eqcenter/recenteqsww/catalogs/eqs7day-M5.xml") render :xml => result.body end If you don't want to hardwire the RSS feed location in an action, you can. But you will have to pass the <tt>:proxy</tt> option to the GeoRssOverlay constructor. When requesting the RSS feed, the browser will in fact call the proxy with the actual URL of the RSS feed in the +q+ parameter of the request. Here is how you would initialize the GeoRssOverlay that way: @map.overlay_init(GeoRssOverlay.new("http://earthquake.usgs.gov/eqcenter/recenteqsww/catalogs/eqs7day-M5.xml", :proxy => url_for(:action => "proxy"))) And here is an example of <tt>proxy</tt> action: def proxy result = Net::HTTP.get_response(URI.parse(@params[:q])) render :xml => result.body end You should probably do some checks to ensure the proxy is not abused but it is of your responsibility. Another option can be passed to the GeoRssOverlay constructor to set an icon to be used by the markers of the overlay: <tt>:icon</tt>. By default it is equal to the default icon (the classic red one). In the view, you should have something like the following: <html> <head><title>Testing GeoRss</title> <%= GMap.header(:with_vml => false) %> <%= javascript_include_tag("geoRssOverlay") %> <%= @map.header_width_height(600,400) %> <%= @map.to_html %> </head> <body> <%= @map.div %> </body> </html> Note the inclusion of the <tt>geoRssOverlay.js</tt> file. Other options to pass to the GeoRssOverlay constructor are the following: - <tt>:list_div</tt>: In case you want to make a list of all the markers, with a link on which you can click in order to display the info on the marker, use this option to indicate the ID of the DIV (that you must place yourself on your page). - <tt>:list_item_class<tt>: class of the DIV containing each item of the list. Ignored if option <tt>:list_div</tt> is not set. - <tt>:limit</tt>: Maximum number of items to display on the map. - <tt>:content_div</tt>: Instead of having an info window appear, indicates the ID of the DIV where this info should be displayed. ===Adding new map types It is now possible to easily add new map types, on top of the already existing ones, like G_SATELLITE_MAP or G_NORMAL_MAP. The imagery for these new map types can come from layers of the standard map types or can be taken either from a WMS server or from pretiled images on a server (that can be generated with a tool that comes with the YM4R gem: refer to the README of the gem to know more about it). For exemple, here is how you would setup layers from a public WMS server of the DMAP team of the American Navy: layer = WMSLayer.new("http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms", "20:3,6:3,0:27,0:29,6:19", :copyright => {'prefix' => "Map Copyright 2006", 'copyright_texts'=> ["DMAP"]}, :use_geographic => true, :opacity => 0.8) This sets up a connection to a WMS service, requesting layers <tt>20:3,6:3,0:27,0:29,6:19</tt> (you would have to look at the GetCapabilities document of the service to know what the valid layers are). The copyright notice attributes the data to DMAP. The images will be 80% opaque. For the rest of the options, the default values are used: default styles (<tt>:style</tt> option), PNG format (<tt>:format</tt> option), valid for all zoom levels (<tt>:zoom_range</tt> option). The option <tt>:merc_proj</tt> is irrelevant here since the <tt>:use_geographic</tt> option is true. The arguments <tt>:use_geographic</tt> and <tt>:merc_proj</tt> warrant some explanation. The Google Maps are in the Simple Mercator projection in the WGS84 datum and currently do not support the display of data in projections other than that (at least if you want to display markers and lines on top of it). Unfortunately, different WMS servers do not identify this projection the same way. So you can give to the WMSLayer constructor your server type through the <tt>:merc_proj</tt> option and it will figure out what is the correct identifier. Currently, this works only for <tt>:mapserver</tt> (EPSG:54004) and <tt>:geoserver</tt> (EPSG:41001). For others you can directly pass a number corresponding to the EPSG definition of the simple Mercator projection of your server. On top of that, some servers just don't support the Simple Mercator projection. This is why there is a <tt>:use_geographic</tt> option, that can be set to +true+. It is in order to tell the WMSLayer that it should request its tiles using LatLon coordinates in the WGS84 datum (which should be supported by any server in a consistant way). Unfortunately it is not perfect since the deformation is quite important for low zoom levels (< 5 for the US). Higher than that, the deformation is not that visible. However, if you control the WMS server, it is recommended that you don't use <tt>:use_geographic</tt> and instead use the <tt>:merc_proj</tt> option and setup the Mercator projection in your server if it is not done by default. <b>Note that you need to include the <tt>wms-gs.js</tt> javascript file in your HTML page in order to make use of the WMSLayer functionality.</b> You should have something like that in your template: <%= javascript_include_tag("wms-gs") %> This file uses code by John Deck with the participation of others (read the top of the javascript file to know more). Here is how to define a pretiled layer: layer = PreTiledLayer.new("http://localhost:3000/tiles", :copyright => {'prefix' => "Map C 2006", 'copyright_texts' => ["Open Atlas"]}, :zoom_range => 13..13, :opacity => 0.7, :format => "gif") I tell the PreTiledLayer constructor where I can find the tiles, setup the Copyright string, the valid zooms for the map, the opacity and the format. Tiles must have standardized names: <tt>tile_#{zoom}_#{x_tile}_#{y_tile}.#{format}</tt> (here the format is "gif"). You can use tools found in the YM4R gem to generate tiles in this format either from local maps or from WMS servers (useful to create tiles from geographic data files without having to run a map server or to cache images from slow servers). Again refer to the documentation of the gem for more information on how to do this. Instead of having the tiles requested directly, you can also decide to have an action on the server which takes care of it. You can used the class PreTiledLayerFromAction for this. In this case, the first argument of the constructor is an url of an action. The arguments +x+, +y+ and +z+ will be passed to it. layer = PreTiledLayerFromAction.new(url_for(:action => :get_tile), :copyright => {'prefix' => "Map C 2006", 'copyright_texts' => ["Open Atlas"]}, :zoom_range => 13..14, :opacity => 0.7) The other constructor arguments have the same meaning as PreTiledLayer. Here is an uninteresting example of action that serves tiles: def get_tile x = @params[:x] y = @params[:y] z = @params[:z] begin send_file "#{RAILS_ROOT}/public/tiles/tile_#{z}_#{x}_#{y}.png" , :type => 'image/png', :disposition => 'inline' rescue Exception render :nothing => true end end You can add a layer to a new map type the following way: map_type = GMapType.new(layer,"My WMS") This is actually the simplest configuration possible. Your map type has only one data layer and is called "My WMS". You can add more that one layer: Either one that you have created yourself or existing ones. For example: map_type = GMapType.new([GMapType::G_SATELLITE_MAP.get_tile_layers[0],layer,GMapType::G_HYBRID_MAP.get_tile_layers[1]], "Test WMS") Here for the "Test WMS" map type, we also take the first layer of the "Satellite" map type in the background and overlay the second layer of the "Hybrid" map type (roads, country boundaries, etc... transparently overlaid on top of the preceding layers) so when the "Test WMS" map type is selected in the interface, all three layers will be displayed. Finally to add a map type to a GMap: @map.add_map_type_init(map_type) If you want to wipe out the existing map types (for example the 3 default ones), you can add a +false+ argument to the +add_map_type_init+ method and the +map_type+ will be the only one. If you want to setup the map as the default one when the map is initially displayed, you should first declare the map type then add it to the map as indicated above and finally setting it as the default map type: @map.declare_init(map_type,"my_map_type") @map.add_map_type_init(map_type) @map.set_map_type_init(map_type) Future versions of the plugin may simplify that. ===Google Geocoding A helper to perform geocoding on the server side (in Ruby) is included. Here is an example of request: results = Geocoding::get("Rue Clovis Paris") You can also pass to the +get+ method an options hash to manage the various API key options (see the section on <tt>GMap.header</tt> for details). +results+ is an array of Geocoding::Placemark objects, with 2 additional attributes: +status+ and +name+. You should check if +status+ equals <tt>Geocoding::GEO_SUCCESS</tt> to know if the request has been successful. You can then access the various data elements. Here is an example that will display a marker on Paris: results = Geocoding::get("Rue Clovis Paris") if results.status == Geocoding::GEO_SUCCESS coord = results[0].latlon @map.overlay_init(GMarker.new(coord,:info_window => "Rue Clovis Paris")) end You could also have performed the geocoding on the client side with the following code, which is functionnality equivalent to the code above: GMarker.new("Rue Clovis Paris",:info_window => "Rue Clovis Paris") ===Local Search Local Search control has been added to the map and control objects. There are two places you need to implement it to get it to work. This is because the local search control needs an additional library added to the import as well as the control added to the map. In your controller, you add ':local_search => true' to the @map.control_init like this: @map = GMap.new("map_div") @map.control_init(:large_map => true, :map_type => true, :local_search => true) And in your view, you pass ':local_search => true' to the GMap.header like this: <%= GMap.header(:local_search => true) %> <%= @map.to_html %> <%= @map.div(:width => 600, :height => 400) %> You can pass options to the control_init as well. They are: :anchor => [:bottom_left (default), :bottom_right, :top_left, :top_right] :offset_width => 10 (default) :offset_height => 20 (default) :local_search_options => "{suppressZoomToBounds : true, resultList : google.maps.LocalSearch.RESULT_LIST_INLINE, suppressInitialResultSelection : true, searchFormHint : 'Local Search powered by Google', linkTarget : GSearch.LINK_TARGET_BLANK}" So if you wanted the local search control to be at the bottom right of the map, 30 pixel in from the right and 20 pixels above the bottom and some local search options it would look like this: @map.control_init(:large_map => true, :map_type => true, :local_search => true, :anchor => :bottom_right, :offset_width => 30, :offset_height => 20, :local_search_options => "{suppressZoomToBounds : true, resultList : google.maps.LocalSearch.RESULT_LIST_INLINE, suppressInitialResultSelection : true, searchFormHint : 'Local Search powered by Google', linkTarget : GSearch.LINK_TARGET_BLANK}") ==Sensor Google now requires the 'sensor' attribute in the GET API request. It is defaulted to 'false', but if you are writing an application for one of the new cellphones with GPS out there then you can enable it by using the following call: <%= GMap.header :sensor => true %> ==Recent changes - 'sensor' added to GET URL per Google API Terms of Use. 'false' by default. - Local Search overlay added. See above for implementation. - GMarker can now be placed by address (in addition to coordinates). Some code to geocode the address when the marker is initialized is added - Addition of a +center_zoom_on_points_init+ to center and zoom on a group of pixel - In JS, addition of methods to GMap2 and GMarkerGroup to center and zoom on a group of points or markers (thanks to Glen Barnes) - Support for easy setup of fullscreen maps ==TODO - Add support for easy manipulation of external Google Maps-related libraries: Advanced tooltip manipulation (PdMarker),... - Addition of all GeoRss vocabularies (with all features: polylines...) to the geoRssOverlay extension - Tutorials ==Disclaimer This software is not endorsed in any way by Google. ==Acknowledgement The YM4R/GM plugin bundles JavaScript libraries from John Deck (WMS layers on Google Maps), Jef Poskanzer (Clusterer on Google Maps) and Mikel Maron (GeoRss on Google Maps). ==License The YM4R/GM plugin is released under the MIT license. The <tt>clusterer.js</tt> file is redistributed with a different license (but still compatible with the MIT license). Check the top of the file in <tt>PLUGIN_ROOT/javascript</tt> to know more. ==Support Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut+ym4r@gmail.com.
lostmahbles/ym4r_gm
My copy of the subversion repo at svn://rubyforge.org/var/svn/ym4r/Plugins/GM/trunk/ym4r_gm
Ruby