You are here

gmap.module in GMap Module 6.2

Same filename and directory in other branches
  1. 5 gmap.module
  2. 6 gmap.module
  3. 7.2 gmap.module
  4. 7 gmap.module

GMap -- Routines to use the Google Maps API in Drupal.

File

gmap.module
View source
<?php

/**
 * @file
 * GMap -- Routines to use the Google Maps API in Drupal.
 */

/**
 * Define the Google Maps API version being used.
 *
 * Current minimum version: 2.113
 *
 * Minimum version last changed on: June 9 2008
 *
 * Reason: G_SATELLITE_3D_MAP support in gmap_addons. See http://code.google.com/apis/earth/.
 *
 * See http://groups.google.com/group/Google-Maps-API/web/api-version-changes
 * for details on using other version numbers.
 */
define('GMAP_API_VERSION', '3');

/**
 * Get the defaults for a gmap.
 */
function gmap_defaults() {
  $defaults = array(
    'width' => '300px',
    'height' => '200px',
    'zoom' => 3,
    'maxzoom' => 14,
    'controltype' => 'Small',
    'align' => 'None',
    'latlong' => '40,0',
    'maptype' => 'Map',
    'mtc' => 'standard',
    'baselayers' => array(
      'Map',
      'Satellite',
      'Hybrid',
    ),
    'styles' => array(
      'line_default' => array(
        '0000ff',
        5,
        45,
        '',
        0,
        0,
      ),
      'poly_default' => array(
        '000000',
        3,
        25,
        'ff0000',
        45,
      ),
      'highlight_color' => 'ff0000',
    ),
    'line_colors' => array(
      '#00cc00',
      '#ff0000',
      '#0000ff',
    ),
  );
  $defaults['behavior'] = array();
  $m = array();
  $behaviors = gmap_module_invoke('behaviors', $m);
  foreach ($behaviors as $k => $v) {
    $defaults['behavior'][$k] = $v['default'];
  }
  $defaults = array_merge($defaults, variable_get('gmap_default', array()));
  return $defaults;
}

/**
 * Implementation of hook_theme().
 */
function gmap_theme() {
  return array(
    'views_view_gmap' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_views_marker_label' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_marker_popup' => array(
      'arguments' => array(
        'label',
      ),
    ),
    'gmap_overlay_edit' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_macrotext' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_dimension' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_address' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_align' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap_style' => array(
      'arguments' => array(
        'element',
      ),
    ),
    'gmap' => array(
      'arguments' => array(
        'element',
      ),
    ),
  );
}

/**
 * Invokes hook_gmap() in every module.
 *
 * We can't use module_invoke_all() because we pass $map by reference.
 */
function gmap_module_invoke($op, &$map) {
  $return = array();
  foreach (module_implements('gmap') as $module) {
    $function = $module . '_gmap';
    $result = $function($op, $map);
    if (isset($result) && is_array($result)) {
      $return = array_merge_recursive($return, $result);
    }
    elseif (isset($result)) {
      $return[] = $result;
    }
  }
  return $return;
}

/**
 * Implementation of hook_gmap().
 */
