Google Maps plugin for DikuWiki

Download and Installation

Download the package from here (7.21 KiB, 92 downloads) and unpack it to your dokuwiki installation e.g. to wiki/lib/plugins/.

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 of all possible options:

{{googlemaps>address1;address2;address3[zoom=16,width=800,height=600,size=small,control=hierarchical,overviewmap=true,type=embedded]|alternative text}}

Google maps plugin can generate google maps in embedded frame (when type=embedded) or as link to google maps (when type is not set (default)).

Alternative text may include any other inline formatting supported by dokuwiki, and is rendered only for default type (if one specify alternative text then this type is forced, as there is no opportunity to render it in embedded mode). If alternative text is skipped then address is used as a link text.

embedded type supports several addresses to be marked on Google Maps (only first address will be used in default type). It is advised not to specify zoom in this case, which will be set automatically to optimal value to display all address markers on the map.

Parameters

Parameter name Possible valid values
zoom number from 1 to 19 which specifies the initial zoom; if not defined, then it is set to optimal value
width size in px – the width of embedded frame (overrides the default parameter value)
height size in px – the height of embedded frame (overrides the default parameter value)
size small (default) – generate small size window with limited set of controls
large – generate large size window with all controls
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”
all – 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
type unset (default) – render the link to google maps
embedded – render embedded frame with google maps

Default options

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.
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

Demonstration

The following wiki-code

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

produces the following result:

The sample result of plugin's output

You may try in playground.

Configuration

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

  • Register your website here and receive you unique Google API key.
  • Modify the configuration file and put a newly generated key to google_api_key.

Documentation

Source

syntax/maps.php

syntax/maps.php source code

<?php
/**
 * Plugin google_maps: Generates embedded Google Maps frame or link to Google Maps.
 *
 * @license    GPLv2 (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
{
  private $RE_NON_SYNTAX_SEQ = '[^\[\]{}|]+';
  private $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'    => '2016-09-20',
      'name'    => 'Google Maps Plugin',
      'desc'    => 'Adds a Google Maps frame
         syntax: {{googlemaps>address1;address2;address3[zoom=16,size=small,control=hierarchical,overviewmap=true,width=800,height=600,type=embedded]|alternative 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, Doku_Handler $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))
        {
          return array('');  // this is an error
        }
 
        $options = array();
 
        if (isset($matches[2]))
        {
          $entries = explode(',', $matches[2]);
 
          foreach ($entries as $entry)
          {
            $key_value = explode('=', $entry);
 
            $options[trim($key_value[0])] = trim($key_value[1]);
          }
        }
 
        return array($state, array($matches[1], &$options));
    }
 
    return array($state, $match);
  }
 
  function render($mode, Doku_Renderer $renderer, $data)
  {
    if ($mode == 'xhtml')
    {
      list($state, $match) = $data;
 
      switch($state)
      {
        case DOKU_LEXER_SPECIAL:
        case DOKU_LEXER_ENTER:
          list($text, $options) = $match;
 
          // All locations are in this array:
          $locations = array();
          $i = 0;
 
          foreach (explode(";", $text) as $q)
          {
            $q = trim($q);
            if (strlen($q))
            {
              $locations[$i++] = htmlspecialchars(html_entity_decode($q));
            }
          }
 
          // This type is available only in DOKU_LEXER_SPECIAL state:
          if ($state == DOKU_LEXER_SPECIAL && $options['type'] == 'embedded')
          {
            // Dynamic injection of this script via JS causes FF to hang, so we have to include it for each map:
            $renderer->doc .= "\n<script type='text/javascript' src='//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";
 
            // Embedded div:
            $renderer->doc .= "\n<div class='gmaps_frame' style='width: $width; height: $height'";
 
            foreach ($locations as $i => $q)
            {
              $renderer->doc .= " location$i='$q'";
            }
 
            // Copy values into attributes:
            foreach (array('size', 'control', 'overviewmap', 'zoom') as $attr_name)
            {
              $attr_value = $this->getConfigValue($options, $attr_name);
 
              if (strlen($attr_value))
              {
                $renderer->doc .= ' ' . $attr_name . '="' . $attr_value . '"';
              }
            }
 
            // Important to leave one hanging node inside <div>, otherwise maps start overlappig.
            $renderer->doc .= '></div>';
 
            return true;
          }
 
          // If we are here it means:
          // * state == DOKU_LEXER_SPECIAL and type != embedded ==> we render a link with a text equal to address, as there is no alternative text in this state
          // * state == DOKU_LEXER_ENTER   and type != embedded ==> we start rendering a link; the alternative text will be rendered by dokuwiki renderer and may include any formatting
          // * state == DOKU_LEXER_ENTER   and type == embedded ==> the is unsupported combination, but we render a link the same as with type != embedded
 
          // 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 = "//maps.google.com/maps?q=$locations[0]$params";
 
          // External link:
          $renderer->doc .= "<a href='$url' class='gmaps_link'>";
 
          if ($state == DOKU_LEXER_SPECIAL)
          {
             $renderer->doc .= "$text</a>";
          }
 
          return true;
 
        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

script.js source code

/**
 * Plugin google_maps: Generates embedded Google Maps frame or link to Google Maps.
 *
 * @license    GPLv2 (http://www.gnu.org/licenses/gpl.html)
 * @author     Dmitry Katsubo <dma_k@mail.ru>
 */
 
