Fix remaining #x4dat#s.
[platal.git] / include / geocoding.inc.php
index 816afeb..e23c0ac 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /***************************************************************************
- *  Copyright (C) 2003-2009 Polytechnique.org                              *
+ *  Copyright (C) 2003-2010 Polytechnique.org                              *
  *  http://opensource.polytechnique.org/                                   *
  *                                                                         *
  *  This program is free software; you can redistribute it and/or modify   *
@@ -27,6 +27,9 @@ abstract class Geocoder {
     // Unknown key-value pairs available in the input map are retained as-is.
     abstract public function getGeocodedAddress(array $address);
 
+    // Cleans the address from its geocoded data
+    abstract public function stripGeocodingFromAddress(array $address);
+
     // Updates geoloc_administrativeareas, geoloc_subadministrativeareas and
     // geoloc_localities databases with new geocoded data and returns the
     // corresponding id.
@@ -52,6 +55,29 @@ abstract class Geocoder {
             }
         }
     }
+
+    // Returns the part of the text preceeding the line with the postal code
+    // and the city name, within the limit of $limit number of lines.
+    static public function getFirstLines($text, $postalCode, $limit)
+    {
+        $textArray  = explode("\n", $text);
+        for ($i = 0; $i < count($textArray); ++$i) {
+            if ($i > $limit || strpos($textLine, $postalCode) !== false) {
+                $limit = $i; break;
+            }
+        }
+        return implode("\n", array_slice($textArray, 0, $limit));
+    }
+
+    // Returns the number of non geocoded addresses for a user.
+    static public function countNonGeocoded($pid)
+    {
+        $res = XDB::query("SELECT  COUNT(*)
+                             FROM  profile_addresses
+                            WHERE  pid = {?} AND FIND_IN_SET('home', type) AND accuracy = 0",
+                          $pid);
+        return $res->fetchOneCell();
+    }
 }
 
 // Implementation of a Geocoder using the Google Maps API. Please refer to
@@ -69,7 +95,7 @@ class GMapsGeocoder extends Geocoder {
 
     public function getGeocodedAddress(array $address) {
         $address = $this->prepareAddress($address);
-        $textAddress = $address['text'];
+        $textAddress = $this->getTextToGeocode($address);
 
         // Try to geocode the full address.
         if (($geocodedData = $this->getPlacemarkForAddress($textAddress))) {
@@ -94,6 +120,15 @@ class GMapsGeocoder extends Geocoder {
         return $address;
     }
 
+    public function stripGeocodingFromAddress(array $address) {
+        unset($address['geoloc'], $address['geoloc_choice'], $address['geocodedPostalText'],
+              $address['countryId'], $address['country'], $address['administrativeAreaName'],
+              $address['subAdministrativeAreaName'], $address['localityName'],
+              $address['thoroughfareName'], $address['postalCode']);
+        $address['accuracy'] = 0;
+        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) {
@@ -108,10 +143,6 @@ class GMapsGeocoder extends Geocoder {
         // 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;
     }
 
@@ -128,7 +159,7 @@ class GMapsGeocoder extends Geocoder {
     // 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['postalText'] = $this->getPostalAddress($address['text']);
         $address['updateTime'] = time();
         unset($address['changed']);
         return $address;
@@ -281,6 +312,7 @@ class GMapsGeocoder extends Geocoder {
         if ($extraLines) {
             $address['geoloc'] = $extraLines . "\n" . $address['geoloc'];
         }
+        $address['geocodedPostalText'] = $this->getPostalAddress($address['geoloc']);
         $geoloc = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
                                           array("", "\n"), $address['geoloc']));
         $text   = strtoupper(preg_replace(array("/[0-9,\"'#~:;_\- ]/", "/\r\n/"),
@@ -303,10 +335,90 @@ class GMapsGeocoder extends Geocoder {
         }
         if ($same) {
             $address['text'] = $address['geoloc'];
-            unset($address['geoloc']);
+            $address['postalText'] = $address['geocodedPostalText'];
+            unset($address['geoloc'], $address['geocodedPostalText']);
+        } else {
+            $address['geoloc'] = str_replace("\n", "\r\n", $address['geoloc']);
+            $address['geocodedPostalText'] = str_replace("\n", "\r\n", $address['geocodedPostalText']);
         }
+        $address['text'] = str_replace("\n", "\r\n", $address['text']);
+        $address['postalText'] = str_replace("\n", "\r\n", $address['postalText']);
     }
  
+    // Returns the address formated for postal use.
+    // The main rules are (cf AFNOR XPZ 10-011):
+    // -everything in upper case;
+    // -if there are more then than 38 characters in a lign, split it;
+    // -if there are more then than 32 characters in the description of the "street", use abbreviations.
+    private function getPostalAddress($text) {
+         static $abbreviations = array(
+             "IMPASSE"   => "IMP",
+             "RUE"       => "R",
+             "AVENUE"    => "AV",
+             "BOULEVARD" => "BVD",
+             "ROUTE"     => "R",
+             "STREET"    => "ST",
+             "ROAD"      => "RD",
+             );
+
+        $text = strtoupper($text);
+        $arrayText = explode("\n", $text);
+        $postalText = "";
+
+        foreach ($arrayText as $i => $lign) {
+            $postalText .= (($i == 0) ? "" : "\n");
+            if (($length = strlen($lign)) > 32) {
+                $words = explode(" ", $lign);
+                $count = 0;
+                foreach ($words as $word) {
+                    if (isset($abbreviations[$word])) {
+                        $word = $abbreviations[$word];
+                    }
+                    if ($count + ($wordLength = strlen($word)) <= 38) {
+                        $postalText .= (($count == 0) ? "" : " ") . $word;
+                        $count += (($count == 0) ? 0 : 1) + $wordLength;
+                    } else {
+                        $postalText .= "\n" . $word;
+                        $count = strlen($word);
+                    }
+                }
+            } else {
+                $postalText .= $lign;
+            }
+        }
+        return $postalText;
+    }
+
+    // Trims the name of the real country if it contains an ISO 3166-1 non-country
+    // item. For that purpose, we compare the last but one line of the address with
+    // all non-country items of ISO 3166-1.
+    private function getTextToGeocode($address)
+    {
+        $res = XDB::iterator('SELECT  country, countryFR
+                                FROM  geoloc_countries
+                               WHERE  belongsTo IS NOT NULL');
+        $countries = array();
+        foreach ($res as $item) {
+            $countries[] = $item[0];
+            $countries[] = $item[1];
+        }
+        $textLines  = explode("\n", $address['text']);
+        $countLines = count($textLines);
+        $needle     = strtoupper(trim($textLines[$countLines - 2]));
+        $isPseudoCountry = false;
+        foreach ($countries as $country) {
+            if (strtoupper($country) == $needle) {
+                $isPseudoCountry = true;
+                break;
+            }
+        }
+
+        if ($isPseudoCountry) {
+            return $address['text'];
+        }
+        return implode("\n", array_slice($textLines, 0, -1));
+    }
+
     // 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)