function gmap_gmap($op, &$map) {
  switch ($op) {
    case 'macro':
      return array(
        'points' => array(
          'multiple' => TRUE,
        ),
        'markers' => array(
          'multiple' => TRUE,
        ),
        'feed' => array(
          'multiple' => TRUE,
        ),
        'style' => array(
          'multiple' => TRUE,
        ),
      );
    case 'pre_theme_map':
      $path = drupal_get_path('module', 'gmap') . '/js/';

      // Activate markers if needed.
      if (isset($map['behavior']['dynmarkers']) && $map['behavior']['dynmarkers'] || !empty($map['markers'])) {
        static $header_set = FALSE;
        if (!$header_set) {
          $header_set = TRUE;

          // If the user is using private download method, it's up to them to get the path set up.
          if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
            $markerpath = variable_get('gmap_private_markerfile', '');
            if (empty($markerpath) || !file_exists($markerpath)) {
              drupal_set_message(t('GMap marker file settings are not configured properly for Private download method, markers will not work!'), 'error');
            }
            else {
              drupal_add_js($markerpath, 'module', 'header', FALSE, TRUE, FALSE);
            }
          }
          else {
            $markerpath = file_create_path('js');
            if (!$markerpath || !file_exists("{$markerpath}/gmap_markers.js")) {
              gmap_regenerate_markers();
              $markerpath = file_create_path('js');
            }
            drupal_add_js("{$markerpath}/gmap_markers.js", 'module', 'header', FALSE, TRUE, FALSE);
          }
        }
        drupal_add_js($path . 'icon.js');
        drupal_add_js($path . 'marker.js');
        drupal_add_js($path . 'highlight.js');
        $mm = variable_get('gmap_mm_type', 'gmap');

        // If you really really want to override the marker manager, implement
        // this, take $mm by ref, and have fun. --Bdragon
        if (function_exists('_gmap_markermanager_override')) {
          _gmap_markermanager_override($mm);
        }
        drupal_add_js($path . $mm . '_marker.js');
      }
      if (isset($map['behavior']['locpick']) && $map['behavior']['locpick']) {
        drupal_add_js($path . 'locpick.js');
      }
      if (!empty($map['markers']) || !empty($map['lines'])) {
        drupal_add_js($path . 'markerloader_static.js');
      }
      if (!empty($map['shapes'])) {
        drupal_add_js($path . 'shapeloader_static.js');
        drupal_add_js($path . 'gmap_shapes.js');
      }
      if (isset($map['feed']) && is_array($map['feed'])) {
        drupal_add_js($path . 'markerloader_georss.js');
      }
      break;
    case 'macro_multiple':
      return array(
        'points',
        'markers',
        'feed',
        'circle',
        'rpolygon',
        'polygon',
        'line',
        'style',
      );
    case 'behaviors':
      return array(
        'locpick' => array(
          'title' => t('Location chooser'),
          'default' => FALSE,
          'help' => t('Used to activate location choosing using a gmap.'),
          'internal' => TRUE,
        ),
        'nodrag' => array(
          'title' => t('Disable dragging'),
          'default' => FALSE,
          'help' => t('Remove the ability for the user to drag the map. If dragging is disabled, keyboard shortcuts are implicitly disabled.'),
        ),
        'nokeyboard' => array(
          'title' => t('Disable keyboard'),
          'default' => TRUE,
          'help' => t('Disable the keyboard shortcuts.'),
        ),
        'nomousezoom' => array(
          'title' => t('Disable mousezoom'),
          'default' => FALSE,
          'help' => t('Disable using the scroll wheel to zoom the map.'),
        ),
        'nocontzoom' => array(
          'title' => t('Disable Continuous Zoom'),
          'default' => FALSE,
          'help' => t('Disable dynamically resizing images while waiting for tiles to load when zooming.'),
        ),
        'autozoom' => array(
          'title' => t('Use AutoZoom'),
          'default' => FALSE,
          'help' => t('Automatically zoom the map to fit all markers when markers are added.'),
        ),
        'dynmarkers' => array(
          'title' => t('Unconditionally enable marker interface'),
          'default' => FALSE,
          'help' => t('Load the marker loader system even if no markers to load are detected. Useful if you are injecting markers from somewhere else.'),
        ),
        'overview' => array(
          'title' => t('Enable Overview Map'),
          'default' => FALSE,
          'help' => t('Enable the "overview map" in the bottom right corner.'),
          'previewable' => TRUE,
        ),
        /*        'notype' => array(
                    'title' => t('Disable map type control'),
                    'default' => FALSE,
                    'help' => t('Removes the map type control from the upper right corner. Recommended for very narrow maps.'),
                    'previewable' => TRUE,
                  ), */
        'collapsehack' => array(
          'title' => t('Work around bugs when maps appear in collapsible fieldsets'),
          'default' => FALSE,
          'help' => t('Enabling this will work around some issues that can occur when maps appear inside collapsible fieldsets.'),
        ),
        // Note to myself, who keeps forgetting what a scale control actually IS.:
        // |------------  1mi ------------|
        'scale' => array(
          'title' => t('Add scale control to map.'),
          'default' => FALSE,
          'help' => t('Adds a scale control to the map in the default position.'),
          'previewable' => TRUE,
        ),
        'extramarkerevents' => array(
          'title' => t('Enable extra marker events.'),
          'default' => FALSE,
          'help' => t('Used for advanced javascript work, this will enable the <em>mouseovermarker</em>, <em>mouseoutmarker</em>, and <em>dblclickmarker</em> events.'),
          'internal' => TRUE,
        ),
        'clickableshapes' => array(
          'title' => t('Enable clickable shapes.'),
          'default' => FALSE,
          'help' => t('Used for advanced javascript work, this will enable the <em>clickshape</em> event.'),
          'internal' => TRUE,
        ),
        'googlebar' => array(
          'title' => t('Enable Google Bar'),
          'default' => FALSE,
          'help' => t('Enable the "Google Bar" at bottom of the map.'),
          'previewable' => TRUE,
        ),
        'highlight' => array(
          'title' => t('Highlight marker on rollover'),
          'default' => FALSE,
          'help' => t('Highlight marker by creating circle on mouse rollover event.'),
          'previewable' => TRUE,
        ),
      );
      break;
    case 'baselayers':
      $map['Google']['Map'] = array(
        'title' => t('Map: Standard street map.'),
        'default' => TRUE,
        'help' => t('The standard default street map. Internal name: G_NORMAL_MAP'),
      );
      $map['Google']['Satellite'] = array(
        'title' => t('Satellite: Standard satellite map.'),
        'default' => TRUE,
        'help' => t('Satellite view without street overlay. Internal name: G_SATELLITE_MAP'),
      );
      $map['Google']['Hybrid'] = array(
        'title' => t('Hybrid: Hybrid satellite map.'),
        'default' => TRUE,
        'help' => t('Satellite view with street overlay. Internal name: G_HYBRID_MAP'),
      );
      $map['Google']['Physical'] = array(
        'title' => t('Terrain: Physical feature map.'),
        'default' => FALSE,
        'help' => t('Map with physical data (terrain, vegetation.) Internal name: G_PHYSICAL_MAP'),
      );
      break;
  }
}

/**
 * Set up the HTML header for GMap.
 * If you are going to include a custom JS file that extends GMap, you probabaly
 * want to call this first to ensure that the core js files have been added.
 */
function _gmap_doheader() {
  static $gmap_initialized = FALSE;
  if ($gmap_initialized) {
    return;
  }
  $gmap_path = drupal_get_path('module', 'gmap');
  drupal_add_css($gmap_path . '/gmap.css');
  drupal_add_js($gmap_path . '/js/gmap.js');
  $mm = variable_get('gmap_mm_type', 'gmap');
  $mms = variable_get('gmap_markermanager', array());
  if (empty($mms[$mm])) {
    $mms[$mm] = array();
  }

  // If you really really want to override the marker manager, implement
  // this, take $mm by ref, and have fun. --Bdragon
  if (function_exists('_gmap_markermanager_override')) {
    _gmap_markermanager_override($mm, $mms);
  }
  if ($mm == 'clusterer' || $mm == 'clustermarker') {

    // Needed for access to clusterer marker.
    drupal_add_js($gmap_path . '/js/icon.js');
  }
  if (isset($mms[$mm]['filename'])) {
    drupal_add_js($gmap_path . '/thirdparty/' . $mms[$mm]['filename']);
  }
  drupal_add_js($gmap_path . '/js/marker.js');
  drupal_add_js($gmap_path . '/js/highlight.js');
  drupal_add_js($gmap_path . '/js/' . $mm . '_marker.js');
  drupal_add_js(array(
    'gmap_markermanager' => $mms[$mm],
  ), 'setting');

  // @@@
  drupal_add_js($gmap_path . '/js/poly.js');
  global $language;
  $file = 'api/js';
  $query = array(
    'v' => variable_get('gmap_api_version', GMAP_API_VERSION),
    'language' => $language->language,
    'sensor' => 'false',
  );
  if ($key = gmap_get_key()) {
    $query['key'] = $key;
  }
  drupal_set_html_head('<script src="' . check_url(url('https://maps.googleapis.com/maps/' . $file, array(
    'query' => $query,
  ))) . '" type="text/javascript"></script>');
  $gmap_initialized = TRUE;
}

