geofield.module 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. <?php
  2. require_once('geofield.widgets.inc');
  3. require_once('geofield.formatters.inc');
  4. require_once('geofield.openlayers.inc');
  5. require_once('geofield.feeds.inc');
  6. /**
  7. * Implements hook_field_info().
  8. */
  9. function geofield_field_info() {
  10. return array(
  11. 'geofield' => array(
  12. 'label' => 'Geofield',
  13. 'description' => t('This field stores geo information.'),
  14. 'default_widget' => 'geofield_wkt',
  15. 'default_formatter' => 'geofield_wkt',
  16. 'instance_settings' => array(
  17. 'local_solr' => array(
  18. 'enabled' => FALSE,
  19. 'lat_field' => 'lat',
  20. 'lng_field' => 'lng',
  21. ),
  22. ),
  23. 'property_type' => 'geofield',
  24. 'property_callbacks' => array('geofield_property_info_callback'),
  25. ),
  26. );
  27. }
  28. /**
  29. * Implements hook_field_presave().
  30. * PDO throws an error when attempting to insert an empty string into a float
  31. * field. Go through all values and convert empty strings to NULL.
  32. */
  33. function geofield_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  34. if ($field['type'] === 'geofield') {
  35. foreach ($items as $delta => $item) {
  36. if (!empty($item)) {
  37. foreach ($item as $k => $v) {
  38. if ($v === '') {
  39. $item[$k] = NULL;
  40. }
  41. }
  42. $widget = $instance['widget'];
  43. if ($widget['type'] == 'geofield_wkt') {
  44. $master_column = 'wkt';
  45. }
  46. elseif ($widget['type'] == 'geofield_latlon') {
  47. $master_column = 'latlon';
  48. }
  49. elseif ($widget['type'] == 'geofield_bounds') {
  50. $master_column = 'bounds';
  51. }
  52. elseif ($widget['type'] == 'geofield_geolocation') {
  53. $master_column = 'latlon';
  54. }
  55. else {
  56. $master_column = 'wkt';
  57. }
  58. $item += array('master_column' => $master_column);
  59. geofield_compute_values($item, $item['master_column']);
  60. $items[$delta] = $item;
  61. }
  62. }
  63. }
  64. }
  65. /**
  66. * Implements hook_content_is_empty().
  67. */
  68. function geofield_field_is_empty($item, $field) {
  69. // TODO: This is ugly. Please fix.
  70. if (!empty($item['master_column'])) {
  71. switch ($item['master_column']) {
  72. case 'wkt';
  73. return (empty($item['wkt']));
  74. case 'latlon':
  75. return (empty($item['lat']) && empty($item['lon']));
  76. case 'bounds':
  77. return (empty($item['left']) && empty($item['right']) && empty($item['top']) && empty($item['bottom']));
  78. }
  79. }
  80. else {
  81. return (empty($item['wkt']));
  82. }
  83. }
  84. /**
  85. * Implements hook_view_api().
  86. */
  87. function geofield_views_api() {
  88. return array(
  89. 'api' => '3.0-alpha1',
  90. 'path' => drupal_get_path('module', 'geofield') . '/views',
  91. );
  92. }
  93. /**
  94. * Implements hook_ctools_plugin_type().
  95. */
  96. function geofield_ctools_plugin_type() {
  97. return array(
  98. 'behaviors' => array(
  99. 'use hooks' => TRUE,
  100. )
  101. );
  102. }
  103. /**
  104. * Implements hook_ctools_plugin_api().
  105. */
  106. function geofield_ctools_plugin_api($module, $api) {
  107. return array('version' => 1);
  108. }
  109. /**
  110. * Implements hook_requirements().
  111. */
  112. function geofield_requirements($phase) {
  113. $requirements = array();
  114. $geophp = geofield_load_geophp();
  115. // Report geoPHP library status
  116. if ($geophp) {
  117. try {
  118. $geophp_version = geoPHP::version();
  119. }
  120. catch (Exception $e) {
  121. $geophp_version = 0;
  122. }
  123. if ($geophp_version >= 0.6) {
  124. $requirements['geofield_geophp'] = array(
  125. 'title' => t('GeoPHP Library Installed'),
  126. 'severity' => REQUIREMENT_OK,
  127. 'value' => t('GeoPHP %version library installed at %path', array('%path' => $geophp, '%version' => geoPHP::version())),
  128. );
  129. }
  130. elseif ($geophp_version >= 0.3) {
  131. $requirements['geofield_geophp'] = array(
  132. 'title' => t('Old GeoPHP Library'),
  133. 'severity' => REQUIREMENT_INFO,
  134. 'value' => t('GeoPHP library was found, but you are running an old version. The version you are running is compatible, but it is not current, possibly leading to problems. You can download the newest version from %link and place in your libraries diectory as per the geofield installation instructions.', array('%link' => 'https://github.com/downloads/phayes/geoPHP/geoPHP.tar.gz')),
  135. );
  136. }
  137. else {
  138. $requirements['geofield_geophp'] = array(
  139. 'title' => t('Old GeoPHP Library'),
  140. 'severity' => REQUIREMENT_ERROR,
  141. 'value' => t('GeoPHP library was found, but you are running an old version. This will lead to problems when working with geofield. Please download from %link and place in your libraries diectory as per the geofield installation instructions.', array('%link' => 'https://github.com/downloads/phayes/geoPHP/geoPHP.tar.gz')),
  142. );
  143. }
  144. }
  145. else {
  146. $requirements['geofield_geophp'] = array(
  147. 'title' => t('GeoPHP Library Not Found'),
  148. 'severity' => REQUIREMENT_ERROR,
  149. 'value' => t('GeoPHP library was not found. This will lead to problems when working with geofield. Please download from %link and place in your libraries diectory as per the geofield installation instructions.', array('%link' => 'https://github.com/downloads/phayes/geoPHP/geoPHP.tar.gz')),
  150. );
  151. }
  152. return $requirements;
  153. }
  154. /**
  155. * Implements hook_field_instance_settings_form().
  156. */
  157. function geofield_field_instance_settings_form($field, $instance) {
  158. $form = array();
  159. // Add in local solr settings
  160. if (module_exists('apachesolr')) {
  161. if (isset($instance['settings']['solrspatial'])) $setting = $instance['settings']['solrspatial'];
  162. else $setting = array();
  163. $form['solrspatial'] = array(
  164. '#type' => 'fieldset',
  165. '#title' => t('Local Solr Settings'),
  166. '#tree' => TRUE,
  167. );
  168. $form['solrspatial']['enabled'] = array(
  169. '#type' => 'checkbox',
  170. '#title' => t('Index field in Solr for spatial search'),
  171. '#default_value' => isset($setting['enabled']) ? $setting['enabled'] : '',
  172. );
  173. $form['solrspatial']['lat_field'] = array(
  174. '#type' => 'textfield',
  175. '#title' => t('Name of the Solr Latitude Field'),
  176. '#default_value' => isset($setting['lat_field']) ? $setting['lat_field'] : '',
  177. );
  178. $form['solrspatial']['lng_field'] = array(
  179. '#type' => 'textfield',
  180. '#title' => t('Name of the Solr Lonitude Field'),
  181. '#default_value' => isset($setting['lng_field']) ? $setting['lng_field'] : '',
  182. );
  183. $form['solrspatial']['latlng_field'] = array(
  184. '#type' => 'textfield',
  185. '#title' => t('Name of the Solr LatLon Field'),
  186. '#default_value' => isset($setting['latlng_field']) ? $setting['latlng_field'] : '',
  187. );
  188. }
  189. return $form;
  190. }
  191. /**
  192. * Loads the geoPHP library.
  193. *
  194. * @return
  195. * Returns the filename of the included geoPHP library when successful, FALSE
  196. * otherwise.
  197. */
  198. function geofield_load_geophp() {
  199. static $static_cache = FALSE;
  200. if (!$static_cache) {
  201. $path = libraries_get_path('geoPHP');
  202. $file = $path . '/geoPHP.inc';
  203. if (file_exists($file)) {
  204. if (include_once($file)) {
  205. $static_cache = $file;
  206. }
  207. }
  208. }
  209. return $static_cache;
  210. }
  211. /**
  212. * Geofield Compute Values
  213. *
  214. * Compute all dependant values. We compute all other values from whichever
  215. * column is specified in the master_column value
  216. *
  217. * Steps:
  218. * 1. Load the geoPHP library
  219. * 2. Load the Geometry object from the master-column
  220. * 3. Get out all the computer values from the Geometry object
  221. * 4. Set all the values
  222. *
  223. * Allowed values for master_column are wkt, latlon, bounds
  224. */
  225. function geofield_compute_values(&$values, $master_column = 'wkt') {
  226. // If only a wkt string has been passed in, then format it correctly by wrapping it in an array
  227. if ($master_column == 'wkt' && !is_array($values)) {
  228. $values = array('wkt' => $values);
  229. }
  230. // Load up geoPHP to do the conversions
  231. $geophp = geofield_load_geophp();
  232. if (!$geophp) {
  233. drupal_set_message(t("Unable to load geoPHP library. Not all values will be calculated correctly"), 'error');
  234. return;
  235. }
  236. // Load up the geometry object from the master-column data
  237. if ($master_column == 'wkt') {
  238. $wkt = $values['wkt'];
  239. if ($wkt) {
  240. $geometry = geoPHP::load($wkt, 'wkt');
  241. }
  242. }
  243. if ($master_column == 'latlon') {
  244. $lat = $values['lat'];
  245. $lon = $values['lon'];
  246. if (is_numeric($lat) && is_numeric($lon)) {
  247. $geometry = new Point(floatval($lon), floatval($lat));
  248. }
  249. }
  250. if ($master_column == 'bounds') {
  251. $top = $values['top'];
  252. $bottom = $values['bottom'];
  253. $right = $values['right'];
  254. $left = $values['left'];
  255. if (is_numeric($top) && is_numeric($bottom) && is_numeric($right) && is_numeric($left)) {
  256. $wkt_bounds_format = 'POLYGON((left bottom,right bottom,right top,left top,left bottom))';
  257. $wkt = strtr($wkt_bounds_format, array('top' => $top, 'bottom' => $bottom, 'right' => $right, 'left' => $left));
  258. $geometry = geoPHP::load($wkt, 'wkt');
  259. }
  260. }
  261. // Get values from geometry
  262. if (isset($geometry)) {
  263. $values = geofield_get_values_from_geometry($geometry);
  264. }
  265. else {
  266. $values = array();
  267. }
  268. return $values;
  269. }
  270. /**
  271. * Given a geometry object from geoPHP, return a values array
  272. */
  273. function geofield_get_values_from_geometry($geometry) {
  274. $centroid = $geometry->getCentroid();
  275. $bounding = $geometry->getBBox();
  276. $values['wkt'] = $geometry->out('wkt');
  277. $values['geo_type'] = drupal_strtolower($geometry->getGeomType());
  278. $values['lat'] = $centroid->getY();
  279. $values['lon'] = $centroid->getX();
  280. $values['top'] = $bounding['maxy'];
  281. $values['bottom'] = $bounding['miny'];
  282. $values['right'] = $bounding['maxx'];
  283. $values['left'] = $bounding['minx'];
  284. return $values;
  285. }
  286. /**
  287. * Implements hook_apachesolr_field_mappings().
  288. */
  289. function geofield_apachesolr_field_mappings() {
  290. return array(
  291. 'geofield' => array(
  292. 'indexing_callback' => 'geofield_apachesolr_index',
  293. 'facets' => TRUE,
  294. )
  295. );
  296. }
  297. /**
  298. * Name callback for field name
  299. */
  300. function geofield_apachesolr_index($node, $field_name, $index_key, $field_info) {
  301. $return = array();
  302. if (isset($node->$field_name)) {
  303. // Load the instance settings for the field
  304. $instance = field_info_instance('node', $field_name, $node->type);
  305. if (!empty($instance['settings']['solrspatial'])) {
  306. if ($values = field_get_items('node', $node, $field_name)) {
  307. $values = reset($values);
  308. $return = array(
  309. array(
  310. 'key' => $instance['settings']['solrspatial']['lat_field'],
  311. 'value' => $values['lat']
  312. ),
  313. array(
  314. 'key' => $instance['settings']['solrspatial']['lng_field'],
  315. 'value' => $values['lon']
  316. ),
  317. array(
  318. 'key' => $instance['settings']['solrspatial']['latlng_field'],
  319. 'value' => $values['lat'] . ',' . $values['lon']
  320. ),
  321. array(
  322. 'key' => 'ss_geo_wkt',
  323. 'value' => $values['wkt'],
  324. ),
  325. );
  326. }
  327. }
  328. }
  329. return $return;
  330. }
  331. /**
  332. * Implements hook_apachesolr_query_alter()
  333. */
  334. function geofield_apachesolr_query_alter($query) {
  335. // Add the WKT field field
  336. $query->addParam('fl', 'ss_geo_wkt');
  337. }
  338. // Latitude and Longitude string conversion
  339. // ----------------------------------------
  340. /**
  341. * Decimal-Degrees-Seconds to Decimal Degrees
  342. *
  343. * Converts string to decimal degrees. Has some error correction for messy strings
  344. */
  345. function geofield_latlon_DMStoDEC($dms) {
  346. if (is_numeric($dms)) {
  347. // It's already decimal degrees, just return it
  348. return $dms;
  349. }
  350. // If it contains both an H and M, then it's an angular hours
  351. if (stripos($dms, 'H') !== FALSE && stripos($dms, 'M') !== FALSE) {
  352. $dms = strtr($dms, "'\"HOURSMINTECNDAhoursmintecnda", " ");
  353. $dms = preg_replace('/\s\s+/', ' ', $dms);
  354. $dms = explode(" ", $dms);
  355. $deg = $dms[0];
  356. $min = $dms[1];
  357. $sec = $dms[2];
  358. $dec = floatval(($deg*15) + ($min/4) + ($sec/240));
  359. return $dec;
  360. }
  361. // If it contains an S or a W, then it's a negative
  362. if (stripos($dms, 'S') !== FALSE || stripos($dms, 'W') !== FALSE) {
  363. $direction = -1;
  364. }
  365. else {
  366. $direction = 1;
  367. }
  368. // Strip all characters and replace them with empty space
  369. $dms = strtr($dms, "�'\"NORTHSEAWnorthseaw'", " ");
  370. $dms = preg_replace('/\s\s+/', ' ', $dms);
  371. $dms = explode(" ", $dms);
  372. $deg = $dms[0];
  373. $min = $dms[1];
  374. $sec = $dms[2];
  375. // Direction should be checked only for nonnegative coordinates
  376. $dec = floatval($deg+((($min*60)+($sec))/3600));
  377. if ($dec > 0) {
  378. $dec = $direction * $dec;
  379. }
  380. return $dec;
  381. }
  382. /**
  383. * Decimal Degrees to Decimal-Degrees-Seconds
  384. *
  385. * Converts decimal longitude / latitude to DMS ( Degrees / minutes / seconds )
  386. */
  387. function geofield_latlon_DECtoDMS($dec, $axis) {
  388. if ($axis == 'lat') {
  389. if ($dec < 0) $direction = 'S';
  390. else $direction = 'N';
  391. }
  392. if ($axis == 'lon') {
  393. if ($dec < 0) $direction = 'W';
  394. else $direction = 'E';
  395. }
  396. $vars = explode(".", $dec);
  397. $deg = abs($vars[0]);
  398. if (isset($vars[1])) {
  399. $tempma = "0." . $vars[1];
  400. }
  401. else {
  402. $tempma = "0";
  403. }
  404. $tempma = $tempma * 3600;
  405. $min = floor($tempma / 60);
  406. $sec = $tempma - ($min*60);
  407. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\" " . $direction;
  408. }
  409. /**
  410. * Decimal Degrees to Celestial coordinate system (CCS) units
  411. *
  412. * Converts decimal latitude to DMS ( Degrees / minutes / seconds ) and decimal longitude to Angular Hours / Minutes / Seconds
  413. */
  414. function geofield_latlon_DECtoCCS($dec, $axis) {
  415. // Declination (celestial latitude) should be representeted in Degrees / minutes / seconds
  416. if ($axis == 'lat') {
  417. $vars = explode("." , $dec);
  418. $deg = $vars[0];
  419. if (isset($vars[1])) {
  420. $tempma = "0." . $vars[1];
  421. }
  422. else {
  423. $tempma = "0";
  424. }
  425. $tempma = $tempma * 3600;
  426. $min = floor($tempma / 60);
  427. $sec = $tempma - ($min*60);
  428. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\"";
  429. }
  430. // Right ascension (celestial longitude) should be representeted in Hours / Minutes / Seconds
  431. if ($axis == 'lon') {
  432. $tempma = $dec / 15;
  433. $vars = explode(".", $tempma);
  434. $hrs = $vars[0];
  435. if (isset($vars[1])) {
  436. $tempma = "0." . $vars[1];
  437. }
  438. else {
  439. $tempma = "0";
  440. }
  441. $tempma = $tempma * 60;
  442. $vars = explode(".", $tempma);
  443. $min = $vars[0];
  444. if (isset($vars[1])) {
  445. $tempma = "0." . $vars[1];
  446. }
  447. else {
  448. $tempma = "0";
  449. }
  450. $sec = $tempma * 60;
  451. return $hrs . "h " . $min . "m " . round($sec, 3) . "s";
  452. }
  453. }
  454. /**
  455. * Callback to alter the property info of geofield fields.
  456. *
  457. * @see geofield_field_info().
  458. */
  459. function geofield_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  460. $name = $field['field_name'];
  461. $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
  462. $property['type'] = ($field['cardinality'] != 1) ? 'list<geofield>' : 'geofield';
  463. $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  464. $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  465. // $property['auto creation'] = 'geofield_default_values';
  466. $property['property info'] = geofield_data_property_info('Geofield');
  467. unset($property['query callback']);
  468. }
  469. /**
  470. * Defines info for the properties of the geofield field data structure.
  471. */
  472. function geofield_data_property_info($name = NULL) {
  473. // Build an array of basic property information for the geofield field.
  474. $properties = array(
  475. 'wkt' => array(
  476. 'label' => 'Well-known text',
  477. 'type' => 'text',
  478. ),
  479. 'geo_type' => array(
  480. 'label' => 'Geo Type',
  481. 'options list' => '_geofield_geo_types_options_callback',
  482. 'required' => TRUE,
  483. ),
  484. 'lat' => array(
  485. 'label' => 'Latitude',
  486. 'type' => 'decimal',
  487. 'required' => TRUE,
  488. 'setter callback' => 'entity_property_verbatim_set',
  489. ),
  490. 'lon' => array(
  491. 'label' => 'Longitude',
  492. 'type' => 'decimal',
  493. 'required' => TRUE,
  494. 'setter callback' => 'entity_property_verbatim_set',
  495. ),
  496. 'left' => array(
  497. 'label' => 'Left Latitude',
  498. 'type' => 'decimal',
  499. 'setter callback' => 'entity_property_verbatim_set',
  500. ),
  501. 'top' => array(
  502. 'label' => 'Top Longitude',
  503. 'type' => 'decimal',
  504. 'setter callback' => 'entity_property_verbatim_set',
  505. ),
  506. 'right' => array(
  507. 'label' => 'Right Latitude',
  508. 'type' => 'decimal',
  509. 'setter callback' => 'entity_property_verbatim_set',
  510. ),
  511. 'bottom' => array(
  512. 'label' => 'Bottom Longitude',
  513. 'type' => 'decimal',
  514. 'setter callback' => 'entity_property_verbatim_set',
  515. ),
  516. 'srid' => array(
  517. 'label' => 'Projection (SRID)',
  518. 'type' => 'integer'
  519. ),
  520. 'latlon' => array(
  521. 'label' => 'LatLong Pair',
  522. 'type' => 'string',
  523. 'getter callback' => 'geofield_return_latlon',
  524. ),
  525. );
  526. // Add the default values for each of the geofield properties.
  527. foreach ($properties as $key => &$value) {
  528. $value += array(
  529. 'description' => !empty($name) ? t('!label of field %name', array('!label' => $value['label'], '%name' => $name)) : '',
  530. 'getter callback' => 'entity_property_verbatim_get',
  531. );
  532. }
  533. return $properties;
  534. }
  535. function _geofield_geo_types_options_callback() {
  536. $geophp = geofield_load_geophp();
  537. if (!$geophp) {
  538. return;
  539. }
  540. return geoPHP::geometryList();
  541. }
  542. /**
  543. * Gets the a latlong property.
  544. */
  545. function geofield_return_latlon($data, array $options, $name) {
  546. if ((is_array($data) || (is_object($data) && $data instanceof ArrayAccess))) {
  547. return $data['lat'] . ',' . $data['lon'];
  548. }
  549. return NULL;
  550. }