GoogleMapAPIv3 Module for Drupal

Jim Salmons's picture

Although it may never see the light of day as a published contributed module at www.Drupal.org, I welcome anyone interested to download, use, improve, and spread around my GoogleMapAPIv3 module. For those who just want to grab and run; you'll need these two files:

The README in the module subdirectory will tell you all you need to know to get going.

For those who want to know more, keep reading.

A Bit of www.DropaBalm.com Background

GoogleMapping_at_DropaBalm.png
Click to open this page at the DropaBalm.com demo site.

Timlynn and I are working on a very interesting new 'Portfolio Life' business venture at www.DropaBalm.com. In a nutshell, we will tap the power of story and social networking dynamics to encourage the gifting of lip balms among friends, family, and others. The idea is to have fun while helping some great non-profit organizations and their worthy causes.

Our base platform is the venerable and potent Drupal content management system and its equally potent and flexible Ubercart ecommerce contributed super-module. (Ubercart itself consists of a pile of individual modules and noone would likely do an Ubercart-powered site without adding even more great Ubercart core-extending modules.)

Of course the challenge is to balance ease of building with deployed performance. It may be easy to 'get where you want to be' by cobbling together a mammoth collection of contributed modules. But you may also wind up with a platform that has poor performance because you also have a large collection of features that you never need or use. Some modules are well-designed to load 'just enough' functionality as you use them. Many, especially older ones, are not.

This is the dilemma I faced when deciding how to add Google Maps to the 'Story-Net' (story-driven social network gaming) features at www.DropaBalm.com. There is a large collection of contributed models at www.Drupal.org that provide all manner of Google Map features. Many leverage the power of CCK (Content Construction Kit) and Views to provide flexible, no-programming mapping features to your Drupal site.

But our requirements are different. As I'll relate in a later post, we're using a MySQL-friendly (we're actually using MariaDB) database plug-in/engine, OQGraph, to provide SQL-friendly graph algorithms to work in concert with our location-based mapping of social networks. This is sufficiently atypical of a basic Drupal CMS site that it is unrealistic to expect to cobble this capability together with CCK, Views and other contributed modules.

In addition, Version 2 of the Google Maps Javascript API is now deprecated in favor of the more flexible and easier-to-use Version 3. Unfortunately, most of the Drupal GoogleMap contributed modules are built on the Version 2 API and have not been updated to Version 3. There is, however, a widely-used and freely available PHP class – Brad Wedell's excellent php-google-map-api – that provides PHP developers a means to use the Version 3 Google Map API. So, to get lean and focused Google Maps using the version 3 API, I created this very limited-purpose module.

To be fully transparent, I doubt that I will continue using this module further into evolution of www.DropaBalm.com. I expect to migrate to the Jefferson Institute's remarkable collection of data visualization modules/framework, VIDI. But this will be the subject of another (and later) post.

A No-frills Drupal Module for Google Maps using Javascript v.3 API

As mentioned and linked above, GoogleMapAPIv3 is available here as a free, use at your own risk and reward module. There is already at least one Drupal contributed module project that purports to be in the process of providing Version 3 Javascript API features. So I am not sure if this module will live long enough and have a niche to be published. In the meantime, for those developers needed to scratch this itch as I did, here's what I have to offer... (The rest of this post is essentially a lightly formatted version of the module's README file.)

GoogleMapAPIv3 Module README

This module provides a Drupal-friendly wrapper on Brad Wedell's PHP GoogleMapAPI class with Javascript V.3 support.

Features

  1. A minimal wrapper on the GoogleMapAPI class. No admin UI, just an API for using this class in your themes and/or custom code.
  2. Allows Drupal's database functions to handle database connection.
  3. Provides a Drupal-based geocode cache.
  4. Provides a means to work-around or fix the issue with "double ?" (more than one question mark character) in a Javascript include reference in theme templates and/or custom code. (See Installation Instructions below for instructions on getting this to work.)