/**
 * Convert a macro string into a GMap array.
 *
 * @param $instring
 *   Macro to process.
 * @param $ver
 *   Version to treat macro as.
 *   Set to 1 when processing very old macros, otherwise leave as is.
 * @return
 *   A GMap array.
 */
function gmap_parse_macro($instring, $ver = 2) {
  require_once drupal_get_path('module', 'gmap') . '/gmap_parse_macro.inc';
  return _gmap_parse_macro($instring, $ver);
}

/**
 * Theme a marker popup.
 * This will get called for markers embedded in macros.
 * @ingroup themeable
 */
function theme_gmap_marker_popup($label) {
  return $label;
}

/**
 * Location chooser utility function.
 *
 * Creates a map that can be interactively used to fill a form with a
 * location (latitude, longitude and zoom level).
 *
 * Note: This is a utility function designed for location.module, there is no
 * guarantee it will not be removed eventually.
 *
 * @param $map
 * Either a macro to use as the base map for setting a location, or an already set map associative array.
 * @param $form
 * A formset associative array. Cannot be more than one deep.
 * @param $fields
 * An associative array for the field names.  'latitude', 'longitude'=>name of respective array, 'address' is optional.
 * @return
 * A string with the google map code to be inserted onto the page.
 *
 */
function gmap_set_location($map, &$form, $fields) {
  static $ctr = 0;
  $ctr++;
  if (!is_array($map)) {
    $map = array_merge(gmap_defaults(), gmap_parse_macro($map));
  }
  $id = 'loc' . $ctr;
  $map['id'] = $id;

  // This is a locpick map.
  $map['behavior']['locpick'] = TRUE;
  $element = array(
    '#type' => 'gmap',
    '#map' => $map['id'],
    '#settings' => $map,
  );
  $form[$fields['latitude']]['#map'] = $id;
  gmap_widget_setup($form[$fields['latitude']], 'locpick_latitude');
  $form[$fields['longitude']]['#map'] = $id;
  gmap_widget_setup($form[$fields['longitude']], 'locpick_longitude');
  if (isset($fields['address'])) {
    $form[$fields['address']]['#map'] = $id;
    gmap_widget_setup($form[$fields['address']], 'locpick_address');
  }
  return theme('gmap', $element);
}

/**
 * Handle filter preparation.
 */
function _gmap_prepare($intext) {
  $out = FALSE;
  $matches = array();
  preg_match_all('/\\[gmap([^\\[\\]]+ )* \\] /x', $intext, $matches);
  $i = 0;
  while (isset($matches[1][$i])) {
    $out[0][$i] = $matches[0][$i];
    if ($matches[1][$i][0] == '1') {
      $ver = 1;
      $matches[1][$i] = substr($matches[0][$i], 1);
    }
    else {
      $ver = 2;
    }
    $map = array(
      '#settings' => gmap_parse_macro($matches[1][$i], $ver),
    );
    $out[1][$i] = theme('gmap', $map);
    $i++;
  }

  // endwhile process macro
  return $out;
}

/**
 * Make sure a string is a valid css dimension.
 */
function gmap_todim($instring) {
  if (!is_string($instring)) {
    return FALSE;
  }
  $s = strtolower(trim($instring));
  $matches = array();
  if (preg_match('/^([\\d.]+)\\s*(em|ex|px|in|cm|mm|pt|pc|%)$/', $s, $matches)) {
    return $matches[1] . $matches[2];
  }
  else {
    return FALSE;
  }
}

/**
 * Ensure a textfield is a valid css dimension string.
 */
function gmap_dimension_validate(&$elem, &$form_state) {
  $value = gmap_todim($elem['#value']);
  if ($value) {

    // Normalize the css dimension string.
    form_set_value($elem, $value, $form_state);
  }
  else {
    form_error($elem, t('The specified value is not a valid CSS dimension.'));
  }
}

/**
 * Implementation of hook_filter().
 */
function gmap_filter($op, $delta = 0, $format = -1, $text = '') {
  switch ($op) {
    case 'list':
      return array(
        0 => t('GMap macro expander'),
      );
    case 'name':
      return t('Google map filter');
    case 'description':
      return t('Converts a Google Map macro into the HTML required for inserting a Google Map.');
    case 'process':
      $gmaps = _gmap_prepare($text);

      //returns an array of $tables[0] = table macro $table[1]= table html
      if ($gmaps) {

        // there are table macros in this node
        return str_replace($gmaps[0], $gmaps[1], $text);
      }
      else {
        return $text;
      }
    case 'prepare':
      return $text;
    case 'no cache':
      return TRUE;
  }
}

/**
 * Implementation of hook_filter_tips().
 */
function gmap_filter_tips($delta, $format, $long = FALSE) {
  if (user_access('create gmap macro')) {

    // only display macro if user can create one
    return t('Insert Google Map macro.') . '<a href="' . url('map/macro') . '" target="_blank" >' . t('Create a macro') . '</a>';
  }
  else {
    return t('Insert Google Map macro.');
  }
}

/**
 * Implementation of hook_menu().
 */
function gmap_menu() {
  $items['admin/settings/gmap'] = array(
    'title' => 'GMap',
    'description' => 'Configure GMap settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'gmap_admin_settings',
    ),
    'file' => 'gmap_settings_ui.inc',
    'access arguments' => array(
      'administer site configuration',
    ),
    'type' => MENU_NORMAL_ITEM,
  );
  return $items;
}

