One of my rails projects uses the fantastic YM4R gem to do Google Maps integration. The gem does a lot of the grunt work for you – rendering the map, doing updates via RJS, and (of course) doing parameter setting and such in a ruby-like way. Also on that page is the Spatial Adapter plugin for MySQL/PostGIS, which lets you store points and shapes in a database in an easy way. Combining these two, it is not hard to get a Google Maps display rendering points stored in your database, and the author has even written a sweet tutorial to help you get started.Now, I got all this working and needed a little more: creation of points that are dragable, dynamic point fetching based on viewer bounds (this is necessary if you have a ton of points in your DB and don’t want the thing to crawl when you are using it).Please email me if you’d like me to go more in depth on my setup, because there was def. a lot of tweaking and such to get it all working. For now though, I’m going to assume you’ve got a simple implementation working, based perhaps on the tutorial, and are looking to expand it.
Point Creation
So you’ve probably got a controller for your map, and the first thing you need to do is make new points. The code in the view is just a simple form that is going to call our add_point action, like this:
<%= form_remote_tag :url => {:controller => \'map\', :action => \'add_point\'}, :class => \"headerForm\"%>
<%= submit_tag \"New Location\" %>
<%= end_form_tag %>
Your actual function will probably look something like this:
def add_point
@map = Variable.new("map") @marker = GMarker.new(session[:coord],:info_window => info_window_for_new_place,:draggable => true)
end
Note that I grabbed the actual coord from the session – it too needs to be initialized to some default value that will get changed when the user drags the point. Also not that I set draggable to true. Info_window_for_new_place is just a function that returns the relevant HTML for the bubble window. Finally, you will have an add_point.rjs view that simply does:
page << @map.add_overlay(@marker)
Adding Event Handlers
Next is to modify whatever your initialization function for the map is to add event_inits for the various user actions that we want to respond to. So here are my event_inits for the actions corresponding to new point creation and map movement: @map.event_init(@map,"addoverlay","overlayAdded") @map.event_init(@map,"moveend","mapMoved")
Those actions are actually going to get handled by some javascript in your view that then calls (via AJAX) the req’d actions back on your server. Here is the javascript code I wrote for this (sadly, since I needed some extra control, the Ajax requests had the be written by hand and not using rail connivences) :
<script type=\"text/javascript\">
function overlayAdded(overlay){
if((overlay instanceof GMarker) && overlay.draggingEnabled()) {
GEvent.addListener(overlay,\"dragend\", movePoint);
}
}
function movePoint() {
thePoint = this.getPoint();
infoWindow = map.closeInfoWindow();
new Ajax.Request(\'/map/move_point?pointy=\' + thePoint.y + \'&pointx=\' +thePoint.x, {asynchronous:true, evalScripts:true});
}
function mapMoved(){
theBounds = map.getBounds();
new Ajax.Request(\'/map/display_points_in_bounds?swpointy=\' + theBounds.getSouthWest().y + \'&swpointx=\' +theBounds.getSouthWest().x + \'&nepointy=\' + theBounds.getNorthEast().y + \'&nepointx=\' + theBounds.getNorthEast().x, {asynchronous:true, evalScripts:true});
}
</script>
Here you can see that the overlayadded handler that we called in the controller code. When a new point is added it adds another handler for dragging the point around. movePoint shuts the info window so it will drag too, and calls a method to update the point in the database (actually, it just updates the session data and only updates the DB later if the user saves it). Finally, the mapMoved method runs whenever the zoom level changes or the map gets moved, and it gets the new bounds and passes them to a function display_points_in_bounds. That function can use something like the following to get all the points in those bounds (limited to 10 by user count in this case):
@locations = Place.find_all_by_geom([[params[:swpointy].to_f,params[:swpointx].to_f], [params[:nepointy].to_f, params[:nepointx].to_f]],
rder => "users_count DESC LIMIT 10")
Again you will need an rjs file to actually add the points. So that is the idea, there’s a ton here you can do with handlers. Please post questions and comments here, and I’d be happy to expand on this if people would find it useful! The entire project using this technology will likely be open sourced at some point. I’m also going to also post an entry about integration with flickr – be sure to checkout greatsit to get an idea of the level of integration that is happening with both of these.