Plugin google_maps for DikuWiki

Description & Requirements

Plugin allows to embed Google map frames to the page or create external links to Google Maps service. Useful when listing addresses. Works with any browser, where Google Maps works.

Syntax

Complete list:

{{googlemaps>address[zoom=16,size=small,control=hierarchical,overviewmap=true,width=800,height=600,type=internal]|alternate text}}

where:

zoom,size,control,overviewmap see in section below
type internal – show google maps in embedded frame
(default) – generate link to google maps

If alternate text is skipped then address is used.

Default parameters

Parameter name Possible valid values
google_api_key Valid Google API key for the current site. See below for more details about how to generate it.
size small (default) – generate small size window
large – generate large size window
The sizes for the frames are taken from small_* and large_* parameters below.
control hierarchical (default) – show two buttons in top-right corner: «Map» and «Satellite»
default – show three buttons: «Map», «Satellite» and «Hybrid»
none – show no buttons
overviewmap true (default) – show the link to overview map in right-bottom corner
false – do not show overview map
small_width (default is 425) size in px – the width of small frame, if size=small was specified
small_height (default is 350) size in px – the height of small frame, if size=small was specified
large_width (default is 550) size in px – the width of large frame, if size=large was specified
large_height (default is 450) size in px – the height of large frame, if size=large was specified
zoom (default is 15) number from 1 to 19 which specifies the initial zoom

Demonstration

The following wiki-code

{{googlemaps>Gomel, Belarus|I was ''born'' here}}.
I lived here: {{googlemaps>Tuinstraat 23, Delft, NL[type=internal]}}

produces the following result:

The sample result of plugin's output

You may try in playground.

Installation

Download the package from here and unpack it to your dokuwiki installation e.g. to wiki/lib/plugins/.

Configuration

Important is to register your site at Google to receive an access to Google services. Registration is free:

Documentation

Source

syntax.php source code

<?php
/**
 * Plugin google_maps: Generates external or internal link to Google Maps.
 *
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Dmitry Katsubo <dma_k@mail.ru>
 */
 
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'syntax.php');
 
/**
 * All DokuWiki plugins to extend the parser/rendering mechanism
 * need to inherit from this class
 */
class syntax_plugin_google_maps extends DokuWiki_Syntax_Plugin
{
    var $re_non_syntax_seq = '[^\[\]{}|]+';
    var $re_plugin_body;
 
    function syntax_plugin_google_maps()
    {
        $this->re_plugin_body = "{$this->re_non_syntax_seq}(?:\\[{$this->re_non_syntax_seq}\\])?";
    }
 
    function getInfo()
    {
        return array(
            'author'    => 'Dmitry Katsubo',
            'email'        => 'dma_k@mail.ru',
            'date'        => '2008-10-03',
            'name'        => 'Google Maps Plugin',
            'desc'        => 'Adds a Google Maps frame
                 syntax: {{googlemaps>address[zoom=16,size=small,control=hierarchical,overviewmap=true,width=800,height=600,type=internal]|alternate text}}',
            'url'    => 'http://centurion.dynalias.com/wiki/plugin/google_maps',
        );
    }
 
    function getAllowedTypes()
    {
        return array('formatting');
    }
 
    function getType()
    {
        return 'substition';
    }
 
    function getSort()
    {
        return 159;
    }
 
    function connectTo($mode)
    {
        $this->Lexer->addSpecialPattern("{{googlemaps>{$this->re_plugin_body}}}", $mode, 'plugin_google_maps');
        $this->Lexer->addEntryPattern("{{googlemaps>{$this->re_plugin_body}\|(?={$this->re_non_syntax_seq}}})", $mode, 'plugin_google_maps');
    }
 
    function postConnect()
    {
        $this->Lexer->addExitPattern("}}", 'plugin_google_maps');
    }
 
    private function getConfigValue($options, $option_name, $config_prefix = null)
    {
        // Also escape HTML to protect the page:
        return(htmlspecialchars(
            isset($options[$option_name]) ?
            $options[$option_name] :
            $this->getConf($config_prefix . $option_name)
        ));
    }
 
    function handle($match, $state, $pos, &$handler)
    {
        switch ($state)
        {
            case DOKU_LEXER_SPECIAL:
            case DOKU_LEXER_ENTER:
                $matches = array();
 
                if (!preg_match("/{{googlemaps>({$this->re_non_syntax_seq})(?:\\[({$this->re_non_syntax_seq})\\])?/", $match, $matches))
                {
                    // syslog(LOG_DEBUG, "Input $match cannot be matched against /{{googlemaps>{$this->re_plugin_body}");
                    return array('');  // this is an error
                }
 
                $options = array();
 
                if (isset($matches[2]))
                {
                    preg_replace('/\s+/', '', $matches[2]);
                    $entries = explode(',', $matches[2]);
 
                    foreach ($entries as $entry)
                    {
                        $key_value = explode('=', $entry);
 
                        $options[$key_value[0]] = $key_value[1];
                    }
                }
 
                return array($state, array($matches[1], &$options));
        }
 
        return array($state, $match);
    }
 