/**
 * Regenerate the markerdata file.
 */
function gmap_regenerate_markers() {
  $contents = '';

  // Create the js/ within the files folder.
  $jspath = file_create_path('js');
  file_check_directory($jspath, FILE_CREATE_DIRECTORY);
  $contents .= "// GMap marker image data.\n";
  $contents .= "Drupal.gmap.iconpath = " . drupal_to_js(base_path() . variable_get('gmap_markerfiles', drupal_get_path('module', 'gmap') . '/markers')) . ";\n";
  $contents .= "Drupal.gmap.icondata = " . drupal_to_js(gmap_get_icondata(TRUE)) . ";\n";
  file_save_data($contents, "{$jspath}/gmap_markers.js", FILE_EXISTS_REPLACE);

  // Also regenerate the cached marker titles array
  gmap_get_marker_titles(TRUE);
}

/**
 * Implementation of hook_flush_caches().
 */
function gmap_flush_caches() {
  gmap_regenerate_markers();
}

/**
 * Implementation of hook_elements().
 */
function gmap_elements() {
  return array(
    'gmap' => array(
      '#input' => FALSE,
      // This isn't a *form* input!!
      '#settings' => array_merge(gmap_defaults(), array(
        'points' => array(),
        'pointsOverlays' => array(),
        'lines' => array(),
      )),
    ),
    'gmap_macrotext' => array(
      '#input' => TRUE,
      '#gmap_newtype' => 'textarea',
      '#theme' => 'gmap_macrotext',
      '#process' => array(
        'process_gmap_control',
      ),
    ),
    'gmap_overlay_edit' => array(
      '#input' => TRUE,
      '#process' => array(
        'process_gmap_overlay_edit',
      ),
    ),
    'gmap_style' => array(
      '#input' => TRUE,
      '#tree' => TRUE,
      '#gmap_style_type' => 'poly',
      '#process' => array(
        'process_gmap_style',
      ),
    ),
    'gmap_address' => array(
      '#input' => TRUE,
      '#process' => array(
        'process_gmap_address',
      ),
    ),
    'gmap_align' => array(
      '#input' => TRUE,
      '#process' => array(
        'process_gmap_align',
      ),
    ),
    'gmap_latitude' => array(
      '#input' => TRUE,
      '#gmap_newtype' => 'textfield',
      '#process' => array(
        'process_gmap_control',
      ),
    ),
    'gmap_longitude' => array(
      '#input' => TRUE,
      '#gmap_newtype' => 'textfield',
      '#process' => array(
        'process_gmap_control',
      ),
    ),
    'gmap_latlon' => array(
      '#input' => TRUE,
      '#gmap_newtype' => 'textfield',
      '#process' => array(
        'process_gmap_control',
      ),
    ),
    'gmap_markerchooser' => array(
      '#input' => TRUE,
      '#process' => array(
        'process_gmap_markerchooser',
      ),
    ),
    'gmap_dimension' => array(
      '#input' => TRUE,
      '#gmap_newtype' => 'textfield',
      '#process' => array(
        'process_gmap_control',
      ),
      '#element_validate' => array(
        'gmap_dimension_validate',
      ),
    ),
  );
}

/**
 * Generic gmap control #process function.
 */
function process_gmap_control($element, $edit, &$form_state, $complete_form) {
  $control = substr($element['#type'], 5);
  $element['#type'] = $element['#gmap_newtype'];
  unset($element['#gmap_newtype']);
  gmap_widget_setup($element, $control);

  // Attempt to run any #process handlers of our transmogrified type.
  // However, if we aren't the only #process handler, we assume someone else
  // is taking care of setting up any default handlers.
  $chain = FALSE;
  if (count($element['#process']) == 1) {

    // Unset #process so we can pull in the default.
    unset($element['#process']);
    $chain = TRUE;
  }

  // Inherit #input from the new type.
  unset($element['#input']);

  // Merge in the defaults for the target element type.
  $element += _element_info($element['#type']);
  if (isset($element['#input']) && $element['#input']) {
    if (isset($element['#process']) && $chain) {

      // Chain process.
      foreach ($element['#process'] as $process) {
        if (function_exists($process)) {
          $form = $process($element, $edit, $form_state, $complete_form);
        }
      }
    }
  }
  return $element;
}

/**
 * Style fieldset #process function.
 */
function process_gmap_style($element) {
  $isline = $element['#gmap_style_type'] == 'line';

  // Stroke color
  $element[0] = array(
    '#type' => 'textfield',
    '#size' => 6,
    '#maxlength' => 6,
    '#field_prefix' => '#',
    '#title' => t('Stroke color'),
    '#value' => $element['#value'][0],
    '#attributes' => array(
      'class' => 'gmap_style',
    ),
  );

  // Stroke weight
  $element[1] = array(
    '#type' => 'textfield',
    '#title' => t('Stroke weight'),
    '#description' => t('Thickness of line, in pixels.'),
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => t('px'),
    '#value' => $element['#value'][1],
    '#attributes' => array(
      'class' => 'gmap_style',
    ),
  );

  // Stroke opacity
  $element[2] = array(
    '#type' => 'textfield',
    '#title' => t('Stroke opacity'),
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => '%',
    '#value' => $element['#value'][2],
    '#attributes' => array(
      'class' => 'gmap_style',
    ),
  );

  // Fill color
  $element[3] = array(
    '#type' => 'textfield',
    '#title' => t('Fill color'),
    '#description' => t('Hex color value for fill color. Example: #<em>00AA33</em>'),
    '#size' => 6,
    '#maxlength' => 6,
    '#field_prefix' => '#',
    '#value' => $isline ? '' : $element['#value'][3],
    '#disabled' => $isline,
    '#attributes' => array(
      'class' => 'gmap_style',
    ),
  );
  $element[4] = array(
    '#type' => 'textfield',
    '#title' => t('Fill opacity'),
    '#description' => t('Opacity of fill, from 0 to 100%.'),
    '#size' => 3,
    '#maxlength' => 3,
    '#field_suffix' => '%',
    '#value' => $isline ? '' : $element['#value'][4],
    '#disabled' => $isline,
    '#attributes' => array(
      'class' => 'gmap_style',
    ),
  );
  unset($element['#input']);
  $element += _element_info('fieldset');
  return $element;
}

