gmap.module

Tracking 5.x-1.x branch
  1. drupal
    1. 5 contributions/gmap/gmap.module
    2. 6 contributions/gmap/gmap.module
    3. 7 contributions/gmap/gmap.module

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

Functions & methods

NameDescription
gmap_admin_settingsSettings page. (remove for d6)
gmap_decimalUtility function to allow high-precision decimals to work with the SQL layer. Use concatenation. (Apparently unquoted %s is bad.)
gmap_defaultsGet the defaults for a gmap.
gmap_dimension_validateEnsure a textfield is a valid css dimension string.
gmap_elementsImplementation of hook_elements().
gmap_filterImplementation of hook_filter().
gmap_filter_tipsImplementation of hook_filter_tips().
gmap_geocodeUtility 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_mapidGenerate a dynamic map identifier.
gmap_get_icondataGet the JSON icon data for all the default markers.
gmap_get_idGet 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_keyRetrieve the Google Maps key that is in use for the site.
gmap_get_marker_titlesGet the list of marker titles.
gmap_gmapImplementation of hook_gmap().
gmap_keys_serviceImplementation of hook_keys_service(). (from the keys api)
gmap_map_cleanupPerform some normalization on the map object to prevent errors.
gmap_menuImplementation of hook_menu().
gmap_module_invokeInvokes hook_gmap() in every module.
gmap_parse_macroConvert a macro string into a GMap array.
gmap_regenerate_markersRegenerate the markerdata file.
gmap_set_locationLocation chooser utility function.
gmap_simple_mapSimple way to draw a map from inside a theme.
gmap_todimMake sure a string is a valid css dimension.
gmap_widget_setupSet up widget. This function will change a form element's ID so it is found by the GMap handlers system.
process_gmap_addressAddress widget #process function.
process_gmap_alignAlignment selector #process function.
process_gmap_controlGeneric gmap control #process function.
process_gmap_markerchooserMarker chooser #process function.
process_gmap_overlay_editOverlay editor #process function.
process_gmap_styleStyle fieldset #process function.
theme_gmapGmap element theme hook
theme_gmap_address
theme_gmap_align
theme_gmap_macrotext
theme_gmap_marker_popupTheme a marker popup. This will get called for markers embedded in macros.
theme_gmap_overlay_editOverlay editor theme function.
theme_gmap_styleTheme a gmap_style fieldset.
_gmap_doheaderSet 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_prepareHandle filter preparation.

Constants

