From 4c906759f226682a25fa0a8c8a2e3f3de8dd0c72 Mon Sep 17 00:00:00 2001 From: =?utf8?q?St=C3=A9phane=20Jacob?= Date: Sun, 15 Feb 2009 18:47:29 +0100 Subject: [PATCH] Improves code for geocoding thanks to Vincent, fixes issue with street numbers when only the street name is geocoded. --- configs/platal.ini | 6 +- include/geocoding.inc.php | 344 +++++++++++++++++++ include/geoloc.inc.php | 498 ++++++---------------------- modules/profile/addresses.inc.php | 8 +- modules/profile/page.inc.php | 9 +- upgrade/newdirectory-0.0.1/15_addresses.sql | 6 +- 6 files changed, 452 insertions(+), 419 deletions(-) create mode 100644 include/geocoding.inc.php diff --git a/configs/platal.ini b/configs/platal.ini index c6980ec..f263a75 100644 --- a/configs/platal.ini +++ b/configs/platal.ini @@ -24,8 +24,10 @@ mbox_helper = "/usr/bin/banana-mbox-helper" event_forum = "" event_reply = "" -[Geoloc] -webservice_url = "" +[Geocoder] +email = "" +gmaps_key = "" +gmaps_url = "http://maps.google.com/maps/geo" [Lists] rpchost = "localhost" diff --git a/include/geocoding.inc.php b/include/geocoding.inc.php new file mode 100644 index 0000000..816afeb --- /dev/null +++ b/include/geocoding.inc.php @@ -0,0 +1,344 @@ + 'geoloc_administrativeareas', + 'subAdministrativeArea' => 'geoloc_subadministrativeareas', + 'locality' => 'geoloc_localities', + ); + + if (isset($address[$area . 'Name']) && isset($databases[$area])) { + $res = XDB::query("SELECT id + FROM " . $databases[$area] . " + WHERE name = {?}", + $address[$area . 'Name']); + if ($res->numRows() == 0) { + $address[$area . 'Id'] = XDB::execute("INSERT INTO " . $databases[$area] . " (name, country) + VALUES ({?}, {?})", + $address[$area . 'Name'], $address['countryId']); + } else { + $address[$area . 'Id'] = $res->fetchOneCell(); + } + } + } +} + +// 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 +// +// It requires the properties gmaps_key and 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; + + public function getGeocodedAddress(array $address) { + $address = $this->prepareAddress($address); + $textAddress = $address['text']; + + // Try to geocode the full address. + if (($geocodedData = $this->getPlacemarkForAddress($textAddress))) { + return $this->getUpdatedAddress($address, $geocodedData, null); + } + + // If the full geocoding failed, try to geocode only the final part of the address. + // We start by geocoding everything but the first line, and continue until we get + // a result. To respect the limit of GMaps calls, we ignore the first few lines + // if there are too many address lines. + $addressLines = explode("\n", $textAddress); + $linesCount = count($addressLines); + 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))) { + return $this->getUpdatedAddress($address, $geocodedData, $extraLines); + } + } + + // No geocoding could be done, the initial address is returned as-is. + return $address; + } + + // Updates the address with the geocoded information from Google Maps. Also + // cleans up the final informations. + private function getUpdatedAddress(array $address, array $geocodedData, $extraLines) { + $this->fillAddressWithGeocoding(&$address, $geocodedData); + + // If the accuracy is 6, it means only the street has been gecoded + // but not the number, thus we need to fix it. + if ($address['accuracy'] == 6) { + $this->fixStreetNumber($address); + } + + // We can now format the address. + $this->formatAddress($address, $extraLines); + + // Some entities in ISO 3166 are not countries, thus they have to be replaced + // by the country they belong to. + // TODO: fixCountry($address); + + return $address; + } + + // 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) { + $url = $this->getGeocodingUrl($address); + $geoData = $this->getGeoJsonFromUrl($url); + + return ($geoData ? $this->getPlacemarkFromJson($geoData) : null); + } + + // Prepares address to be geocoded + private function prepareAddress($address) { + $address['text'] = preg_replace('/\s*\n\s*/m', "\n", trim($address['text'])); + // TODO: $address['postalAddress'] = getPostalAddress($address['text']); + $address['updateTime'] = time(); + unset($address['changed']); + return $address; + } + + // Builds the Google Maps geocoder url to fetch information about @p address. + // Returns the built url. + private function getGeocodingUrl($address) { + global $globals; + + $parameters = array( + 'key' => $globals->geocoder->gmaps_key, + 'sensor' => 'false', // The queried address wasn't obtained from a GPS sensor. + 'hl' => 'fr', // Output langage. + 'oe' => 'utf8', // Output encoding. + 'output' => 'json', // Output format. + 'gl' => 'fr', // Location preferences (addresses are in France by default). + 'q' => $address, // The queries address. + ); + + return $globals->geocoder->gmaps_url . '?' . http_build_query($parameters); + } + + // Fetches JSON-encoded data from a Google Maps API url, and decode them. + // Returns the json array on success, and null otherwise. + private function getGeoJsonFromUrl($url) { + global $globals; + + // Prepare a backtrace object to log errors. + $bt = null; + if ($globals->debug & DEBUG_BT) { + if (!isset(PlBacktrace::$bt['Geoloc'])) { + new PlBacktrace('Geoloc'); + } + $bt = &PlBacktrace::$bt['Geoloc']; + $bt->start($url); + } + + // Fetch the geocoding data. + $rawData = file_get_contents($url); + if (!$rawData) { + if ($bt) { + $bt->stop(0, "Could not retrieve geocoded address from GoogleMaps."); + } + return null; + } + + // Decode the JSON-encoded data, and check for their validity. + $data = json_decode($rawData, true); + if ($bt) { + $bt->stop(count($data), null, $data); + } + + return $data; + } + + // 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) { + 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; + } + } + + return $result; + } + + // Fills the address with the geocoded data + private function fillAddressWithGeocoding(&$address, $geocodedData) { + // 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 + $address['geoloc'] = str_replace(", ", "\n", $geocodedData['address']); + if (isset($geocodedData['AddressDetails']['Accuracy'])) { + $address['accuracy'] = $geocodedData['AddressDetails']['Accuracy']; + } + + $currentPosition = $geocodedData['AddressDetails']; + if (isset($currentPosition['Country'])) { + $currentPosition = $currentPosition['Country']; + $address['countryId'] = $currentPosition['CountryNameCode']; + $address['country'] = $currentPosition['CountryName']; + } + if (isset($currentPosition['AdministrativeArea'])) { + $currentPosition = $currentPosition['AdministrativeArea']; + $address['administrativeAreaName'] = $currentPosition['AdministrativeAreaName']; + } + if (isset($currentPosition['SubAdministrativeArea'])) { + $currentPosition = $currentPosition['SubAdministrativeArea']; + $address['subAdministrativeAreaName'] = $currentPosition['SubAdministrativeAreaName']; + } + if (isset($currentPosition['Locality'])) { + $currentPosition = $currentPosition['Locality']; + $address['localityName'] = $currentPosition['LocalityName']; + } + if (isset($currentPosition['Thoroughfare'])) { + $address['thoroughfareName'] = $currentPosition['Thoroughfare']['ThoroughfareName']; + } + 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']; + } + } + + // 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, $extraLines) { + $same = true; + if ($extraLines) { + $address['geoloc'] = $extraLines . "\n" . $address['geoloc']; + } + $geoloc = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"), + array("", "\n"), $address['geoloc'])); + $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); + + 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) { + if (levenshtein($arrayText[$i], trim($arrayGeoloc[$i])) > 3) { + $same = false; + } + } + } + if ($same) { + $address['text'] = $address['geoloc']; + unset($address['geoloc']); + } + } + + // Search for the lign from the given address that is the closest to the geocoded thoroughfareName + // and replaces the corresponding lign in the geocoded text by it. + static protected function fixStreetNumber(&$address) + { + if (isset($address['thoroughfareName'])) { + $thoroughfareName = $address['thoroughfareName']; + $thoroughfareToken = strtoupper(trim(preg_replace(array("/[,\"'#~:;_\-]/", "/\r\n/"), + array("", "\n"), $thoroughfareName))); + $geolocLines = explode("\n", $address['geoloc']); + $textLines = explode("\n", $address['text']); + $mindist = strlen($thoroughfareToken); + $minpos = 0; + $pos = 0; + foreach ($textLines as $i => $token) { + if (($l = levenshtein(strtoupper(trim(preg_replace(array("/[,\"'#~:;_\-]/", "/\r\n/"), + array("", "\n"), $token))), + $thoroughfareToken)) < $mindist) { + $mindist = $l; + $minpos = $i; + } + } + foreach ($geolocLines as $i => $line) { + if (strtoupper(trim($thoroughfareName)) == strtoupper(trim($line))) { + $pos = $i; + break; + } + } + $geolocLines[$pos] = $textLines[$minpos]; + $address['geoloc'] = implode("\n", $geolocLines); + } + } +} + +// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8: +?> diff --git a/include/geoloc.inc.php b/include/geoloc.inc.php index ff58ddb..0a6bfac 100644 --- a/include/geoloc.inc.php +++ b/include/geoloc.inc.php @@ -19,388 +19,6 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ***************************************************************************/ -// {{{ geoloc_country($current, $avail_only = false) -/** donne la liste déroulante des pays - * @param $current pays actuellement selectionné - */ -function geoloc_country($current, $avail_only = false) -{ - if ($avail_only) { - $res = XDB::iterRow('SELECT g.a2, g.pays - FROM geoloc_pays AS g - INNER JOIN adresses AS a ON(a.country = g.a2) - GROUP BY g.a2 - ORDER BY g.pays'); - } else { - $res = XDB::iterRow('SELECT a2,pays FROM geoloc_pays ORDER BY pays'); - } - $html = ""; - while (list($my_id, $my_pays) = $res->next()) { - $html .= sprintf("\n", - $my_id, ($current==$my_id?"selected='selected'":""), $my_pays); - } - return $html; -} - -// }}} -// {{{ geoloc_region($country, $current, $avail_only = false) -/** donne la liste deroulante des regions pour un pays - * @param $pays le pays dont on veut afficher les regions - * @param $current la region actuellement selectionnee - */ -function geoloc_region($country, $current, $avail_only = false) -{ - if ($avail_only) { - $res = XDB::iterRow('SELECT r.region, r.name - FROM geoloc_region AS r - INNER JOIN adresses AS a ON (a.country = r.a2 AND a.region = r.region) - WHERE r.a2 = {?} - GROUP BY r.region - ORDER BY r.name', $country); - } else { - $res = XDB::iterRow('SELECT region,name - FROM geoloc_region - WHERE a2 = {?} - ORDER BY name', $country); - } - $html = ""; - while (list($regid, $regname) = $res->next()) { - $html .= sprintf("\n", - $regid, ($current==$regid?"selected='selected'":""), $regname); - } - return $html; -} -// }}} -// {{{ get_cities_maps($array) -/* get all the maps id of the cities contained in an array */ -function get_cities_maps($array) -{ - global $globals; - implode("\n",$array); - $url = $globals->geoloc->webservice_url."findMaps.php?datatext=".urlencode(implode("\n", $array)); - if (!($f = @fopen($url, 'r'))) return false; - $maps = array(); - while (!feof($f)) - { - $l = trim(fgets($f)); - $tab = explode(';', $l); - $i = $tab[0]; - unset($tab[0]); - $maps[$i] = $tab; - } - return $maps; -} -// }}} -// {{{ get_new_maps($url) -/** set new maps from url **/ -function get_new_maps($url) -{ - if (!($f = @fopen($url, 'r'))) { - return false; - } - XDB::query('TRUNCATE TABLE geoloc_maps'); - $s = ''; - while (!feof($f)) { - $l = fgetcsv($f, 1024, ';', '"'); - foreach ($l as $i => $val) { - if ($val != 'NULL') { - $l[$i] = '\''.addslashes($val).'\''; - } - } - $s .= ',('.implode(',',$l).')'; - } - XDB::execute('INSERT INTO geoloc_maps VALUES '.substr($s, 1)); - return true; -} -// }}} -// {{{ geolocGoogle (array $address) -// retrieve the infos on a text address - -function geolocGoogle (array &$address) -{ - /* keys - * www.polytechnique.org: - * ABQIAAAAIlFNe1A494mwR9Zf4R3t0xRsw9kzQBeaENRP66lRw7Ru3uVJcRR73lY1tmAdYGqw-pyHTdynmicz0w - * www.polytechnique.net and dev.polytechnique.net: - * ABQIAAAAIlFNe1A494mwR9Zf4R3t0xT8SmDPc83znji5QwIVTgAvxgX5zRRMagHx_rmGeQF5SnCzmyqiSeSAxA - * dev.m4x.org: - * ABQIAAAAIlFNe1A494mwR9Zf4R3t0xQ31muaRX97DHXrOFfMwMMCxEnhaxQIPDe9Ct3D6ZvWuGiWllkGAP3IqA - * - * Documentation: - * http://code.google.com/intl/fr/apis/maps/documentation/geocoding/ - * http://code.google.com/apis/maps/documentation/reference.html#GGeoAddressAccuracy */ - - $success = true; - $key = 'ABQIAAAAIlFNe1A494mwR9Zf4R3t0xQ31muaRX97DHXrOFfMwMMCxEnhaxQIPDe9Ct3D6ZvWuGiWllkGAP3IqA'; - $webservice = "http://maps.google.com/maps/geo?"; - $baseurl = $webservice . "&key=$key" . "&sensor=false&output=json&oe=utf8&gl=fr&hl=fr&q="; - - $url = $baseurl . urlencode($address['text']); - if (!geolocalizeAddress($url, $gAddress)) { - $addressLines = explode("\n", $address['text']); - $nbLines = count($addressLines); - $currentState = array(); - $success = false; - for ($i = 1; !$success && ($i < $nbLines); $i++) { - for ($j = 0; $j < $i; $j++) { - $currentState[$j] = 0; - } - while($j < $nbLines) { - $currentState[$j] = 1; - $j++; - } - do { - $partialAddress = ""; - for ($j = 0; $j < $nbLines; $j++) { - if ($currentState[$j] == 1) { - $partialAddress .= $addressLines[$j] . " "; - } - } - $url = $baseurl . urlencode(trim($partialAddress)); - $success = geolocalizeAddress($url, $gAddress); - } while (!$success && nextCurrentState($currentState, $nbLines)); - } - if ($success) { - $extras = ""; - for ($i = 0; $i < $nbLines; $i++) { - if ($currentState[$i] == 0) { - $extras .= $addressLines[$i] . ", "; - } - } - trim($extras, ", "); - $address['extras'] = $extras; - } - } - if ($success) { - fillAddress($address, $gAddress); - formatAddress($address); - } - return $success; -} - -// }}} -// {{{ nextCurrentState(&$currentState, $nbLines) - -function nextCurrentState(&$currentState, $nbLines) -{ - $lastOne = 0; - $nbZeros = 2; - for ($i = 0; $i < $nbLines; $i++) { - if ($currentState[$i] == 1) { - $lastOne = $i; - $nbZeros = 2; - } else { - $nbZeros++; - } - } - if ($lastOne == 0) { - return false; - } elseif ($currentState[$lastOne - 1] == 0) { - $currentState[$lastOne - 1] = 1; - $currentState[$lastOne] = 0; - return true; - } else { - $lastZero = -1; - for ($j = 0; $j < $lastOne; $j++) { - if ($currentState[$j] == 0) { - $lastZero = $j; - } - } - if ($lastZero == -1) { - return false; - } else { - $currentState[$lastZero] = 1; - for ($k = $lastZero + 1; $k < $lastZero + $nbZeros; $k++) { - $currentState[$k] = 0; - } - for ($k = $lastZero + $nbZeros; $k < $nbLines; $k++) { - $currentState[$k] = 1; - } - return true; - } - } -} - -// }}} -// {{{ geolocalizeAddress ($url, &$result) - -function geolocalizeAddress ($url, &$result = array()) -{ - global $globals; - - if ($globals->debug & DEBUG_BT) { - if (!isset(PlBacktrace::$bt['Geoloc'])) { - new PlBacktrace('Geoloc'); - } - PlBacktrace::$bt['Geoloc']->start($url); - } - - if ($f = file_get_contents($url, 'r')) { - $data = json_decode($f, true); - if ($globals->debug & DEBUG_BT) { - PlBacktrace::$bt['Geoloc']->stop(count($data), null, $data); - } - if ($data['Status']['code'] != 200) { - return false; - } - $nbResults = count($data['Placemark']); - $idAccuracy = 0; - if ($nbResults > 1) { - $bestAccuracy = $data['Placemark'][0]['AddressDetails']['Accuracy']; - for ($i = 1; $i < $nbResults; $i++) { - if ($data['Placemark'][$i]['AddressDetails']['Accuracy'] > $bestAccuracy) { - unset($data['Placemark'][$idAccuracy]); - $bestAccuracy = $data['Placemark'][$i]['AddressDetails']['Accuracy']; - $idAccuracy = $i; - } else { - unset($data['Placemark'][$i]); - } - } - } - $result = $data['Placemark'][$idAccuracy]; - return true; - } - if ($globals->debug & DEBUG_BT) { - PlBacktrace::$bt['Geoloc']->stop(0, "Can't fetch result."); - } - return false; -} - -// }}} -// {{{ fillAddress(array &$address, $gAddress) - -function fillAddress(array &$address, array $gAddress) -{ - // An address is Country -> AdministrativeArea -> SubAdministrativeArea -> Locality -> Thoroughfare - // with all the shortcuts possible - - // postalText - $address['geoloc'] = str_replace(", ", "\n", $gAddress['address']); - if (isset($gAddress['AddressDetails']['Accuracy'])) { - $address['accuracy'] = $gAddress['AddressDetails']['Accuracy']; - } - $currentPosition = $gAddress['AddressDetails']; - if (isset($currentPosition['Country'])) { - $currentPosition = $currentPosition['Country']; - $address['countryId'] = $currentPosition['CountryNameCode']; - $address['country'] = $currentPosition['CountryName']; - } - if (isset($currentPosition['AdministrativeArea'])) { - $currentPosition = $currentPosition['AdministrativeArea']; - $address['administrativeAreaName'] = $currentPosition['AdministrativeAreaName']; - } - if (isset($currentPosition['SubAdministrativeArea'])) { - $currentPosition = $currentPosition['SubAdministrativeArea']; - $address['subAdministrativeAreaName'] = $currentPosition['SubAdministrativeAreaName']; - } - if (isset($currentPosition['Locality'])) { - $currentPosition = $currentPosition['Locality']; - $address['localityName'] = $currentPosition['LocalityName']; - } - if (isset($currentPosition['Thoroughfare'])) { - $address['thoroughfareName'] = $currentPosition['Thoroughfare']['ThoroughfareName']; - } - if (isset($currentPosition['PostalCode'])) { - $address['postalCode'] = $currentPosition['PostalCode']['PostalCodeNumber']; - } - - // Coordinates - if (isset($gAddress['Point']['coordinates'][0])) { - $address['latitude'] = $gAddress['Point']['coordinates'][0]; - } - if (isset($gAddress['Point']['coordinates'][1])) { - $address['longitude'] = $gAddress['Point']['coordinates'][1]; - } - if (isset($gAddress['ExtendedData']['LatLonBox']['north'])) { - $address['north'] = $gAddress['ExtendedData']['LatLonBox']['north']; - } - if (isset($gAddress['ExtendedData']['LatLonBox']['south'])) { - $address['south'] = $gAddress['ExtendedData']['LatLonBox']['south']; - } - if (isset($gAddress['ExtendedData']['LatLonBox']['east'])) { - $address['east'] = $gAddress['ExtendedData']['LatLonBox']['east']; - } - if (isset($gAddress['ExtendedData']['LatLonBox']['west'])) { - $address['west'] = $gAddress['ExtendedData']['LatLonBox']['west']; - } -} - -// }}} -// {{{ formatAddress(array &$address) - -function formatAddress(array &$address) -{ - $same = true; - $text = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"), - array("", "\n"), $address['text'])); - $geoloc = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"), - array("", "\n"), $address['geoloc'])); - if (isset($address['extras']) && $address['extras']) { - $address['geoloc'] = $address['extras'] . "\n" . $address['geoloc']; - $extras = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"), - array("", "\n"), $address['extras'])); - $geoloc = $extras . $geoloc; - unset($address['extras']); - } - - $arrayText = explode("\n", $text); - $arrayGeoloc = explode("\n", $geoloc); - $nbText = count($arrayText); - $nbGeoloc = count($arrayGeoloc); - - if ((($nbText > $nbGeoloc) || ($nbText < $nbGeoloc - 1)) - || (($nbText == $nbGeoloc - 1) && ($arrayText[$nbText - 1] == strtoupper($address['country'])))) { - $same = false; - } else { - foreach ($arrayText as $i => $lignText) { - if (levenshtein($lignText, trim($arrayGeoloc[$i])) > 3) { - $same = false; - } - } - } - if ($same) { - $address['text'] = $address['geoloc']; - unset($address['geoloc']); - } -} - -// }}} -// {{{ cleanText(&$text) - -function cleanText(&$text) -{ - $lines = explode("\n", $text); - $n = count($lines); - $text = ""; - for ($i = 0; $i < $n; $i++) { - if (trim($lines[$i])) { - $text .= trim($lines[$i]) . "\n"; - } - } - $text = trim($text); -} - -// }}} -// {{{ getAreaId(array &$address, $area) - -function getAreaId(array &$address, $area) -{ - if (isset($address[$area . 'Name'])) { - $res = XDB::query("SELECT id - FROM geoloc_" . $area . " - WHERE name = {?}", - $address[$area . 'Name']); - if ($res->numRows() == 0) { - $address[$area . 'Id'] = XDB::execute("INSERT INTO geoloc_" . $area . " (name, country) - VALUES ({?}, {?})", - $address[$area . 'Name'], $address['countryId']); - } else { - $address[$area . 'Id'] = $res->fetchOneCell(); - } - } -} - -// }}} // {{{ get_address_text($adr) /** make the text of an address that can be read by a mailman * @param $adr an array with all the usual fields @@ -466,28 +84,6 @@ function compare_addresses_text($a, $b) } // }}} -// {{{ fixNumber($oldtext, &$new) - -function fixNumber($oldtext, &$new) -{ - $ThoroughfareName = $new['AddressDetails']['Country']['AdministrativeArea']['SubAdministrativeArea']['Locality']['Thoroughfare']['ThoroughfareName']; - $ThoroughfareName = trim(strtoupper(preg_replace(array("/[,\"'#~:;_\-]/", "/\r\n/"), - array("", "\n"), $ThoroughfareName))); - $oldarray = explode("\n", trim(strtoupper(preg_replace(array("/[,\"'#~:;_\-]/", "/\r\n/"), - array("", "\n"), $oldtext)))); - $mindist = strlen($ThoroughfareName); - $minpos = 0; - foreach ($oldarray as $i => $oldline) { - if (($l = levenshtein(trim($oldline), $ThoroughfareName)) < $mindist) { - $mindist = $l; - $minpos = $i; - } - } - $nb = explode(" ", $oldarray[$minpos]); - $new['text'] = $nb[0] . " " . $new['text']; -} - -// }}} // {{{ localize_addresses($uid) /* localize all the address of a user and modify the database * if the new address match with the old one @@ -668,6 +264,100 @@ function set_smallest_levels() return true; } // }}} +// {{{ geoloc_country($current, $avail_only = false) +/** donne la liste déroulante des pays + * @param $current pays actuellement selectionné + */ +function geoloc_country($current, $avail_only = false) +{ + if ($avail_only) { + $res = XDB::iterRow('SELECT g.a2, g.pays + FROM geoloc_pays AS g + INNER JOIN adresses AS a ON(a.country = g.a2) + GROUP BY g.a2 + ORDER BY g.pays'); + } else { + $res = XDB::iterRow('SELECT a2,pays FROM geoloc_pays ORDER BY pays'); + } + $html = ""; + while (list($my_id, $my_pays) = $res->next()) { + $html .= sprintf("\n", + $my_id, ($current==$my_id?"selected='selected'":""), $my_pays); + } + return $html; +} + +// }}} +// {{{ geoloc_region($country, $current, $avail_only = false) +/** donne la liste deroulante des regions pour un pays + * @param $pays le pays dont on veut afficher les regions + * @param $current la region actuellement selectionnee + */ +function geoloc_region($country, $current, $avail_only = false) +{ + if ($avail_only) { + $res = XDB::iterRow('SELECT r.region, r.name + FROM geoloc_region AS r + INNER JOIN adresses AS a ON (a.country = r.a2 AND a.region = r.region) + WHERE r.a2 = {?} + GROUP BY r.region + ORDER BY r.name', $country); + } else { + $res = XDB::iterRow('SELECT region,name + FROM geoloc_region + WHERE a2 = {?} + ORDER BY name', $country); + } + $html = ""; + while (list($regid, $regname) = $res->next()) { + $html .= sprintf("\n", + $regid, ($current==$regid?"selected='selected'":""), $regname); + } + return $html; +} +// }}} +// {{{ get_cities_maps($array) +/* get all the maps id of the cities contained in an array */ +function get_cities_maps($array) +{ + global $globals; + implode("\n",$array); + $url = $globals->geoloc->webservice_url."findMaps.php?datatext=".urlencode(implode("\n", $array)); + if (!($f = @fopen($url, 'r'))) return false; + $maps = array(); + while (!feof($f)) + { + $l = trim(fgets($f)); + $tab = explode(';', $l); + $i = $tab[0]; + unset($tab[0]); + $maps[$i] = $tab; + } + return $maps; +} +// }}} +// {{{ get_new_maps($url) +/** set new maps from url **/ +function get_new_maps($url) +{ + if (!($f = @fopen($url, 'r'))) { + return false; + } + XDB::query('TRUNCATE TABLE geoloc_maps'); + $s = ''; + while (!feof($f)) { + $l = fgetcsv($f, 1024, ';', '"'); + foreach ($l as $i => $val) { + if ($val != 'NULL') { + $l[$i] = '\''.addslashes($val).'\''; + } + } + $s .= ',('.implode(',',$l).')'; + } + XDB::execute('INSERT INTO geoloc_maps VALUES '.substr($s, 1)); + return true; +} +// }}} function geoloc_to_x($lon, $lat) { diff --git a/modules/profile/addresses.inc.php b/modules/profile/addresses.inc.php index 6eca8bd..6574761 100644 --- a/modules/profile/addresses.inc.php +++ b/modules/profile/addresses.inc.php @@ -95,7 +95,7 @@ class ProfileAddress extends ProfileGeoloc private function saveAddress($addrid, array &$address) { - require_once "geoloc.inc.php"; + require_once "geocoding.inc.php"; $flags = new PlFlagSet(); if ($address['current']) { @@ -115,9 +115,9 @@ class ProfileAddress extends ProfileGeoloc array("", "\n"), $address['text'])), 'CEDEX')) !== false) { $flags->addFlag('cedex'); } - getAreaId($address, "administrativeArea"); - getAreaId($address, "subAdministrativeArea"); - getAreaId($address, "locality"); + Geocoder::getAreaId($address, "administrativeArea"); + Geocoder::getAreaId($address, "subAdministrativeArea"); + Geocoder::getAreaId($address, "locality"); XDB::execute("INSERT INTO profile_addresses (pid, type, id, flags, accuracy, text, postalText, postalCode, localityId, subAdministrativeAreaId, administrativeAreaId, diff --git a/modules/profile/page.inc.php b/modules/profile/page.inc.php index 8408c15..27e1958 100644 --- a/modules/profile/page.inc.php +++ b/modules/profile/page.inc.php @@ -258,17 +258,14 @@ abstract class ProfileGeoloc implements ProfileSetting { protected function geolocAddress(array &$address, &$success) { - require_once 'geoloc.inc.php'; + require_once 'geocoding.inc.php'; $success = true; if ($address['changed'] == 1) { - cleanText($address['text']); - geolocGoogle($address); - $address['updateTime'] = time(); - // postalAddress + $gmapsGeocoder = new GMapsGeocoder(); + $address = $gmapsGeocoder->getGeocodedAddress($address); if (isset($address['geoloc'])) { $success = false; } - unset($address['changed']); } if (isset($address['geoloc_choice']) && $address['geoloc_choice'] == 0) { $mailer = new PlMailer('geoloc/geoloc.mail.tpl'); diff --git a/upgrade/newdirectory-0.0.1/15_addresses.sql b/upgrade/newdirectory-0.0.1/15_addresses.sql index e437e22..dff260f 100644 --- a/upgrade/newdirectory-0.0.1/15_addresses.sql +++ b/upgrade/newdirectory-0.0.1/15_addresses.sql @@ -57,7 +57,7 @@ INSERT INTO geoloc_countries (iso_3166_1, iso_3166_2, iso_3166_3, worldRegion, SELECT a2, a3, n3, worldrgn, pays, country, capital, nat, phoneprf, phoneformat, license_plate FROM geoloc_pays; -CREATE TABLE IF NOT EXISTS geoloc_administrativeArea ( +CREATE TABLE IF NOT EXISTS geoloc_administrativeareas ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, country CHAR(2) NOT NULL, @@ -68,7 +68,7 @@ CREATE TABLE IF NOT EXISTS geoloc_administrativeArea ( INDEX(country) ) CHARSET=utf8; -CREATE TABLE IF NOT EXISTS geoloc_subAdministrativeArea ( +CREATE TABLE IF NOT EXISTS geoloc_subadministrativeareas ( id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, country CHAR(2) NOT NULL, @@ -79,7 +79,7 @@ CREATE TABLE IF NOT EXISTS geoloc_subAdministrativeArea ( INDEX(country) ) CHARSET=utf8; -CREATE TABLE IF NOT EXISTS geoloc_locality ( +CREATE TABLE IF NOT EXISTS geoloc_localities ( id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, country CHAR(2) NOT NULL, -- 2.1.4