/**
 * Theme a gmap_style fieldset.
 * @ingroup themeable
 */
function theme_gmap_style($element) {

  // Fieldsets print #value at the end, so we need to empty it out.
  // Otherwise, it puts "Array" at the end of the fieldset.
  $element['#value'] = '';
  return theme('fieldset', $element, $element['#children']);
}

/**
 * Overlay editor #process function.
 */
function process_gmap_overlay_edit($element) {

  // Conver the root element into a fieldset.
  $element['#type'] = 'fieldset';
  if (!isset($element['#title'])) {
    $element['#title'] = t('Overlay editor');
  }
  $element['#tree'] = TRUE;
  $element['mapclicktype'] = array(
    '#type' => 'select',
    '#title' => t('Click map'),
    '#map' => $element['#map'],
    '#options' => array(
      'Points' => t('Points'),
      'Lines' => t('Lines'),
      'Circles' => t('Circles'),
      'GPolygon' => t('Filled Polygons'),
    ),
  );
  gmap_widget_setup($element['mapclicktype'], 'overlayedit_mapclicktype');
  $element['markerclicktype'] = array(
    '#type' => 'select',
    '#title' => t('Click marker / segment'),
    '#map' => $element['#map'],
    '#options' => array(
      'Remove' => t('Remove'),
      //      'updatestyle' => t('Update Styles'),
      //      'removestyle' => t('Remove Styles'),
      'Edit Info' => t('Edit Info'),
    ),
  );
  gmap_widget_setup($element['markerclicktype'], 'overlayedit_markerclicktype');
  $element['marker'] = array(
    '#type' => 'select',
    '#map' => $element['#map'],
    '#options' => gmap_get_marker_titles(),
    '#title' => t('Marker'),
    '#theme' => 'gmap_overlay_edit',
  );
  gmap_widget_setup($element['marker'], 'overlayedit');
  $element['linestyle'] = array(
    '#type' => 'gmap_style',
    '#title' => t('Line style'),
    '#gmap_style_type' => 'line',
    '#default_value' => array(
      '0000ff',
      5,
      45,
      '',
      '',
    ),
  );
  gmap_widget_setup($element['linestyle'], 'overlayedit_linestyle', $element['#map']);
  $element['linestyle']['linestyle_apply'] = array(
    '#tree' => FALSE,
    '#type' => 'checkbox',
    '#title' => t('Use for new and changed lines'),
    '#default_value' => FALSE,
  );
  gmap_widget_setup($element['linestyle']['linestyle_apply'], 'overlayedit_linestyle_apply', $element['#map']);
  $element['polystyle'] = array(
    '#type' => 'gmap_style',
    '#title' => t('Polygon style'),
    '#gmap_style_type' => 'poly',
    '#default_value' => array(
      '000000',
      3,
      25,
      'ff0000',
      45,
    ),
  );
  gmap_widget_setup($element['polystyle'], 'overlayedit_polystyle', $element['#map']);
  $element['polystyle']['polystyle_apply'] = array(
    '#tree' => FALSE,
    '#type' => 'checkbox',
    '#title' => t('Use for new and changed polygons'),
    '#default_value' => FALSE,
  );
  gmap_widget_setup($element['polystyle']['polystyle_apply'], 'overlayedit_polystyle_apply', $element['#map']);
  $element += _element_info('fieldset');
  return $element;
}

/**
 * Alignment selector #process function.
 */
function process_gmap_align($element) {
  $element['#type'] = 'select';
  gmap_widget_setup($element, 'align');
  $element['#options'] = drupal_map_assoc(array(
    'None',
    'Right',
    'Left',
    'Center',
  ));
  $element['#theme'] = 'gmap_align';
  $element += _element_info('select');
  return $element;
}

/**
 * Address widget #process function.
 */
function process_gmap_address($element) {
  $element['#type'] = 'textfield';
  gmap_widget_setup($element, 'address');
  $element['#theme'] = 'gmap_address';
  $element += _element_info('textfield');
  return $element;
}

/**
 * Marker chooser #process function.
 */
function process_gmap_markerchooser($element) {
  $element['#type'] = 'select';
  $options = gmap_get_marker_titles();
  if (!isset($element['#required']) || !$element['#required']) {
    $options = array(
      '' => t('Default'),
    ) + $options;
  }
  $element['#options'] = $options;
  $element += _element_info('select');
  return $element;
}

/**
 * Overlay editor theme function.
 * @ingroup themeable
 */
function theme_gmap_overlay_edit($element) {
  $path = drupal_get_path('module', 'gmap');
  drupal_add_js($path . '/js/gmap.js');
  drupal_add_js($path . '/js/gmap_shapes.js');
  drupal_add_js($path . '/js/overlay_edit.js');
  return theme('select', $element);
}

/**
 * Perform some normalization on the map object
 * to prevent errors.
 */