NameDescription
GMAP_API_VERSIONDefine the Google Maps API version being used.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * GMap -- Routines to use the Google Maps API in Drupal.
  5. */
  6. /**
  7. * Define the Google Maps API version being used.
  8. *
  9. * Current minimum version: 2.113
  10. *
  11. * Minimum version last changed on: June 9 2008
  12. *
  13. * Reason: G_SATELLITE_3D_MAP support in gmap_addons. See http://code.google.com/apis/earth/.
  14. *
  15. * See http://groups.google.com/group/Google-Maps-API/web/api-version-changes
  16. * for details on using other version numbers.
  17. */
  18. define('GMAP_API_VERSION', '2.115');
  19. /**
  20. * Get the defaults for a gmap.
  21. */
  22. function gmap_defaults() {
  23. $defaults = array(
  24. 'width' => '300px',
  25. 'height' => '200px',
  26. 'zoom' => 3,
  27. 'maxzoom' => 14,
  28. 'controltype' => 'Small',
  29. 'align' => 'None',
  30. 'latlong' => '40,0',
  31. 'maptype' => 'Map',
  32. 'mtc' => 'standard',
  33. 'baselayers' => array('Map', 'Satellite', 'Hybrid'),
  34. 'styles' => array(
  35. 'line_default' => array('0000ff', 5, 45, '', 0, 0),
  36. 'poly_default' => array('000000', 3, 25, 'ff0000', 45),
  37. ),
  38. 'line_colors' => array('#00cc00', '#ff0000', '#0000ff'),
  39. );
  40. $defaults['behavior'] = array();
  41. $m = array();
  42. $behaviors = gmap_module_invoke('behaviors', $m);
  43. foreach ($behaviors as $k => $v) {
  44. $defaults['behavior'][$k] = $v['default'];
  45. }
  46. $defaults = array_merge($defaults, variable_get('gmap_default', array()));
  47. return $defaults;
  48. }
  49. /**
  50. * Invokes hook_gmap() in every module.
  51. *
  52. * We can't use module_invoke_all() because we pass $map by reference.
  53. */
  54. function gmap_module_invoke($op, &$map) {
  55. $return = array();
  56. foreach (module_implements('gmap') as $module) {
  57. $function = $module . '_gmap';
  58. $result = $function($op, $map);
  59. if (isset($result) && is_array($result)) {
  60. $return = array_merge_recursive($return, $result);
  61. }
  62. elseif (isset($result)) {
  63. $return[] = $result;
  64. }
  65. }
  66. return $return;
  67. }
  68. /**
  69. * Implementation of hook_gmap().
  70. */
  71. function gmap_gmap($op, &$map) {
  72. switch ($op) {
  73. case 'macro':
  74. return array(
  75. 'points' => array(
  76. 'multiple' => TRUE,
  77. ),
  78. 'markers' => array(
  79. 'multiple' => TRUE,
  80. ),
  81. 'feed' => array(
  82. 'multiple' => TRUE,
  83. ),
  84. 'style' => array(
  85. 'multiple' => TRUE,
  86. ),
  87. );
  88. case 'pre_theme_map':
  89. $path = drupal_get_path('module', 'gmap') . '/js/';
  90. // Activate markers if needed.
  91. if ((isset($map['behavior']['dynmarkers']) && $map['behavior']['dynmarkers']) || !empty($map['markers'])) {
  92. static $header_set = FALSE;
  93. if (!$header_set) {
  94. $header_set = TRUE;
  95. // If the user is using private download method, it's up to them to get the path set up.
  96. if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) != FILE_DOWNLOADS_PUBLIC) {
  97. $markerpath = variable_get('gmap_private_markerfile', '');
  98. if (empty($markerpath) || !file_exists($markerpath)) {
  99. drupal_set_message(t('GMap marker file settings are not configured properly for Private download method, markers will not work!'), 'error');
  100. }
  101. else {
  102. drupal_add_js($markerpath, 'module', 'header', FALSE, TRUE, FALSE);
  103. }
  104. }
  105. // With public method, we can handle all bookkeeping ourselves.
  106. else {
  107. $markerpath = file_create_path('js');
  108. if (!$markerpath || !file_exists("$markerpath/gmap_markers.js")) {
  109. gmap_regenerate_markers();
  110. $markerpath = file_create_path('js');
  111. }
  112. drupal_add_js("$markerpath/gmap_markers.js", 'module', 'header', FALSE, TRUE, FALSE);
  113. }
  114. }
  115. drupal_add_js($path . 'icon.js');
  116. drupal_add_js($path . 'marker.js');
  117. $mm = variable_get('gmap_mm_type', 'gmap');
  118. // If you really really want to override the marker manager, implement
  119. // this, take $mm by ref, and have fun. --Bdragon
  120. if (function_exists('_gmap_markermanager_override')) {
  121. _gmap_markermanager_override($mm);
  122. }
  123. drupal_add_js($path . $mm . '_marker.js');
  124. }
  125. if (isset($map['behavior']['locpick']) && $map['behavior']['locpick']) {
  126. drupal_add_js($path . 'locpick.js');
  127. }
  128. if (!empty($map['markers']) || !empty($map['lines'])) {
  129. drupal_add_js($path . 'markerloader_static.js');
  130. }
  131. if (!empty($map['shapes'])) {
  132. drupal_add_js($path . 'shapeloader_static.js');
  133. drupal_add_js($path . 'gmap_shapes.js');
  134. }
  135. if (isset($map['feed']) && is_array($map['feed'])) {
  136. drupal_add_js($path . 'markerloader_georss.js');
  137. }
  138. break;
  139. case 'macro_multiple':
  140. return array('points', 'markers', 'feed', 'circle', 'rpolygon', 'polygon', 'line', 'style');
  141. case 'behaviors':
  142. return array(
  143. 'locpick' => array(
  144. 'title' => t('Location chooser'),
  145. 'default' => FALSE,
  146. 'help' => t('Used to activate location choosing using a gmap.'),
  147. 'internal' => TRUE,
  148. ),
  149. 'nodrag' => array(
  150. 'title' => t('Disable dragging'),
  151. 'default' => FALSE,
  152. 'help' => t('Remove the ability for the user to drag the map. If dragging is disabled, keyboard shortcuts are implicitly disabled.'),
  153. ),
  154. 'nokeyboard' => array(
  155. 'title' => t('Disable keyboard'),
  156. 'default' => TRUE,
  157. 'help' => t('Disable the keyboard shortcuts.'),
  158. ),
  159. 'nomousezoom' => array(
  160. 'title' => t('Disable mousezoom'),
  161. 'default' => FALSE,
  162. 'help' => t('Disable using the scroll wheel to zoom the map.'),
  163. ),
  164. 'nocontzoom' => array(
  165. 'title' => t('Disable Continuous Zoom'),
  166. 'default' => FALSE,
  167. 'help' => t('Disable dynamically resizing images while waiting for tiles to load when zooming.'),
  168. ),
  169. 'autozoom' => array(
  170. 'title' => t('Use AutoZoom'),
  171. 'default' => FALSE,
  172. 'help' => t('Automatically zoom the map to fit all markers when markers are added.'),
  173. ),
  174. 'dynmarkers' => array(
  175. 'title' => t('Unconditionally enable marker interface'),
  176. 'default' => FALSE,
  177. 'help' => t('Load the marker loader system even if no markers to load are detected. Useful if you are injecting markers from somewhere else.'),
  178. ),
  179. 'overview' => array(
  180. 'title' => t('Enable Overview Map'),
  181. 'default' => FALSE,
  182. 'help' => t('Enable the "overview map" in the bottom right corner.'),
  183. 'previewable' => TRUE,
  184. ),
  185. /* 'notype' => array(
  186. 'title' => t('Disable map type control'),
  187. 'default' => FALSE,
  188. 'help' => t('Removes the map type control from the upper right corner. Recommended for very narrow maps.'),
  189. 'previewable' => TRUE,
  190. ), */
  191. 'collapsehack' => array(
  192. 'title' => t('Work around bugs when maps appear in collapsible fieldsets'),
  193. 'default' => FALSE,
  194. 'help' => t('Enabling this will work around some issues that can occur when maps appear inside collapsible fieldsets.'),
  195. ),
  196. // Note to myself, who keeps forgetting what a scale control actually IS.:
  197. // |------------ 1mi ------------|
  198. 'scale' => array(
  199. 'title' => t('Add scale control to map.'),
  200. 'default' => FALSE,
  201. 'help' => t('Adds a scale control to the map in the default position.'),
  202. 'previewable' => TRUE,
  203. ),
  204. 'extramarkerevents' => array(
  205. 'title' => t('Enable extra marker events.'),
  206. 'default' => FALSE,
  207. 'help' => t('Used for advanced javascript work, this will enable the <em>mouseovermarker</em>, <em>mouseoutmarker</em>, and <em>dblclickmarker</em> events.'),
  208. 'internal' => TRUE,
  209. ),
  210. 'clickableshapes' => array(
  211. 'title' => t('Enable clickable shapes.'),
  212. 'default' => FALSE,
  213. 'help' => t('Used for advanced javascript work, this will enable the <em>clickshape</em> event.'),
  214. 'internal' => TRUE,
  215. ),
  216. );
  217. break;
  218. case 'baselayers':
  219. $map['Google']['Map'] = array(
  220. 'title' => t('Map: Standard street map.'),
  221. 'default' => TRUE,
  222. 'help' => t('The standard default street map. Internal name: G_NORMAL_MAP'),
  223. );
  224. $map['Google']['Satellite'] = array(
  225. 'title' => t('Satellite: Standard satellite map.'),
  226. 'default' => TRUE,
  227. 'help' => t('Satellite view without street overlay. Internal name: G_SATELLITE_MAP'),
  228. );
  229. $map['Google']['Hybrid'] = array(
  230. 'title' => t('Hybrid: Hybrid satellite map.'),
  231. 'default' => TRUE,
  232. 'help' => t('Satellite view with street overlay. Internal name: G_HYBRID_MAP'),
  233. );
  234. $map['Google']['Physical'] = array(
  235. 'title' => t('Terrain: Physical feature map.'),
  236. 'default' => FALSE,
  237. 'help' => t('Map with physical data (terrain, vegetation.) Internal name: G_PHYSICAL_MAP'),
  238. );
  239. break;
  240. }
  241. }
  242. /**
  243. * Set up the HTML header for GMap.
  244. * If you are going to include a custom JS file that extends GMap, you probabaly
  245. * want to call this first to ensure that the core js files have been added.
  246. */
  247. function _gmap_doheader() {
  248. static $gmap_initialized = FALSE;
  249. if ($gmap_initialized) {
  250. return;
  251. }
  252. $gmap_path = drupal_get_path('module', 'gmap');
  253. drupal_add_css($gmap_path . '/gmap.css');
  254. drupal_add_js($gmap_path . '/js/gmap.js');
  255. $mm = variable_get('gmap_mm_type', 'gmap');
  256. $mms = variable_get('gmap_markermanager', array());
  257. if (empty($mms[$mm])) {
  258. $mms[$mm] = array();
  259. }
  260. // If you really really want to override the marker manager, implement
  261. // this, take $mm by ref, and have fun. --Bdragon
  262. if (function_exists('_gmap_markermanager_override')) {
  263. _gmap_markermanager_override($mm, $mms);
  264. }
  265. if ($mm == 'clusterer' || $mm == 'clustermarker') {
  266. // Needed for access to clusterer marker.
  267. drupal_add_js($gmap_path . '/js/icon.js');
  268. }
  269. if (isset($mms[$mm]['filename'])) {
  270. drupal_add_js($gmap_path . '/thirdparty/' . $mms[$mm]['filename']);
  271. }
  272. drupal_add_js($gmap_path . '/js/marker.js');
  273. drupal_add_js($gmap_path . '/js/' . $mm . '_marker.js');
  274. drupal_add_js(array('gmap_markermanager' => $mms[$mm]), 'setting');
  275. // @@@
  276. drupal_add_js($gmap_path . '/js/poly.js');
  277. global $locale;
  278. $query = array(
  279. 'file' => 'api',
  280. 'v' => variable_get('gmap_api_version', GMAP_API_VERSION),
  281. 'key' => gmap_get_key(),
  282. 'hl' => $locale,
  283. );
  284. drupal_set_html_head('<script src="'. check_url(url('http://maps.google.com/maps', drupal_query_string_encode($query))) .'" type="text/javascript"></script>');
  285. $gmap_initialized = TRUE;
  286. }
  287. /**
  288. * Convert a macro string into a GMap array.
  289. *
  290. * @param $instring
  291. * Macro to process.
  292. * @param $ver
  293. * Version to treat macro as.
  294. * Set to 1 when processing very old macros, otherwise leave as is.
  295. * @return
  296. * A GMap array.
  297. */
  298. function gmap_parse_macro($instring, $ver = 2) {
  299. require_once drupal_get_path('module', 'gmap') . '/gmap_parse_macro.inc';
  300. return _gmap_parse_macro($instring, $ver);
  301. }
  302. /**
  303. * Theme a marker popup.
  304. * This will get called for markers embedded in macros.
  305. * @ingroup themeable
  306. */
  307. function theme_gmap_marker_popup($label) {
  308. return $label;
  309. }
  310. /**
  311. * Location chooser utility function.
  312. *
  313. * Creates a map that can be interactively used to fill a form with a
  314. * location (latitude, longitude and zoom level).
  315. *
  316. * Note: This is a utility function designed for location.module, there is no
  317. * guarantee it will not be removed eventually.
  318. *
  319. * @param $map
  320. * Either a macro to use as the base map for setting a location, or an already set map associative array.
  321. * @param $form
  322. * A formset associative array. Cannot be more than one deep.
  323. * @param $fields
  324. * An associative array for the field names. 'latitude', 'longitude'=>name of respective array, 'address' is optional.
  325. * @return
  326. * A string with the google map code to be inserted onto the page.
  327. *
  328. */
  329. function gmap_set_location($map, &$form, $fields) {
  330. static $ctr = 0;
  331. $ctr++;
  332. if (!is_array($map)) {
  333. $map = array_merge(gmap_defaults(), gmap_parse_macro($map));
  334. }
  335. $id = 'loc' . $ctr;
  336. $map['id'] = $id;
  337. // This is a locpick map.
  338. $map['behavior']['locpick'] = TRUE;
  339. $element = array(
  340. '#type' => 'gmap',
  341. '#map' => $map['id'],
  342. '#settings' => $map,
  343. );
  344. $form[$fields['latitude']]['#map']=$id;
  345. gmap_widget_setup($form[$fields['latitude']], 'locpick_latitude');
  346. $form[$fields['longitude']]['#map']=$id;
  347. gmap_widget_setup($form[$fields['longitude']], 'locpick_longitude');
  348. if (isset($fields['address'])) {
  349. $form[$fields['address']]['#map'] = $id;
  350. gmap_widget_setup($form[$fields['address']], 'locpick_address');
  351. }
  352. return theme('gmap', $element);
  353. }
  354. /**
  355. * Handle filter preparation.
  356. */
  357. function _gmap_prepare($intext) {
  358. $out = FALSE;
  359. $matches = array();
  360. preg_match_all('/\[gmap([^\[\]]+ )* \] /x', $intext, $matches);
  361. $i = 0;
  362. while (isset($matches[1][$i])) {
  363. $out[0][$i] = $matches[0][$i];
  364. if ($matches[1][$i][0] == '1') {
  365. $ver = 1;
  366. $matches[1][$i] = substr($matches[0][$i], 1);
  367. }
  368. else {
  369. $ver = 2;
  370. }
  371. $map = array('#settings' => gmap_parse_macro($matches[1][$i], $ver));
  372. $out[1][$i] = theme('gmap', $map);
  373. $i++;
  374. } // endwhile process macro
  375. return $out;
  376. }
  377. /**
  378. * Make sure a string is a valid css dimension.
  379. */
  380. function gmap_todim($instring) {
  381. if (!is_string($instring)) {
  382. return FALSE;
  383. }
  384. $s = strtolower(trim($instring));
  385. $matches = array();
  386. if (preg_match('/^([\d.]+)\s*(em|ex|px|in|cm|mm|pt|pc|%)$/', $s, $matches)) {
  387. return $matches[1] . $matches[2];
  388. }
  389. else {
  390. return FALSE;
  391. }
  392. }
  393. /**
  394. * Ensure a textfield is a valid css dimension string.
  395. */
  396. function gmap_dimension_validate(&$elem) {
  397. $value = gmap_todim($elem['#value']);
  398. if ($value) {
  399. // Normalize the css dimension string.
  400. form_set_value($elem, $value);
  401. }
  402. else {
  403. form_error($elem, t('The specified value is not a valid CSS dimension.'));
  404. }
  405. }
  406. /**
  407. * Implementation of hook_filter().
  408. */
  409. function gmap_filter($op, $delta = 0, $format = -1, $text = '') {
  410. switch ($op) {
  411. case 'list':
  412. return (array(0 => t('GMap macro expander')));
  413. case 'name':
  414. return t('Google map filter');
  415. case 'description':
  416. return t('Converts a Google Map macro into the HTML required for inserting a Google Map.');
  417. case 'process':
  418. $gmaps = _gmap_prepare($text); //returns an array of $tables[0] = table macro $table[1]= table html
  419. if ($gmaps) { // there are table macros in this node
  420. return str_replace($gmaps[0], $gmaps[1], $text);
  421. }
  422. else {
  423. return $text;
  424. }
  425. case 'prepare':
  426. return $text;
  427. case 'no cache':
  428. return TRUE; // @@@ Possibly improve efficiency in the future?
  429. }
  430. }
  431. /**
  432. * Implementation of hook_filter_tips().
  433. */
  434. function gmap_filter_tips($delta, $format, $long = FALSE) {
  435. if (user_access('create gmap macro')) { // only display macro if user can create one
  436. return t('Insert Google Map macro.') . '<a href="' . url('map/macro') . '" target="_blank" >' . t('Create a macro') . '</a>';
  437. }
  438. else {
  439. return t('Insert Google Map macro.');
  440. }
  441. }
  442. /**
  443. * Implementation of hook_menu().
  444. */
  445. function gmap_menu($may_cache) {
  446. if ($may_cache) {
  447. $items = array();
  448. $items[] = array(
  449. 'path' => 'admin/settings/gmap',
  450. 'title' => t('GMap'),
  451. 'description' => t('Configure GMap settings'),
  452. 'callback' => 'drupal_get_form',
  453. 'callback arguments' => 'gmap_admin_settings',
  454. 'access' => user_access('administer site configuration'),
  455. 'type' => MENU_NORMAL_ITEM,
  456. );
  457. return $items;
  458. }
  459. }
  460. /**
  461. * Regenerate the markerdata file.
  462. */
  463. function gmap_regenerate_markers() {
  464. $contents = '';
  465. // Create the js/ within the files folder.
  466. $jspath = file_create_path('js');
  467. file_check_directory($jspath, FILE_CREATE_DIRECTORY);
  468. $contents .= "// GMap marker image data.\n";
  469. $contents .= "Drupal.gmap.iconpath = " . drupal_to_js(base_path() . variable_get('gmap_markerfiles', drupal_get_path('module', 'gmap') . '/markers')) . ";\n";
  470. $contents .= "Drupal.gmap.icondata = " . drupal_to_js(gmap_get_icondata(TRUE)) . ";\n";
  471. file_save_data($contents, "$jspath/gmap_markers.js", FILE_EXISTS_REPLACE);
  472. // Also regenerate the cached marker titles array
  473. gmap_get_marker_titles(TRUE);
  474. }
  475. /**
  476. * Settings page. (remove for d6)
  477. */
  478. function gmap_admin_settings() {
  479. require_once(drupal_get_path('module', 'gmap') .'/gmap_settings_ui.inc');
  480. return _gmap_admin_settings();
  481. }
  482. /**
  483. * Implementation of hook_elements().
  484. */
  485. function gmap_elements() {
  486. return array(
  487. 'gmap' => array(
  488. '#input' => FALSE, // This isn't a *form* input!!
  489. '#settings' => array_merge(gmap_defaults(), array(
  490. 'points' => array(),
  491. 'pointsOverlays' => array(),
  492. 'lines' => array(),
  493. )),
  494. ),
  495. 'gmap_macrotext' => array(
  496. '#input' => TRUE,
  497. '#gmap_newtype' => 'textarea',
  498. '#theme' => 'gmap_macrotext',
  499. '#process' => array('process_gmap_control' => array()),
  500. ),
  501. 'gmap_overlay_edit' => array('#input' => TRUE, '#process' => array('process_gmap_overlay_edit' => array())),
  502. 'gmap_style' => array('#input' => TRUE, '#tree' => TRUE, '#gmap_style_type' => 'poly', '#process' => array('process_gmap_style' => array())),
  503. 'gmap_address' => array('#input' => TRUE, '#process' => array('process_gmap_address' => array())),
  504. 'gmap_align' => array('#input' => TRUE, '#process' => array('process_gmap_align' => array())),
  505. 'gmap_latitude' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control' => array())),
  506. 'gmap_longitude' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control' => array())),
  507. 'gmap_latlon' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control' => array())),
  508. 'gmap_markerchooser' => array('#input' => TRUE, '#process' => array('process_gmap_markerchooser' => array())),
  509. 'gmap_dimension' => array('#input' => TRUE, '#gmap_newtype' => 'textfield', '#process' => array('process_gmap_control' => array()), '#validate' => array('gmap_dimension_validate' => array())),
  510. );
  511. }
  512. /**
  513. * Generic gmap control #process function.
  514. */
  515. function process_gmap_control($element, $edit) {
  516. $control = substr($element['#type'], 5);
  517. $element['#type'] = $element['#gmap_newtype'];
  518. unset($element['#gmap_newtype']);
  519. gmap_widget_setup($element, $control);
  520. // Inherit #input from the new type.
  521. unset($element['#input']);
  522. // Merge in the defaults for the target element type.
  523. $element += _element_info($element['#type']);
  524. return $element;
  525. }
  526. /**
  527. * Style fieldset #process function.
  528. */
  529. function process_gmap_style($element) {
  530. $isline = ($element['#gmap_style_type'] == 'line');
  531. // Stroke color
  532. $element[0] = array(
  533. '#type' => 'textfield',
  534. '#size' => 6,
  535. '#maxlength' => 6,
  536. '#field_prefix' => '#',
  537. '#title' => t('Stroke color'),
  538. '#value' => $element['#value'][0],
  539. '#attributes' => array('class' => 'gmap_style'),
  540. );
  541. // Stroke weight
  542. $element[1] = array(
  543. '#type' => 'textfield',
  544. '#title' => t('Stroke weight'),
  545. '#description' => t('Thickness of line, in pixels.'),
  546. '#size' => 3,
  547. '#maxlength' => 3,
  548. '#field_suffix' => t('px'),
  549. '#value' => $element['#value'][1],
  550. '#attributes' => array('class' => 'gmap_style'),
  551. );
  552. // Stroke opacity
  553. $element[2] = array(
  554. '#type' => 'textfield',
  555. '#title' => t('Stroke opacity'),
  556. '#size' => 3,
  557. '#maxlength' => 3,
  558. '#field_suffix' => '%',
  559. '#value' => $element['#value'][2],
  560. '#attributes' => array('class' => 'gmap_style'),
  561. );
  562. // Fill color
  563. $element[3] = array(
  564. '#type' => 'textfield',
  565. '#title' => t('Fill color'),
  566. '#description' => t('Hex color value for fill color. Example: #<em>00AA33</em>'),
  567. '#size' => 6,
  568. '#maxlength' => 6,
  569. '#field_prefix' => '#',
  570. '#value' => $isline ? '' : $element['#value'][3],
  571. '#disabled' => $isline,
  572. '#attributes' => array('class' => 'gmap_style'),
  573. );
  574. $element[4] = array(
  575. '#type' => 'textfield',
  576. '#title' => t('Fill opacity'),
  577. '#description' => t('Opacity of fill, from 0 to 100%.'),
  578. '#size' => 3,
  579. '#maxlength' => 3,
  580. '#field_suffix' => '%',
  581. '#value' => $isline ? '' : $element['#value'][4],
  582. '#disabled' => $isline,
  583. '#attributes' => array('class' => 'gmap_style'),
  584. );
  585. unset($element['#input']);
  586. $element += _element_info('fieldset');
  587. return $element;
  588. }
  589. /**
  590. * Theme a gmap_style fieldset.
  591. * @ingroup themeable
  592. */
  593. function theme_gmap_style($element) {
  594. // Fieldsets print #value at the end, so we need to empty it out.
  595. // Otherwise, it puts "Array" at the end of the fieldset.
  596. $element['#value'] = '';
  597. return theme('fieldset', $element, $element['#children']);
  598. }
  599. /**
  600. * Overlay editor #process function.
  601. */
  602. function process_gmap_overlay_edit($element) {
  603. // Conver the root element into a fieldset.
  604. $element['#type'] = 'fieldset';
  605. if (!isset($element['#title'])) {
  606. $element['#title'] = t('Overlay editor');
  607. }
  608. $element['#tree'] = TRUE;
  609. $element['mapclicktype'] = array(
  610. '#type' => 'select',
  611. '#title' => t('Click map'),
  612. '#map' => $element['#map'],
  613. '#options' => array(
  614. 'Points' => t('Points'),
  615. 'Lines' => t('Lines'),
  616. 'Circles' => t('Circles'),
  617. 'GPolygon' => t('Filled Polygons'),
  618. ),
  619. );
  620. gmap_widget_setup($element['mapclicktype'], 'overlayedit_mapclicktype');
  621. $element['markerclicktype'] = array(
  622. '#type' => 'select',
  623. '#title' => t('Click marker / segment'),
  624. '#map' => $element['#map'],
  625. '#options' => array(
  626. 'Remove' => t('Remove'),
  627. // 'updatestyle' => t('Update Styles'),
  628. // 'removestyle' => t('Remove Styles'),
  629. 'Edit Info' => t('Edit Info'),
  630. ),
  631. );
  632. gmap_widget_setup($element['markerclicktype'], 'overlayedit_markerclicktype');
  633. $element['marker'] = array(
  634. '#type' => 'select',
  635. '#map' => $element['#map'],
  636. '#options' => gmap_get_marker_titles(),
  637. '#title' => t('Marker'),
  638. '#theme' => 'gmap_overlay_edit',
  639. );
  640. gmap_widget_setup($element['marker'], 'overlayedit');
  641. $element['linestyle'] = array(
  642. '#type' => 'gmap_style',
  643. '#title' => t('Line style'),
  644. '#gmap_style_type' => 'line',
  645. '#default_value' => array('0000ff', 5, 45, '', ''), // @@@
  646. );
  647. gmap_widget_setup($element['linestyle'], 'overlayedit_linestyle', $element['#map']);
  648. $element['linestyle']['linestyle_apply'] = array(
  649. '#tree' => FALSE,
  650. '#type' => 'checkbox',
  651. '#title' => t('Use for new and changed lines'),
  652. '#default_value' => FALSE,
  653. );
  654. gmap_widget_setup($element['linestyle']['linestyle_apply'], 'overlayedit_linestyle_apply', $element['#map']);
  655. $element['polystyle'] = array(
  656. '#type' => 'gmap_style',
  657. '#title' => t('Polygon style'),
  658. '#gmap_style_type' => 'poly',
  659. '#default_value' => array('000000', 3, 25, 'ff0000', 45), // @@@
  660. );
  661. gmap_widget_setup($element['polystyle'], 'overlayedit_polystyle', $element['#map']);
  662. $element['polystyle']['polystyle_apply'] = array(
  663. '#tree' => FALSE,
  664. '#type' => 'checkbox',
  665. '#title' => t('Use for new and changed polygons'),
  666. '#default_value' => FALSE,
  667. );
  668. gmap_widget_setup($element['polystyle']['polystyle_apply'], 'overlayedit_polystyle_apply', $element['#map']);
  669. $element += _element_info('fieldset');
  670. return $element;
  671. }
  672. /**
  673. * Alignment selector #process function.
  674. */
  675. function process_gmap_align($element) {
  676. $element['#type'] = 'select';
  677. gmap_widget_setup($element, 'align');
  678. $element['#options'] = drupal_map_assoc(array('None', 'Right', 'Left', 'Center'));
  679. $element['#theme'] = 'gmap_align';
  680. $element += _element_info('select');
  681. return $element;
  682. }
  683. /**
  684. * Address widget #process function.
  685. */
  686. function process_gmap_address($element) {
  687. $element['#type'] = 'textfield';
  688. gmap_widget_setup($element, 'address');
  689. $element['#theme'] = 'gmap_address';
  690. $element += _element_info('textfield');
  691. return $element;
  692. }
  693. /**
  694. * Marker chooser #process function.
  695. */
  696. function process_gmap_markerchooser($element) {
  697. $element['#type'] = 'select';
  698. $element['#options'] = gmap_get_marker_titles();
  699. $element += _element_info('select');
  700. return $element;
  701. }
  702. /**
  703. * Overlay editor theme function.
  704. * @ingroup themeable
  705. */
  706. function theme_gmap_overlay_edit($element) {
  707. $path = drupal_get_path('module', 'gmap');
  708. drupal_add_js($path . '/js/gmap.js');
  709. drupal_add_js($path . '/js/gmap_shapes.js');
  710. drupal_add_js($path . '/js/overlay_edit.js');
  711. return theme('select', $element);
  712. }
  713. /**
  714. * Perform some normalization on the map object
  715. * to prevent errors.
  716. */
  717. function gmap_map_cleanup(&$map) {
  718. // Google is picky about this one.
  719. $map['zoom'] = (int)$map['zoom'];
  720. // Normalize latitude / longitude
  721. if ($map['latlong']) {
  722. $map['latlon'] = $map['latlong'];
  723. unset($map['latlong']);
  724. }
  725. // Normalize extent.
  726. if (isset($map['extent']) && is_string($map['extent'])) {
  727. $tmp = explode(',', $map['extent']);
  728. if (count($tmp) == 4) {
  729. // String form of extent has x,y, but native array form is lat,lon, so need to flip them a bit.
  730. $map['extent'] = array(array($tmp[1], $tmp[0]), array($tmp[3], $tmp[2]));
  731. }
  732. else {
  733. // Extent was unparseable, don't pass it.
  734. unset($map['extent']);
  735. }
  736. }
  737. if (isset($map['latlon']) && (!isset($map['latitude']) || !isset($map['longitude']))) {
  738. list($map['latitude'], $map['longitude']) = explode(',', $map['latlon']);
  739. }
  740. unset($map['latlon']);
  741. foreach ($map['baselayers'] as $k => $v) {
  742. if (!$v) {
  743. unset($map['baselayers'][$k]);
  744. }
  745. }
  746. }
  747. function theme_gmap_macrotext($element) {
  748. drupal_add_js(drupal_get_path('module', 'gmap') . '/js/macro.js');
  749. // @@@
  750. drupal_add_js(drupal_get_path('module', 'gmap') . '/js/macrobuilder.js');
  751. return theme('textarea', $element);
  752. }
  753. function theme_gmap_address($element) {
  754. drupal_add_js(drupal_get_path('module', 'gmap') . '/js/address.js');
  755. $element['#autocomplete_path'] = '';
  756. return theme('textfield', $element);
  757. }
  758. function theme_gmap_align($element) {
  759. drupal_add_js(drupal_get_path('module', 'gmap') . '/js/align.js');
  760. $element['#multiple'] = FALSE;
  761. return theme('select', $element);
  762. }
  763. /**
  764. * Gmap element theme hook
  765. */
  766. function theme_gmap($element) {
  767. // Usability: Prevent js errors on first visit to settings page, etc.
  768. // Of course it will still error if the *wrong* key is on file.
  769. if (gmap_get_key() == '') {
  770. return t('Unable to render map: Google Maps API key is missing.');
  771. }
  772. // Track the mapids we've used already.
  773. static $mapids = array();
  774. _gmap_doheader();
  775. // Convert from raw map array if needed.
  776. if (!isset($element['#settings'])) {
  777. $element = array(
  778. '#settings' => $element,
  779. );
  780. }
  781. $mapid = FALSE;
  782. if (isset($element['#map']) && $element['#map']) {
  783. // The default mapid is #map.
  784. $mapid = $element['#map'];
  785. }
  786. if (isset($element['#settings']['id'])) {
  787. // Settings overrides it.
  788. $mapid = $element['#settings']['id'];
  789. }
  790. if (!$mapid) {
  791. // Hmm, no mapid. Generate one.
  792. $mapid = gmap_get_auto_mapid();
  793. }
  794. // Push the mapid back into #map.
  795. $element['#map'] = $mapid;
  796. gmap_widget_setup($element, 'gmap', $mapid);
  797. if (!$element['#settings']) {
  798. $element['#settings'] = array();
  799. }
  800. // Push the mapid back into #settings.
  801. $element['#settings']['id'] = $mapid;
  802. $mapdefaults = gmap_defaults();
  803. $map = array_merge($mapdefaults, $element['#settings']);
  804. // Styles is a subarray.
  805. if (isset($element['#settings']['styles'])) {
  806. $map['styles'] = array_merge($mapdefaults['styles'], $element['#settings']['styles']);
  807. }
  808. gmap_map_cleanup($map);
  809. // Add a class around map bubble contents.
  810. // @@@ Bdragon sez: Becw, this doesn't belong here. Theming needs to get fixed instead..
  811. if (isset($map['markers'])) {
  812. foreach ($map['markers'] as $i => $marker) {
  813. if (isset($marker['text'])) {
  814. $map['markers'][$i]['text'] = '<div class="gmap-popup">' . $marker['text'] . '</div>';
  815. }
  816. }
  817. }
  818. switch (strtolower($map['align'])) {
  819. case 'left':
  820. $element['#attributes']['class'] .= ' gmap-left';
  821. break;
  822. case 'right':
  823. $element['#attributes']['class'] .= ' gmap-right';
  824. break;
  825. case 'center':
  826. case 'centre':
  827. $element['#attributes']['class'] .= ' gmap-center';
  828. }
  829. $style = array();
  830. $style[] = 'width: ' . $map['width'];
  831. $style[] = 'height: ' . $map['height'];
  832. $element['#attributes']['class'] = trim($element['#attributes']['class'] . ' gmap gmap-map gmap-' . $mapid . '-gmap');
  833. // Some markup parsers (IE) don't handle empty inners well. Use the space to let users know javascript is required.
  834. // @@@ Bevan sez: Google static maps could be useful here.
  835. // @@@ Bdragon sez: Yeah, would be nice, but hard to guarantee functionality. Not everyone uses the static markerloader.
  836. $o = '<div style="' . implode('; ', $style) . ';" id="' . $element['#id'] . '"' . drupal_attributes($element['#attributes']) . '>' . t('Javascript is required to view this map.') . '</div>';
  837. gmap_module_invoke('pre_theme_map', $map);
  838. if (isset($mapids[$element['#map']])) {
  839. drupal_set_message(t('Duplicate map detected! GMap does not support multiplexing maps onto one MapID! GMap MapID: %mapid', array('%mapid' => $element['#map'])), 'error');
  840. // Return the div anyway. All but one map for a given id will be a graymap,
  841. // because obj.map gets stomped when trying to multiplex maps!
  842. return $o;
  843. }
  844. $mapids[$element['#map']] = TRUE;
  845. // Put map data in a setting.
  846. drupal_add_js(array('gmap' => array($element['#map'] => $map)), 'setting');
  847. return $o;
  848. }
  849. /**
  850. * Set up widget.
  851. * This function will change a form element's ID so it is found
  852. * by the GMap handlers system.
  853. * @param &$element
  854. * The form element to modify.
  855. * @param $type
  856. * The gmap widget type to map to.
  857. * @param $map
  858. * The map id. If not defined, $element['#map'] will be used.
  859. * @return
  860. * None.
  861. */
  862. function gmap_widget_setup(&$element, $type, $map=NULL) {
  863. if (!$map) {
  864. if (isset($element['#map'])) {
  865. $map = $element['#map'];
  866. }
  867. else {
  868. // Hmm, missing #map. Try to figure it out.
  869. if (isset($element['#settings']['id'])) {
  870. $map = $element['#settings']['id'];
  871. }
  872. }
  873. }
  874. if (!isset($element['#attributes']['class'])) {
  875. $element['#attributes']['class'] = '';
  876. }
  877. $element['#attributes']['class'] = trim(implode(' ', array(
  878. $element['#attributes']['class'],
  879. 'gmap-control',
  880. 'gmap-' . $type,
  881. )));
  882. $element['#id'] = gmap_get_id($map, $type);
  883. $element['#map'] = $map;
  884. }
  885. /**
  886. * Get a CSS id for a map and type.
  887. * Since CSS ids have to be unique, GMap related IDs are assigned by
  888. * this function.
  889. */
  890. function gmap_get_id($map, $type) {
  891. static $serial = array();
  892. if (!isset($serial[$map])) {
  893. $serial[$map] = array();
  894. }
  895. if (!isset($serial[$map][$type])) {
  896. $serial[$map][$type] = -1;
  897. }
  898. $serial[$map][$type]++;
  899. return 'gmap-' . $map . '-' . $type . $serial[$map][$type];
  900. }
  901. /**
  902. * Generate a dynamic map identifier.
  903. */
  904. function gmap_get_auto_mapid() {
  905. static $auto = 0;
  906. $auto++;
  907. return 'auto' . $auto . 'map';
  908. }
  909. /**
  910. * Get the list of marker titles.
  911. */
  912. function gmap_get_marker_titles($reset = FALSE) {
  913. static $titles;
  914. if (!$reset) {
  915. if (is_array($titles)) {
  916. return $titles;
  917. }
  918. $cached = cache_get('gmap_marker_titles', 'cache');
  919. if (!empty($cached)) {
  920. $titles = unserialize($cached->data);
  921. if (is_array($titles)) {
  922. return $titles;
  923. }
  924. }
  925. }
  926. require_once(drupal_get_path('module', 'gmap') . '/gmap_markerinfo.inc');
  927. $titles = _gmap_get_marker_titles();
  928. cache_set('gmap_marker_titles', 'cache', serialize($titles));
  929. return $titles;
  930. }
  931. /**
  932. * Get the JSON icon data for all the default markers.
  933. */
  934. function gmap_get_icondata($reset = FALSE) {
  935. static $icons;
  936. if (is_array($icons) && !$reset) {
  937. return $icons;
  938. }
  939. $icons = cache_get('gmap_icondata');
  940. if ($icons) {
  941. $icons = unserialize($icons->data);
  942. }
  943. if ($reset || !$icons) {
  944. require_once(drupal_get_path('module', 'gmap') . '/gmap_markerinfo.inc');
  945. $icons = _gmap_get_icondata();
  946. }
  947. cache_set('gmap_icondata', 'cache', serialize($icons));
  948. return $icons;
  949. }
  950. /**
  951. * Utility function to allow high-precision decimals to work with the SQL layer.
  952. * Use concatenation. (Apparently unquoted %s is bad.)
  953. */
  954. function gmap_decimal($num) {
  955. // Paraphrased from postgresql documentation:
  956. //
  957. // Numbers in SQL can be in one of these forms:
  958. // digits
  959. // digits.[digits][e[+-]digits]
  960. // [digits].digits[e[+-]digits]
  961. // digitse[+-]digits
  962. // where "digits" is one or more decimal digits.
  963. // Trim extra whitespace
  964. $num = trim($num);
  965. // Check if we're in an acceptable form.
  966. if (preg_match('/^[+\-]?((\d+)|(\d+\.\d*)|(\d*\.\d+))(e[+\-]?\d+)?$/', $num)===1) {
  967. // Good, we can pass that right along.
  968. return $num;
  969. }
  970. // Otherwise, cast to float, possibly losing precision.
  971. return (float) $num;
  972. }
  973. /**
  974. * Utility function to use the google maps geocoder server side.
  975. * This is an easy, quick way to geocode a single address.
  976. * Note: This is a REMOTE CALL TO GOOGLE. Do NOT use this where performance matters,
  977. * as it could possibly take several seconds for this function to return.
  978. * See http://www.google.com/apis/maps/documentation/reference.html#GGeoStatusCode
  979. * for a description of the possible status codes.
  980. */
  981. function gmap_geocode($address, $tld = 'com') {
  982. $key = gmap_get_key();
  983. $data = drupal_http_request('http://maps.google.' . $tld . '/maps/geo?q=' . drupal_urlencode($address) . '&output=csv&key=' . $key);
  984. if ($data->code == 200) {
  985. $r = explode(',', $data->data);
  986. return array(
  987. 'status' => (int)$r[0],
  988. 'accuracy' => (int)$r[1],
  989. 'latitude' => $r[2],
  990. 'longitude' => $r[3],
  991. );
  992. }
  993. // Non 200 is G_GEO_SERVER_ERROR (500).
  994. return array(
  995. 'status' => 500,
  996. );
  997. }
  998. /**
  999. * Simple way to draw a map from inside a theme.
  1000. * @param $latitude
  1001. * Latitude of marker.
  1002. * @param $longitude
  1003. * Longitude of marker.
  1004. * @param $markername
  1005. * Marker to use.
  1006. * '' will fall back to google's default marker.
  1007. * @param $info
  1008. * What to show in the bubble when the marker is clicked.
  1009. * Leave blank if you don't want a bubble.
  1010. * @param $zoom
  1011. * Map zoom.
  1012. * 'default' will use the default zoom from the settings page.
  1013. * 3 is usually a good value to use.
  1014. * @param $width
  1015. * Map width.
  1016. * 'default' will use the default width from the settings page.
  1017. * @param $height
  1018. * Map height.
  1019. * 'default' will use the default height from the settings page.
  1020. * @param $autoshow
  1021. * If set to TRUE, automatically show the marker bubble.
  1022. * @param $map
  1023. * Override parts of the map array.
  1024. * If you need to do much with this, you should probabaly be putting together
  1025. * the map array manually.
  1026. */
  1027. function gmap_simple_map($latitude, $longitude, $markername = '', $info = '', $zoom = 'auto', $width = 'default', $height = 'default', $autoshow = FALSE, $map = array()) {
  1028. $settings = array(
  1029. 'id' => gmap_get_auto_mapid(),
  1030. 'latitude' => $latitude, // Center the map
  1031. 'longitude' => $longitude, // on the marker.
  1032. );
  1033. if ($zoom != 'default') {
  1034. $settings['zoom'] = $zoom;
  1035. }
  1036. if ($width != 'default') {
  1037. $settings['width'] = $width;
  1038. }
  1039. if ($height != 'default') {
  1040. $settings['height'] = $height;
  1041. }
  1042. $settings['markers'] = array(array(
  1043. 'latitude' => $latitude,
  1044. 'longitude' => $longitude,
  1045. 'markername' => $markername,
  1046. 'offset' => 0,
  1047. ));
  1048. if (!empty($info)) {
  1049. $settings['markers'][0]['text'] = $info;
  1050. }
  1051. if ($autoshow) {
  1052. $settings['markers'][0]['autoclick'] = TRUE;
  1053. }
  1054. if (!empty($map)) {
  1055. $settings = array_merge($settings, $map);
  1056. }
  1057. return theme('gmap', array('#settings' => $settings));
  1058. }
  1059. /**
  1060. * Implementation of hook_keys_service(). (from the keys api)
  1061. */
  1062. function gmap_keys_service() {
  1063. // @@@ Remove after everyone has upgraded.
  1064. if (module_exists('keys_api')) {
  1065. return array(
  1066. 'gmap' => array(
  1067. 'name' => t('Gmap'),
  1068. 'description' => t('Google Maps API Key'),
  1069. ),
  1070. );
  1071. }
  1072. elseif (module_exists('keys')) {
  1073. // @greenSkin:
  1074. // What is your reasoning behind predefining this?
  1075. // I'll avoid overriding you for now, but this seems rather arbitrary.
  1076. // Reference: http://drupal.org/cvs?commit=310498
  1077. // Probe keys to determine if it is defining our key for us.
  1078. $test = array();
  1079. if (function_exists('keys_keys_service')) {
  1080. $test = keys_keys_service();
  1081. }
  1082. if (!isset($test['google_maps'])) {
  1083. // Be forward compatible with future versions of keys api
  1084. // that no longer define it.
  1085. return array(
  1086. 'google_maps' => array(
  1087. 'name' => t('Google Maps'),
  1088. 'description' => t('Google Maps API Key'),
  1089. ),
  1090. );
  1091. }
  1092. }
  1093. }
  1094. /**
  1095. * Retrieve the Google Maps key that is in use for the site.
  1096. */
  1097. function gmap_get_key() {
  1098. $key = variable_get('googlemap_api_key', '');
  1099. if (module_exists('keys_api')) {
  1100. $key = keys_api_get_key('gmap', $_SERVER['HTTP_HOST']);
  1101. }
  1102. elseif (module_exists('keys')) {
  1103. $key = keys_get_key('google_maps');
  1104. }
  1105. return $key;
  1106. }