location_handler_argument_location_proximity.inc 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * @file
  4. * Location proximity argument handler.
  5. */
  6. /**
  7. * Argument handler to accept proximity
  8. */
  9. class location_handler_argument_location_proximity extends views_handler_argument {
  10. function option_definition() {
  11. $options = parent::option_definition();
  12. // As only us and uk use miles, make km the default otherwise.
  13. $country = variable_get('location_default_country', 'us');
  14. $options['search_units'] = array('default' => ($country == 'us' || $country == 'uk' ? 'mile' : 'km'));
  15. $options['search_method'] = array('default' => 'mbr');
  16. $options['type'] = array('default' => 'postal');
  17. return $options;
  18. }
  19. /**
  20. * Add a form elements to select options for this argument.
  21. */
  22. function options_form(&$form, &$form_state) {
  23. parent::options_form($form, $form_state);
  24. $form['type'] = array(
  25. '#title' => t('Coordinate Type'),
  26. '#type' => 'select',
  27. '#options' => array(
  28. 'postal' => t('Postal Code (Zipcode)'),
  29. 'latlon' => t('Decimal Latitude and Longitude coordinates, comma delimited'),
  30. ),
  31. '#default_value' => $this->options['type'],
  32. '#description' => t('Type of center point.') . '<br />' . t('Postal code argument format: country_postcode_distance or postcode_distance') . '<br />' . t('Lat/Lon argument format: lat,lon_distance') . '<br />' . t('where distance is either a number or a comma delimited pair of decimal degrees'),
  33. );
  34. // Units used.
  35. $form['search_units'] = array(
  36. '#type' => 'select',
  37. '#title' => t('Distance unit'),
  38. '#options' => array(
  39. 'km' => t('Kilometers'),
  40. 'm' => t('Meters'),
  41. 'mile' => t('Miles'),
  42. 'dd' => t('Decimal degrees'),
  43. ),
  44. '#default_value' => $this->options['search_units'],
  45. '#description' => t('Select the unit of distance. Decimal degrees should be comma delimited.'),
  46. );
  47. $form['search_method'] = array(
  48. '#title' => t('Method'),
  49. '#type' => 'select',
  50. '#options' => array(
  51. 'dist' => t('Circular Proximity'),
  52. 'mbr' => t('Rectangular Proximity'),
  53. ),
  54. '#default_value' => $this->options['search_method'],
  55. '#description' => t('Method of determining proximity. Please note that Circular Proximity does not work with Decimal degrees.'),
  56. );
  57. }
  58. function calculate_coords() {
  59. if (!empty($this->value['latitude']) && !empty($this->value['longitude'])) {
  60. // If there are already coordinates, there's no work for us.
  61. return TRUE;
  62. }
  63. // @@@ Switch to mock location object and rely on location more?
  64. if ($this->options['type'] == 'postal') {
  65. if (!isset($this->value['country'])) {
  66. $this->value['country'] = variable_get('location_default_country', 'us');
  67. }
  68. // Zip code lookup.
  69. if (!empty($this->value['postal_code']) && !empty($this->value['country'])) {
  70. location_load_country($this->value['country']);
  71. $coord = location_get_postalcode_data($this->value);
  72. if ($coord) {
  73. $this->value['latitude'] = $coord['lat'];
  74. $this->value['longitude'] = $coord['lon'];
  75. }
  76. else {
  77. $coord = location_latlon_rough($this->value);
  78. if ($coord) {
  79. $this->value['latitude'] = $coord['lat'];
  80. $this->value['longitude'] = $coord['lon'];
  81. }
  82. else {
  83. return FALSE;
  84. }
  85. }
  86. }
  87. else {
  88. return FALSE;
  89. }
  90. }
  91. return TRUE;
  92. }
  93. /**
  94. * Set up the query for this argument.
  95. *
  96. * The argument sent may be found at $this->argument.
  97. */
  98. function query($group_by = FALSE) {
  99. // Get and process argument.
  100. if ($this->options['type'] == 'postal') {
  101. foreach ($this->view->argument as $argument) {
  102. if ($argument->field == 'distance') {
  103. $arg_parts = explode('_', $this->view->args[$argument->position]);
  104. if (count($arg_parts) == 3) {
  105. $this->value['country'] = drupal_strtolower($arg_parts[0]);
  106. $this->value['postal_code'] = $arg_parts[1];
  107. $this->value['search_distance'] = $arg_parts[2];
  108. }
  109. else {
  110. $this->value['postal_code'] = $arg_parts[0];
  111. $this->value['search_distance'] = $arg_parts[1];
  112. }
  113. break;
  114. }
  115. }
  116. }
  117. else if ($this->options['type'] == 'latlon') {
  118. foreach ($this->view->argument as $argument) {
  119. if ($argument->field == 'distance') {
  120. list($coords, $this->value['search_distance']) = explode('_', $this->view->args[$argument->position]);
  121. list($this->value['latitude'], $this->value['longitude']) = explode(',', $coords);
  122. break;
  123. }
  124. }
  125. }
  126. // Coordinates available?
  127. if (!$this->calculate_coords()) {
  128. // Distance set?
  129. if (!empty($this->value['search_distance'])) {
  130. // Hmm, distance set but unable to resolve coordinates.
  131. // Force nothing to match.
  132. $this->query->add_where(0, "1 = 0");
  133. }
  134. return;
  135. }
  136. $this->ensure_my_table();
  137. $lat = $this->value['latitude'];
  138. $lon = $this->value['longitude'];
  139. // search_distance
  140. if ($this->options['search_units'] == 'dd') {
  141. list($lat_distance, $lon_distance) = explode(',', $this->value['search_distance']);
  142. $latrange[0] = $lat - $lat_distance;
  143. $latrange[1] = $lat + $lat_distance;
  144. $lonrange[0] = $lon - $lon_distance;
  145. $lonrange[1] = $lon + $lon_distance;
  146. }
  147. else {
  148. $distance = $this->value['search_distance'];
  149. if ($this->options['search_units'] == 'm') {
  150. $distance_meters = $distance;
  151. }
  152. else {
  153. $distance_meters = _location_convert_distance_to_meters($distance, $this->options['search_units']);
  154. }
  155. $latrange = earth_latitude_range($lon, $lat, $distance_meters);
  156. $lonrange = earth_longitude_range($lon, $lat, $distance_meters);
  157. }
  158. // Add MBR check (always).
  159. // In case we go past the 180/-180 mark for longitude.
  160. if ($lonrange[0] > $lonrange[1]) {
  161. $where = "$this->table_alias.latitude > :minlat AND $this->table_alias.latitude < :maxlat AND (($this->table_alias.longitude < 180 AND $this->table_alias.longitude > :minlon) OR ($this->table_alias.longitude < :maxlon AND $this->table_alias.longitude > -180))";
  162. }
  163. else {
  164. $where = "$this->table_alias.latitude > :minlat AND $this->table_alias.latitude < :maxlat AND $this->table_alias.longitude > :minlon AND $this->table_alias.longitude < :maxlon";
  165. }
  166. $this->query->add_where_expression(0, $where, array(':minlat' => $latrange[0], ':maxlat' => $latrange[1], ':minlon' => $lonrange[0], ':maxlon' => $lonrange[1]));
  167. if ($this->options['search_method'] == 'dist') {
  168. // Add radius check.
  169. $this->query->add_where_expression(0, earth_distance_sql($lon, $lat, $this->table_alias) . ' < :distance', array(':distance' => $distance_meters));
  170. }
  171. }
  172. }