geofield.module 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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_field_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_field_instance_settings_form().
  111. */
  112. function geofield_field_instance_settings_form($field, $instance) {
  113. $form = array();
  114. // Add in local solr settings
  115. if (module_exists('apachesolr')) {
  116. if (isset($instance['settings']['solrspatial'])) $setting = $instance['settings']['solrspatial'];
  117. else $setting = array();
  118. $form['solrspatial'] = array(
  119. '#type' => 'fieldset',
  120. '#title' => t('Local Solr Settings'),
  121. '#tree' => TRUE,
  122. );
  123. $form['solrspatial']['enabled'] = array(
  124. '#type' => 'checkbox',
  125. '#title' => t('Index field in Solr for spatial search'),
  126. '#default_value' => isset($setting['enabled']) ? $setting['enabled'] : '',
  127. );
  128. $form['solrspatial']['lat_field'] = array(
  129. '#type' => 'textfield',
  130. '#title' => t('Name of the Solr Latitude Field'),
  131. '#default_value' => isset($setting['lat_field']) ? $setting['lat_field'] : '',
  132. );
  133. $form['solrspatial']['lng_field'] = array(
  134. '#type' => 'textfield',
  135. '#title' => t('Name of the Solr Lonitude Field'),
  136. '#default_value' => isset($setting['lng_field']) ? $setting['lng_field'] : '',
  137. );
  138. $form['solrspatial']['latlng_field'] = array(
  139. '#type' => 'textfield',
  140. '#title' => t('Name of the Solr LatLon Field'),
  141. '#default_value' => isset($setting['latlng_field']) ? $setting['latlng_field'] : '',
  142. );
  143. }
  144. return $form;
  145. }
  146. /**
  147. * Geofield Compute Values
  148. *
  149. * Compute all dependant values. We compute all other values from whichever
  150. * column is specified in the master_column value
  151. *
  152. * Steps:
  153. * 1. Load the geoPHP library
  154. * 2. Load the Geometry object from the master-column
  155. * 3. Get out all the computer values from the Geometry object
  156. * 4. Set all the values
  157. *
  158. * Allowed values for master_column are wkt, latlon, bounds
  159. */
  160. function geofield_compute_values(&$values, $master_column = 'wkt') {
  161. // If only a wkt string has been passed in, then format it correctly by wrapping it in an array
  162. if ($master_column == 'wkt' && !is_array($values)) {
  163. $values = array('wkt' => $values);
  164. }
  165. // Load up geoPHP to do the conversions
  166. $geophp = geophp_load();
  167. if (!$geophp) {
  168. drupal_set_message(t("Unable to load geoPHP library. Not all values will be calculated correctly"), 'error');
  169. return;
  170. }
  171. // Load up the geometry object from the master-column data
  172. if ($master_column == 'wkt') {
  173. $wkt = $values['wkt'];
  174. if ($wkt) {
  175. $geometry = geoPHP::load($wkt, 'wkt');
  176. }
  177. }
  178. if ($master_column == 'latlon') {
  179. $lat = $values['lat'];
  180. $lon = $values['lon'];
  181. if (is_numeric($lat) && is_numeric($lon)) {
  182. $geometry = new Point(floatval($lon), floatval($lat));
  183. }
  184. }
  185. if ($master_column == 'bounds') {
  186. $top = $values['top'];
  187. $bottom = $values['bottom'];
  188. $right = $values['right'];
  189. $left = $values['left'];
  190. if (is_numeric($top) && is_numeric($bottom) && is_numeric($right) && is_numeric($left)) {
  191. $wkt_bounds_format = 'POLYGON((left bottom,right bottom,right top,left top,left bottom))';
  192. $wkt = strtr($wkt_bounds_format, array('top' => $top, 'bottom' => $bottom, 'right' => $right, 'left' => $left));
  193. $geometry = geoPHP::load($wkt, 'wkt');
  194. }
  195. }
  196. // Get values from geometry
  197. if (isset($geometry)) {
  198. $values = geofield_get_values_from_geometry($geometry);
  199. }
  200. else {
  201. $values = array();
  202. }
  203. return $values;
  204. }
  205. /**
  206. * Given a geometry object from geoPHP, return a values array
  207. */
  208. function geofield_get_values_from_geometry($geometry) {
  209. $centroid = $geometry->getCentroid();
  210. $bounding = $geometry->getBBox();
  211. $values['wkt'] = $geometry->out('wkt');
  212. $values['geo_type'] = drupal_strtolower($geometry->getGeomType());
  213. $values['lat'] = $centroid->getY();
  214. $values['lon'] = $centroid->getX();
  215. $values['top'] = $bounding['maxy'];
  216. $values['bottom'] = $bounding['miny'];
  217. $values['right'] = $bounding['maxx'];
  218. $values['left'] = $bounding['minx'];
  219. return $values;
  220. }
  221. /**
  222. * Implements hook_apachesolr_field_mappings().
  223. */
  224. function geofield_apachesolr_field_mappings() {
  225. return array(
  226. 'geofield' => array(
  227. 'indexing_callback' => 'geofield_apachesolr_index',
  228. 'facets' => TRUE,
  229. )
  230. );
  231. }
  232. /**
  233. * Name callback for field name
  234. */
  235. function geofield_apachesolr_index($node, $field_name, $index_key, $field_info) {
  236. $return = array();
  237. if (isset($node->$field_name)) {
  238. // Load the instance settings for the field
  239. $instance = field_info_instance('node', $field_name, $node->type);
  240. if (!empty($instance['settings']['solrspatial'])) {
  241. if ($values = field_get_items('node', $node, $field_name)) {
  242. $values = reset($values);
  243. $return = array(
  244. array(
  245. 'key' => $instance['settings']['solrspatial']['lat_field'],
  246. 'value' => $values['lat']
  247. ),
  248. array(
  249. 'key' => $instance['settings']['solrspatial']['lng_field'],
  250. 'value' => $values['lon']
  251. ),
  252. array(
  253. 'key' => $instance['settings']['solrspatial']['latlng_field'],
  254. 'value' => $values['lat'] . ',' . $values['lon']
  255. ),
  256. array(
  257. 'key' => 'ss_geo_wkt',
  258. 'value' => $values['wkt'],
  259. ),
  260. );
  261. }
  262. }
  263. }
  264. return $return;
  265. }
  266. /**
  267. * Implements hook_apachesolr_query_alter()
  268. */
  269. function geofield_apachesolr_query_alter($query) {
  270. // Add the WKT field field
  271. $query->addParam('fl', 'ss_geo_wkt');
  272. }
  273. // Latitude and Longitude string conversion
  274. // ----------------------------------------
  275. /**
  276. * Decimal-Degrees-Seconds to Decimal Degrees
  277. *
  278. * Converts string to decimal degrees. Has some error correction for messy strings
  279. */
  280. function geofield_latlon_DMStoDEC($dms) {
  281. if (is_numeric($dms)) {
  282. // It's already decimal degrees, just return it
  283. return $dms;
  284. }
  285. // If it contains both an H and M, then it's an angular hours
  286. if (stripos($dms, 'H') !== FALSE && stripos($dms, 'M') !== FALSE) {
  287. $dms = strtr($dms, "'\"HOURSMINTECNDAhoursmintecnda", " ");
  288. $dms = preg_replace('/\s\s+/', ' ', $dms);
  289. $dms = explode(" ", $dms);
  290. $deg = $dms[0];
  291. $min = $dms[1];
  292. $sec = $dms[2];
  293. $dec = floatval(($deg*15) + ($min/4) + ($sec/240));
  294. return $dec;
  295. }
  296. // If it contains an S or a W, then it's a negative
  297. if (stripos($dms, 'S') !== FALSE || stripos($dms, 'W') !== FALSE) {
  298. $direction = -1;
  299. }
  300. else {
  301. $direction = 1;
  302. }
  303. // Strip all characters and replace them with empty space
  304. $dms = strtr($dms, "�'\"NORTHSEAWnorthseaw'", " ");
  305. $dms = preg_replace('/\s\s+/', ' ', $dms);
  306. $dms = explode(" ", $dms);
  307. $deg = $dms[0];
  308. $min = $dms[1];
  309. $sec = $dms[2];
  310. // Direction should be checked only for nonnegative coordinates
  311. $dec = floatval($deg+((($min*60)+($sec))/3600));
  312. if ($dec > 0) {
  313. $dec = $direction * $dec;
  314. }
  315. return $dec;
  316. }
  317. /**
  318. * Decimal Degrees to Decimal-Degrees-Seconds
  319. *
  320. * Converts decimal longitude / latitude to DMS ( Degrees / minutes / seconds )
  321. */
  322. function geofield_latlon_DECtoDMS($dec, $axis) {
  323. if ($axis == 'lat') {
  324. if ($dec < 0) $direction = 'S';
  325. else $direction = 'N';
  326. }
  327. if ($axis == 'lon') {
  328. if ($dec < 0) $direction = 'W';
  329. else $direction = 'E';
  330. }
  331. $vars = explode(".", $dec);
  332. $deg = abs($vars[0]);
  333. if (isset($vars[1])) {
  334. $tempma = "0." . $vars[1];
  335. }
  336. else {
  337. $tempma = "0";
  338. }
  339. $tempma = $tempma * 3600;
  340. $min = floor($tempma / 60);
  341. $sec = $tempma - ($min*60);
  342. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\" " . $direction;
  343. }
  344. /**
  345. * Decimal Degrees to Celestial coordinate system (CCS) units
  346. *
  347. * Converts decimal latitude to DMS ( Degrees / minutes / seconds ) and decimal longitude to Angular Hours / Minutes / Seconds
  348. */
  349. function geofield_latlon_DECtoCCS($dec, $axis) {
  350. // Declination (celestial latitude) should be representeted in Degrees / minutes / seconds
  351. if ($axis == 'lat') {
  352. $vars = explode("." , $dec);
  353. $deg = $vars[0];
  354. if (isset($vars[1])) {
  355. $tempma = "0." . $vars[1];
  356. }
  357. else {
  358. $tempma = "0";
  359. }
  360. $tempma = $tempma * 3600;
  361. $min = floor($tempma / 60);
  362. $sec = $tempma - ($min*60);
  363. return $deg . "&deg; " . $min . "' " . round($sec, 3) . "\"";
  364. }
  365. // Right ascension (celestial longitude) should be representeted in Hours / Minutes / Seconds
  366. if ($axis == 'lon') {
  367. $tempma = $dec / 15;
  368. $vars = explode(".", $tempma);
  369. $hrs = $vars[0];
  370. if (isset($vars[1])) {
  371. $tempma = "0." . $vars[1];
  372. }
  373. else {
  374. $tempma = "0";
  375. }
  376. $tempma = $tempma * 60;
  377. $vars = explode(".", $tempma);
  378. $min = $vars[0];
  379. if (isset($vars[1])) {
  380. $tempma = "0." . $vars[1];
  381. }
  382. else {
  383. $tempma = "0";
  384. }
  385. $sec = $tempma * 60;
  386. return $hrs . "h " . $min . "m " . round($sec, 3) . "s";
  387. }
  388. }
  389. /**
  390. * Callback to alter the property info of geofield fields.
  391. *
  392. * @see geofield_field_info().
  393. */
  394. function geofield_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
  395. $name = $field['field_name'];
  396. $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
  397. $property['type'] = ($field['cardinality'] != 1) ? 'list<geofield>' : 'geofield';
  398. $property['getter callback'] = 'entity_metadata_field_verbatim_get';
  399. $property['setter callback'] = 'entity_metadata_field_verbatim_set';
  400. // $property['auto creation'] = 'geofield_default_values';
  401. $property['property info'] = geofield_data_property_info('Geofield');
  402. unset($property['query callback']);
  403. }
  404. /**
  405. * Defines info for the properties of the geofield field data structure.
  406. */
  407. function geofield_data_property_info($name = NULL) {
  408. // Build an array of basic property information for the geofield field.
  409. $properties = array(
  410. 'wkt' => array(
  411. 'label' => 'Well-known text',
  412. 'type' => 'text',
  413. ),
  414. 'geo_type' => array(
  415. 'label' => 'Geo Type',
  416. 'options list' => '_geofield_geo_types_options_callback',
  417. 'required' => TRUE,
  418. ),
  419. 'lat' => array(
  420. 'label' => 'Latitude',
  421. 'type' => 'decimal',
  422. 'required' => TRUE,
  423. 'setter callback' => 'entity_property_verbatim_set',
  424. ),
  425. 'lon' => array(
  426. 'label' => 'Longitude',
  427. 'type' => 'decimal',
  428. 'required' => TRUE,
  429. 'setter callback' => 'entity_property_verbatim_set',
  430. ),
  431. 'left' => array(
  432. 'label' => 'Left Latitude',
  433. 'type' => 'decimal',
  434. 'setter callback' => 'entity_property_verbatim_set',
  435. ),
  436. 'top' => array(
  437. 'label' => 'Top Longitude',
  438. 'type' => 'decimal',
  439. 'setter callback' => 'entity_property_verbatim_set',
  440. ),
  441. 'right' => array(
  442. 'label' => 'Right Latitude',
  443. 'type' => 'decimal',
  444. 'setter callback' => 'entity_property_verbatim_set',
  445. ),
  446. 'bottom' => array(
  447. 'label' => 'Bottom Longitude',
  448. 'type' => 'decimal',
  449. 'setter callback' => 'entity_property_verbatim_set',
  450. ),
  451. 'srid' => array(
  452. 'label' => 'Projection (SRID)',
  453. 'type' => 'integer'
  454. ),
  455. 'latlon' => array(
  456. 'label' => 'LatLong Pair',
  457. 'type' => 'string',
  458. 'getter callback' => 'geofield_return_latlon',
  459. ),
  460. );
  461. // Add the default values for each of the geofield properties.
  462. foreach ($properties as $key => &$value) {
  463. $value += array(
  464. 'description' => !empty($name) ? t('!label of field %name', array('!label' => $value['label'], '%name' => $name)) : '',
  465. 'getter callback' => 'entity_property_verbatim_get',
  466. );
  467. }
  468. return $properties;
  469. }
  470. function _geofield_geo_types_options_callback() {
  471. $geophp = geophp_load();
  472. if (!$geophp) {
  473. return;
  474. }
  475. return geoPHP::geometryList();
  476. }
  477. /**
  478. * Gets the a latlong property.
  479. */
  480. function geofield_return_latlon($data, array $options, $name) {
  481. if ((is_array($data) || (is_object($data) && $data instanceof ArrayAccess))) {
  482. return $data['lat'] . ',' . $data['lon'];
  483. }
  484. return NULL;
  485. }