function gmap_map_cleanup(&$map) {

  // Google is picky about this one.
  $map['zoom'] = (int) $map['zoom'];

  // Normalize latitude / longitude
  if ($map['latlong']) {
    $map['latlon'] = $map['latlong'];
    unset($map['latlong']);
  }

  // Normalize extent.
  if (isset($map['extent']) && is_string($map['extent'])) {
    $tmp = explode(',', $map['extent']);
    if (count($tmp) == 4) {

      // String form of extent has x,y, but native array form is lat,lon, so need to flip them a bit.
      $map['extent'] = array(
        array(
          $tmp[1],
          $tmp[0],
        ),
        array(
          $tmp[3],
          $tmp[2],
        ),
      );
    }
    else {

      // Extent was unparseable, don't pass it.
      unset($map['extent']);
    }
  }
  if (isset($map['latlon']) && (!isset($map['latitude']) || !isset($map['longitude']))) {
    list($map['latitude'], $map['longitude']) = explode(',', $map['latlon']);
  }
  unset($map['latlon']);
  foreach ($map['baselayers'] as $k => $v) {
    if (!$v) {
      unset($map['baselayers'][$k]);
    }
  }
}
function theme_gmap_macrotext($element) {
  drupal_add_js(drupal_get_path('module', 'gmap') . '/js/macro.js');

  // @@@
  drupal_add_js(drupal_get_path('module', 'gmap') . '/js/macrobuilder.js');
  return theme('textarea', $element);
}
function theme_gmap_address($element) {
  drupal_add_js(drupal_get_path('module', 'gmap') . '/js/address.js');
  $element['#autocomplete_path'] = '';
  return theme('textfield', $element);
}
function theme_gmap_align($element) {
  drupal_add_js(drupal_get_path('module', 'gmap') . '/js/align.js');
  $element['#multiple'] = FALSE;
  return theme('select', $element);
}

/**
 * Gmap element theme hook
 */
function theme_gmap($element) {

  // Track the mapids we've used already.
  static $mapids = array();
  _gmap_doheader();

  // Convert from raw map array if needed.
  if (!isset($element['#settings'])) {
    $element = array(
      '#settings' => $element,
    );
  }
  $mapid = FALSE;
  if (isset($element['#map']) && $element['#map']) {

    // The default mapid is #map.
    $mapid = $element['#map'];
  }
  if (isset($element['#settings']['id'])) {

    // Settings overrides it.
    $mapid = $element['#settings']['id'];
  }
  if (!$mapid) {

    // Hmm, no mapid. Generate one.
    $mapid = gmap_get_auto_mapid();
  }

  // Push the mapid back into #map.
  $element['#map'] = $mapid;
  gmap_widget_setup($element, 'gmap', $mapid);
  if (!$element['#settings']) {
    $element['#settings'] = array();
  }

  // Push the mapid back into #settings.
  $element['#settings']['id'] = $mapid;
  $mapdefaults = gmap_defaults();
  $map = array_merge($mapdefaults, $element['#settings']);

  // Styles is a subarray.
  if (isset($element['#settings']['styles'])) {
    $map['styles'] = array_merge($mapdefaults['styles'], $element['#settings']['styles']);
  }
  gmap_map_cleanup($map);

  // Add a class around map bubble contents.
  // @@@ Bdragon sez: Becw, this doesn't belong here. Theming needs to get fixed instead..
  if (isset($map['markers'])) {
    foreach ($map['markers'] as $i => $marker) {
      if (isset($marker['text'])) {
        $map['markers'][$i]['text'] = '<div class="gmap-popup">' . $marker['text'] . '</div>';
      }
    }
  }
  switch (strtolower($map['align'])) {
    case 'left':
      $element['#attributes']['class'] .= ' gmap-left';
      break;
    case 'right':
      $element['#attributes']['class'] .= ' gmap-right';
      break;
    case 'center':
    case 'centre':
      $element['#attributes']['class'] .= ' gmap-center';
  }
  $style = array();
  $style[] = 'width: ' . $map['width'];
  $style[] = 'height: ' . $map['height'];
  $element['#attributes']['class'] = trim($element['#attributes']['class'] . ' gmap gmap-map gmap-' . $mapid . '-gmap');

  // Some markup parsers (IE) don't handle empty inners well. Use the space to let users know javascript is required.
  // @@@ Bevan sez: Google static maps could be useful here.
  // @@@ Bdragon sez: Yeah, would be nice, but hard to guarantee functionality. Not everyone uses the static markerloader.
  $o = '<div style="' . implode('; ', $style) . ';" id="' . $element['#id'] . '"' . drupal_attributes($element['#attributes']) . '>' . t('Javascript is required to view this map.') . '</div>';
  gmap_module_invoke('pre_theme_map', $map);
  if (isset($mapids[$element['#map']])) {
    drupal_set_message(t('Duplicate map detected! GMap does not support multiplexing maps onto one MapID! GMap MapID: %mapid', array(
      '%mapid' => $element['#map'],
    )), 'error');

    // Return the div anyway. All but one map for a given id will be a graymap,
    // because obj.map gets stomped when trying to multiplex maps!
    return $o;
  }
  $mapids[$element['#map']] = TRUE;

  // Put map data in a setting.
  drupal_add_js(array(
    'gmap' => array(
      $element['#map'] => $map,
    ),
  ), 'setting');
  return $o;
}

/**
 * Set up widget.
 * This function will change a form element's ID so it is found
 * by the GMap handlers system.
 * @param &$element
 *   The form element to modify.
 * @param $type
 *   The gmap widget type to map to.
 * @param $map
 *   The map id. If not defined, $element['#map'] will be used.
 * @return
 *   None.
 */
function gmap_widget_setup(&$element, $type, $map = NULL) {
  if (!$map) {
    if (isset($element['#map'])) {
      $map = $element['#map'];
    }
    else {

      // Hmm, missing #map. Try to figure it out.
      if (isset($element['#settings']['id'])) {
        $map = $element['#settings']['id'];
      }
    }
  }
  if (!isset($element['#attributes']['class'])) {
    $element['#attributes']['class'] = '';
  }
  $element['#attributes']['class'] = trim(implode(' ', array(
    $element['#attributes']['class'],
    'gmap-control',
    'gmap-' . $type,
  )));
  $element['#id'] = gmap_get_id($map, $type);
  $element['#map'] = $map;
}

/**
 * Get a CSS id for a map and type.
 * Since CSS ids have to be unique, GMap related IDs are assigned by
 * this function.
 */
