Skip to: Site menu | Main content

Custom GMap Solution for Dynamically Updated Markers (Part 1)

I recently upgraded OffRoadAtlas.com with a custom map interface based on the GMap module. I was able to do this while leaving the GMap module is virtually un-hacked (with the exception of modifying the "GMAP_API_VERSION" variable) - all of the customizations were made via an additional "helper" module that was written specifically for this site.

The site utilizes a map on the home page that shows all the off-roading areas and clubs in the database. The problem I was trying to solve was that I didn't want to have to load all 1,500+ points each time the home page was hit. Not only was this a waste of resources, but it also slowed things down considerably for the user.

Also, I knew that I was going to implement a system that automatically zoomed and centered the map to each user's location based on their IP address, so I didn't want to have to load markers that weren't going to be in the visible area of the map.

To make things even more complicated, I didn't want to use the default GMap info bubble when an icon was clicked on. I wanted the user to be able to see a running list of points they've clicked on and to have some interactivity between markers on the map and non-map HTML elements.

Over my next few blog posts, I'll talk about how I implemented each feature.

Dynamic Markers

To accomplish the first major task of dynamically loading markers based on the current visible map, I wrote a custom module that uses Views to return a list of markers based on latitude and longitude of the current visible area of the map. Here's a quick rundown of the procedure the first time the user hits the page:

1. Load the GMap in a custom block.
2. Using Javascript, get the mininum and maximum latitudes and longitudes of the visible map.
3. Compose an AHAH query for a custom themed view to return only the visible nodes.
4. Use Javascript to place the markers on the map

Then, whenever the map is panned or zoomed, steps 2-4 are repeated.

I decided to generate a custom block for each "dynamic GMap" so that each map can have a different set of variables. Doing it this way allows me to specify a unique GMap macro and view name for each dyanmic GMap that I wanted to use on the site.

The heart of the custom module is some Views API stuff that defines a Views plugin that outputs the view using JSON instead of HTML. I also had to define some new argument handlers to be able to properly grab the correct nodes based on the min/max lat/long. Here's the function that defines the plugin:

function gmap_dynamic_views_style_plugins() {
return array(
'gmap_dynamic_js' => array(
'name' => t('GMap Dynamic javascript variables'),
'theme' => 'views_view_location_javascript_variables',
'needs_table_header' => false,
'needs_fields' => true,
'even_empty' => true,
'validate' => 'gmap_views_validate',
)
);
}

The key array element above is the "theme", as it is this theme function that actually outputs the required JSON that the Javascript function receives and parses.

function theme_views_view_location_javascript_variables($view, $results) {
// get the various marker types from the gmap module
$markertypes = variable_get('gmap_node_markers', array());
$gmap_dynamic_results['total_returned'] = count($results);
$gmap_dynamic_results['max_allowed'] = $view->nodes_per_page;
$gmap_dynamic_results['too_many_returned_message'] = 'Only the first '. $gmap_dynamic_results['max_allowed'] .' results displayed. Zoom in for additional results.';
$gmap_dynamic_markers = array();
foreach ($results as $result) {
$type = $result->gmap_node_type;
foreach($result as $key => $value) {
$marker['markername'] = isset($markertypes[$type]) ? $markertypes[$type] : 'drupal';
$marker['type'] = $type;
switch ($key) {
case 'location_latitude':
$marker['latitude'] = $value;
break;
case 'location_longitude':
$marker['longitude'] = $value;
break;
case 'nid':
$marker['nid'] = $value;
break;
case 'node_title':
$other_stuff['title_link'] = l($value, 'node/'. $result->nid);
$marker['title'] = $value;
break;
default:
$other_stuff[$key] = $value;
break;
}
}
$marker['description'] = theme('gmap_dynamic_marker_description', $type, $other_stuff);
$gmap_dynamic_markers[] = $marker;
}
$gmap_dynamic_results['markers'] = $gmap_dynamic_markers;
drupal_set_header('Content-Type: text/javascript');
print drupal_to_js($gmap_dynamic_results);
module_invoke_all('exit');
exit;
}

This function has quite a bit of customization in it for the site, as it also returns some status variables and maps the Views fields to their desired Javascript variable names. In the end, I use the "drupal_to_js" function to actually create the JSON content.

The Javascript portion is keyed on the Google Map "moveend" GEvent, everytime the map stops moving, Javascript code kicks off a function that clears all the current markers, displays the "loading" throbber, then makes the AHAH call. The URL for the AHAH call looks something like this:

http://www.offroadatlas.com/dynamic/37.32212035945174/37.97451499202459/-97.196044921875/-95.108642578125

The first argument, "dynamic", is simply the URL I assigned to the view, while the rest of the parameters are mininum latitude, maximum latitude, minimum longitude, and maximum longitude. The "dynamic" view takes these 4 arguments and uses them to filter for the desired nodes. The rest of the view is pretty standard, just defining the content types and fields to be returned.

Once the JSON is returned to the client, it is parsed and the markers are displayed using the 3rd party Google Maps marker manager PdMarker. This was required for the advanced marker functionality that I wanted. PdMarker extends the standard GMarker interface and (among other things) adds a unique HTML ID to each marker so that it can be referenced from outside of the GMap. When adding a marker, it works virtually identical to the standard GMarker, so it was a snap to implement.

I also included some code that Bevan shared from the most-excellent The Hub site that allows me to load a few markers at a time, as this gives the user some much needed visual feedback that something is actually happening when the markers are being fetched.

Finally, another feature I implemented was a Javascript client-side marker cache. This is keyed on the min/max lat/long - if the user is just zooming in and out, then a round-trip to the server isn't necessary, the markers are just grabbed from the cache.

In my next post, I'll talk about how PdMarker made it possible to have GMap markers interact with the HTML elements on the rest of the page.

Submitted by michael on Mon, 08/25/2008 - 10:08am
Filed under:

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Parts 2 and 3

Jason,

The short answer to your question is "yes".

The long answer is for you to read parts 2 and 3 of the article for more information on how I put it all together.

http://www.anelloconsulting.com/custom_gmap_solution_automatically_centering_user_location_part_3

http://www.anelloconsulting.com/custom_gmap_solution_dynamically_updated_markers_part_2

-mike

Map Location Based On IP

Did you implement the custom view based on the user's IP address?

Also, I knew that I was going to implement a system that automatically zoomed and centered the map to each user's location based on their IP address, so I didn't want to have to load markers that weren't going to be in the visible area of the map.

I'm very interested in doing that on dmvlocate.com. If you browse to the site you'll immediately see that it is excruciatingly slow due to 1000's of nodes being displayed on the front page in a Google Map.

Any hints or suggestions you could throw my way would be very, very much appreciated.