Installation Instructions

  1. Download the GoogleMapAPIv3 module.
  2. Unzip it into sites/all/modules and enable it in Drupal.
  3. Get the GoogleMap.php file (and optionally the JSMin.php companion file) from Brad Wedell or use my tweaked version (see below) from either:

    Once downloaded, place these files in the empty /googlemapapiv3/GoogleMap subdirectory of this module.

  4. BEFORE referencing this module's API in your theme templates or custom code, you need to tweak any theme template through which you will include a GoogleMapAPIv3 map. At a minimum, this means a slight tweak to your page.tpl.php file. See the Fix Double-? Issue below.
  5. Once you have the module enabled and one of the 'double-?' work-arounds in place, you can simply use Brad Wedell's API documentation to include PHP-based Javascript generating code that uses the Version 3 GoogleMap API in your themes or custom modules.

Fix Double-? Issue

One nice thing about the V.3 GoogleMap API is that API keys are no longer required. However, for licensing reasons with its third-party partners providing geocoding information, every call into the GoogleAPI needs to have – whether needed or not – the GPS-related 'sensor' parameter, e.g. "http://maps.google.com/maps/api/js?sensor=false".

A problem you will have as a consequence of this is related to Drupal's (at least in Drupal 6.x) cache-triggering mechanism. The '?x' suffix appended by Drupal's core system to any call that includes a Javascript or CSS file ensures that the most current version of the file is accessed rather than a cached version. Obviously, this throws a monkey-wrench into situations where the reference being (again) suffixed already includes a '?'-parameter portion in the CSS or Javascript include being processed. The second '?' should, in this context, be an '&' rather than a '?'. (This long-standing issue has, apparently, been fixed in Drupal 7.)

In addition, using drupal_add_js, such as:

drupal_add_js('http://maps.google.com/maps/api/js?sensor=false', 'module',
  'header', FALSE, TRUE, FALSE);

can be problematic. Not only would this be hit with the 'double ?' issue, the 'http://' protocol reference to an external script can result in the string being prefixed with a '/' character as drupal_add_js assumes you are using a relatvie reference to a file on your server.

So you have two options:

  • Option A. Simply include the needed script including reference in your template file by adding the needed line in the header. For example, in page.tpl.php just after the CSS and Javascript script includes of the theme-provided variable contents add this:

    <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

  • Option B. Replace the 'scripts' variable substitution line in your template. Again for example, in page.tpl.php, change this line:

    <?php print $scripts; ?>

    to this:

    <?php print preg_replace('%(^.*<.*?)/(http.*?\?.*)\?(.*?>.*)%im',
      '$1$2&$3', $scripts); ?>

    which uses a regular expression to fix both the external file and double-? issues.

About the included files

When you download the currently available GoogleMapAPI source via www.code.google.com/p/php-google-map-api/ there may be updates included so see the Project's documentation. Note, however, that the version of GoogleMapAPI.php and JSMin.php downloadable from Sohodojo.biz are slightly modified to include these four differences:

  • The file-ending '?>' PHP closing tag is removed as per the Drupal (and PHP.net) best practice.
  • The polyline-handling methods have been tweaked to recognize and appropriately implement the 'geodesic' option in V3's PolyLineOptions API.
  • A minor bug is fixed to enable shadow files for custom markers to display.
  • I've included mobcom's tweak to autoclose infowindows as described in this Google Code project post.

All other needed (and important but not actually contributing to core functionality) tweaks to the GoogleMapAPI class are made in the module as overrides in the GoogleMapAPIv3 subclass which is implemented in the module.

Essentially, these overrides are needed to massage the Javascript and page-rendering generated code to be Drupal-friendly. That is, since the output of the 'stock' GoogleMapAPI class must pass through Drupal's various hook-based 'gauntlet', some non-essential bits needed to be commented out to keep them from being visible on a Drupal page that includes a map generated by the original GoogleMapAPI class.

