Fragmented Thought

Automatically set Drupal location for 'find nearby' views

By

Published

Lance Gliser

Heads up! This content is more than six months old. Take some time to verify everything still works as expected.

There's an interesting conflict between wanting to offer automatic location detection of users, and allowing them to correct you. Most major sites will take one of two options:

  • Guess at your location
  • Ask for it

Most clients I've found do not want to ask, they prefer to guess, and be corrected. Home depot is a notable exception that I remember, they demand your zip code upfront. But things like Redbox, Google, etc try to detect you first. This kind of behavior is actually somewhat easy to do in Drupal views. I'll show you how to setup a views proximity location filter to auto detect location when none is provided.

Starting point. You'll need the following things running:

  • Drupal
  • Views
  • Location
  • Smart IP
  • Gmap module
  • A custom module
  • A 'nearby' view

Step 1 - Create a form alter to allow you to alter the exposed views filters:

function custom_form_views_exposed_form_alter(&$form, &$form_state){ switch( $form_state['view']->name ){ case 'find_a_retailer': module_load_include('inc', 'custom', 'retailer/retailer'); _custom_views_exposed_form_find_a_retailer($form, $form_state); break; } }

Step 2 - Add a validation hook to the form

/** * Customizations for the find a retailer exposed form */ function _custom_views_exposed_form_find_a_retailer(&$form, $form_state){ // Add a hook to detect country and reformat postal codes to allow easier canadian access $form['#validate'][] = '_custom_views_exposed_form_find_a_retailer_validate'; }

Step 3 - Default the location

There are 3 ways to do this. If you're in the US, method 1 is super easy and quick. Method 2 is... questionable, but Method 3 is just nasty, requiring a full zip code list of the country in question.

function _custom_views_exposed_form_find_a_retailer_validate(&$form, &$form_state){ // If no zip code has been supplied, attempt to determine one based on the IP information if( empty($form_state['values']['distance']['postal_code']) ){ // Prevent ip detected pages from being cached drupal_page_is_cacheable(FALSE); $ip = NULL; if (user_access('administer smart_ip') && variable_get('smart_ip_debug', FALSE)) { $ip = variable_get('smart_ip_test_ip_address', ip_address()); } if( empty($ip) ){ $ip = ip_address(); } $location = smart_ip_get_location($ip); if( !empty($location['country_code']) && !empty($location['zip']) ){ $country = strtolower( $location['country_code'] ); $postal_code = strtoupper( $location['zip'] ); } else { // Method 1: Check our external source // Note this works only for US, as geonames returns only the first three characters of canadian postal codes, and we need all 6 /* $response = drupal_http_request('http://ws.geonames.org/findNearbyPostalCodesJSON?formatted=true&lat=' . $location['latitude'] . '&lng=' . $location['longitude']); if( $response->code == 200 ){ $data = json_decode( $response->data ); // Set the country $country = strtolower( $data->postalCodes[0]->countryCode ); $postal_code = strtoupper( $data->postalCodes[0]->postalCode ); } */ // Method 2: Attempt to find a postal code by searching for an existing location 'near' location /* $params = array( 'latitude' => $location['latitude'], 'longitude' => $location['longitude'], 'limit' => 1, 'distance' => 15, // miles ); module_load_include('inc', 'location', 'earth'); // Distance needs to be converted from miles to meters! $params['distance'] = $params['distance'] / 0.000621371192; // Get location parameters we need $distance_sql = earth_distance_sql($params['longitude'], $params['latitude'], 'location'); $latitude_range = earth_latitude_range($params['longitude'], $params['latitude'], $params['distance']); $longitude_range = earth_longitude_range($params['longitude'], $params['latitude'], $params['distance']); $query = "SELECT location.latitude AS gmap_lat ,location.longitude AS gmap_lon ," . $distance_sql . " AS location_distance ,location.country as location_country ,location.postal_code as location_postal_code FROM location location JOIN location_instance li ON li.lid = location.lid JOIN node node ON node.nid = li.nid WHERE location.latitude > :latitude_minimum AND location.latitude < :latitude_maximum AND location.longitude > :longitude_minimum AND location.longitude < :longitude_maximum AND location.postal_code != '' ORDER BY location_distance ASC LIMIT 1"; $result = db_query($query, array( ':latitude_minimum' => $latitude_range[0], ':latitude_maximum' => $latitude_range[1], ':longitude_minimum' => $longitude_range[0], ':longitude_maximum' => $longitude_range[1], ))->fetchObject(); // Convert meters back to miles // $result->location_distance = $result->location_distance * 0.000621371192; if( $result ){ $country = strtolower( $result->location_country ); $postal_code = strtoupper( $result->location_postal_code ); } */ // Method 3 // Using a preloaded zip codes database, identify the closest zipcode to the coordinates $params = array( 'latitude' => $location['latitude'], 'longitude' => $location['longitude'], 'limit' => 1, 'distance' => 15, // miles ); module_load_include('inc', 'location', 'earth'); // Distance needs to be converted from miles to meters! $params['distance'] = $params['distance'] / 0.000621371192; // Get location parameters we need $distance_sql = earth_distance_sql($params['longitude'], $params['latitude'], 'zipcodes'); $latitude_range = earth_latitude_range($params['longitude'], $params['latitude'], $params['distance']); $longitude_range = earth_longitude_range($params['longitude'], $params['latitude'], $params['distance']); $query = "SELECT " . $distance_sql . " AS location_distance ,zipcodes.country as country ,zipcodes.zip as postal_code FROM zipcodes WHERE zipcodes.latitude > :latitude_minimum AND zipcodes.latitude < :latitude_maximum AND zipcodes.longitude > :longitude_minimum AND zipcodes.longitude < :longitude_maximum ORDER BY location_distance ASC LIMIT 1"; $result = db_query($query, array( ':latitude_minimum' => $latitude_range[0], ':latitude_maximum' => $latitude_range[1], ':longitude_minimum' => $longitude_range[0], ':longitude_maximum' => $longitude_range[1], ))->fetchObject(); // Convert meters back to miles // $result->location_distance = $result->location_distance * 0.000621371192; if( $result ){ $country = strtolower( $result->country ); $postal_code = strtoupper( $result->postal_code ); } } if( !empty( $country ) && !empty( $postal_code ) ){ form_set_value( array( '#parents' => array('distance', 'country') ) ,$country ,$form_state ); // Set the postal code form_set_value( array( '#parents' => array('distance', 'postal_code') ) ,$postal_code ,$form_state ); } } }