geofield.formatters.inc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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_latlon' => array(
  32. 'label' => t('Latitude/Longitude'),
  33. 'field types' => array('geofield'),
  34. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees', 'labels' => 1),
  35. ),
  36. 'geofield_lat' => array(
  37. 'label' => t('Latitude Only'),
  38. 'field types' => array('geofield'),
  39. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees'),
  40. ),
  41. 'geofield_lon' => array(
  42. 'label' => t('Longitude Only'),
  43. 'field types' => array('geofield'),
  44. 'settings' => array('data' => 'full', 'format' => 'decimal_degrees'),
  45. ),
  46. 'geofield_geo_type' => array(
  47. 'label' => t('Geometry Type'),
  48. 'field types' => array('geofield'),
  49. 'settings' => array('data' => 'full'),
  50. ),
  51. );
  52. if (module_exists('openlayers')) {
  53. $formatters['geofield_openlayers'] = array(
  54. 'label' => t('OpenLayers'),
  55. 'field types' => array('geofield'),
  56. 'settings' => array('data' => 'full', 'map_preset' => 'geofield_formatter_map'),
  57. );
  58. }
  59. // Accessibility formatters for blind users with screen-readers
  60. $formatters['geofield_def_list'] = array(
  61. 'label' => t('Definition List (Accessibility)'),
  62. 'field types' => array('geofield'),
  63. 'settings' => array('data' => '', 'address' => 0),
  64. );
  65. $formatters['geofield_description'] = array(
  66. 'label' => t('Decriptive Text (Accessibility)'),
  67. 'field types' => array('geofield'),
  68. 'settings' => array('data' => '', 'address' => 0),
  69. );
  70. return $formatters;
  71. }
  72. /**
  73. * Helper function for getting data options
  74. */
  75. function _geofield_formatter_settings_data_options($formatter) {
  76. return array(
  77. 'full' => t('Use full geometry'),
  78. 'centroid' => t('Use centroid'),
  79. 'bounding' => t('Use bounding box'),
  80. );
  81. }
  82. /**
  83. * Helper function for getting format options
  84. */
  85. function _geofield_formatter_settings_format_options($formatter) {
  86. return array(
  87. 'decimal_degrees' => t('Decimal degrees'),
  88. 'degrees_minutes_seconds' => t('Degrees minutes seconds'),
  89. 'ccs' => t('Astronomical Celestial Coordinates System (CCS)'),
  90. );
  91. }
  92. /**
  93. * Implements hook_field_formatter_settings_form().
  94. */
  95. function geofield_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  96. $display = $instance['display'][$view_mode];
  97. $settings = $display['settings'];
  98. // Map preset formatter
  99. if ($display['type'] == 'geofield_openlayers' && module_exists('openlayers')) {
  100. // Get preset options, filtered to those which have the GeoField placeholder layer
  101. $presets = openlayers_presets();
  102. $preset_options = array();
  103. foreach ($presets as $preset) {
  104. if (in_array('geofield_formatter', $preset->data['layers'])) {
  105. $preset_options[$preset->name] = $preset->title;
  106. }
  107. }
  108. $element['map_preset'] = array(
  109. '#title' => t('OpenLayers Preset'),
  110. '#type' => 'select',
  111. '#default_value' => $settings['map_preset'] ? $settings['map_preset'] : 'geofield_formatter_map',
  112. '#required' => TRUE,
  113. '#options' => $preset_options,
  114. '#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.'),
  115. );
  116. }
  117. $data_options = _geofield_formatter_settings_data_options($display['type']);
  118. $element['data'] = array(
  119. '#title' => t('Data options'),
  120. '#type' => 'select',
  121. '#default_value' => $settings['data'] ? $settings['data'] : 'full',
  122. '#required' => TRUE,
  123. '#options' => $data_options,
  124. );
  125. if ($display['type'] == 'geofield_latlon' || $display['type'] == 'geofield_lat' || $display['type'] == 'geofield_lon') {
  126. $format_options = _geofield_formatter_settings_format_options($display['type']);
  127. $element['format'] = array(
  128. '#title' => t('Format'),
  129. '#type' => 'select',
  130. '#default_value' => $settings['format'] ? $settings['format'] : 'decimal_degrees',
  131. '#required' => TRUE,
  132. '#options' => $format_options,
  133. );
  134. }
  135. if ($display['type'] == 'geofield_latlon') {
  136. $element['labels'] = array(
  137. '#title' => t('Display Labels'),
  138. '#type' => 'checkbox',
  139. '#default_value' => $settings['labels'],
  140. );
  141. }
  142. if ($display['type'] == 'geofield_def_list' || $display['type'] == 'geofield_description') {
  143. $element['address'] = array(
  144. '#title' => t('Include reverse-geocoded address'),
  145. '#type' => 'checkbox',
  146. '#default_value' => $settings['address'],
  147. );
  148. }
  149. return $element;
  150. }
  151. /**
  152. * Implements hook_field_formatter_settings_summary().
  153. */
  154. function geofield_field_formatter_settings_summary($field, $instance, $view_mode) {
  155. $display = $instance['display'][$view_mode];
  156. $settings = $display['settings'];
  157. $summary = array();
  158. $data_options = _geofield_formatter_settings_data_options($display['type']);
  159. // Styles could be lost because of enabled/disabled modules that defines
  160. // their styles in code.
  161. if (!empty($data_options[$settings['data']])) {
  162. $summary[] = t('Data options: @data', array('@data' => $data_options[$settings['data']]));
  163. }
  164. else {
  165. $summary[] = t('No data options set');
  166. }
  167. if ($display['type'] == 'geofield_openlayers' && !empty($settings['map_preset'])) {
  168. $openlayers_presets = openlayers_preset_options();
  169. $summary[] = t('Openlayers Map: @data', array('@data' => $openlayers_presets[$settings['map_preset']]));
  170. }
  171. if ($display['type'] == 'geofield_latlon') {
  172. $format_options = _geofield_formatter_settings_format_options($display['type']);
  173. // Display this setting only if image is linked.
  174. if (isset($format_options[$settings['format']])) {
  175. $summary[] = $format_options[$settings['format']];
  176. }
  177. }
  178. if (!empty($settings['address']) && $settings['address']) {
  179. $summary[] = t('Including reverse-geocoded address');
  180. }
  181. return implode('<br />', $summary);
  182. }
  183. /**
  184. * Implements hook_field_formatter_view().
  185. */
  186. function geofield_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  187. $element = array();
  188. // Transform into centroid or bounding if needed
  189. if ($display['settings']['data'] != 'full') {
  190. geofield_load_geophp();
  191. if ($display['settings']['data'] == 'centroid') {
  192. foreach ($items as $delta => $item) {
  193. $centroid_wkt = 'POINT(' . $item['lon'] . ' ' . $item['lat'] . ')';
  194. $centroid = geoPHP::load($centroid_wkt, 'wkt');
  195. $items[$delta] = geofield_get_values_from_geometry($centroid);
  196. }
  197. }
  198. if ($display['settings']['data'] == 'bounding') {
  199. foreach ($items as $delta => $item) {
  200. $envelope_wkt = 'POLYGON ((' . $item['left'] . ' ' . $item['top'] . ', ' . $item['right'] . ' ' . $item['top'] . ', ' . $item['right'] . ' ' . $item['bottom'] . ', ' . $item['left'] . ' ' . $item['bottom'] . ', ' . $item['left'] . ' ' . $item['top'] . ')))';
  201. $envelope = geoPHP::load($envelope_wkt, 'wkt');
  202. $items[$delta] = geofield_get_values_from_geometry($envelope);
  203. }
  204. }
  205. }
  206. // If we are a lat, lon, or latlon, and we are using degrees-minutes-seconds (instead of decimal degrees), then do a transformation
  207. if (isset($display['settings']['format'])) {
  208. if ($display['settings']['format'] == 'degrees_minutes_seconds') {
  209. foreach ($items as $delta => $item) {
  210. $items[$delta]['lat'] = geofield_latlon_DECtoDMS($item['lat'], 'lat');
  211. $items[$delta]['lon'] = geofield_latlon_DECtoDMS($item['lon'], 'lon');
  212. }
  213. }
  214. }
  215. // If we are a lat, lon, or latlon, and we are using celestial coordinate system (instead of decimal degrees), then do a transformation
  216. if (isset($display['settings']['format'])) {
  217. if ($display['settings']['format'] == 'ccs') {
  218. foreach ($items as $delta => $item) {
  219. $items[$delta]['lat'] = geofield_latlon_DECtoCCS($item['lat'], 'lat');
  220. $items[$delta]['lon'] = geofield_latlon_DECtoCCS($item['lon'], 'lon');
  221. }
  222. }
  223. }
  224. switch ($display['type']) {
  225. case 'geofield_wkt':
  226. foreach ($items as $delta => $item) {
  227. $element[$delta] = array('#markup' => $item['wkt']);
  228. }
  229. return $element;
  230. case 'geofield_geojson':
  231. geofield_load_geophp();
  232. foreach ($items as $delta => $item) {
  233. $geometry = geoPHP::load($item['wkt'], 'wkt');
  234. $json = $geometry->out('json');
  235. $element[$delta] = array('#markup' => $json);
  236. }
  237. return $element;
  238. case 'geofield_kml':
  239. geofield_load_geophp();
  240. foreach ($items as $delta => $item) {
  241. $geometry = geoPHP::load($item['wkt'], 'wkt');
  242. $kml = $geometry->out('kml');
  243. $element[$delta] = array('#markup' => $kml);
  244. }
  245. return $element;
  246. case 'geofield_gpx':
  247. geofield_load_geophp();
  248. foreach ($items as $delta => $item) {
  249. $geometry = geoPHP::load($item['wkt'], 'wkt');
  250. $kml = $geometry->out('gpx');
  251. $element[$delta] = array('#markup' => $kml);
  252. }
  253. return $element;
  254. case 'geofield_latlon':
  255. foreach ($items as $delta => $item) {
  256. if ($display['settings']['labels']) {
  257. $output = t('Latitude: !latitude <br/>Longitude: !longitude', array('!latitude' => $item['lat'], '!longitude' => $item['lon']));
  258. }
  259. else {
  260. $output = $item['lat'] . ', ' . $item['lon'];
  261. }
  262. $element[$delta] = array('#markup' => $output);
  263. }
  264. return $element;
  265. case 'geofield_lat':
  266. foreach ($items as $delta => $item) {
  267. $element[$delta] = array('#markup' => $item['lat']);
  268. }
  269. return $element;
  270. case 'geofield_lon':
  271. foreach ($items as $delta => $item) {
  272. $element[$delta] = array('#markup' => $item['lon']);
  273. }
  274. return $element;
  275. case 'geofield_geo_type':
  276. foreach ($items as $delta => $item) {
  277. $element[$delta] = array('#markup' => $item['geo_type']);
  278. }
  279. return $element;
  280. case 'geofield_openlayers':
  281. $map_name = $display['settings']['map_preset'] ? $display['settings']['map_preset'] : 'geofield_formatter_map';
  282. $element[0] = array('#markup' => _geofield_openlayers_formatter($map_name, $items));
  283. return $element;
  284. case 'geofield_def_list':
  285. foreach ($items as $delta => $item) {
  286. $element[$delta] = array('#markup' => _geofield_def_list_formatter($item, $display['settings']));
  287. }
  288. return $element;
  289. case 'geofield_description':
  290. foreach ($items as $delta => $item) {
  291. $element[$delta] = array('#markup' => _geofield_description_formatter($item, $display['settings']));
  292. }
  293. return $element;
  294. }
  295. return $element;
  296. }
  297. function _geofield_openlayers_formatter($map_name, $items) {
  298. $features = array();
  299. // Create array of $features
  300. foreach ($items as $delta) {
  301. $features[] = array(
  302. 'wkt' => $delta['wkt'],
  303. 'projection' => '4326',
  304. );
  305. }
  306. // Get map preset
  307. $preset = openlayers_preset_load($map_name);
  308. $map = openlayers_build_map($preset->data);
  309. if (!isset($map['layers']['geofield_formatter'])) {
  310. drupal_set_message(t('Trying to render a geofield formatter on a map without the placeholder layer'), 'error');
  311. }
  312. // Add the features to the placeholder layer
  313. $map['layers']['geofield_formatter']['features'] = $features;
  314. // Return themed map if no errors found
  315. if (empty($map['errors'])) {
  316. $js = array('openlayers' => array('maps' => array($map['id'] => $map)));
  317. drupal_add_js($js, 'setting');
  318. // Push map through theme function and return
  319. $output = theme('openlayers_map', array(
  320. 'map' => $map,
  321. 'map_name' => $map_name
  322. ));
  323. }
  324. return $output;
  325. }
  326. function _geofield_def_list_formatter($item, $settings) {
  327. geofield_load_geophp();
  328. $geometry = geoPHP::load($item['wkt'], 'wkt');
  329. // Single types
  330. $single_types = array('Point', 'LineStrnig', 'Polygon');
  331. if (in_array($geometry->geometryType(), $single_types)) {
  332. $centroid = new Point($item['lon'], $item['lat']);
  333. $info = _geofield_formatter_get_info($geometry, $centroid, $settings['address']);
  334. return _geofield_def_list_formatter_dl($info);
  335. }
  336. else {
  337. $output = '';
  338. foreach ($geometry->getComponents() as $component) {
  339. $centroid = $component->centroid();
  340. $info = _geofield_formatter_get_info($component, $centroid, $settings['address']);
  341. $output .= _geofield_def_list_formatter_dl($info);
  342. }
  343. return $output;
  344. }
  345. }
  346. function _geofield_def_list_formatter_dl($info) {
  347. $output = '<dl>';
  348. foreach ($info as $dt => $dd) {
  349. $output .= '<dt>' . drupal_ucfirst($dt) . '</dt><dd>' . $dd . '</dd>';
  350. }
  351. $output .= '</dl>';
  352. return $output;
  353. }
  354. function _geofield_description_formatter($item, $settings) {
  355. geofield_load_geophp();
  356. $geometry = geoPHP::load($item['wkt'], 'wkt');
  357. // Single types
  358. $single_types = array('Point', 'LineString', 'Polygon');
  359. if (in_array($geometry->geometryType(), $single_types)) {
  360. $centroid = new Point($item['lon'], $item['lat']);
  361. $info = _geofield_formatter_get_info($geometry, $centroid, $settings['address']);
  362. return _geofield_description_formatter_text($info);
  363. }
  364. else {
  365. $output = t('A collection of shapes');
  366. $output .= '<ol>';
  367. foreach ($geometry->getComponents() as $component) {
  368. $centroid = $component->centroid();
  369. $info = _geofield_formatter_get_info($component, $centroid, $settings['address']);
  370. $output .= '<li>' . _geofield_description_formatter_text($info) . '</li>';
  371. }
  372. $output .= '</ol>';
  373. return $output;
  374. }
  375. }
  376. function _geofield_description_formatter_text($info) {
  377. $text = t('@shape with a center of latitude @lat and longitude @lon.', array('@shape' => $info['shape'], '@lat' => $info['latitude'], '@lon' => $info['longitude']));
  378. if (isset($info['address'])) {
  379. $text .= ' ' . t('It has an approximate address of @address.', array('@address' => $info['address']));
  380. }
  381. if (isset($info['area'])) {
  382. $text .= ' ' . t('It has an area of @area.', array('@area' => $info['area']));
  383. }
  384. if (isset($info['length'])) {
  385. $text .= ' ' . t('It has a length of @length.', array('@length' => $info['length']));
  386. }
  387. return $text;
  388. }
  389. function _geofield_formatter_get_info($geometry, $centroid, $address = FALSE, $units = 'degrees') {
  390. $info = array(
  391. 'latitude' => $centroid->y(),
  392. 'longitude' => $centroid->x(),
  393. );
  394. // Get the shape
  395. if ($geometry->geometryType() == 'Point') {
  396. $info['shape'] = 'Point';
  397. }
  398. if ($geometry->geometryType() == 'LineString') {
  399. $info['shape'] = 'Line';
  400. }
  401. if ($geometry->geometryType() == 'Polygon') {
  402. //@@TODO: Get more useful information like 'Triangle', 'Square', 'Rectangle', 'Pentagon'
  403. $info['shape'] = 'Polygon';
  404. }
  405. if ($address) {
  406. $info['address'] = $centroid->out('google_geocode');
  407. }
  408. //@@TODO, convert length and area to proper units
  409. #if ($geometry->geometryType() == 'LineString') {
  410. # $info['length'] = $geometry->length();
  411. #}
  412. #if ($geometry->geometryType() == 'Polygon') {
  413. # $info['area'] = $geometry->area();
  414. #}
  415. return $info;
  416. }