geofield.formatters.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. <?php
  2. /**
  3. * @file
  4. * Drupal field formatter hooks and helper functions.
  5. */
  6. /**
  7. * Implements hook_field_formatter_info().
  8. */
  9. function geofield_field_formatter_info() {
  10. $formatters = array(
  11. 'geofield_wkt' => array(
  12. 'label' => t('Well Known Text (WKT)'),
  13. 'field types' => array('geofield'),
  14. 'settings' => array('data' => 'full'),
  15. ),
  16. 'geofield_geojson' => array(
  17. 'label' => t('GeoJSON'),
  18. 'field types' => array('geofield'),
  19. 'settings' => array('data' => 'full'),
  20. ),
  21. 'geofield_kml' => array(
  22. 'label' => t('KML'),
  23. 'field types' => array('geofield'),
  24. 'settings' => array('data' => 'full'),
  25. ),
  26. 'geofield_gpx' => array(
  27. 'label' => t('GPX'),
  28. 'field types' => array('geofield'),
  29. 'settings' => array('data' => 'full'),
  30. ),
  31. 'geofield_geohash' => array(
  32. 'label' => t('Geohash'),
  33. 'field types' => array('geofield'),
  34. 'settings' => array('data' => 'full'),
  35. ),
  36. 'geofield_latlon' => array(
  37. 'label' => t('Latitude/Longitude'),
  38. 'field types' => array('geofield'),
  39. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees', 'labels' => 1),
  40. ),
  41. 'geofield_lat' => array(
  42. 'label' => t('Latitude Only'),
  43. 'field types' => array('geofield'),
  44. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees'),
  45. ),
  46. 'geofield_lon' => array(
  47. 'label' => t('Longitude Only'),
  48. 'field types' => array('geofield'),
  49. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees'),
  50. ),
  51. 'geofield_geo_type' => array(
  52. 'label' => t('Geometry Type'),
  53. 'field types' => array('geofield'),
  54. 'settings' => array('data' => 'full'),
  55. ),
  56. );
  57. if (module_exists('openlayers')) {
  58. $formatters['geofield_openlayers'] = array(
  59. 'label' => t('OpenLayers'),
  60. 'field types' => array('geofield'),
  61. 'settings' => array('data' => 'full', 'map_preset' => 'geofield_formatter_map'),
  62. );
  63. }
  64. // Accessibility formatters for blind users with screen-readers
  65. $formatters['geofield_def_list'] = array(
  66. 'label' => t('Definition List (Accessibility)'),
  67. 'field types' => array('geofield'),
  68. 'settings' => array('data' => '', 'address' => 0),
  69. );
  70. $formatters['geofield_description'] = array(
  71. 'label' => t('Descriptive Text (Accessibility)'),
  72. 'field types' => array('geofield'),
  73. 'settings' => array('data' => '', 'address' => 0),
  74. );
  75. return $formatters;
  76. }
  77. /**
  78. * Helper function for getting data options
  79. */
  80. function _geofield_formatter_settings_data_options($formatter) {
  81. return array(
  82. 'full' => t('Use full geometry'),
  83. 'centroid' => t('Use centroid'),
  84. 'bounding' => t('Use bounding box'),
  85. );
  86. }
  87. /**
  88. * Helper function for getting format options
  89. */
  90. function _geofield_formatter_settings_format_options($formatter) {
  91. return array(
  92. 'decimal_degrees' => t('Decimal degrees'),
  93. 'degrees_minutes_seconds' => t('Degrees minutes seconds'),
  94. 'ccs' => t('Astronomical Celestial Coordinates System (CCS)'),
  95. );
  96. }
  97. /**
  98. * Implements hook_field_formatter_settings_form().
  99. */
  100. function geofield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  101. $display = $instance['display'][$view_mode];
  102. $settings = $display['settings'];
  103. // Map preset formatter
  104. if ($display['type'] == 'geofield_openlayers' && module_exists('openlayers')) {
  105. // Get preset options, filtered to those which have the GeoField placeholder layer
  106. $presets = openlayers_presets();
  107. $preset_options = array();
  108. foreach ($presets as $preset) {
  109. if (in_array('geofield_formatter', $preset->data['layers'])) {
  110. $preset_options[$preset->name] = $preset->title;
  111. }
  112. }
  113. $element['map_preset'] = array(
  114. '#title' => t('OpenLayers Preset'),
  115. '#type' => 'select',
  116. '#default_value' => $settings['map_preset'] ? $settings['map_preset'] : 'geofield_formatter_map',
  117. '#required' => TRUE,
  118. '#options' => $preset_options,
  119. '#description' => t('Select which OpenLayers map you would like to use. Only maps which have the geofield placeholder layer may be selected. If your preferred map is not here, add the geofield placeholder layer to it first.'),
  120. );
  121. }
  122. $data_options = _geofield_formatter_settings_data_options($display['type']);
  123. $element['data'] = array(
  124. '#title' => t('Data options'),
  125. '#type' => 'select',
  126. '#default_value' => $settings['data'] ? $settings['data'] : 'full',
  127. '#required' => TRUE,
  128. '#options' => $data_options,
  129. );
  130. if ($display['type'] == 'geofield_latlon' || $display['type'] == 'geofield_lat' || $display['type'] == 'geofield_lon') {
  131. $format_options = _geofield_formatter_settings_format_options($display['type']);
  132. $element['format'] = array(
  133. '#title' => t('Format'),
  134. '#type' => 'select',
  135. '#default_value' => $settings['format'] ? $settings['format'] : 'decimal_degrees',
  136. '#required' => TRUE,
  137. '#options' => $format_options,
  138. );
  139. }
  140. if ($display['type'] == 'geofield_latlon') {
  141. $element['labels'] = array(
  142. '#title' => t('Display Labels'),
  143. '#type' => 'checkbox',
  144. '#default_value' => $settings['labels'],
  145. );
  146. }
  147. if ($display['type'] == 'geofield_def_list' || $display['type'] == 'geofield_description') {
  148. $element['address'] = array(
  149. '#title' => t('Include reverse-geocoded address'),
  150. '#type' => 'checkbox',
  151. '#default_value' => $settings['address'],
  152. );
  153. }
  154. return $element;
  155. }
  156. /**
  157. * Implements hook_field_formatter_settings_summary().
  158. */
  159. function geofield_field_formatter_settings_summary($field, $instance, $view_mode) {
  160. $display = $instance['display'][$view_mode];
  161. $settings = $display['settings'];
  162. $summary = array();
  163. $data_options = _geofield_formatter_settings_data_options($display['type']);
  164. // Styles could be lost because of enabled/disabled modules that defines
  165. // their styles in code.
  166. if (!empty($data_options[$settings['data']])) {
  167. $summary[] = t('Data options: @data', array('@data' => $data_options[$settings['data']]));
  168. }
  169. else {
  170. $summary[] = t('No data options set');
  171. }
  172. if ($display['type'] == 'geofield_openlayers' && !empty($settings['map_preset'])) {
  173. $openlayers_presets = openlayers_preset_options();
  174. $summary[] = t('Openlayers Map: @data', array('@data' => $openlayers_presets[$settings['map_preset']]));
  175. }
  176. if ($display['type'] == 'geofield_latlon') {
  177. $format_options = _geofield_formatter_settings_format_options($display['type']);
  178. // Display this setting only if image is linked.
  179. if (isset($format_options[$settings['format']])) {
  180. $summary[] = $format_options[$settings['format']];
  181. }
  182. }
  183. if (!empty($settings['address']) && $settings['address']) {
  184. $summary[] = t('Including reverse-geocoded address');
  185. }
  186. return implode('<br />', $summary);
  187. }
  188. /**
  189. * Implements hook_field_formatter_view().
  190. */
  191. function geofield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  192. $element = array();
  193. // First check to see if we have any value and remove any unset deltas
  194. foreach ($items as $delta => $item) {
  195. if (empty($item['geom'])) {
  196. unset($items[$delta]);
  197. }
  198. }
  199. // Transform into centroid or bounding if needed
  200. if ($display['settings']['data'] != 'full') {
  201. geophp_load();
  202. if ($display['settings']['data'] == 'centroid') {
  203. foreach ($items as $delta => $item) {
  204. $centroid_wkt = 'POINT(' . $item['lon'] . ' ' . $item['lat'] . ')';
  205. $centroid = geoPHP::load($centroid_wkt);
  206. $items[$delta] = geofield_get_values_from_geometry($centroid);
  207. }
  208. }
  209. if ($display['settings']['data'] == 'bounding') {
  210. foreach ($items as $delta => $item) {
  211. $envelope_wkt = 'POLYGON ((' . $item['left'] . ' ' . $item['top'] . ', ' . $item['right'] . ' ' . $item['top'] . ', ' . $item['right'] . ' ' . $item['bottom'] . ', ' . $item['left'] . ' ' . $item['bottom'] . ', ' . $item['left'] . ' ' . $item['top'] . '))';
  212. $envelope = geoPHP::load($envelope_wkt);
  213. $items[$delta] = geofield_get_values_from_geometry($envelope);
  214. }
  215. }
  216. }
  217. // If we are a lat, lon, or latlon, and we are using degrees-minutes-seconds (instead of decimal degrees), then do a transformation
  218. if (isset($display['settings']['format'])) {
  219. if ($display['settings']['format'] == 'degrees_minutes_seconds') {
  220. foreach ($items as $delta => $item) {
  221. $items[$delta]['lat'] = geofield_latlon_DECtoDMS($item['lat'], 'lat');
  222. $items[$delta]['lon'] = geofield_latlon_DECtoDMS($item['lon'], 'lon');
  223. }
  224. }
  225. }
  226. // If we are a lat, lon, or latlon, and we are using celestial coordinate system (instead of decimal degrees), then do a transformation
  227. if (isset($display['settings']['format'])) {
  228. if ($display['settings']['format'] == 'ccs') {
  229. foreach ($items as $delta => $item) {
  230. $items[$delta]['lat'] = geofield_latlon_DECtoCCS($item['lat'], 'lat');
  231. $items[$delta]['lon'] = geofield_latlon_DECtoCCS($item['lon'], 'lon');
  232. }
  233. }
  234. }
  235. switch ($display['type']) {
  236. case 'geofield_wkt':
  237. geophp_load();
  238. foreach ($items as $delta => $item) {
  239. $geometry = geoPHP::load($item['geom']);
  240. $wkt = $geometry->out('wkt');
  241. $microdata = _geofield_item_microdata_propertyvalue($entity, $instance, $item);
  242. $element[$delta] = array('#markup' => $wkt . $microdata);
  243. }
  244. return $element;
  245. case 'geofield_geojson':
  246. geophp_load();
  247. foreach ($items as $delta => $item) {
  248. $geometry = geoPHP::load($item['geom']);
  249. $json = $geometry->out('json');
  250. $element[$delta] = array('#markup' => $json);
  251. }
  252. return $element;
  253. case 'geofield_kml':
  254. geophp_load();
  255. foreach ($items as $delta => $item) {
  256. $geometry = geoPHP::load($item['geom']);
  257. $kml = $geometry->out('kml');
  258. $element[$delta] = array('#markup' => $kml);
  259. }
  260. return $element;
  261. case 'geofield_gpx':
  262. geophp_load();
  263. foreach ($items as $delta => $item) {
  264. $geometry = geoPHP::load($item['geom']);
  265. $kml = $geometry->out('gpx');
  266. $element[$delta] = array('#markup' => $kml);
  267. }
  268. return $element;
  269. case 'geofield_geohash':
  270. geophp_load();
  271. foreach ($items as $delta => $item) {
  272. $geometry = geoPHP::load($item['geom']);
  273. $geohash = $geometry->out('geohash');
  274. $element[$delta] = array('#markup' => $geohash);
  275. }
  276. return $element;
  277. case 'geofield_latlon':
  278. foreach ($items as $delta => $item) {
  279. $microdata = _geofield_item_microdata_propertyvalue($entity, $instance, $item);
  280. if ($display['settings']['labels']) {
  281. $element[$delta] = array('#markup' => t('Latitude: !latitude <br/>Longitude: !longitude' . $microdata, array('!latitude' => $item['lat'], '!longitude' => $item['lon'])));
  282. }
  283. else {
  284. $element[$delta] = array('#markup' => $item['lat'] . ', ' . $item['lon'] . $microdata);
  285. }
  286. }
  287. return $element;
  288. case 'geofield_lat':
  289. foreach ($items as $delta => $item) {
  290. $microdata = _geofield_item_microdata_propertyvalue($entity, $instance, $item);
  291. $element[$delta] = array('#markup' => $item['lat'] . $microdata);
  292. }
  293. return $element;
  294. case 'geofield_lon':
  295. foreach ($items as $delta => $item) {
  296. $microdata = _geofield_item_microdata_propertyvalue($entity, $instance, $item);
  297. $element[$delta] = array('#markup' => $item['lon'] . $microdata);
  298. }
  299. return $element;
  300. case 'geofield_geo_type':
  301. foreach ($items as $delta => $item) {
  302. $element[$delta] = array('#markup' => $item['geo_type']);
  303. }
  304. return $element;
  305. case 'geofield_openlayers':
  306. $map_name = $display['settings']['map_preset'] ? $display['settings']['map_preset'] : 'geofield_formatter_map';
  307. $element[0] = array('#markup' => _geofield_openlayers_formatter($map_name, $items));
  308. return $element;
  309. case 'geofield_def_list':
  310. foreach ($items as $delta => $item) {
  311. $element[$delta] = array('#markup' => _geofield_def_list_formatter($item, $display['settings']));
  312. }
  313. return $element;
  314. case 'geofield_description':
  315. foreach ($items as $delta => $item) {
  316. $element[$delta] = array('#markup' => _geofield_description_formatter($item, $display['settings']));
  317. }
  318. return $element;
  319. }
  320. return $element;
  321. }
  322. /**
  323. * Function to return appropriate GeoCoordinates/GeoShape properties.
  324. *
  325. * This is developing code, as an example. Output can be made available to the
  326. * theme, as specific site may want to display or not data based on other
  327. * factors.
  328. */
  329. function _geofield_item_microdata_propertyvalue($entity, $instance, $item) {
  330. $output = '';
  331. // If there is no microdata mapping for the field, return an empty string.
  332. if (isset($entity->microdata[$instance['field_name']])) {
  333. $microdata = $entity->microdata[$instance['field_name']];
  334. }
  335. else {
  336. return '';
  337. }
  338. if ($item['geo_type'] == 'point') {
  339. foreach (array('lat', 'lon') as $property) {
  340. $microdata[$property]['#attributes']['content'] = $item[$property];
  341. $output .= '<meta' . drupal_attributes($microdata[$property]['#attributes']) .' />';
  342. }
  343. }
  344. else {
  345. $schemaorg_shape = geofield_schemaorg_shape($item);
  346. $microdata['schemaorg_shape']['#attributes']['content'] = $schemaorg_shape;
  347. $output .= '<meta' . drupal_attributes($microdata['schemaorg_shape']['#attributes']) .' />';
  348. }
  349. return $output;
  350. }
  351. function _geofield_openlayers_formatter($map_name, $items) {
  352. $features = array();
  353. geophp_load();
  354. // Create array of $features
  355. foreach ($items as $delta) {
  356. if (array_key_exists('geom', $delta)) {
  357. $geometry = geoPHP::load($delta['geom']);
  358. }
  359. else {
  360. $geometry = geoPHP::load($delta);
  361. }
  362. $features[] = array(
  363. 'wkt' => $geometry->out('wkt'),
  364. 'projection' => 'EPSG:4326',
  365. );
  366. }
  367. // Get map preset
  368. $preset = openlayers_preset_load($map_name);
  369. $map = openlayers_build_map($preset->data);
  370. if (!isset($map['layers']['geofield_formatter'])) {
  371. drupal_set_message(t('Trying to render a geofield formatter on a map without the placeholder layer'), 'error');
  372. }
  373. // Add the features to the placeholder layer
  374. $map['layers']['geofield_formatter']['features'] = $features;
  375. // Return themed map if no errors found
  376. if (empty($map['errors'])) {
  377. $js = array('openlayers' => array('maps' => array($map['id'] => $map)));
  378. drupal_add_js($js, 'setting');
  379. // Push map through theme function and return
  380. $output = theme('openlayers_map', array(
  381. 'map' => $map,
  382. 'map_name' => $map_name
  383. ));
  384. }
  385. return $output;
  386. }
  387. function _geofield_def_list_formatter($item, $settings) {
  388. geophp_load();
  389. $geometry = geoPHP::load($item['geom']);
  390. // Single types
  391. $single_types = array('Point', 'LineString', 'Polygon');
  392. if (in_array($geometry->geometryType(), $single_types)) {
  393. $centroid = new Point($item['lon'], $item['lat']);
  394. $info = _geofield_formatter_get_info($geometry, $centroid, $settings['address']);
  395. return _geofield_def_list_formatter_dl($info);
  396. }
  397. else {
  398. $output = '';
  399. foreach ($geometry->getComponents() as $component) {
  400. $centroid = $component->centroid();
  401. $info = _geofield_formatter_get_info($component, $centroid, $settings['address']);
  402. $output .= _geofield_def_list_formatter_dl($info);
  403. }
  404. return $output;
  405. }
  406. }
  407. function _geofield_def_list_formatter_dl($info) {
  408. $output = '<dl>';
  409. foreach ($info as $dt => $dd) {
  410. $output .= '<dt>' . drupal_ucfirst($dt) . '</dt><dd>' . $dd . '</dd>';
  411. }
  412. $output .= '</dl>';
  413. return $output;
  414. }
  415. function _geofield_description_formatter($item, $settings) {
  416. geophp_load();
  417. $geometry = geoPHP::load($item['geom']);
  418. if (!$geometry) return '';
  419. // Single types
  420. $single_types = array('Point', 'LineString', 'Polygon');
  421. if (in_array($geometry->geometryType(), $single_types)) {
  422. $centroid = new Point($item['lon'], $item['lat']);
  423. $info = _geofield_formatter_get_info($geometry, $centroid, $settings['address']);
  424. return _geofield_description_formatter_text($info);
  425. }
  426. else {
  427. $output = t('A collection of shapes');
  428. $output .= '<ol>';
  429. foreach ($geometry->getComponents() as $component) {
  430. $centroid = $component->centroid();
  431. $info = _geofield_formatter_get_info($component, $centroid, $settings['address']);
  432. $output .= '<li>' . _geofield_description_formatter_text($info) . '</li>';
  433. }
  434. $output .= '</ol>';
  435. return $output;
  436. }
  437. }
  438. function _geofield_description_formatter_text($info) {
  439. $text = t('@shape with a center of latitude @lat and longitude @lon.', array('@shape' => $info['shape'], '@lat' => $info['latitude'], '@lon' => $info['longitude']));
  440. if (isset($info['address'])) {
  441. $text .= ' ' . t('It has an approximate address of @address.', array('@address' => $info['address']));
  442. }
  443. if (isset($info['area'])) {
  444. $text .= ' ' . t('It has an area of @area.', array('@area' => $info['area']));
  445. }
  446. if (isset($info['length'])) {
  447. $text .= ' ' . t('It has a length of @length.', array('@length' => $info['length']));
  448. }
  449. return $text;
  450. }
  451. function _geofield_formatter_get_info($geometry, $centroid, $address = FALSE, $units = 'degrees') {
  452. $info = array(
  453. 'latitude' => $centroid->y(),
  454. 'longitude' => $centroid->x(),
  455. );
  456. // Get the shape
  457. if ($geometry->geometryType() == 'Point') {
  458. $info['shape'] = 'Point';
  459. }
  460. if ($geometry->geometryType() == 'LineString') {
  461. $info['shape'] = 'Line';
  462. }
  463. if ($geometry->geometryType() == 'Polygon') {
  464. //@@TODO: Get more useful information like 'Triangle', 'Square', 'Rectangle', 'Pentagon'
  465. $info['shape'] = 'Polygon';
  466. }
  467. if ($address) {
  468. $info['address'] = $centroid->out('google_geocode');
  469. }
  470. //@@TODO, convert length and area to proper units
  471. #if ($geometry->geometryType() == 'LineString') {
  472. # $info['length'] = $geometry->length();
  473. #}
  474. #if ($geometry->geometryType() == 'Polygon') {
  475. # $info['area'] = $geometry->area();
  476. #}
  477. return $info;
  478. }