    function render($mode, &$renderer, $data)
    {
        if ($mode == 'xhtml')
        {
            list($state, $match) = $data;
 
            switch($state)
            {
                case DOKU_LEXER_SPECIAL:
                case DOKU_LEXER_ENTER:
                    list($text, $options) = $match;
 
                    $query = htmlspecialchars(html_entity_decode(trim($text)));
 
                    // This type is available only in DOKU_LEXER_SPECIAL state:
                    if ($state == DOKU_LEXER_SPECIAL && $options['type'] == 'internal')
                    {
                        // Injection of this script causes FF to hang. so we have to generate it for each map:
                        $renderer->doc .= "\n<script type='text/javascript' src='http://maps.google.com/maps?file=api&v=2.x&key=" . $this->getConf('google_api_key') . "'></script>";
 
                        // Default values:
                        $size            = $this->getConfigValue($options, 'size');
                        $width          = $this->getConfigValue($options, 'width', $size . '_') . "px";
                        $height         = $this->getConfigValue($options, 'height', $size . '_') . "px";
 
                        // Internal div:
                        $renderer->doc .= "\n<div class='gmapsint' style='width: $width; height: $height' query='$query' ";
 
                        // Copy values into attributes:
                        foreach (array('size', 'control', 'overviewmap', 'zoom') as $attr_name)
                        {
                            $renderer->doc .= " $attr_name='" . $this->getConfigValue($options, $attr_name) . "'";
                        }
 
                        // Important to leave one hanging node inside <div>, otherwise maps start overlappig.
                        // That is done implicitly here.
                        $renderer->doc .= '></div>';
 
                        return true;
                    }
 
                    if ($options['type'] != 'internal')
                    {
                        // Concat params:
                        $params = '&';
                        // If not defined, Google Maps engine will automatically select the best zoom:
                        if ($options['zoom'])
                        {
                            $params .= "z=" . $options['zoom'];
                        }
 
                        // Query is already escaped, params are taken from options:
                        $url = "http://maps.google.com/maps?q=$query$params";
 
                        // External link:
                        $renderer->doc .= "<a href='$url' class='gmapsext'>";
 
                        if ($state == DOKU_LEXER_SPECIAL)
                        {
                             $renderer->doc .= "$text</a>";
                        }
 
                        return true;
                    }
 
                    return false;
 
                case DOKU_LEXER_UNMATCHED:
                    $renderer->doc .= $renderer->_xmlEntities($match);
                    return true;
 
                case DOKU_LEXER_EXIT:
                    $renderer->doc .= '</a>';
                    return true;
 
                default:
                    //$renderer->doc .= "<div class='error'>Cannot handle mode $style</div>";
            }
        }
 
        return false;
    }
}
?>

script.js source code

var max_geo_results = 5;
 
function createMarker(point, desc)
{
  var marker = new GMarker(point);
 
  // Note: Without wrapping into a function, listeners are added to the same objects!
  GEvent.addListener(marker, "click", function() {
    marker.openInfoWindowHtml(desc);
  });
 
  return marker;
}
 
function queryGoogleGeo(map, query, zoom)
{
  var geocoder = new GClientGeocoder();
 
  geocoder.getLocations(query,
    function generateMarkersFromGoogleGeoResult(response)
    {
      // Was not able to locate any data:
      if (response == null || response.Status.code != 200) {
        alert("Sorry, we were unable to locate " + query + " address");
        return;
      }
 
      var places = response.Placemark;
 
      for (var i = 0; i < places.length && i < max_geo_results; i++) {
        var place = places[i];
        var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
 
        if (i == 0) {
          map.setCenter(point, zoom);
        }
 
        map.addOverlay(createMarker(point, '<div class="gmapsmarker"><strong>' + place.address + '</strong><br/>'
          + place.AddressDetails.Country.CountryNameCode
        ));
      }
    });
}
 
/**
 * Initialisation function. Creates Gmap objects and loads Geo information.
 */
function loadMaps()
{
  var headNode = document.getElementsByTagName("head").item(0);
  var divNodes = document.body.getElementsByTagName('div');
 
  for (var i = 0; i < divNodes.length; i++) {
    if (divNodes[i].className.match(/\bgmapsint\b/)) {
      var attrs = divNodes[i].attributes;
 
      // Create a map:
      var map = new GMap2(divNodes[i]);
      map.setCenter(new GLatLng(34, 0), 1); // default point
 
      // left-top navigator and zoomer
      if (attrs.size.value == 'small')
        map.addControl(new GSmallMapControl());
      else
      if (attrs.size.value == 'large')
        map.addControl(new GLargeMapControl());
 
      // right-top map type switch buttons
      if (attrs.control.value == 'hierarchical')
        map.addControl(new GHierarchicalMapTypeControl());
      else
      if (attrs.control.value == 'default')
        map.addControl(new GMapTypeControl());
 
      // mini-map in the bottom-right corner
      if (attrs.overviewmap.value == 'true') {
        var overviewMap = new GOverviewMapControl();
        map.addControl(overviewMap);
        overviewMap.hide();
      }
 
      map.enableScrollWheelZoom();
 
      var query = attrs.query.value;
      var zoom = parseInt(attrs.zoom.value);
 
      //queryGeonames(map, query, zoom);
      queryGoogleGeo(map, query, zoom);
    }
  }
}
 
// A special Wiki-wide function, defined in lib/scripts/events.js:
addInitEvent(loadMaps);

style.css source code

a.gmapsext {
  background: transparent url(gmaps_link.png) no-repeat left center;
  padding: 1px 0px 1px 12px;
}
 
div.gmapsint {
  border: 1px solid __border__;
}
 
div.gmapsmarker {
//  margin-top: 10px;
  font-size: 90%;
}

The latest source code snapshot can be taken from SVN: svn co https://centurion.dynalias.com/svn/public/trunk/web/dokuwiki/lib/plugins/google_maps/

Revision History

ToDo

There is nothing right now in my ToDo list, as the aim was achieved. Please, send your suggestions to dma_k@mail.ru.

Bugs

Tested with IE 6.0 SP1, FF 2.0.0.11. No bugs at the moment. Please, report bugs or feature requests to JIRA.