diff --git a/models/behaviors/geocodable.php b/Model/Behavior/Geocodable.php similarity index 80% rename from models/behaviors/geocodable.php rename to Model/Behavior/Geocodable.php index 758839e..2c54eda 100644 --- a/models/behaviors/geocodable.php +++ b/Model/Behavior/Geocodable.php @@ -1,5 +1,9 @@ 2 ) ), + 'google-json' => array( + 'url' => 'http://maps.google.com/maps/geo?q=${address}&output=json&key=${key}', + 'format' => '${address1} ${address2}, ${city}, ${zip} ${state}, ${country}', + 'pattern' => false, + 'matches' => array(), + 'parser' => '_google_json_parser', + ), 'yahoo' => array( 'url' => 'http://api.local.yahoo.com/MapsService/V1/geocode?appid=${key}&location=${address}', 'format' => '${address1} ${address2}, ${city}, ${zip} ${state}, ${country}', @@ -168,6 +179,13 @@ public function beforeSave($model) { !empty($latitudeField) && !empty($longitudeField) && !isset($model->data[$model->alias][$latitudeField]) && !isset($model->data[$model->alias][$longitudeField]) ) { + + // If data['address'] is set, but data['address1'] is not, assume data['address'] is the street address, not the full address + if (!empty($model->data[$model->alias]['address']) && empty($model->data[$model->alias]['address1'])) { + $model->data[$model->alias]['address1'] = $model->data[$model->alias]['address']; + unset($model->data[$model->alias]['address']); + } + $data = $model->data[$model->alias]; if (!empty($settings['models'])) { $data = $this->_data($settings['models'], $data); @@ -175,10 +193,12 @@ public function beforeSave($model) { $geocode = $this->geocode($model, $data, false); if (!empty($geocode)) { $address = array(); - list($address[$latitudeField], $address[$longitudeField]) = $geocode; + $address[$latitudeField] = $geocode['latitude']; + $address[$longitudeField] = $geocode['longitude']; if (!empty($settings['fields']['address'])) { $address[$settings['fields']['address']] = $this->_address($settings, $data); } + $address = $this->_standardize($settings, $address, $geocode); $model->data[$model->alias] = array_merge( $model->data[$model->alias], @@ -198,7 +218,7 @@ public function beforeSave($model) { * @param object $model * @param mixed $address Array with address info (address, city, etc.) or full address as string * @param bool $save Set to true to save result in model, false otherwise - * @return mixed Array (latitude, longitude), or false if error + * @return mixed Array (latitude, longitude, (array)address), or false if error */ public function geocode($model, $address, $save = true) { $settings = $this->settings[$model->alias]; @@ -245,8 +265,8 @@ public function geocode($model, $address, $save = true) { )); if (!empty($coordinates)) { $coordinates = array( - $coordinates[$model->alias][$settings['fields']['latitude']], - $coordinates[$model->alias][$settings['fields']['longitude']], + 'latitude' => $coordinates[$model->alias][$settings['fields']['latitude']], + 'longitude' => $coordinates[$model->alias][$settings['fields']['longitude']], ); } } @@ -261,14 +281,14 @@ public function geocode($model, $address, $save = true) { } if (!empty($coordinates)) { - foreach($coordinates as $i => $coordinate) { - $coordinates[$i] = floatval($coordinate); - } + $coordinates['latitude'] = floatval($coordinates['latitude']); + $coordinates['longitude'] = floatval($coordinates['longitude']); } if ($save && !empty($coordinates) && !empty($data)) { - $data[$model->alias][$settings['fields']['latitude']] = $coordinates[0]; - $data[$model->alias][$settings['fields']['longitude']] = $coordinates[1]; + $data[$model->alias][$settings['fields']['latitude']] = $coordinates['latitude']; + $data[$model->alias][$settings['fields']['longitude']] = $coordinates['longitude']; + $data[$model->alias] = $this->_standardize($settings, $data[$model->alias], $coordinates); if (!empty($data[$model->alias][$settings['fields']['state']])) { $model->create(); @@ -309,6 +329,7 @@ public function near($model, $type, $origin, $distance = null, $unit = 'k', $que $point = $origin; } else { $point = $this->geocode($model, $origin); + $point = array( $point['latitude'], $point['longitude'] ); } if (empty($point)) { @@ -357,6 +378,7 @@ public function distance($model, $origin, $destination, $unit = 'k') { $$var = $data; } else { $$var = $this->geocode($model, $data); + $$var = array( $$var['latitude'], $$var['longitude'] ); } } @@ -473,18 +495,52 @@ protected function _fetchCoordinates($settings, $address) { $url = str_replace(array_keys($vars), $vars, $service['url']); $result = $this->socket->get($url); - if (empty($result) || !preg_match($service['pattern'], $result, $matches)) { - return false; + if (empty($result)) return false; + if (!empty($service['parser'])) { + $parser = $service['parser']; + $coordinates = $this->$parser($result); } + else if (!empty($service['pattern'])) { + if (!preg_match($service['pattern'], $result, $matches)) { + return false; + } - $coordinates = array( - $matches[$service['matches']['latitude']], - $matches[$service['matches']['longitude']] - ); + $coordinates = array( + 'latitude' => $matches[$service['matches']['latitude']], + 'longitude' => $matches[$service['matches']['longitude']] + ); + } return $coordinates; } + /** + * Extract the useful data from the full Google JSON response + * + * @param string $json JSON string from Google Maps API + * @return array Lat/lon, street, city, state, zip, country + */ + protected function _google_json_parser($json) { + $data = json_decode($json); + + $place = array(); + if (@$data->Status->code == 200) { + $placemark =& $data->Placemark[0]; + $locality = $placemark->AddressDetails->Country->AdministrativeArea->SubAdministrativeArea->Locality; + $place = array( + 'latitude' => $placemark->Point->coordinates[0], + 'longitude' => $placemark->Point->coordinates[1], + 'address' => $placemark->address, + 'address1' => $locality->Thoroughfare->ThoroughfareName, + 'city' => $locality->LocalityName, + 'state' => $placemark->AddressDetails->Country->AdministrativeArea->AdministrativeAreaName, + 'zip' => $locality->PostalCode->PostalCodeNumber, + 'country' => $placemark->AddressDetails->Country->CountryNameCode, + ); + } + return $place; + } + /** * Build full address from given address * @@ -495,6 +551,13 @@ protected function _fetchCoordinates($settings, $address) { protected function _address($settings, $address) { if (is_array($address)) { $elements = array(); + + // If data['address'] is set, but data['address1'] is not, assume data['address'] is the street address, not the full address + if (!empty($address['address']) && empty($address['address1'])) { + $address['address1'] = $address['address']; + unset($address['address']); + } + foreach($settings['addressFields'] as $type => $fields) { $fields = array_merge(array($type => $type), (array) $fields); $elements['${' . $type . '}'] = str_replace(',', ' ', trim(current(array_intersect_key($address, array_flip($fields))))); @@ -515,12 +578,32 @@ protected function _address($settings, $address) { foreach($replacements as $pattern => $replacement) { $address = preg_replace($pattern, $replacement, $address); } - $address = preg_replace('/,\s*$/', '', $address); + + // Clean up any leading or trailing commas + $address = preg_replace('/(,\s*)*$/', '', $address); + $address = preg_replace('/^(\s*,)*/', '', $address); + $address = trim($address); } return $address; } + /** + * Standardize address fields in model using fields from geocoded results + * @param array $settings Settings + * @param array $data Model data + * @param array $geocode Geocoded data + * @return array $data Standardized model data + */ + protected function _standardize($settings, $data, $geocode) { + foreach (array_keys($this->default['addressFields']) as $field) { + if (!empty($geocode[$field])) { + $data[$settings['fields'][$field]] = $geocode[$field]; + } + } + return $data; + } + /** * Adds missing address information based on specified settings. * E.g: 'city' => array('model' => 'City', 'referenceField' => 'city_id', 'field' => 'name') diff --git a/models/geo_address.php b/Model/GeoAddress.php similarity index 95% rename from models/geo_address.php rename to Model/GeoAddress.php index 67c1c9b..bc9bb2d 100644 --- a/models/geo_address.php +++ b/Model/GeoAddress.php @@ -17,7 +17,7 @@ class GeoAddress extends AppModel { * @return array Array of records */ public function find($conditions = null, $fields = array(), $order = null, $recursive = null) { - $findMethods = array_merge($this->_findMethods, array('near'=>true)); + $findMethods = array_merge($this->findMethods, array('near'=>true)); $findType = (is_string($conditions) && $conditions != 'count' && array_key_exists($conditions, $findMethods) ? $conditions : null); if (empty($findType) && is_string($conditions) && $conditions == 'count' && !empty($fields['type']) && array_key_exists($fields['type'], $findMethods)) { $findType = $fields['type']; diff --git a/tests/cases/behaviors/geocodable.test.php b/Test/Case/Model/Behavior/GeocodableTest.php similarity index 97% rename from tests/cases/behaviors/geocodable.test.php rename to Test/Case/Model/Behavior/GeocodableTest.php index 49dc544..d2af3af 100644 --- a/tests/cases/behaviors/geocodable.test.php +++ b/Test/Case/Model/Behavior/GeocodableTest.php @@ -1,7 +1,7 @@ assertEqual($result, $expected); if (!$this->skipIf(empty($this->Geocodable->settings[$this->Address->alias]['key']), 'No service API Key provided for test')) { - $result = round($this->Address->distance($origin, '3700 Rocinante Blvd, Tampa, FL', 'm'), 1); - $expected = 3.4; + $result = round($this->Address->distance($origin, '502 Warren Road, Tampa, FL', 'm'), 1); + $expected = 3.1; $this->assertEqual($result, $expected); } } @@ -484,7 +484,8 @@ public function testFind() { public function testPaginate() { $Controller = new Controller(); $Controller->uses = array('TestAddress'); - $Controller->params['url'] = array(); + $Controller->request = new CakeRequest(); + $Controller->request->params['named'] = array(); $Controller->constructClasses(); $Controller->paginate = array('TestAddress' => array( diff --git a/tests/fixtures/address_fixture.php b/Test/Fixture/AddressFixture.php similarity index 100% rename from tests/fixtures/address_fixture.php rename to Test/Fixture/AddressFixture.php diff --git a/tests/fixtures/city_fixture.php b/Test/Fixture/CityFixture.php similarity index 100% rename from tests/fixtures/city_fixture.php rename to Test/Fixture/CityFixture.php diff --git a/tests/fixtures/country_fixture.php b/Test/Fixture/CountryFixture.php similarity index 100% rename from tests/fixtures/country_fixture.php rename to Test/Fixture/CountryFixture.php diff --git a/tests/fixtures/geo_address_fixture.php b/Test/Fixture/GeoAddressFixture.php similarity index 100% rename from tests/fixtures/geo_address_fixture.php rename to Test/Fixture/GeoAddressFixture.php diff --git a/tests/fixtures/state_fixture.php b/Test/Fixture/StateFixture.php similarity index 100% rename from tests/fixtures/state_fixture.php rename to Test/Fixture/StateFixture.php diff --git a/views/helpers/geomap.php b/View/Helper/Geomap.php similarity index 100% rename from views/helpers/geomap.php rename to View/Helper/Geomap.php