(function() {
// Globals:
var GMAPS_MAX_RETRY_COUNT = 5;
var GMAPS_RETRY_DELAY = 100;
var GMAPS_MAX_GEO_RESULTS = 1;
var GMAPS_GEOCODER = null;
 
/*
 * This function creates a new marker with a given HTML shown when a marker is clicked.
 */
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;
}
 
/*
 * This recursive function sends an ansynchronous query to Google GeoCoder and marks results on the map.
 */
function queryGoogleGeo(map, bounds, locations, index, zoom, retry)
{
  if (GMAPS_GEOCODER == null)
  {
    // Can be initialized only at this point, as Google libraries should have been included:
    GMAPS_GEOCODER = new GClientGeocoder();
  }
 
  GMAPS_GEOCODER.getLocations(locations[index],
    function generateMarkersFromGoogleGeoResult(response)
    {
      // Was not able to locate any data:
      if (response == null)
      {
        alert("No response from GeoCoder for location " + locations[index] + ". Giving up.");
        return;
      }
      else if (response.Status.code == 602)
      {
        if (retry++ >= GMAPS_MAX_RETRY_COUNT)
        {
          alert("The maximum amount of retries (" + GMAPS_MAX_RETRY_COUNT + ") has been reached for location " + locations[index] + ". Giving up.");
          return;
        }
 
        setTimeout(queryGoogleGeo, GMAPS_RETRY_DELAY, map, bounds, locations, index, zoom, retry);
        return;
      }
      else if (response.Status.code != 200)
      {
        alert("Invalid response code (" + response.Status.code + ") from GeoCoder for location " + locations[index] + ". Giving up.");
        return;
      }
 
        var places = response.Placemark;
 
      for (var i = 0; i < places.length && i < GMAPS_MAX_GEO_RESULTS; i++)
        {
          var place = places[i];
          var point = new GLatLng(place.Point.coordinates[1], place.Point.coordinates[0]);
 
          bounds.extend(point);
 
          map.addOverlay(createMarker(point, '<div class="gmaps_marker"><strong>' + place.address + '</strong><br/>'
            + place.AddressDetails.Country.CountryNameCode
          ));
        }
 
      if (index == locations.length - 1)
      {
        if (zoom == null)
        {
          if (!bounds.isEmpty())
          {
            // We select the best zoom for the boundary:
            zoom = map.getBoundsZoomLevel(bounds);
          }
        }
        else
        {
          // zoom is required to be an integer:
          zoom = parseInt(zoom);
        }
 
        map.setCenter(bounds.getCenter(), zoom);
      }
      else
      {
        // Query recuresively other locations:
        queryGoogleGeo(map, bounds, locations, index + 1, zoom, retry);
      }
    });
}
 
/**
 * Initialisation function. Creates Gmap objects and loads Geo information.
 */
function loadMaps()
{
  jQuery('div.gmaps_frame').each(function() {
    var attrs = this.attributes;
 
      // Create a map:
    var map = new GMap2(this);
      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 == 'all')
        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 locations = new Array();
 
      var n = 0;
      while (true)
      {
        if (attrs['location' + n] == null)
        {
          break;
        }
 
        locations[n] = attrs['location' + n].value;
        n++;
      }
 
    queryGoogleGeo(map, new GLatLngBounds(), locations, 0, attrs.zoom == null ? null : attrs.zoom.value, 0);
  });
  }
 
// A special Wiki-wide function, defined in lib/scripts/events.js:
jQuery(loadMaps);
})();

style.css

style.css source code

a.gmaps_link {
  background: transparent url(gmaps_link.png) no-repeat left center; /* "top" does not look nicely in headers; "center" creates artifacts when link is split into several lines */
  padding: 1px 0px 1px 12px;
}
 
div.gmaps_frame {
  border: 1px solid __border__;
}
 
div.gmaps_marker {
  font-size: 90%;
}

The latest source code can be taken from Github.

Release History

  • 2008-03-17 (r05) — Initial version.
  • 2008-04-12 (4.48 KiB, 267 downloads)
  • 2008-10-03 (r47) (4.75 KiB, 273 downloads) — Plugin was refactored to allow formatting in comment. Also aligned in compliance with common practices how to process the flow.
  • 2010-10-15 (r205) (7.17 KiB, 437 downloads) — Three google-related plugins are merged into one bundle. Multiple addresses support was added.
  • 2016-09-20 (7.21 KiB, 92 downloads) — HTTPS fix. API adapted for “Elenor of Tsort” DokuWiki release.

Bugs and ToDo

Tested with IE v11, FF v42 for Windows, Chrome 53.0.2785.116 for Windows, Chrome 53.0.2785.124 for Android. No bugs at the moment.

There is nothing right now in my ToDo list. Please, add comment to DokuWiki.

plugin/google_maps.txt · Last modified: 2010/10/18 13:40 by dmitry
 
 
Recent changes RSS feed Driven by DokuWiki