function gmap_get_id($map, $type) {
  static $serial = array();
  if (!isset($serial[$map])) {
    $serial[$map] = array();
  }
  if (!isset($serial[$map][$type])) {
    $serial[$map][$type] = -1;
  }
  $serial[$map][$type]++;
  return 'gmap-' . $map . '-' . $type . $serial[$map][$type];
}

/**
 * Generate a dynamic map identifier.
 */
function gmap_get_auto_mapid() {
  static $auto = 0;
  $auto++;
  return 'auto' . $auto . 'map';
}

/**
 * Get the list of marker titles.
 */
function gmap_get_marker_titles($reset = FALSE) {
  static $titles;
  if (!$reset) {
    if (is_array($titles)) {
      return $titles;
    }
    $cached = cache_get('gmap_marker_titles', 'cache');
    if (!empty($cached)) {
      $titles = $cached->data;
      if (is_array($titles)) {
        return $titles;
      }
    }
  }
  require_once drupal_get_path('module', 'gmap') . '/gmap_markerinfo.inc';
  $titles = _gmap_get_marker_titles();
  cache_set('gmap_marker_titles', $titles, 'cache');
  return $titles;
}

/**
 * Get the JSON icon data for all the default markers.
 */
function gmap_get_icondata($reset = FALSE) {
  static $icons;
  if (is_array($icons) && !$reset) {
    return $icons;
  }
  $icons = cache_get('gmap_icondata');
  if ($icons) {
    $icons = $icons->data;
  }
  if ($reset || !$icons) {
    require_once drupal_get_path('module', 'gmap') . '/gmap_markerinfo.inc';
    $icons = _gmap_get_icondata();
  }
  cache_set('gmap_icondata', $icons, 'cache');
  return $icons;
}

/**
 * Utility function to allow high-precision decimals to work with the SQL layer.
 * Use concatenation. (Apparently unquoted %s is bad.)
 */
function gmap_decimal($num) {

  // Paraphrased from postgresql documentation:
  //
  // Numbers in SQL can be in one of these forms:
  //   digits
  //   digits.[digits][e[+-]digits]
  //   [digits].digits[e[+-]digits]
  //   digitse[+-]digits
  // where "digits" is one or more decimal digits.
  // Trim extra whitespace
  $num = trim($num);

  // Check if we're in an acceptable form.
  if (preg_match('/^[+\\-]?((\\d+)|(\\d+\\.\\d*)|(\\d*\\.\\d+))(e[+\\-]?\\d+)?$/', $num) === 1) {

    // Good, we can pass that right along.
    return $num;
  }

  // Otherwise, cast to float, possibly losing precision.
  return (double) $num;
}

/**
 * Utility function to use the google maps geocoder server side.
 * This is an easy, quick way to geocode a single address.
 * Note: This is a REMOTE CALL TO GOOGLE. Do NOT use this where performance matters,
 * as it could possibly take several seconds for this function to return.
 * See http://www.google.com/apis/maps/documentation/reference.html#GGeoStatusCode
 *  for a description of the possible status codes.
 */
function gmap_geocode($address, $tld = 'com') {
  $result = drupal_http_request('https://maps.googleapis.' . $tld . '/maps/api/geocode/xml?address=' . drupal_urlencode($address) . '&sensor=false');
  $data = new SimpleXMLElement($result->data);
  if ($data->status == 'OK') {
    $r = explode(',', $data->data);
    return array(
      'status' => (string) $data->status,
      'latitude' => (double) $data->result->geometry->location->lat,
      'longitude' => (double) $data->result->geometry->location->lng,
    );
  }
  return array(
    'status' => $data->status,
  );
}

/**
 * Simple way to draw a map from inside a theme.
 * @param $latitude
 *   Latitude of marker.
 * @param $longitude
 *   Longitude of marker.
 * @param $markername
 *   Marker to use.
 *   '' will fall back to google's default marker.
 * @param $info
 *   What to show in the bubble when the marker is clicked.
 *   Leave blank if you don't want a bubble.
 * @param $zoom
 *   Map zoom.
 *   'default' will use the default zoom from the settings page.
 *   3 is usually a good value to use.
 * @param $width
 *   Map width.
 *   'default' will use the default width from the settings page.
 * @param $height
 *   Map height.
 *   'default' will use the default height from the settings page.
 * @param $autoshow
 *   If set to TRUE, automatically show the marker bubble.
 * @param $map
 *   Override parts of the map array.
 *   If you need to do much with this, you should probabaly be putting together
 *   the map array manually.
 */
function gmap_simple_map($latitude, $longitude, $markername = '', $info = '', $zoom = 'auto', $width = 'default', $height = 'default', $autoshow = FALSE, $map = array()) {
  $settings = array(
    'id' => gmap_get_auto_mapid(),
    'latitude' => $latitude,
    // Center the map
    'longitude' => $longitude,
  );
  if ($zoom != 'default') {
    $settings['zoom'] = $zoom;
  }
  if ($width != 'default') {
    $settings['width'] = $width;
  }
  if ($height != 'default') {
    $settings['height'] = $height;
  }
  $settings['markers'] = array(
    array(
      'latitude' => $latitude,
      'longitude' => $longitude,
      'markername' => $markername,
      'offset' => 0,
    ),
  );
  if (!empty($info)) {
    $settings['markers'][0]['text'] = $info;
  }
  if ($autoshow) {
    $settings['markers'][0]['autoclick'] = TRUE;
  }
  if (!empty($map)) {
    $settings = array_merge($settings, $map);
  }
  return theme('gmap', array(
    '#settings' => $settings,
  ));
}

/**
 * Implementation of hook_keys_service(). (from the keys api)
 */
