<?php
/***************************************************************************
- * Copyright (C) 2003-2010 Polytechnique.org *
+ * Copyright (C) 2003-2013 Polytechnique.org *
* http://opensource.polytechnique.org/ *
* *
* This program is free software; you can redistribute it and/or modify *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
***************************************************************************/
-// Implementation of a Geocoder using the Google Maps API. Please refer to
-// the following links for details:
-// http://code.google.com/apis/maps/documentation/services.html#Geocoding
-// http://code.google.com/intl/en/apis/maps/documentation/geocoding/
-// http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy
+// Implementation of a Geocoder using the Google Maps API v3. Please refer
+// to the following link for details:
+// http://code.google.com/apis/maps/documentation/geocoding/
//
-// It requires the properties gmaps_key and gmaps_url to be defined in section
-// Geocoder in plat/al's configuration (platal.ini & platal.conf).
+// It requires the properties gmaps_url to be defined in section Geocoder
+// in plat/al's configuration (platal.ini & platal.conf).
class GMapsGeocoder extends Geocoder {
// Maximum number of Geocoding calls to the Google Maps API.
const MAX_GMAPS_RPC_CALLS = 5;
- // Maximum levenshtein distance authorized between input and geocoded text in a single line.
- const MAX_LINE_DISTANCE = 5;
- // Maximum levenshtein distance authorized between input and geocoded text in the whole text.
- const MAX_TOTAL_DISTANCE = 6;
- public function getGeocodedAddress(Address &$address) {
+ static public function buildStaticMapURL($latitude, $longitude, $color, $separator = '&')
+ {
+ if (!$latitude || !$longitude) {
+ return null;
+ }
+
+ $parameters = array(
+ 'size' => '300x100',
+ 'markers' => 'color:' . $color . '|' . $latitude . ',' . $longitude,
+ 'zoom' => '12',
+ 'sensor' => 'false'
+ );
+
+ return Platal::globals()->maps->static_map . '?' . http_build_query($parameters, '', $separator);
+ }
+
+ public function getGeocodedAddress(Address $address, $defaultLanguage = null, $forceLanguage = false) {
$this->prepareAddress($address);
$textAddress = $this->getTextToGeocode($address->text);
+ if (is_null($defaultLanguage)) {
+ $defaultLanguage = Platal::globals()->geocoder->gmaps_language;
+ }
// Try to geocode the full address.
- if (($geocodedData = $this->getPlacemarkForAddress($textAddress))) {
- $this->getUpdatedAddress($address, $geocodedData, null);
+ $address->geocoding_calls = 1;
+ if (($geocodedData = $this->getPlacemarkForAddress($textAddress, $defaultLanguage))) {
+ $this->getUpdatedAddress($address, $geocodedData, null, $forceLanguage);
return;
}
for ($i = max(1, $linesCount - self::MAX_GMAPS_RPC_CALLS + 1); $i < $linesCount; ++$i) {
$extraLines = implode("\n", array_slice($addressLines, 0, $i));
$toGeocode = implode("\n", array_slice($addressLines, $i));
- if (($geocodedData = $this->getPlacemarkForAddress($toGeocode))) {
- $this->getUpdatedAddress($address, $geocodedData, $extraLines);
+ ++$address->geocoding_calls;
+ if (($geocodedData = $this->getPlacemarkForAddress($toGeocode, $defaultLanguage))) {
+ $this->getUpdatedAddress($address, $geocodedData, $extraLines, $forceLanguage);
return;
}
}
}
- public function stripGeocodingFromAddress(Address &$address) {
- $address->geocodedText = null;
- $address->geoloc_choice = null;
- $address->countryId = null;
- $address->country = null;
- $address->administrativeAreaName = null;
- $address->subAdministrativeAreaName = null;
- $address->localityName = null;
- $address->thoroughfareName = null;
- $address->postalCode = null;
- $address->accuracy = 0;
+ public function stripGeocodingFromAddress(Address $address) {
+ $address->formatted_address = '';
+ $address->types = '';
+ $address->latitude = null;
+ $address->longitude = null;
+ $address->southwest_latitude = null;
+ $address->southwest_longitude = null;
+ $address->northeast_latitude = null;
+ $address->northeast_longitude = null;
+ $address->location_type = null;
+ $address->partial_match = false;
}
// Updates the address with the geocoded information from Google Maps. Also
// cleans up the final informations.
- private function getUpdatedAddress(Address &$address, array $geocodedData, $extraLines) {
+ private function getUpdatedAddress(Address $address, array $geocodedData, $extraLines, $forceLanguage) {
$this->fillAddressWithGeocoding($address, $geocodedData, false);
- $this->formatAddress($address, $extraLines);
+ $this->formatAddress($address, $extraLines, $forceLanguage);
}
// Retrieves the Placemark object (see #getPlacemarkFromJson()) for the @p
// address, by querying the Google Maps API. Returns the array on success,
// and null otherwise.
- private function getPlacemarkForAddress($address, $defaultLanguage = null) {
- if (is_null($defaultLanguage)) {
- $defaultLanguage = Platal::globals()->geocoder->gmaps_hl;
- }
-
+ private function getPlacemarkForAddress($address, $defaultLanguage) {
$url = $this->getGeocodingUrl($address, $defaultLanguage);
$geoData = $this->getGeoJsonFromUrl($url);
- return ($geoData ? $this->getPlacemarkFromJson($geoData) : null);
+ return ($geoData ? $this->getPlacemarkFromJson($geoData, $url) : null);
}
// Prepares address to be geocoded
- private function prepareAddress(Address &$address) {
+ private function prepareAddress(Address $address) {
$address->text = preg_replace('/\s*\n\s*/m', "\n", trim($address->text));
}
global $globals;
$parameters = array(
- 'key' => $globals->geocoder->gmaps_key,
- 'sensor' => 'false', // The queried address wasn't obtained from a GPS sensor.
- 'hl' => $defaultLanguage,
- 'oe' => 'utf8', // Output encoding.
- 'output' => 'json', // Output format.
- 'gl' => $globals->geocoder->gmaps_gl,
- 'q' => $address, // The queries address.
+ 'language' => $defaultLanguage,
+ 'region' => $globals->geocoder->gmaps_region,
+ 'sensor' => 'false', // The queried address wasn't obtained from a GPS sensor.
+ 'address' => $address, // The queries address.
);
- return $globals->geocoder->gmaps_url . '?' . http_build_query($parameters);
+ return $globals->geocoder->gmaps_url . 'json?' . http_build_query($parameters);
}
// Fetches JSON-encoded data from a Google Maps API url, and decode them.
}
// Extracts the most appropriate placemark from the JSON data fetched from
- // Google Maps. Returns a Placemark array on success, and null otherwise. See
- // http://code.google.com/apis/maps/documentation/services.html#Geocoding_Structured
- // for details on the Placemark structure.
- private function getPlacemarkFromJson(array $data) {
- // Check for geocoding failures.
- if (!isset($data['Status']['code']) || $data['Status']['code'] != 200) {
- // TODO: handle non-200 codes in a better way, since the code might
- // indicate a temporary error on Google's side.
- return null;
- }
-
- // Check that at least one placemark was found.
- if (count($data['Placemark']) == 0) {
+ // Google Maps. Returns a Placemark array on success, and null otherwise.
+ // http://code.google.com/apis/maps/documentation/geocoding/#StatusCodes
+ private function getPlacemarkFromJson(array $data, $url) {
+ // Check for geocoding status.
+ $status = $data['status'];
+
+ // If no result, return null.
+ if ($status == 'ZERO_RESULTS') {
return null;
}
- // Extract the placemark with the best accuracy. This is not always the
- // best result (since the same address may yield two different placemarks).
- $result = $data['Placemark'][0];
- foreach ($data['Placemark'] as $place) {
- if ($place['AddressDetails']['Accuracy'] > $result['AddressDetails']['Accuracy']) {
- $result = $place;
- }
+ // If there are results return the first one.
+ if ($status == 'OK') {
+ return $data['results'][0];
}
- return $result;
+ // Report the error.
+ $mailer = new PlMailer('profile/geocoding.mail.tpl');
+ $mailer->assign('status', $status);
+ $mailer->assign('url', $url);
+ $mailer->send();
+ return null;
}
// Fills the address with the geocoded data
- private function fillAddressWithGeocoding(Address &$address, $geocodedData, $isLocal) {
- // The geocoded address three is
- // Country -> AdministrativeArea -> SubAdministrativeArea -> Locality -> Thoroughfare
- // with all the possible shortcuts
- // The address is formatted as xAL, or eXtensible Address Language, an international
- // standard for address formatting.
- // xAL documentation: http://www.oasis-open.org/committees/ciq/ciq.html#6
- if ($isLocal) {
- $ext = 'Local';
- } else {
- $ext = ucfirst(Platal::globals()->geocoder->gmaps_hl);
- $address->geocodedText = str_replace(', ', "\n", $geocodedData['address']);
- }
-
- if (isset($geocodedData['AddressDetails']['Accuracy'])) {
- $address->accuracy = $geocodedData['AddressDetails']['Accuracy'];
- }
-
- $currentPosition = $geocodedData['AddressDetails'];
- if (isset($currentPosition['Country'])) {
- $country = 'country' . $ext;
- $currentPosition = $currentPosition['Country'];
- $address->countryId = $currentPosition['CountryNameCode'];
- $address->$country = $currentPosition['CountryName'];
- }
- if (isset($currentPosition['AdministrativeArea'])) {
- $administrativeAreaName = 'administrativeAreaName' . $ext;
- $currentPosition = $currentPosition['AdministrativeArea'];
- $address->$administrativeAreaName = $currentPosition['AdministrativeAreaName'];
- }
- if (isset($currentPosition['SubAdministrativeArea'])) {
- $subAdministrativeAreaName = 'subAdministrativeAreaName' . $ext;
- $currentPosition = $currentPosition['SubAdministrativeArea'];
- $address->$subAdministrativeAreaName = $currentPosition['SubAdministrativeAreaName'];
- }
- if (isset($currentPosition['Locality'])) {
- $localityName = 'localityName' . $ext;
- $currentPosition = $currentPosition['Locality'];
- $address->$localityName = $currentPosition['LocalityName'];
- }
- if (isset($currentPosition['PostalCode'])) {
- $address->postalCode = $currentPosition['PostalCode']['PostalCodeNumber'];
- }
-
- // Gets coordinates.
- if (isset($geocodedData['Point']['coordinates'][0])) {
- $address->latitude = $geocodedData['Point']['coordinates'][0];
- }
- if (isset($geocodedData['Point']['coordinates'][1])) {
- $address->longitude = $geocodedData['Point']['coordinates'][1];
- }
- if (isset($geocodedData['ExtendedData']['LatLonBox']['north'])) {
- $address->north = $geocodedData['ExtendedData']['LatLonBox']['north'];
- }
- if (isset($geocodedData['ExtendedData']['LatLonBox']['south'])) {
- $address->south = $geocodedData['ExtendedData']['LatLonBox']['south'];
- }
- if (isset($geocodedData['ExtendedData']['LatLonBox']['east'])) {
- $address->east = $geocodedData['ExtendedData']['LatLonBox']['east'];
- }
- if (isset($geocodedData['ExtendedData']['LatLonBox']['west'])) {
- $address->west = $geocodedData['ExtendedData']['LatLonBox']['west'];
- }
- }
-
- // Compares the geocoded address with the given address and returns true
- // iff their are close enough to be considered as equals or not.
- private function compareAddress($address)
- {
- $same = true;
- $geoloc = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
- array('', "\n"), $address->geocodedText));
- $text = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
- array('', "\n"), $address->text));
- $arrayGeoloc = explode("\n", $geoloc);
- $arrayText = explode("\n", $text);
- $countGeoloc = count($arrayGeoloc);
- $countText = count($arrayText);
-
- $totalDistance = 0;
- if (($countText > $countGeoloc) || ($countText < $countGeoloc - 1)
- || (($countText == $countGeoloc - 1)
- && ($arrayText[$countText - 1] == strtoupper($address->country)))) {
- $same = false;
- } else {
- for ($i = 0; $i < $countGeoloc && $i < $countText; ++$i) {
- $lineDistance = levenshtein($arrayText[$i], trim($arrayGeoloc[$i]));
- $totalDistance += $lineDistance;
- if ($lineDistance > self::MAX_LINE_DISTANCE || $totalDistance > self::MAX_TOTAL_DISTANCE) {
- $same = false;
- break;
- }
- }
- }
-
- return $same;
+ private function fillAddressWithGeocoding(Address $address, $geocodedData, $isLocal) {
+ $address->types = implode(',', $geocodedData['types']);
+ $address->formatted_address = $geocodedData['formatted_address'];
+ $address->components = $geocodedData['address_components'];
+ $address->latitude = $geocodedData['geometry']['location']['lat'];
+ $address->longitude = $geocodedData['geometry']['location']['lng'];
+ $address->southwest_latitude = $geocodedData['geometry']['viewport']['southwest']['lat'];
+ $address->southwest_longitude = $geocodedData['geometry']['viewport']['southwest']['lng'];
+ $address->northeast_latitude = $geocodedData['geometry']['viewport']['northeast']['lat'];
+ $address->northeast_longitude = $geocodedData['geometry']['viewport']['northeast']['lng'];
+ $address->location_type = $geocodedData['geometry']['location_type'];
+ $address->partial_match = isset($geocodedData['partial_match']) ? true : false;
}
// Formats the text of the geocoded address using the unused data and
// compares it to the given address. If they are too different, the user
// will be asked to choose between them.
- private function formatAddress(Address &$address, $extraLines)
+ private function formatAddress(Address $address, $extraLines, $forceLanguage)
{
- if ($extraLines) {
- $address->geocodedText = $extraLines . "\n" . $address->geocodedText;
- }
-
- if ($this->compareAddress($address)) {
- $address->geocodedText = null;
- } else {
- $languages = XDB::fetchOneCell('SELECT IF(ISNULL(gc1.belongsTo), gc1.languages, gc2.languages)
+ /* XXX: Check how to integrate this in the new geocoding system.
+ if (!$forceLanguage) {
+ $languages = XDB::fetchOneCell('SELECT IF(ISNULL(gc1.belongsTo), gl1.language, gl2.language)
FROM geoloc_countries AS gc1
+ INNER JOIN geoloc_languages AS gl1 ON (gc1.iso_3166_1_a2 = gl1.iso_3166_1_a2)
LEFT JOIN geoloc_countries AS gc2 ON (gc1.belongsTo = gc2.iso_3166_1_a2)
+ LEFT JOIN geoloc_languages AS gl2 ON (gc2.iso_3166_1_a2 = gl2.iso_3166_1_a2)
WHERE gc1.iso_3166_1_a2 = {?}',
$address->countryId);
$toGeocode = substr($address->text, strlen($extraLines));
foreach (explode(',', $languages) as $language) {
- if ($language != Platal::globals()->geocoder->gmaps_hl) {
+ if ($language != Platal::globals()->geocoder->gmaps_language) {
$geocodedData = $this->getPlacemarkForAddress($toGeocode, $language);
- $address->geocodedText = str_replace(', ', "\n", $geocodedData['address']);
- if ($extraLines) {
- $address->geocodedText = $extraLines . "\n" . $address->geocodedText;
- }
- if ($this->compareAddress($address)) {
- $this->fillAddressWithGeocoding($address, $geocodedData, true);
- $address->geocodedText = null;
- break;
- }
+ $this->fillAddressWithGeocoding($address, $geocodedData, true);
+ break;
}
}
- $address->geocodedText = str_replace("\n", "\r\n", $address->geocodedText);
- }
+ }*/
$address->text = str_replace("\n", "\r\n", $address->text);
}