geofield.module 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  1. <?php
  2. module_load_include('inc', 'geofield', 'geofield.elements');
  3. module_load_include('inc', 'geofield', 'geofield.widgets');
  4. module_load_include('inc', 'geofield', 'geofield.formatters');
  5. module_load_include('inc', 'geofield', 'geofield.openlayers');
  6. module_load_include('inc', 'geofield', 'geofield.feeds');
  7. module_load_include('inc', 'geofield', 'geofield.apachesolr');
  8. module_load_include('inc', 'geofield', 'geofield.schemaorg');
  9. module_load_include('inc', 'geofield', 'geofield.microdata');
  10. /* *
  11. * Max length of geohashes (imposed by database column length).
  12. */
  13. define('GEOFIELD_GEOHASH_LENGTH', 16);
  14. /**
  15. * Contrib modules can check this global variable to see if geofield is running the new WKB geom schema
  16. */
  17. $geofield_geom_schema = TRUE;
  18. /**
  19. * Implements hook_field_info().
  20. */
  21. function geofield_field_info() {
  22. return array(
  23. 'geofield' => array(
  24. 'label' => 'Geofield',
  25. 'description' => t('This field stores geo information.'),
  26. 'default_widget' => 'geofield_wkt',
  27. 'default_formatter' => 'geofield_wkt',
  28. 'settings' => array(
  29. 'srid' => '4326',
  30. 'backend' => 'default',
  31. ),
  32. 'property_type' => 'geofield',
  33. 'property_callbacks' => array('geofield_property_info_callback'),
  34. 'microdata' => TRUE,
  35. ),
  36. );
  37. }
  38. /**
  39. * Implements hook_field_update_field.
  40. *
  41. * If a geofield has been created, check to see if the plugin controlling it
  42. * defines a 'postinstall' callback, if so, call it.
  43. */
  44. function geofield_field_update_field($field, $prior_field, $has_data) {
  45. if ($field['type'] == 'geofield') {
  46. $backend = ctools_get_plugins('geofield', 'geofield_backend', $field['settings']['backend']);
  47. if (!empty($backend['update_field'])) {
  48. $postinstall_callback = $backend['update_field'];
  49. $postinstall_callback($field, $prior_field, $has_data);
  50. }
  51. }
  52. }
  53. /**
  54. * Implements hook_field_update_instance().
  55. *
  56. * We implement this hook to prevent instance settings that may not apply to our different
  57. * widgets from breaking when switching widgets. See http://drupal.org/node/1840920.
  58. */
  59. function geofield_field_update_instance($instance, $prior_instance) {
  60. if (!empty($instance['widget']['type']) && !empty($prior_instance['widget']['type']) && $instance['widget']['type'] != $prior_instance['widget']['type']) {
  61. $instance['default_value'] = array();
  62. _field_write_instance($instance, TRUE);
  63. field_cache_clear();
  64. }
  65. }
  66. /**
  67. * Implements hook_field_delete_field.
  68. *
  69. * If a geofield has been deleted, check to see if the plugin controlling it
  70. * defines a 'postdelete' callback, if so, call it.
  71. */
  72. function geofield_field_delete_field($field) {
  73. if ($field['type'] == 'geofield') {
  74. $backend = ctools_get_plugins('geofield', 'geofield_backend', $field['settings']['backend']);
  75. if (!empty($backend['delete_field'])) {
  76. $delete_field_callback = $backend['delete_field'];
  77. $delete_field_callback($field);
  78. }
  79. }
  80. }
  81. /**
  82. * Implements hook_field_settings_form().
  83. */
  84. function geofield_field_settings_form($field, $instance, $has_data) {
  85. ctools_include('plugins');
  86. $settings = $field['settings'];
  87. $backend_options = array();
  88. $backends = ctools_get_plugins('geofield', 'geofield_backend');
  89. foreach ($backends as $id => $backend) {
  90. if (isset($backend['requirements'])) {
  91. if ($backend['requirements']) {
  92. $callback = $backend['requirements'];
  93. $error = '';
  94. if (!$callback($error)) {
  95. $form['backend_error'][] = array(
  96. //@@TODO: Use t() func
  97. //@@TODO: css to add some red and bold
  98. '#markup' => '<div class="geofield-backend-error">' . $backend['title'] . ' not usable because ' . $error . '</div>',
  99. );
  100. continue;
  101. }
  102. }
  103. }
  104. $backend_options[$id] = $backend['title'];
  105. }
  106. $form['backend'] = array(
  107. '#type' => 'select',
  108. '#title' => 'Storage Backend',
  109. '#default_value' => $settings['backend'],
  110. '#options' => $backend_options,
  111. '#description' => "Select the Geospatial storage backend you would like to use to store geofield geometry data. If you don't know what this means, select 'Default'.",
  112. '#disabled' => $has_data,
  113. );
  114. $form['settings'] = array(
  115. '#tree' => TRUE,
  116. );
  117. // Expose backend-settings, if they have them
  118. foreach ($backends as $id => $backend) {
  119. if (isset($backend['settings'])) {
  120. if ($backend['settings']) {
  121. $callback = $backend['settings'];
  122. $form[$id] = array(
  123. '#type' => 'fieldset',
  124. '#tree' => TRUE,
  125. '#title' => $backend['title'] . ' Settings',
  126. '#states' => array(
  127. 'visible' => array(
  128. ':input[name="field[settings][backend]"]' => array('value' => $id),
  129. ),
  130. ),
  131. );
  132. $form[$id] = array_merge($form[$id], $callback($field, $instance, $has_data));
  133. }
  134. }
  135. }
  136. return $form;
  137. }
  138. /**
  139. * Implements hook_field_validate().
  140. */
  141. function geofield_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  142. ctools_include('plugins');
  143. $backend = ctools_get_plugins('geofield', 'geofield_backend', $field['settings']['backend']);
  144. foreach ($items as $delta => $item) {
  145. $geom_empty = geofield_geom_is_empty($item);
  146. // Required field empty.
  147. if ($instance['required'] && $geom_empty) {
  148. $errors[$field['field_name']][$langcode][$delta][] = array(
  149. 'error' => 'data_missing',
  150. 'message' => t('%name is required and must not be empty.', array('%name' => $instance['label'])),
  151. );
  152. }
  153. else {
  154. // Geometry errors.
  155. if ($geom_empty) {
  156. return FALSE;
  157. }
  158. else {
  159. $error = geofield_validate_geom($item);
  160. if ($error) {
  161. $errors[$field['field_name']][$langcode][$delta][] = array(
  162. 'error' => 'data_faulty',
  163. 'message' => t('%name: Specified location data is invalid.', array('%name' => $instance['label'])),
  164. );
  165. }
  166. if (!empty($backend['validate'])) {
  167. $validate_callback = $backend['validate'];
  168. $error = $validate_callback($item);
  169. if ($error) {
  170. $errors[$field['field_name']][$langcode][$delta][] = array(
  171. 'error' => 'data_faulty',
  172. 'message' => t('%name: Specified location data is invalid.', array('%name' => $instance['label'])),
  173. );
  174. }
  175. }
  176. }
  177. }
  178. }
  179. }
  180. /**
  181. * Validates input data against the geometry processor
  182. * @param array $item
  183. * Geometry field submission
  184. * @return \Exception|null
  185. * If validates, return NULL, else error text
  186. */
  187. function geofield_validate_geom($item) {
  188. if (is_string($item)) {
  189. try {
  190. $values = geofield_compute_values($item);
  191. }
  192. catch (Exception $e) {
  193. return $e;
  194. }
  195. }
  196. else {
  197. try {
  198. $input_format = !empty($item['input_format']) ? $item['input_format'] : NULL;
  199. $values = geofield_compute_values($item['geom'], $input_format);
  200. }
  201. catch (Exception $e) {
  202. return $e;
  203. }
  204. }
  205. return NULL;
  206. }
  207. /**
  208. * Check whether geometry is empty
  209. * @param array $item
  210. * Geometry field submission
  211. * @return boolean
  212. * If empty, return TRUE
  213. */
  214. function geofield_geom_is_empty($item) {
  215. if (!empty($item['input_format'])) {
  216. switch ($item['input_format']) {
  217. case 'wkt':
  218. if (empty($item['geom'])) {
  219. return TRUE;
  220. }
  221. break;
  222. case 'lat/lon':
  223. if (empty($item['geom']['lat']) || empty($item['geom']['lon'])) {
  224. return TRUE;
  225. }
  226. break;
  227. case 'bounds':
  228. if (empty($item['geom']['top']) || empty($item['geom']['right']) || empty($item['geom']['bottom']) || empty($item['geom']['left'])) {
  229. return TRUE;
  230. }
  231. break;
  232. case 'json':
  233. if (empty($item['geom'])) {
  234. return TRUE;
  235. }
  236. break;
  237. }
  238. }
  239. else {
  240. return empty($item['geom']);
  241. }
  242. }
  243. /**
  244. * Implements hook_field_presave().
  245. */
  246. function geofield_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  247. if ($field['type'] === 'geofield') {
  248. /**
  249. * Edge case. Currently, Drupal will set a field value to the default value if the current value
  250. * is empty, even if it's set by the user. This bypasses our validation, and currently non-valid WKB
  251. * data in geom causes catastrophic failures in entity_load. To compensate, we add the default value
  252. * in early. When the core issue is fixed, we should drop this code.
  253. *
  254. * Geofield Issue: http://drupal.org/node/1886852
  255. * Core Issue: http://drupal.org/node/1253820
  256. */
  257. if ($instance['required'] == 0 && empty($items)) {
  258. $entity_ids = entity_extract_ids($entity_type, $entity);
  259. if (empty($entity_ids[0])) {
  260. $items = isset($instance['default_value']) ? array($instance['default_value']) : array();
  261. }
  262. }
  263. ctools_include('plugins');
  264. $backend = ctools_get_plugins('geofield', 'geofield_backend', $field['settings']['backend']);
  265. $save_callback = $backend['save'];
  266. // For each delta, we compute all the auxiliary columns and transform the geom column into a geometry object
  267. // We then pass the geometry object (now stored in the geom column) to the backend to prepare it for insertion into the database
  268. foreach ($items as $delta => $item) {
  269. $items[$delta] = geofield_compute_values($item);
  270. if (isset($items[$delta]['geom']) && $items[$delta]['geom']) {
  271. $items[$delta]['geom'] = $save_callback($items[$delta]['geom']);
  272. }
  273. }
  274. }
  275. }
  276. /**
  277. * Implements hook_field_load().
  278. *
  279. * Geofield stores it's data as WKB, but a binary format can cause
  280. * issues/confusion with working with other modules, notably Services.
  281. * To improve DX/discoverability of what we're storing, we convert
  282. * to WKT on load.
  283. */
  284. function geofield_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
  285. geophp_load();
  286. if (geoPHP::geosInstalled()) {
  287. // process geometry directly with GEOS to help with performance/memory issues.
  288. $reader = new GEOSWKBReader();
  289. $writer = new GEOSWKTWriter();
  290. $writer->setTrim(TRUE);
  291. foreach ($entities as $id => $entity) {
  292. foreach ($items[$id] as $delta => $item) {
  293. if (!empty($item['geom'])) {
  294. $raw_geom = unpack('H*', $item['geom']);
  295. try {
  296. $geom = $reader->readHEX($raw_geom[1]);
  297. $items[$id][$delta]['geom'] = $writer->write($geom);
  298. }
  299. catch (Exception $e) {
  300. watchdog(WATCHDOG_ERROR, 'Cannot render poorly formatted WKB value %message', array('%message' => $e->getMessage()));
  301. }
  302. }
  303. }
  304. }
  305. }
  306. else {
  307. foreach ($entities as $id => $entity) {
  308. foreach ($items[$id] as $delta => $item) {
  309. if (!empty($item['geom'])) {
  310. $geom = geophp::load($item['geom']);
  311. if ($geom) {
  312. $items[$id][$delta]['geom'] = $geom->out('wkt');
  313. }
  314. }
  315. }
  316. }
  317. }
  318. }
  319. /**
  320. * Implements hook_content_is_empty().
  321. */
  322. function geofield_field_is_empty($item, $field) {
  323. if (isset($item['input_format'])) {
  324. switch ($item['input_format']) {
  325. case GEOFIELD_INPUT_LAT_LON:
  326. return ((trim($item['geom']['lat']) == '') || (trim($item['geom']['lon']) == ''));
  327. case GEOFIELD_INPUT_BOUNDS:
  328. return ((trim($item['geom']['top']) == '') || (trim($item['geom']['right']) == '') ||
  329. (trim($item['geom']['bottom']) == '') || (trim($item['geom']['left']) == ''));
  330. }
  331. }
  332. //@@TODO: Check if it's an empty geometry as per geoPHP $geometry->empty()
  333. return empty($item['geom']);
  334. }
  335. /**
  336. * Implements hook_view_api().
  337. */
  338. function geofield_views_api() {
  339. return array(
  340. 'api' => '3.0',
  341. 'path' => drupal_get_path('module', 'geofield') . '/views',
  342. );
  343. }
  344. /**
  345. * Implements hook_ctools_plugin_type().
  346. */
  347. function geofield_ctools_plugin_type() {
  348. return array(
  349. 'geofield_backend' => array(),
  350. 'behaviors' => array(
  351. 'use hooks' => TRUE,
  352. )
  353. );
  354. }
  355. /**
  356. * Implements hook_ctools_plugin_api().
  357. */
  358. function geofield_ctools_plugin_api($module, $api) {
  359. return array('version' => 1);
  360. }
  361. /**
  362. * Implementation of hook_ctools_plugin_directory().
  363. */
  364. function geofield_ctools_plugin_directory($module, $plugin) {
  365. if ($plugin == 'geofield_backend') {
  366. return 'includes/' . $plugin;
  367. }
  368. }
  369. /**
  370. * Geofield Compute Values
  371. *
  372. * @todo: documentation
  373. * Steps:
  374. * 1. Load the geoPHP library
  375. * 2. Load the Geometry object from the master-column
  376. * 3. Get out all the computer values from the Geometry object
  377. * 4. Set all the values
  378. */
  379. function geofield_compute_values($raw_data, $input_format = NULL) {
  380. // If raw_data is NULL, false, or otherwise empty, just return an empty array of values
  381. if (empty($raw_data)) {
  382. return array();
  383. }
  384. // Load up geoPHP to do the conversions
  385. $geophp = geophp_load();
  386. if (!$geophp) {
  387. drupal_set_message(t("Unable to load geoPHP library. Not all values will be calculated correctly"), 'error');
  388. return;
  389. }
  390. $geometry = geofield_geometry_from_values($raw_data, $input_format);
  391. // Get values from geometry
  392. if (!empty($geometry)) {
  393. $values = geofield_get_values_from_geometry($geometry);
  394. }
  395. else {
  396. $values = array();
  397. }
  398. return $values;
  399. }
  400. /**
  401. * Primary function for processing geoPHP geometry objects from raw data.
  402. * @param $raw_data
  403. * The info we're trying to process. Valid input can be a string or an array. If $raw_data is a string,
  404. * the value is passed directly to geophp for parsing. If $raw_data is an array (as is expected for Lat/Lon or
  405. * Bounds input), process into raw WKT and generate geometry object from there.
  406. * @param $input_format
  407. * Geofield module defined constants that specify a specific type of input. Useful for ensuring that only a specific
  408. * type of data is valid (i.e., if we're expecting WKT, valid GeoJSON doesn't get processed).
  409. * @return
  410. * A populated geoPHP geometry object if valid geometry, no return otherwise.
  411. *
  412. * @TODO: Refactor the function to not check for $input_format from both the optional secondary parameter and
  413. * an array item in $raw_data. This is probably an artifact from how Geofield's widgets pass data to various field
  414. * hooks. We should only check the optional secondary parameter.
  415. * @TODO: Move constants from geofield.widgets.inc to geofield.module
  416. * @TODO: Provide useful failure return (FALSE)
  417. */
  418. function geofield_geometry_from_values($raw_data, $input_format = NULL) {
  419. // Load up geoPHP to do the conversions
  420. $geophp = geophp_load();
  421. if (!$geophp) {
  422. drupal_set_message(t("Unable to load geoPHP library. Not all values will be calculated correctly"), 'error');
  423. return;
  424. }
  425. if (is_array($raw_data)) {
  426. if (!empty($raw_data['input_format'])) {
  427. if ($raw_data['input_format'] == GEOFIELD_INPUT_LAT_LON) {
  428. $geometry = new Point($raw_data['geom']['lon'], $raw_data['geom']['lat']);
  429. }
  430. elseif ($raw_data['input_format'] == GEOFIELD_INPUT_BOUNDS) {
  431. $wkt_bounds_format = 'POLYGON((left bottom,right bottom,right top,left top,left bottom))';
  432. $wkt = strtr($wkt_bounds_format, array('top' => $raw_data['geom']['top'],
  433. 'right' => $raw_data['geom']['right'],
  434. 'bottom' => $raw_data['geom']['bottom'],
  435. 'left' => $raw_data['geom']['left']));
  436. $geometry = geoPHP::load($wkt);
  437. }
  438. else {
  439. $geometry = geoPHP::load($raw_data['geom'], $raw_data['input_format']);
  440. }
  441. } else {
  442. // No input format - let geoPHP figure it out
  443. if (!empty($raw_data['geom'])) {
  444. $geometry = geoPHP::load($raw_data['geom']);
  445. }
  446. // Special case, raw input (Services/Feeds) that only specifies lat/lon.
  447. elseif (!empty($raw_data['lat']) && !empty($raw_data['lon'])) {
  448. $geometry = new Point($raw_data['lon'], $raw_data['lat']);
  449. }
  450. }
  451. }
  452. else {
  453. if ($input_format) {
  454. $geometry = geoPHP::load($raw_data, $input_format);
  455. }
  456. else {
  457. // All we have at this point is a raw string. let GeoPHP figure it out
  458. $geometry = geoPHP::load($raw_data);
  459. }
  460. }
  461. if (isset($geometry)) {
  462. return $geometry;
  463. }
  464. }
  465. /**
  466. * Given a geometry object from geoPHP, return a values array
  467. */
  468. function geofield_get_values_from_geometry($geometry) {
  469. $values = array();
  470. $centroid = $geometry->getCentroid();
  471. $bounding = $geometry->getBBox();
  472. $values['geom'] = $geometry->out('wkb');
  473. $values['geo_type'] = drupal_strtolower($geometry->getGeomType());
  474. if ($centroid) {
  475. $values['lat'] = $centroid->y();
  476. $values['lon'] = $centroid->x();
  477. }
  478. $values['top'] = $bounding['maxy'];
  479. $values['bottom'] = $bounding['miny'];
  480. $values['right'] = $bounding['maxx'];
  481. $values['left'] = $bounding['minx'];
  482. // Truncate geohash to max length.
  483. $values['geohash'] = substr($geometry->out('geohash'), 0, GEOFIELD_GEOHASH_LENGTH);
  484. return $values;
  485. }
  486. // Latitude and Longitude string conversion
  487. // ----------------------------------------
  488. /**
  489. * Decimal-Degrees-Seconds to Decimal Degrees
  490. *
  491. * Converts string to decimal degrees. Has some error correction for messy strings
  492. */
  493. function geofield_latlon_DMStoDEC($dms) {
  494. if (is_numeric($dms)) {
  495. // It's already decimal degrees, just return it
  496. return $dms;
  497. }
  498. // If it contains both an H and M, then it's an angular hours
  499. if (stripos($dms, 'H') !== FALSE && stripos($dms, 'M') !== FALSE) {
  500. $dms = strtr($dms, "'\"HOURSMINTECNDAhoursmintecnda", " ");
  501. $dms = preg_replace('/\s\s+/', ' ', $dms);
  502. $dms = explode(" ", $dms);
  503. $deg = $dms[0];
  504. $min = $dms[1];
  505. $sec = $dms[2];
  506. $dec = floatval(($deg*15) + ($min/4) + ($sec/240));
  507. return $dec;
  508. }
  509. // If it contains an S or a W, then it's a negative
  510. if (stripos($dms, 'S') !== FALSE || stripos($dms, 'W') !== FALSE) {
  511. $direction = -1;
  512. }
  513. else {
  514. $direction = 1;
  515. }
  516. // Strip all characters and replace them with empty space
  517. $dms = strtr($dms, "�'\"NORTHSEAWnorthseaw'", " ");
  518. $dms = preg_replace('/\s\s+/', ' ', $dms);
  519. $dms = explode(" ", $dms);
  520. $deg = $dms[0];
  521. $min = $dms[1];
  522. $sec = $dms[2];
  523. // Direction should be checked only for nonnegative coordinates
  524. $dec = floatval($deg+((($min*60)+($sec))/3600));
  525. if ($dec > 0) {
  526. $dec = $direction * $dec;
  527. }
  528. return $dec;
  529. }
  530. /**
  531. * Decimal Degrees to Decimal-Degrees-Seconds
  532. *
  533. * Converts decimal longitude / latitude to DMS ( Degrees / minutes / seconds )
  534. */
  535. function geofield_latlon_DECtoDMS($dec, $axis) {
  536. if ($axis == 'lat') {
  537. if ($dec < 0) $direction = 'S';
  538. else $direction = 'N';
  539. }
  540. if ($axis == 'lon') {
  541. if ($dec < 0) $direction = 'W';
  542. else $direction = 'E';
  543. }
  544. $vars = explode(".", $dec);
  545. $deg = abs($vars[0]);
  546. if (isset($vars[1])) {
  547. $tempma = "0." . $vars[1];
  548. }
  549. else {
  550. $tempma = "0";
  551. }
  552. $tempma = $tempma * 3600;
  553. $min = floor($tempma / 60);
  554. $sec = $tempma - ($min*60);
  555. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\" " . $direction;
  556. }
  557. /**
  558. * Decimal Degrees to Celestial coordinate system (CCS) units
  559. *
  560. * Converts decimal latitude to DMS ( Degrees / minutes / seconds ) and decimal longitude to Angular Hours / Minutes / Seconds
  561. */
  562. function geofield_latlon_DECtoCCS($dec, $axis) {
  563. // Declination (celestial latitude) should be representeted in Degrees / minutes / seconds
  564. if ($axis == 'lat') {
  565. $vars = explode("." , $dec);
  566. $deg = $vars[0];
  567. if (isset($vars[1])) {
  568. $tempma = "0." . $vars[1];
  569. }
  570. else {
  571. $tempma = "0";
  572. }
  573. $tempma = $tempma * 3600;
  574. $min = floor($tempma / 60);
  575. $sec = $tempma - ($min*60);
  576. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\"";
  577. }
  578. // Right ascension (celestial longitude) should be representeted in Hours / Minutes / Seconds
  579. if ($axis == 'lon') {
  580. $tempma = $dec / 15;
  581. $vars = explode(".", $tempma);
  582. $hrs = $vars[0];
  583. if (isset($vars[1])) {
  584. $tempma = "0." . $vars[1];
  585. }
  586. else {
  587. $tempma = "0";
  588. }
  589. $tempma = $tempma * 60;
  590. $vars = explode(".", $tempma);
  591. $min = $vars[0];
  592. if (isset($vars[1])) {
  593. $tempma = "0." . $vars[1];
  594. }
  595. else {
  596. $tempma = "0";
  597. }
  598. $sec = $tempma * 60;
  599. return $hrs . "h " . $min . "m " . round($sec, 3) . "s";
  600. }
  601. }
  602. /**
  603. * Haversine formula, useful for injecting into an SQL statement. In instances where it isn't possible to pass in variables dynamically (i.e. field
  604. * definitions), this function will bake in values directly into the snippet.
  605. *
  606. * @param $options
  607. * An array of parameters that can be passed along to be injected directly into the SQL snippet. The following array keys are checked...
  608. * - origin_latitude
  609. * - origin_longitude
  610. * - destination_latitude
  611. * - destination_longitude
  612. * - earth_radius
  613. *
  614. * @return
  615. * A string suitable for injection into DBTNG. Any option passed into the function will be baked into the string directly. Any variable missing will
  616. * be represented as :[variable]. (i.e. :earth_radius).
  617. */
  618. function geofield_haversine($options = array()) {
  619. $formula = '( :earth_radius * ACOS( COS( RADIANS(:origin_latitude) ) * COS( RADIANS(:destination_latitude) ) * COS( RADIANS(:destination_longitude) - RADIANS(:origin_longitude) ) + SIN( RADIANS(:origin_latitude) ) * SIN( RADIANS(:destination_latitude) ) ) )';
  620. foreach ($options as $key => $option) {
  621. if (is_numeric($option)) {
  622. $formula = str_replace(':' . $key, $option, $formula);
  623. }
  624. else {
  625. $formula = str_replace(':' . $key, db_escape_field($option), $formula);
  626. }
  627. }
  628. return $formula;
  629. }
  630. /**
  631. * Helper function to get all geofield fields.
  632. *
  633. * @return
  634. * an array of field definitions for all geofields as defined by field_info_fields().
  635. */
  636. function _geofield_get_geofield_fields() {
  637. $geofield_fields = array();
  638. $fields = field_info_fields();
  639. foreach ($fields as $field => $info) {
  640. if ($info['type'] == 'geofield') {
  641. $geofield_fields[$field] = $info;
  642. }
  643. }
  644. return $geofield_fields;
  645. }