Improves code for geocoding thanks to Vincent, fixes issue with street numbers when...
authorStéphane Jacob <jacou@melix.net>
Sun, 15 Feb 2009 17:47:29 +0000 (18:47 +0100)
committerStéphane Jacob <jacou@melix.net>
Mon, 16 Feb 2009 00:58:21 +0000 (01:58 +0100)
configs/platal.ini
include/geocoding.inc.php [new file with mode: 0644]
include/geoloc.inc.php
modules/profile/addresses.inc.php
modules/profile/page.inc.php
upgrade/newdirectory-0.0.1/15_addresses.sql

index c6980ec..f263a75 100644 (file)
@@ -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 (file)
index 0000000..816afeb
--- /dev/null
@@ -0,0 +1,344 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2009 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program is distributed in the hope that it will be useful,        *
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
+ *  GNU General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+// Interface for an address geocoder. It provides support for transforming a free
+// form address into a fully structured one.
+// TODO: define and use an Address object instead of a key-value map.
+abstract class Geocoder {
+    // Geocodes @p the address, and returns the corresponding updated address.
+    // Unknown key-value pairs available in the input map are retained as-is.
+    abstract public function getGeocodedAddress(array $address);
+
+    // Updates geoloc_administrativeareas, geoloc_subadministrativeareas and
+    // geoloc_localities databases with new geocoded data and returns the
+    // corresponding id.
+    static public function getAreaId(array &$address, $area)
+    {
+        static $databases = array(
+            'administrativeArea'    => '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:
+?>
index ff58ddb..0a6bfac 100644 (file)
  *  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("<option value=\"%s\" %s>%s</option>\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 = "<option value=\"\"></option>";
-    while (list($regid, $regname) = $res->next()) {
-        $html .= sprintf("<option value=\"%s\" %s>%s</option>\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("<option value=\"%s\" %s>%s</option>\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 = "<option value=\"\"></option>";
+    while (list($regid, $regname) = $res->next()) {
+        $html .= sprintf("<option value=\"%s\" %s>%s</option>\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)
 {
index 6eca8bd..6574761 100644 (file)
@@ -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,
index 8408c15..27e1958 100644 (file)
@@ -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');
index e437e22..dff260f 100644 (file)
@@ -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,