How to Use

The basic process of adding a GoogleMapAPIv3 map to your content is:

  1. Create the Map object, do a bit of stock prep to ensure the rendered page will display the map.
  2. Pull whatever Drupal data together than you need to build your map.
  3. Use the PHP-based object methods to push your Drupal data through the PHP object into what will become the Javascript generated by the PHP object that will be run client-side on your output page.

Here's a skeletal cookbook of what the essentials of this look like:

  $my_map = new GoogleMapAPIv3();

  // Note: You can do whatever you need to do to pull your map together right
  // here. But let's push it down into a function call (see below).
  $themed_content = pull_my_map_together($my_map);

  // Add relevant Javascript to the header needed to render a client-side map.
  // Note: This is not needed if you hard-code add this script reference in your
  // page.tpl.php template file. If, however, you are using the regex fix so that
  // this is only added on-demand as needed, then include...
  drupal_add_js('http://maps.google.com/maps/api/js?sensor=false', 'module', 'header', FALSE, TRUE, FALSE);

  // Regardless of whether you use Option A or B to get the Google JS V3 API 
  // active, you will need this: 
  drupal_add_js($sn_map->getMapJS(), 'inline', 'header', FALSE, TRUE, FALSE);

  // Now the onload function which will take care of drawing the map.
  drupal_add_js('Drupal.behaviors.printMapOnLoad = function(context) 
    {this.onLoad' . $my_map->map_id . '();}' , 'inline');

  // Add the empty div where the map will be rendered client-side:
  $themed_content .= '<div id="map" style="width: 500px; height: 500px; position:relative;"></div>';

  return $themed_content;

And here is a sample function called to add bits to the Map object:

  function pull_my_map_together(&$my_map) {
    // GoogleMap-related vars, etc.
    $color = 'red';
    $line_weight = 4;
    $line_opacity = '';
    $geodesic = true;
  
    // Prep the custom marker icon (adjust to your need)...
    $marker_icon = base_path() . path_to_theme() . '/images/marker_icons/marker.png';
    $icon_shadow = base_path() . path_to_theme() . '/images/marker_icons/shadow-marker.png';
    // Icon and Infowindow anchor parameters depend on your needs...
    $icon_key = $my_map->setMarkerIconKey($marker_icon, $icon_shadow, 20, 56, 20, 56);
  
    // You will likely have to do way more than this to pull together the 
    // location data you want to show...
    $result = db_query("SELECT from_address, to_address FROM {some_drupal_table}");
  
    while ($locationdata = db_fetch_object($result)) {
      // Cache the address if it's not already known...
      if (!$my_map->getCache($locationdata->from_address)) {
        $lonlat = $my_map->geoGetCoords($locationdata->from_address);
        $my_map->putCache($locationdata->from_address, $lonlat['lon'], $lonlat['lat']);
      };
      // Pull together the infobox marker verbiage...
      $infowin_text = "This is infowin content...";
      // Add the first map marker...
      $my_map->addMarkerByAddress($locationdata->from_address, 
        'From address title', $infowin_text, '', $marker_icon, $icon_shadow);
  
      if (!$my_map->getCache($locationdata->to_address)) {
        $lonlat = $my_map->geoGetCoords($locationdata->to_address);
        $my_map->putCache($locationdata->to_address, $lonlat['lon'], $lonlat['lat']);
      };
  
      // Add the second map marker...
      $my_map->addMarkerByAddress($locationdata->to_address, 
        'To address title', $infowin_text, $marker_icon, $icon_shadow);
      // Now add a polyline to the map connecting the two locations...
      $my_map->addPolyLineByAddress($locationdata->from_address, 
        $locationdata->to_address, false, $color, $line_weight, 
        $line_opacity, $geodesic);
    }
  };

This should be enough to get you going...

Happy Mapping,
--Sohodojo Jim--
AKA DropaBalm Jambo :-)

Section: