Skip to content

Commit

Permalink
Abstract Geolocation (and use the Google Geocoding API instead of the…
Browse files Browse the repository at this point in the history
… depreciated Google Maps API v2)

Update tests with new data an links to Google Maps to verify the results.
  • Loading branch information
m14t committed Jun 9, 2011
1 parent 24631a9 commit 873d159
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 61 deletions.
127 changes: 127 additions & 0 deletions lib/GoogleGeoCoder.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?php


/**
* Geocode an address using Google's Geocoder API
*
* @package Locatable_Extension
* @subpackage geocoder
* @author Matt Farmer <work@mattfarmer.net>
* @link http://code.google.com/apis/maps/documentation/geocoding/index.html
*/
class GoogleGeoCoder
{

protected
$geo_url = "http://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=",
$logger = null,
$mapping = array(
'postal_code' => 'zipcode',
'locality' => 'city',
'neighborhood' => 'city',
'sublocality' => 'city',
'administrative_area_level_2' => 'county',
'administrative_area_level_1' => 'state',
'country' => 'country',
),
$save_short = array(
'country',
'state',
),
$data = array();

public function __construct( $geocode )
{
$this->geocode = $geocode;
$this->__fetchData();
}

public function __call($func, $args)
{
//-- Add Generic get* metonds
switch( substr($func, 0, 3) )
{
case 'get':
$field = sfInflector::underscore(substr($func, 3));
if ( 0 === strlen($field) )
{
return $this->data[$args[0]];
}
if ( array_key_exists( $field, $this->data ) )
{
return $this->data[$field];
}
else
{
return null;
}
break;
default:
throw new Exception('Fatal Error: Call to undefined method '. __CLASS__ .'::'. $func .'() in '. __FILE__ .' on '. __LINE__);
break;
}
}

protected function __fetchData()
{
$url = $this->geo_url . urlencode($this->geocode);

$session = curl_init($url);
curl_setopt($session, CURLOPT_RETURNTRANSFER, true);
$res = json_decode(curl_exec($session));
if ( !$res )
{
//-- TODO: Pull out the curl response error code here.
$this->log('err', 'Error looking up location ('.$this->geocode.'). No response given.');
}
else
{
if ( "OK" != $res->status )
{
$this->log('err', 'Error looking up location ('.$this->geocode.'). Status: '.$res->status);
}
else
{
$this->__parseData( $res->results[0] );
}
}
}

protected function __parseData($result)
{
foreach( $result->address_components as $comp )
{
foreach ( $this->mapping as $googleKey => $pluginKey )
{
if ( in_array($googleKey, $comp->types) )
{
$this->data[$pluginKey] = $comp->long_name;
if ( in_array($pluginKey, $this->save_short) )
{
$this->data[$pluginKey.'_short'] = $comp->short_name;
}
}
}
}
$this->data['latitude'] = $result->geometry->location->lat;
$this->data['longitude'] = $result->geometry->location->lng;
}

protected function getLogger()
{
if ( is_null($this->logger) )
{
$this->logger = sfContext::getInstance()->getLogger();
}
return $this->logger;
}

protected function log($levelFunc, $msg)
{
if (sfConfig::get('sf_logging_enabled'))
{
$this->getLogger()->$levelFunc( 'GoogleGeoCoder: '.$msg );
}
}

}
86 changes: 30 additions & 56 deletions lib/template/Geolocatable.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,33 @@
* @package Locatable_Extension
* @subpackage template
* @author Brent Shaffer
* @author Matt Farmer <work@mattfarmer.net>
* @copyright Copyright (c) 2008 Centre{source}, Brent Shaffer 2008-12-22. All rights reserved.
*/
class Doctrine_Template_Geolocatable extends Doctrine_Template
{
/**
* Array of locatable options
*/
protected $_url = 'http://maps.google.com/maps/geo',
$_options = array('columns' => array(
'latitude' => array(
'name' => 'latitude',
'type' => 'double',
'length' => 16,
'alias' => null,
'options' => array('length' => 16, 'scale' => 10)),
'longitude' => array(
'name' => 'longitude',
'type' => 'double',
'length' => 16,
'alias' => null,
'options' => array('length' => 16, 'scale' => 10)),
), 'fields' => array(),
'distance_unit' => 'miles',
);
protected $_options = array(
'columns' => array(
'latitude' => array(
'name' => 'latitude',
'type' => 'double',
'length' => 16,
'alias' => null,
'options' => array('length' => 16, 'scale' => 10)),
'longitude' => array(
'name' => 'longitude',
'type' => 'double',
'length' => 16,
'alias' => null,
'options' => array('length' => 16, 'scale' => 10)),
),
'fields' => array(),
'distance_unit' => 'miles',
'geocoder_class' => 'GoogleGeoCoder',
);


/**
Expand Down Expand Up @@ -71,54 +74,25 @@ public function setTableDefinition()
$this->addListener(new Doctrine_Template_Listener_Geolocatable($this->_options));
}

// =======================
// = Geocoding Functions =
// =======================
public function buildGeoQuery()
{
$obj = $this->getInvoker();
$query = array();
foreach ($this->_options['fields'] as $field)
{
$query[] = $obj->$field;
}

return implode(', ', array_filter($query));
}

public function buildUrlFromQuery($query)
{
return $this->_url
. '?'
. http_build_query(array('q' => $query, 'output' => 'csv'));
}

public function retrieveGeocodesFromUrl($url)
public function refreshGeocodes()
{
$codes = explode(',', file_get_contents($url));
$geocodes = array('latitude' => null, 'longitude' => null);
$obj = $this->getInvoker();

if (count($codes) >= 4)
$query = array();
foreach ($this->_options['fields'] as $field)
{
$geocodes['latitude'] = $codes[2];
$geocodes['longitude'] = $codes[3];
$query[] = $obj->$field;
}

return $geocodes;
}

public function refreshGeocodes($url = null)
{
$obj = $this->getInvoker();
$geocoder_class = $this->_options['geocoder_class'];
$geocoder = new $geocoder_class(implode(', ', $query));

if (!$url)
foreach($this->_options['columns'] as $key => $options )
{
$url = $this->buildUrlFromQuery($this->buildGeoQuery());
$func = 'get'.sfInflector::camelize($options['name']);
$obj[$options['name']] = $geocoder->$func();
}

$geocodes = $this->retrieveGeocodesFromUrl($url);
$obj[$this->_options['columns']['latitude']['name']] = $geocodes['latitude'];
$obj[$this->_options['columns']['longitude']['name']] = $geocodes['longitude'];
}

public function addDistanceQueryTableProxy($query, $latitude, $longitude, $distance = null)
Expand Down
15 changes: 10 additions & 5 deletions test/unit/GeolocatableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
$article->zip = 37211;
$article->save();

$t->is($article->latitude, 36.0775432);
$t->info(" Verify at http://maps.google.com/maps?hl=en&q={$article->latitude},+{$article->longitude}");
$t->is($article->latitude, 36.0558177);
$t->is($article->longitude, -86.7315785);

$t->info('Test Geolocatable Plugin locates with missing fields');
Expand All @@ -23,7 +24,8 @@
$article->zip = 37211;
$article->save();

$t->is($article->latitude, 36.0775432);
$t->info(" Verify at http://maps.google.com/maps?hl=en&q={$article->latitude},+{$article->longitude}");
$t->is($article->latitude, 36.0558177);
$t->is($article->longitude, -86.7315785);

$t->info('Test Geolocatable Plugin locates by address');
Expand All @@ -33,8 +35,9 @@
$article->address = 'Tour Eiffel Champ de Mars 75007 Paris, France';
$article->save();

$t->is($article->latitude, 48.8582780);
$t->is($article->longitude, 2.2942540);
$t->info(" Verify at http://maps.google.com/maps?hl=en&q={$article->latitude},+{$article->longitude}");
$t->is($article->latitude, 48.8582635);
$t->is($article->longitude, 2.2942543);

$t->info('Test Geolocatable Plugin updates latitude / longitude automatically when saved');

Expand All @@ -46,13 +49,15 @@
$article->save();


$t->is($article->latitude, 36.0775432);
$t->info(" Verify at http://maps.google.com/maps?hl=en&q={$article->latitude},+{$article->longitude}");
$t->is($article->latitude, 36.0558177);
$t->is($article->longitude, -86.7315785);

// Latitude / Longitude data changes with new zipcode
$article->zip = 37212;
$article->save();

$t->info(" Verify at http://maps.google.com/maps?hl=en&q={$article->latitude},+{$article->longitude}");
$t->is($article->latitude, 36.1281626);
$t->is($article->longitude, -86.7969244);

Expand Down

0 comments on commit 873d159

Please sign in to comment.