function gmap_keys_service() {

  // @@@ Remove after everyone has upgraded.
  if (module_exists('keys_api')) {
    return array(
      'gmap' => array(
        'name' => t('Gmap'),
        'description' => t('Google Maps API Key'),
      ),
    );
  }
  elseif (module_exists('keys')) {

    // @greenSkin:
    // What is your reasoning behind predefining this?
    // I'll avoid overriding you for now, but this seems rather arbitrary.
    // Reference: http://drupal.org/cvs?commit=310498
    // Probe keys to determine if it is defining our key for us.
    $test = array();
    if (function_exists('keys_keys_service')) {
      $test = keys_keys_service();
    }
    if (!isset($test['google_maps'])) {

      // Be forward compatible with future versions of keys api
      // that no longer define it.
      return array(
        'google_maps' => array(
          'name' => t('Google Maps'),
          'description' => t('Google Maps API Key'),
        ),
      );
    }
  }
}

/**
 * Retrieve the Google Maps key that is in use for the site.
 */
function gmap_get_key() {
  $key = variable_get('gmap_api_key', '');
  if (module_exists('keys_api')) {
    $key = keys_api_get_key('gmap', $_SERVER['HTTP_HOST']);
  }
  elseif (module_exists('keys')) {
    $key = keys_get_key('google_maps');
  }
  return $key;
}

/**
 * Implementation of hook_views_plugins().
 */
function gmap_views_plugins() {
  return array(
    'module' => 'gmap',
    'style' => array(
      'gmap' => array(
        'title' => t('GMap'),
        'help' => t('Displays rows as a map.'),
        'handler' => 'gmap_plugin_style_gmap',
        'theme' => 'gmap_view_gmap',
        'uses row plugin' => TRUE,
        'uses grouping' => TRUE,
        'uses options' => TRUE,
        'type' => 'normal',
      ),
    ),
  );
}

/**
 * Implementation of hook_views_pre_render().
 */
function gmap_views_pre_render(&$view) {
  static $gmap_processed;

  // Add js only for gmap style views with ajax and not already processed.
  if ($view->plugin_name != 'gmap' && !$view->use_ajax || $gmap_processed) {
    return;
  }

  // Mark the view as already processed.
  $gmap_processed = TRUE;

  // Add js with new views callback.
  drupal_add_js(drupal_get_path('module', 'gmap') . '/js/gmap_views_ajax.js', 'module');
}

/**
 * Implementation of hook_ajax_data_alter().
 */
function gmap_ajax_data_alter(&$object, $views, $view) {

  // Add js callback only with gmap style plugins and with ajax.
  if ($view->plugin_name != 'gmap' || !$view->use_ajax) {
    return;
  }

  // Save settings.
  $js = drupal_add_js(NULL, NULL, 'header');
  $object->settings = $js['setting'];

  // Register custom callback.
  $object->__callbacks = array_merge($object->__callbacks, array(
    'Drupal.gmapAjax.ajaxFixMaps',
  ));
}

/**
 * Preprocess function for theme_gmap_view_gmap().
 */
function template_preprocess_gmap_view_gmap(&$vars) {
  $vars['map_object'] = $vars['rows'];

  // Rows is actually our map object.
  unset($vars['rows']);

  // Theme the map.
  $vars['map'] = theme('gmap', array(
    '#settings' => $vars['map_object'],
  ));
}

Functions

Namesort descending Description
gmap_ajax_data_alter Implementation of hook_ajax_data_alter().
gmap_decimal Utility function to allow high-precision decimals to work with the SQL layer. Use concatenation. (Apparently unquoted %s is bad.)
gmap_defaults Get the defaults for a gmap.
gmap_dimension_validate Ensure a textfield is a valid css dimension string.
gmap_elements Implementation of hook_elements().
gmap_filter Implementation of hook_filter().
gmap_filter_tips Implementation of hook_filter_tips().
gmap_flush_caches Implementation of hook_flush_caches().
gmap_geocode Utility function to use the google maps geocoder server side. This is an easy, quick way to geocode a single address. Note: This is a REMOTE CALL TO GOOGLE. Do NOT use this where performance matters, as it could possibly take several seconds for this…
gmap_get_auto_mapid Generate a dynamic map identifier.
gmap_get_icondata Get the JSON icon data for all the default markers.
gmap_get_id Get a CSS id for a map and type. Since CSS ids have to be unique, GMap related IDs are assigned by this function.
gmap_get_key Retrieve the Google Maps key that is in use for the site.
gmap_get_marker_titles Get the list of marker titles.
gmap_gmap Implementation of hook_gmap().
gmap_keys_service Implementation of hook_keys_service(). (from the keys api)
gmap_map_cleanup Perform some normalization on the map object to prevent errors.
gmap_menu Implementation of hook_menu().
gmap_module_invoke Invokes hook_gmap() in every module.
gmap_parse_macro Convert a macro string into a GMap array.
gmap_regenerate_markers Regenerate the markerdata file.
gmap_set_location Location chooser utility function.
gmap_simple_map Simple way to draw a map from inside a theme.
gmap_theme Implementation of hook_theme().
gmap_todim Make sure a string is a valid css dimension.
gmap_views_plugins Implementation of hook_views_plugins().
gmap_views_pre_render Implementation of hook_views_pre_render().
gmap_widget_setup Set up widget. This function will change a form element's ID so it is found by the GMap handlers system.
process_gmap_address Address widget #process function.
process_gmap_align Alignment selector #process function.
process_gmap_control Generic gmap control #process function.
process_gmap_markerchooser Marker chooser #process function.
process_gmap_overlay_edit Overlay editor #process function.
process_gmap_style Style fieldset #process function.
template_preprocess_gmap_view_gmap Preprocess function for theme_gmap_view_gmap().
theme_gmap Gmap element theme hook
theme_gmap_address
theme_gmap_align
theme_gmap_macrotext
theme_gmap_marker_popup Theme a marker popup. This will get called for markers embedded in macros.
theme_gmap_overlay_edit Overlay editor theme function.
theme_gmap_style Theme a gmap_style fieldset.
_gmap_doheader Set up the HTML header for GMap. If you are going to include a custom JS file that extends GMap, you probabaly want to call this first to ensure that the core js files have been added.
_gmap_prepare Handle filter preparation.

Constants

Namesort descending Description
GMAP_API_VERSION Define the Google Maps API version being used.