]> git.openstreetmap.org Git - nominatim.git/commitdiff
Merge pull request #829 from lonvia/result-as-a-class
authorSarah Hoffmann <lonvia@denofr.de>
Tue, 24 Oct 2017 20:17:59 +0000 (22:17 +0200)
committerGitHub <noreply@github.com>
Tue, 24 Oct 2017 20:17:59 +0000 (22:17 +0200)
Use PlaceLookup in search for retriving place details

lib/Geocode.php
lib/PlaceLookup.php
lib/Result.php [new file with mode: 0644]
lib/ReverseGeocode.php
lib/SearchDescription.php
lib/lib.php
test/bdd/api/search/simple.feature
website/lookup.php
website/reverse.php
website/search.php

index be543012bf76626f95e3e7cc620021b7bf424c7f..ae518d5137623c9e240dd6a10b116aeb03aef6b1 100644 (file)
@@ -12,21 +12,13 @@ class Geocode
 {
     protected $oDB;
 
+    protected $oPlaceLookup;
+
     protected $aLangPrefOrder = array();
 
     protected $bIncludeAddressDetails = false;
-    protected $bIncludeExtraTags = false;
-    protected $bIncludeNameDetails = false;
-
-    protected $bIncludePolygonAsPoints = false;
-    protected $bIncludePolygonAsText = false;
-    protected $bIncludePolygonAsGeoJSON = false;
-    protected $bIncludePolygonAsKML = false;
-    protected $bIncludePolygonAsSVG = false;
-    protected $fPolygonSimplificationThreshold = 0.0;
 
     protected $aExcludePlaceIDs = array();
-    protected $bDeDupe = true;
     protected $bReverseInPlan = false;
 
     protected $iLimit = 20;
@@ -45,7 +37,6 @@ class Geocode
     protected $iMinAddressRank = 0;
     protected $iMaxAddressRank = 30;
     protected $aAddressRankList = array();
-    protected $exactMatchCache = array();
 
     protected $sAllowedTypesSQLList = false;
 
@@ -58,6 +49,7 @@ class Geocode
     public function __construct(&$oDB)
     {
         $this->oDB =& $oDB;
+        $this->oPlaceLookup = new PlaceLookup($this->oDB);
         $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules);
     }
 
@@ -88,69 +80,26 @@ class Geocode
             $aParams = array('q' => $this->sQuery);
         }
 
+        $aParams = array_merge($aParams, $this->oPlaceLookup->getMoreUrlParams());
+
         if ($this->aExcludePlaceIDs) {
             $aParams['exclude_place_ids'] = implode(',', $this->aExcludePlaceIDs);
         }
 
         if ($this->bIncludeAddressDetails) $aParams['addressdetails'] = '1';
-        if ($this->bIncludeExtraTags) $aParams['extratags'] = '1';
-        if ($this->bIncludeNameDetails) $aParams['namedetails'] = '1';
-
-        if ($this->bIncludePolygonAsPoints) $aParams['polygon'] = '1';
-        if ($this->bIncludePolygonAsText) $aParams['polygon_text'] = '1';
-        if ($this->bIncludePolygonAsGeoJSON) $aParams['polygon_geojson'] = '1';
-        if ($this->bIncludePolygonAsKML) $aParams['polygon_kml'] = '1';
-        if ($this->bIncludePolygonAsSVG) $aParams['polygon_svg'] = '1';
-
-        if ($this->fPolygonSimplificationThreshold > 0.0) {
-            $aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold;
-        }
-
         if ($this->bBoundedSearch) $aParams['bounded'] = '1';
-        if (!$this->bDeDupe) $aParams['dedupe'] = '0';
 
         if ($this->aCountryCodes) {
             $aParams['countrycodes'] = implode(',', $this->aCountryCodes);
         }
 
         if ($this->aViewBox) {
-            $aParams['viewbox'] = $this->aViewBox[0].','.$this->aViewBox[3]
-                                  .','.$this->aViewBox[2].','.$this->aViewBox[1];
+            $aParams['viewbox'] = join(',', $this->aViewBox);
         }
 
         return $aParams;
     }
 
-    public function setIncludePolygonAsPoints($b = true)
-    {
-        $this->bIncludePolygonAsPoints = $b;
-    }
-
-    public function setIncludePolygonAsText($b = true)
-    {
-        $this->bIncludePolygonAsText = $b;
-    }
-
-    public function setIncludePolygonAsGeoJSON($b = true)
-    {
-        $this->bIncludePolygonAsGeoJSON = $b;
-    }
-
-    public function setIncludePolygonAsKML($b = true)
-    {
-        $this->bIncludePolygonAsKML = $b;
-    }
-
-    public function setIncludePolygonAsSVG($b = true)
-    {
-        $this->bIncludePolygonAsSVG = $b;
-    }
-
-    public function setPolygonSimplificationThreshold($f)
-    {
-        $this->fPolygonSimplificationThreshold = $f;
-    }
-
     public function setLimit($iLimit = 10)
     {
         if ($iLimit > 50) $iLimit = 50;
@@ -186,20 +135,39 @@ class Geocode
 
     public function setViewbox($aViewbox)
     {
-        $this->aViewBox = array_map('floatval', $aViewbox);
+        $aBox = array_map('floatval', $aViewbox);
 
-        $this->aViewBox[0] = max(-180.0, min(180, $this->aViewBox[0]));
-        $this->aViewBox[1] = max(-90.0, min(90, $this->aViewBox[1]));
-        $this->aViewBox[2] = max(-180.0, min(180, $this->aViewBox[2]));
-        $this->aViewBox[3] = max(-90.0, min(90, $this->aViewBox[3]));
+        $this->aViewBox[0] = max(-180.0, min($aBox[0], $aBox[2]));
+        $this->aViewBox[1] = max(-90.0, min($aBox[1], $aBox[3]));
+        $this->aViewBox[2] = min(180.0, max($aBox[0], $aBox[2]));
+        $this->aViewBox[3] = min(90.0, max($aBox[1], $aBox[3]));
 
-        if (abs($this->aViewBox[0] - $this->aViewBox[2]) < 0.000000001
-            || abs($this->aViewBox[1] - $this->aViewBox[3]) < 0.000000001
+        if ($this->aViewBox[2] - $this->aViewBox[0] < 0.000000001
+            || $this->aViewBox[3] - $this->aViewBox[1] < 0.000000001
         ) {
             userError("Bad parameter 'viewbox'. Not a box.");
         }
     }
 
+    private function viewboxImportanceFactor($fX, $fY)
+    {
+        $fWidth = ($this->aViewBox[2] - $this->aViewBox[0])/2;
+        $fHeight = ($this->aViewBox[3] - $this->aViewBox[1])/2;
+
+        $fXDist = abs($fX - ($this->aViewBox[0] + $this->aViewBox[2])/2);
+        $fYDist = abs($fY - ($this->aViewBox[1] + $this->aViewBox[3])/2);
+
+        if ($fXDist <= $fWidth && $fYDist <= $fHeight) {
+            return 1;
+        }
+
+        if ($fXDist <= $fWidth * 3 && $fYDist <= 3 * $fHeight) {
+            return 0.5;
+        }
+
+        return 0.25;
+    }
+
     public function setQuery($sQueryString)
     {
         $this->sQuery = $sQueryString;
@@ -212,17 +180,12 @@ class Geocode
     }
 
 
-    public function loadParamArray($oParams)
+    public function loadParamArray($oParams, $sForceGeometryType = null)
     {
         $this->bIncludeAddressDetails
          = $oParams->getBool('addressdetails', $this->bIncludeAddressDetails);
-        $this->bIncludeExtraTags
-         = $oParams->getBool('extratags', $this->bIncludeExtraTags);
-        $this->bIncludeNameDetails
-         = $oParams->getBool('namedetails', $this->bIncludeNameDetails);
 
         $this->bBoundedSearch = $oParams->getBool('bounded', $this->bBoundedSearch);
-        $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe);
 
         $this->setLimit($oParams->getInt('limit', $this->iFinalLimit));
         $this->iOffset = $oParams->getInt('offset', $this->iOffset);
@@ -281,6 +244,10 @@ class Geocode
                 }
             }
         }
+
+        $this->oPlaceLookup->loadParamArray($oParams, $sForceGeometryType);
+        $this->oPlaceLookup->setIncludeAddressDetails(false);
+        $this->oPlaceLookup->setIncludePolygonAsPoints($oParams->getBool('polygon'));
     }
 
     public function setQueryFromParams($oParams)
@@ -365,310 +332,6 @@ class Geocode
         return false;
     }
 
-    public function getDetails($aPlaceIDs, $oCtx)
-    {
-        //$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1
-        if (sizeof($aPlaceIDs) == 0) return array();
-
-        $sLanguagePrefArraySQL = getArraySQL(
-            array_map("getDBQuoted", $this->aLangPrefOrder)
-        );
-
-        // Get the details for display (is this a redundant extra step?)
-        $sPlaceIDs = join(',', array_keys($aPlaceIDs));
-
-        $sImportanceSQL = $oCtx->viewboxImportanceSQL('ST_Collect(centroid)');
-        $sImportanceSQLGeom = $oCtx->viewboxImportanceSQL('geometry');
-
-        $sSQL  = "SELECT ";
-        $sSQL .= "    osm_type,";
-        $sSQL .= "    osm_id,";
-        $sSQL .= "    class,";
-        $sSQL .= "    type,";
-        $sSQL .= "    admin_level,";
-        $sSQL .= "    rank_search,";
-        $sSQL .= "    rank_address,";
-        $sSQL .= "    min(place_id) AS place_id, ";
-        $sSQL .= "    min(parent_place_id) AS parent_place_id, ";
-        $sSQL .= "    country_code, ";
-        $sSQL .= "    get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) AS langaddress,";
-        $sSQL .= "    get_name_by_language(name, $sLanguagePrefArraySQL) AS placename,";
-        $sSQL .= "    get_name_by_language(name, ARRAY['ref']) AS ref,";
-        if ($this->bIncludeExtraTags) $sSQL .= "hstore_to_json(extratags)::text AS extra,";
-        if ($this->bIncludeNameDetails) $sSQL .= "hstore_to_json(name)::text AS names,";
-        $sSQL .= "    avg(ST_X(centroid)) AS lon, ";
-        $sSQL .= "    avg(ST_Y(centroid)) AS lat, ";
-        $sSQL .= "    COALESCE(importance,0.75-(rank_search::float/40)) $sImportanceSQL AS importance, ";
-        if ($oCtx->hasNearPoint()) {
-            $sSQL .= $oCtx->distanceSQL('ST_Collect(centroid)')." AS addressimportance,";
-        } else {
-            $sSQL .= "    ( ";
-            $sSQL .= "       SELECT max(p.importance*(p.rank_address+2))";
-            $sSQL .= "       FROM ";
-            $sSQL .= "         place_addressline s, ";
-            $sSQL .= "         placex p";
-            $sSQL .= "       WHERE s.place_id = min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)";
-            $sSQL .= "         AND p.place_id = s.address_place_id ";
-            $sSQL .= "         AND s.isaddress ";
-            $sSQL .= "         AND p.importance is not null ";
-            $sSQL .= "    ) AS addressimportance, ";
-        }
-        $sSQL .= "    (extratags->'place') AS extra_place ";
-        $sSQL .= " FROM placex";
-        $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
-        $sSQL .= "   AND (";
-        $sSQL .= "            placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
-        if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) {
-            $sSQL .= "        OR (extratags->'place') = 'city'";
-        }
-        if ($this->aAddressRankList) {
-            $sSQL .= "        OR placex.rank_address in (".join(',', $this->aAddressRankList).")";
-        }
-        $sSQL .= "       ) ";
-        if ($this->sAllowedTypesSQLList) {
-            $sSQL .= "AND placex.class in $this->sAllowedTypesSQLList ";
-        }
-        $sSQL .= "    AND linked_place_id is null ";
-        $sSQL .= " GROUP BY ";
-        $sSQL .= "     osm_type, ";
-        $sSQL .= "     osm_id, ";
-        $sSQL .= "     class, ";
-        $sSQL .= "     type, ";
-        $sSQL .= "     admin_level, ";
-        $sSQL .= "     rank_search, ";
-        $sSQL .= "     rank_address, ";
-        $sSQL .= "     country_code, ";
-        $sSQL .= "     importance, ";
-        if (!$this->bDeDupe) $sSQL .= "place_id,";
-        $sSQL .= "     langaddress, ";
-        $sSQL .= "     placename, ";
-        $sSQL .= "     ref, ";
-        if ($this->bIncludeExtraTags) $sSQL .= "extratags, ";
-        if ($this->bIncludeNameDetails) $sSQL .= "name, ";
-        $sSQL .= "     extratags->'place' ";
-
-        // postcode table
-        $sSQL .= "UNION ";
-        $sSQL .= "SELECT";
-        $sSQL .= "  'P' as osm_type,";
-        $sSQL .= "  (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,";
-        $sSQL .= "  'place' as class, 'postcode' as type,";
-        $sSQL .= "  null as admin_level, rank_search, rank_address,";
-        $sSQL .= "  place_id, parent_place_id, country_code,";
-        $sSQL .= "  get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) AS langaddress,";
-        $sSQL .= "  postcode as placename,";
-        $sSQL .= "  postcode as ref,";
-        if ($this->bIncludeExtraTags) $sSQL .= "null AS extra,";
-        if ($this->bIncludeNameDetails) $sSQL .= "null AS names,";
-        $sSQL .= "  ST_x(st_centroid(geometry)) AS lon, ST_y(st_centroid(geometry)) AS lat,";
-        $sSQL .= "  (0.75-(rank_search::float/40)) $sImportanceSQLGeom AS importance, ";
-        if ($oCtx->hasNearPoint()) {
-            $sSQL .= $oCtx->distanceSQL('geometry')." AS addressimportance,";
-        } else {
-            $sSQL .= "  (";
-            $sSQL .= "     SELECT max(p.importance*(p.rank_address+2))";
-            $sSQL .= "     FROM ";
-            $sSQL .= "       place_addressline s, ";
-            $sSQL .= "       placex p";
-            $sSQL .= "     WHERE s.place_id = lp.parent_place_id";
-            $sSQL .= "       AND p.place_id = s.address_place_id ";
-            $sSQL .= "       AND s.isaddress";
-            $sSQL .= "       AND p.importance is not null";
-            $sSQL .= "  ) AS addressimportance, ";
-        }
-        $sSQL .= "  null AS extra_place ";
-        $sSQL .= "FROM location_postcode lp";
-        $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
-
-        if (30 >= $this->iMinAddressRank && 30 <= $this->iMaxAddressRank) {
-            // only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines
-            // with start- and endnumber, the common osm housenumbers are usually saved as points
-            $sHousenumbers = "";
-            $i = 0;
-            $length = count($aPlaceIDs);
-            foreach ($aPlaceIDs as $placeID => $housenumber) {
-                $i++;
-                $sHousenumbers .= "(".$placeID.", ".$housenumber.")";
-                if ($i<$length) $sHousenumbers .= ", ";
-            }
-
-            if (CONST_Use_US_Tiger_Data) {
-                // Tiger search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join)
-                $sSQL .= " union";
-                $sSQL .= " SELECT ";
-                $sSQL .= "     'T' AS osm_type, ";
-                $sSQL .= "     (SELECT osm_id from placex p WHERE p.place_id=min(blub.parent_place_id)) as osm_id, ";
-                $sSQL .= "     'place' AS class, ";
-                $sSQL .= "     'house' AS type, ";
-                $sSQL .= "     null AS admin_level, ";
-                $sSQL .= "     30 AS rank_search, ";
-                $sSQL .= "     30 AS rank_address, ";
-                $sSQL .= "     min(place_id) AS place_id, ";
-                $sSQL .= "     min(parent_place_id) AS parent_place_id, ";
-                $sSQL .= "     'us' AS country_code, ";
-                $sSQL .= "     get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) AS langaddress,";
-                $sSQL .= "     null AS placename, ";
-                $sSQL .= "     null AS ref, ";
-                if ($this->bIncludeExtraTags) $sSQL .= "null AS extra,";
-                if ($this->bIncludeNameDetails) $sSQL .= "null AS names,";
-                $sSQL .= "     avg(st_x(centroid)) AS lon, ";
-                $sSQL .= "     avg(st_y(centroid)) AS lat,";
-                $sSQL .= "     -1.15".$sImportanceSQL." AS importance, ";
-                if ($oCtx->hasNearPoint()) {
-                    $sSQL .= $oCtx->distanceSQL('ST_Collect(centroid)')." AS addressimportance,";
-                } else {
-                    $sSQL .= "     (";
-                    $sSQL .= "        SELECT max(p.importance*(p.rank_address+2))";
-                    $sSQL .= "        FROM ";
-                    $sSQL .= "          place_addressline s, ";
-                    $sSQL .= "          placex p";
-                    $sSQL .= "        WHERE s.place_id = min(blub.parent_place_id)";
-                    $sSQL .= "          AND p.place_id = s.address_place_id ";
-                    $sSQL .= "          AND s.isaddress";
-                    $sSQL .= "          AND p.importance is not null";
-                    $sSQL .= "     ) AS addressimportance, ";
-                }
-                $sSQL .= "     null AS extra_place ";
-                $sSQL .= " FROM (";
-                $sSQL .= "     SELECT place_id, ";    // interpolate the Tiger housenumbers here
-                $sSQL .= "         ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) AS centroid, ";
-                $sSQL .= "         parent_place_id, ";
-                $sSQL .= "         housenumber_for_place";
-                $sSQL .= "     FROM (";
-                $sSQL .= "            location_property_tiger ";
-                $sSQL .= "            JOIN (values ".$sHousenumbers.") AS housenumbers(place_id, housenumber_for_place) USING(place_id)) ";
-                $sSQL .= "     WHERE ";
-                $sSQL .= "         housenumber_for_place>=0";
-                $sSQL .= "         AND 30 between $this->iMinAddressRank AND $this->iMaxAddressRank";
-                $sSQL .= " ) AS blub"; //postgres wants an alias here
-                $sSQL .= " GROUP BY";
-                $sSQL .= "      place_id, ";
-                $sSQL .= "      housenumber_for_place"; //is this group by really needed?, place_id + housenumber (in combination) are unique
-                if (!$this->bDeDupe) $sSQL .= ", place_id ";
-            }
-            // osmline
-            // interpolation line search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join)
-            $sSQL .= " UNION ";
-            $sSQL .= "SELECT ";
-            $sSQL .= "  'W' AS osm_type, ";
-            $sSQL .= "  osm_id, ";
-            $sSQL .= "  'place' AS class, ";
-            $sSQL .= "  'house' AS type, ";
-            $sSQL .= "  null AS admin_level, ";
-            $sSQL .= "  30 AS rank_search, ";
-            $sSQL .= "  30 AS rank_address, ";
-            $sSQL .= "  min(place_id) as place_id, ";
-            $sSQL .= "  min(parent_place_id) AS parent_place_id, ";
-            $sSQL .= "  country_code, ";
-            $sSQL .= "  get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL) AS langaddress, ";
-            $sSQL .= "  null AS placename, ";
-            $sSQL .= "  null AS ref, ";
-            if ($this->bIncludeExtraTags) $sSQL .= "null AS extra, ";
-            if ($this->bIncludeNameDetails) $sSQL .= "null AS names, ";
-            $sSQL .= "  AVG(st_x(centroid)) AS lon, ";
-            $sSQL .= "  AVG(st_y(centroid)) AS lat, ";
-            $sSQL .= "  -0.1".$sImportanceSQL." AS importance, ";  // slightly smaller than the importance for normal houses with rank 30, which is 0
-            if ($oCtx->hasNearPoint()) {
-                $sSQL .= $oCtx->distanceSQL('ST_Collect(centroid)')." AS addressimportance,";
-            } else {
-                $sSQL .= "  (";
-                $sSQL .= "     SELECT ";
-                $sSQL .= "       MAX(p.importance*(p.rank_address+2)) ";
-                $sSQL .= "     FROM";
-                $sSQL .= "       place_addressline s, ";
-                $sSQL .= "       placex p";
-                $sSQL .= "     WHERE s.place_id = min(blub.parent_place_id) ";
-                $sSQL .= "       AND p.place_id = s.address_place_id ";
-                $sSQL .= "       AND s.isaddress ";
-                $sSQL .= "       AND p.importance is not null";
-                $sSQL .= "  ) AS addressimportance,";
-            }
-            $sSQL .= "  null AS extra_place ";
-            $sSQL .= "  FROM (";
-            $sSQL .= "     SELECT ";
-            $sSQL .= "         osm_id, ";
-            $sSQL .= "         place_id, ";
-            $sSQL .= "         country_code, ";
-            $sSQL .= "         CASE ";             // interpolate the housenumbers here
-            $sSQL .= "           WHEN startnumber != endnumber ";
-            $sSQL .= "           THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) ";
-            $sSQL .= "           ELSE ST_LineInterpolatePoint(linegeo, 0.5) ";
-            $sSQL .= "         END as centroid, ";
-            $sSQL .= "         parent_place_id, ";
-            $sSQL .= "         housenumber_for_place ";
-            $sSQL .= "     FROM (";
-            $sSQL .= "            location_property_osmline ";
-            $sSQL .= "            JOIN (values ".$sHousenumbers.") AS housenumbers(place_id, housenumber_for_place) USING(place_id)";
-            $sSQL .= "          ) ";
-            $sSQL .= "     WHERE housenumber_for_place>=0 ";
-            $sSQL .= "       AND 30 between $this->iMinAddressRank AND $this->iMaxAddressRank";
-            $sSQL .= "  ) as blub"; //postgres wants an alias here
-            $sSQL .= "  GROUP BY ";
-            $sSQL .= "    osm_id, ";
-            $sSQL .= "    place_id, ";
-            $sSQL .= "    housenumber_for_place, ";
-            $sSQL .= "    country_code "; //is this group by really needed?, place_id + housenumber (in combination) are unique
-            if (!$this->bDeDupe) $sSQL .= ", place_id ";
-
-            if (CONST_Use_Aux_Location_data) {
-                $sSQL .= " UNION ";
-                $sSQL .= "  SELECT ";
-                $sSQL .= "     'L' AS osm_type, ";
-                $sSQL .= "     place_id AS osm_id, ";
-                $sSQL .= "     'place' AS class,";
-                $sSQL .= "     'house' AS type, ";
-                $sSQL .= "     null AS admin_level, ";
-                $sSQL .= "     0 AS rank_search,";
-                $sSQL .= "     0 AS rank_address, ";
-                $sSQL .= "     min(place_id) AS place_id,";
-                $sSQL .= "     min(parent_place_id) AS parent_place_id, ";
-                $sSQL .= "     'us' AS country_code, ";
-                $sSQL .= "     get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) AS langaddress, ";
-                $sSQL .= "     null AS placename, ";
-                $sSQL .= "     null AS ref, ";
-                if ($this->bIncludeExtraTags) $sSQL .= "null AS extra, ";
-                if ($this->bIncludeNameDetails) $sSQL .= "null AS names, ";
-                $sSQL .= "     avg(ST_X(centroid)) AS lon, ";
-                $sSQL .= "     avg(ST_Y(centroid)) AS lat, ";
-                $sSQL .= "     -1.10".$sImportanceSQL." AS importance, ";
-                if ($oCtx->hasNearPoint()) {
-                    $sSQL .= $oCtx->distanceSQL('ST_Collect(centroid)')." AS addressimportance,";
-                } else {
-                    $sSQL .= "     ( ";
-                    $sSQL .= "       SELECT max(p.importance*(p.rank_address+2))";
-                    $sSQL .= "       FROM ";
-                    $sSQL .= "          place_addressline s, ";
-                    $sSQL .= "          placex p";
-                    $sSQL .= "       WHERE s.place_id = min(location_property_aux.parent_place_id)";
-                    $sSQL .= "         AND p.place_id = s.address_place_id ";
-                    $sSQL .= "         AND s.isaddress";
-                    $sSQL .= "         AND p.importance is not null";
-                    $sSQL .= "     ) AS addressimportance, ";
-                }
-                $sSQL .= "     null AS extra_place ";
-                $sSQL .= "  FROM location_property_aux ";
-                $sSQL .= "  WHERE place_id in ($sPlaceIDs) ";
-                $sSQL .= "    AND 30 between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                $sSQL .= "  GROUP BY ";
-                $sSQL .= "     place_id, ";
-                if (!$this->bDeDupe) $sSQL .= "place_id, ";
-                $sSQL .= "     get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) ";
-            }
-        }
-
-        $sSQL .= " order by importance desc";
-        if (CONST_Debug) {
-            echo "<hr>";
-            var_dump($sSQL);
-        }
-        $aSearchResults = chksql(
-            $this->oDB->getAll($sSQL),
-            "Could not get details for place."
-        );
-
-        return $aSearchResults;
-    }
-
     public function getGroupedSearches($aSearches, $aPhrases, $aValidTokens, $bIsStructured)
     {
         /*
@@ -883,7 +546,7 @@ class Geocode
         // Do we have anything that looks like a lat/lon pair?
         $sQuery = $oCtx->setNearPointFromQuery($sQuery);
 
-        $aSearchResults = array();
+        $aResults = array();
         if ($sQuery || $this->aStructuredQuery) {
             // Start with a single blank search
             $aSearches = array(new SearchDescription($oCtx));
@@ -1088,8 +751,6 @@ class Geocode
             if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
 
             // Start the search process
-            // array with: placeid => -1 | tiger-housenumber
-            $aResultPlaceIDs = array();
             $iGroupLoop = 0;
             $iQueryLoop = 0;
             foreach ($aGroupedSearches as $iGroupedRank => $aSearches) {
@@ -1102,93 +763,90 @@ class Geocode
                         _debugDumpGroupedSearches(array($iGroupedRank => array($oSearch)), $aValidTokens);
                     }
 
-                    $aRes = $oSearch->query(
+                    $aResults += $oSearch->query(
                         $this->oDB,
                         $aWordFrequencyScores,
-                        $this->exactMatchCache,
                         $this->iMinAddressRank,
                         $this->iMaxAddressRank,
                         $this->iLimit
                     );
 
-                    foreach ($aRes['IDs'] as $iPlaceID) {
-                        // array for placeID => -1 | Tiger housenumber
-                        $aResultPlaceIDs[$iPlaceID] = $aRes['houseNumber'];
-                    }
                     if ($iQueryLoop > 20) break;
                 }
 
-                if (sizeof($aResultPlaceIDs) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
+                if (sizeof($aResults) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) {
                     // Need to verify passes rank limits before dropping out of the loop (yuk!)
                     // reduces the number of place ids, like a filter
                     // rank_address is 30 for interpolated housenumbers
-                    $sWherePlaceId = 'WHERE place_id in (';
-                    $sWherePlaceId .= join(',', array_keys($aResultPlaceIDs)).') ';
-
-                    $sSQL = "SELECT place_id ";
-                    $sSQL .= "FROM placex ".$sWherePlaceId;
-                    $sSQL .= "  AND (";
-                    $sSQL .= "         placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                    if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) {
-                        $sSQL .= "     OR (extratags->'place') = 'city'";
-                    }
-                    if ($this->aAddressRankList) {
-                        $sSQL .= "     OR placex.rank_address in (".join(',', $this->aAddressRankList).")";
-                    }
-                    $sSQL .= "  ) UNION ";
-                    $sSQL .= " SELECT place_id FROM location_postcode lp ".$sWherePlaceId;
-                    $sSQL .= "  AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
-                    if ($this->aAddressRankList) {
-                        $sSQL .= "     OR lp.rank_address in (".join(',', $this->aAddressRankList).")";
+                    $aFilterSql = array();
+                    $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
+                    if ($sPlaceIds) {
+                        $sSQL = 'SELECT place_id FROM placex ';
+                        $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
+                        $sSQL .= "  AND (";
+                        $sSQL .= "         placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
+                        if (14 >= $this->iMinAddressRank && 14 <= $this->iMaxAddressRank) {
+                            $sSQL .= "     OR (extratags->'place') = 'city'";
+                        }
+                        if ($this->aAddressRankList) {
+                            $sSQL .= "     OR placex.rank_address in (".join(',', $this->aAddressRankList).")";
+                        }
+                        $sSQL .= ")";
+                        $aFilterSql[] = $sSQL;
                     }
-                    $sSQL .= ") ";
-                    if (CONST_Use_US_Tiger_Data && $this->iMaxAddressRank == 30) {
-                        $sSQL .= "UNION ";
-                        $sSQL .= "  SELECT place_id ";
-                        $sSQL .= "  FROM location_property_tiger ".$sWherePlaceId;
+                    $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
+                    if ($sPlaceIds) {
+                        $sSQL = ' SELECT place_id FROM location_postcode lp ';
+                        $sSQL .= 'WHERE place_id in ('.$sPlaceIds.') ';
+                        $sSQL .= "  AND (lp.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank ";
+                        if ($this->aAddressRankList) {
+                            $sSQL .= "     OR lp.rank_address in (".join(',', $this->aAddressRankList).")";
+                        }
+                        $sSQL .= ") ";
+                        $aFilterSql[] = $sSQL;
                     }
-                    if ($this->iMaxAddressRank == 30) {
-                        $sSQL .= "UNION ";
-                        $sSQL .= "  SELECT place_id ";
-                        $sSQL .= "  FROM location_property_osmline ".$sWherePlaceId;
+
+                    $aFilteredIDs = array();
+                    if ($aFilterSql) {
+                        $sSQL = join(' UNION ', $aFilterSql);
+                        if (CONST_Debug) var_dump($sSQL);
+                        $aFilteredIDs = chksql($this->oDB->getCol($sSQL));
                     }
-                    if (CONST_Debug) var_dump($sSQL);
-                    $aFilteredPlaceIDs = chksql($this->oDB->getCol($sSQL));
+
                     $tempIDs = array();
-                    foreach ($aFilteredPlaceIDs as $placeID) {
-                        $tempIDs[$placeID] = $aResultPlaceIDs[$placeID];  //assign housenumber to placeID
+                    foreach ($aResults as $oResult) {
+                        if (($this->iMaxAddressRank == 30 &&
+                             ($oResult->iTable == Result::TABLE_OSMLINE
+                              || $oResult->iTable == Result::TABLE_AUX
+                              || $oResult->iTable == Result::TABLE_TIGER))
+                            || in_array($oResult->iId, $aFilteredIDs)
+                        ) {
+                            $tempIDs[$oResult->iId] = $oResult;
+                        }
                     }
-                    $aResultPlaceIDs = $tempIDs;
+                    $aResults = $tempIDs;
                 }
 
-                if (sizeof($aResultPlaceIDs)) break;
+                if (sizeof($aResults)) break;
                 if ($iGroupLoop > 4) break;
                 if ($iQueryLoop > 30) break;
             }
-
-            // Did we find anything?
-            if (sizeof($aResultPlaceIDs)) {
-                $aSearchResults = $this->getDetails($aResultPlaceIDs, $oCtx);
-            }
         } else {
             // Just interpret as a reverse geocode
             $oReverse = new ReverseGeocode($this->oDB);
             $oReverse->setZoom(18);
 
-            $aLookup = $oReverse->lookupPoint($oCtx->sqlNear, false);
+            $oLookup = $oReverse->lookupPoint($oCtx->sqlNear, false);
 
             if (CONST_Debug) var_dump("Reverse search", $aLookup);
 
-            if ($aLookup['place_id']) {
-                $aSearchResults = $this->getDetails(array($aLookup['place_id'] => -1), $oCtx);
-                $aResultPlaceIDs[$aLookup['place_id']] = -1;
-            } else {
-                $aSearchResults = array();
+            if ($oLookup) {
+                $aResults = array($oLookup->iId => $oLookup);
             }
         }
 
         // No results? Done
-        if (!sizeof($aSearchResults)) {
+        if (!sizeof($aResults)) {
             if ($this->bFallback) {
                 if ($this->fallbackStructuredQuery()) {
                     return $this->lookup();
@@ -1198,6 +856,17 @@ class Geocode
             return array();
         }
 
+        if ($this->aAddressRankList) {
+            $this->oPlaceLookup->setAddressRankList($this->aAddressRankList);
+        }
+        $this->oPlaceLookup->setAllowedTypesSQLList($this->sAllowedTypesSQLList);
+        $this->oPlaceLookup->setLanguagePreference($this->aLangPrefOrder);
+        if ($oCtx->hasNearPoint()) {
+            $this->oPlaceLookup->setAnchorSql($oCtx->sqlNear);
+        }
+
+        $aSearchResults = $this->oPlaceLookup->lookup($aResults);
+
         $aClassType = getClassTypesWithImportance();
         $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery);
         foreach ($aRecheckWords as $i => $sWord) {
@@ -1209,23 +878,15 @@ class Geocode
             var_dump($aRecheckWords);
         }
 
-        $oPlaceLookup = new PlaceLookup($this->oDB);
-        $oPlaceLookup->setIncludePolygonAsPoints($this->bIncludePolygonAsPoints);
-        $oPlaceLookup->setIncludePolygonAsText($this->bIncludePolygonAsText);
-        $oPlaceLookup->setIncludePolygonAsGeoJSON($this->bIncludePolygonAsGeoJSON);
-        $oPlaceLookup->setIncludePolygonAsKML($this->bIncludePolygonAsKML);
-        $oPlaceLookup->setIncludePolygonAsSVG($this->bIncludePolygonAsSVG);
-        $oPlaceLookup->setPolygonSimplificationThreshold($this->fPolygonSimplificationThreshold);
-
-        foreach ($aSearchResults as $iResNum => $aResult) {
+        foreach ($aSearchResults as $iIdx => $aResult) {
             // Default
             $fDiameter = getResultDiameter($aResult);
 
-            $aOutlineResult = $oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2);
+            $aOutlineResult = $this->oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2);
             if ($aOutlineResult) {
                 $aResult = array_merge($aResult, $aOutlineResult);
             }
-            
+
             if ($aResult['extra_place'] == 'city') {
                 $aResult['class'] = 'place';
                 $aResult['type'] = 'city';
@@ -1251,28 +912,12 @@ class Geocode
             // if tag '&addressdetails=1' is set in query
             if ($this->bIncludeAddressDetails) {
                 // getAddressDetails() is defined in lib.php and uses the SQL function get_addressdata in functions.sql
-                $aResult['address'] = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code'], $aResultPlaceIDs[$aResult['place_id']]);
+                $aResult['address'] = getAddressDetails($this->oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code'], $aResults[$aResult['place_id']]->iHouseNumber);
                 if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city'])) {
                     $aResult['address'] = array_merge(array('city' => array_values($aResult['address'])[0]), $aResult['address']);
                 }
             }
 
-            if ($this->bIncludeExtraTags) {
-                if ($aResult['extra']) {
-                    $aResult['sExtraTags'] = json_decode($aResult['extra']);
-                } else {
-                    $aResult['sExtraTags'] = (object) array();
-                }
-            }
-
-            if ($this->bIncludeNameDetails) {
-                if ($aResult['names']) {
-                    $aResult['sNameDetails'] = json_decode($aResult['names']);
-                } else {
-                    $aResult['sNameDetails'] = (object) array();
-                }
-            }
-
             $aResult['name'] = $aResult['langaddress'];
 
             if ($oCtx->hasNearPoint()) {
@@ -1280,6 +925,10 @@ class Geocode
                 $aResult['foundorder'] = $aResult['addressimportance'];
             } else {
                 // Adjust importance for the number of exact string matches in the result
+                $aResult['importance'] *= $this->viewboxImportanceFactor(
+                    $aResult['lon'],
+                    $aResult['lat']
+                );
                 $aResult['importance'] = max(0.001, $aResult['importance']);
                 $iCountWords = 0;
                 $sAddress = $aResult['langaddress'];
@@ -1296,11 +945,7 @@ class Geocode
                 // - approximate importance of address parts
                 $aResult['foundorder'] = -$aResult['addressimportance']/10;
                 // - number of exact matches from the query
-                if (isset($this->exactMatchCache[$aResult['place_id']])) {
-                    $aResult['foundorder'] -= $this->exactMatchCache[$aResult['place_id']];
-                } elseif (isset($this->exactMatchCache[$aResult['parent_place_id']])) {
-                    $aResult['foundorder'] -= $this->exactMatchCache[$aResult['parent_place_id']];
-                }
+                $aResult['foundorder'] -= $aResults[$aResult['place_id']]->iExactMatches;
                 // - importance of the class/type
                 if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance'])
                     && $aClassType[$aResult['class'].':'.$aResult['type']]['importance']
@@ -1311,7 +956,7 @@ class Geocode
                 }
             }
             if (CONST_Debug) var_dump($aResult);
-            $aSearchResults[$iResNum] = $aResult;
+            $aSearchResults[$iIdx] = $aResult;
         }
         uasort($aSearchResults, 'byImportance');
 
@@ -1320,8 +965,10 @@ class Geocode
         $aToFilter = $aSearchResults;
         $aSearchResults = array();
 
+        if (CONST_Debug) var_dump($aToFilter);
+
         $bFirst = true;
-        foreach ($aToFilter as $iResNum => $aResult) {
+        foreach ($aToFilter as $aResult) {
             $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id'];
             if ($bFirst) {
                 $fLat = $aResult['lat'];
@@ -1329,7 +976,7 @@ class Geocode
                 if (isset($aResult['zoom'])) $iZoom = $aResult['zoom'];
                 $bFirst = false;
             }
-            if (!$this->bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
+            if (!$this->oPlaceLookup->doDeDupe() || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']])
                 && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))
             ) {
                 $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true;
@@ -1341,6 +988,7 @@ class Geocode
             if (sizeof($aSearchResults) >= $this->iFinalLimit) break;
         }
 
+        if (CONST_Debug) var_dump($aSearchResults);
         return $aSearchResults;
     } // end lookup()
 } // end class
index 4920a1318d6edd24bbb84c5ae1dab83ef4c3bbcf..a48f5597e66c2218cd1d79891e382b281a20756d 100644 (file)
@@ -2,11 +2,13 @@
 
 namespace Nominatim;
 
+require_once(CONST_BasePath.'/lib/Result.php');
+
 class PlaceLookup
 {
     protected $oDB;
 
-    protected $aLangPrefOrder = array();
+    protected $aLangPrefOrderSql = "''";
 
     protected $bAddressDetails = false;
     protected $bExtraTags = false;
@@ -19,185 +21,467 @@ class PlaceLookup
     protected $bIncludePolygonAsSVG = false;
     protected $fPolygonSimplificationThreshold = 0.0;
 
+    protected $sAnchorSql = null;
+    protected $sAddressRankListSql = null;
+    protected $sAllowedTypesSQLList = null;
+    protected $bDeDupe = true;
+
 
     public function __construct(&$oDB)
     {
         $this->oDB =& $oDB;
     }
 
-    public function setLanguagePreference($aLangPrefOrder)
+    public function doDeDupe()
     {
-        $this->aLangPrefOrder = $aLangPrefOrder;
+        return $this->bDeDupe;
     }
 
-    public function setIncludeAddressDetails($bAddressDetails = true)
+    public function setIncludePolygonAsPoints($b = true)
     {
-        $this->bAddressDetails = $bAddressDetails;
+        $this->bIncludePolygonAsPoints = $b;
     }
 
-    public function setIncludeExtraTags($bExtraTags = false)
+    public function loadParamArray($oParams, $sGeomType = null)
     {
-        $this->bExtraTags = $bExtraTags;
+        $aLangs = $oParams->getPreferredLanguages();
+        $this->aLangPrefOrderSql =
+            'ARRAY['.join(',', array_map('getDBQuoted', $aLangs)).']';
+
+        $this->bAddressDetails = $oParams->getBool('addressdetails', true);
+        $this->bExtraTags = $oParams->getBool('extratags', false);
+        $this->bNameDetails = $oParams->getBool('namedetails', false);
+
+        $this->bDeDupe = $oParams->getBool('dedupe', $this->bDeDupe);
+
+        if ($sGeomType === null || $sGeomType == 'text') {
+            $this->bIncludePolygonAsText = $oParams->getBool('polygon_text');
+        }
+        if ($sGeomType === null || $sGeomType == 'geojson') {
+            $this->bIncludePolygonAsGeoJSON = $oParams->getBool('polygon_geojson');
+        }
+        if ($sGeomType === null || $sGeomType == 'kml') {
+            $this->bIncludePolygonAsKML = $oParams->getBool('polygon_kml');
+        }
+        if ($sGeomType === null || $sGeomType == 'svg') {
+            $this->bIncludePolygonAsSVG = $oParams->getBool('polygon_svg');
+        }
+        $this->fPolygonSimplificationThreshold
+            = $oParams->getFloat('polygon_threshold', 0.0);
+
+        $iWantedTypes =
+            ($this->bIncludePolygonAsText ? 1 : 0) +
+            ($this->bIncludePolygonAsGeoJSON ? 1 : 0) +
+            ($this->bIncludePolygonAsKML ? 1 : 0) +
+            ($this->bIncludePolygonAsSVG ? 1 : 0);
+        if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
+            if (CONST_PolygonOutput_MaximumTypes) {
+                userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
+            } else {
+                userError("Polygon output is disabled");
+            }
+        }
     }
 
-    public function setIncludeNameDetails($bNameDetails = false)
+    public function getMoreUrlParams()
     {
-        $this->bNameDetails = $bNameDetails;
+        $aParams = array();
+
+        if ($this->bAddressDetails) $aParams['addressdetails'] = '1';
+        if ($this->bExtraTags) $aParams['extratags'] = '1';
+        if ($this->bNameDetails) $aParams['namedetails'] = '1';
+
+        if ($this->bIncludePolygonAsPoints) $aParams['polygon'] = '1';
+        if ($this->bIncludePolygonAsText) $aParams['polygon_text'] = '1';
+        if ($this->bIncludePolygonAsGeoJSON) $aParams['polygon_geojson'] = '1';
+        if ($this->bIncludePolygonAsKML) $aParams['polygon_kml'] = '1';
+        if ($this->bIncludePolygonAsSVG) $aParams['polygon_svg'] = '1';
+
+        if ($this->fPolygonSimplificationThreshold > 0.0) {
+            $aParams['polygon_threshold'] = $this->fPolygonSimplificationThreshold;
+        }
+
+        if (!$this->bDeDupe) $aParams['dedupe'] = '0';
+
+        return $aParams;
     }
 
-    public function setIncludePolygonAsPoints($b = true)
+    public function setAnchorSql($sPoint)
     {
-        $this->bIncludePolygonAsPoints = $b;
+        $this->sAnchorSql = $sPoint;
     }
 
-    public function setIncludePolygonAsText($b = true)
+    public function setAddressRankList($aList)
     {
-        $this->bIncludePolygonAsText = $b;
+        $this->sAddressRankListSql = '('.join(',', $aList).')';
     }
 
-    public function setIncludePolygonAsGeoJSON($b = true)
+    public function setAllowedTypesSQLList($sSql)
     {
-        $this->bIncludePolygonAsGeoJSON = $b;
+        $this->sAllowedTypesSQLList = $sSql;
     }
 
-    public function setIncludePolygonAsKML($b = true)
+    public function setLanguagePreference($aLangPrefOrder)
     {
-        $this->bIncludePolygonAsKML = $b;
+        $this->aLangPrefOrderSql =
+            'ARRAY['.join(',', array_map('getDBQuoted', $aLangPrefOrder)).']';
     }
 
-    public function setIncludePolygonAsSVG($b = true)
+    public function setIncludeAddressDetails($bAddressDetails = true)
     {
-        $this->bIncludePolygonAsSVG = $b;
+        $this->bAddressDetails = $bAddressDetails;
+    }
+
+    private function addressImportanceSql($sGeometry, $sPlaceId)
+    {
+        if ($this->sAnchorSql) {
+            $sSQL = 'ST_Distance('.$this->sAnchorSql.','.$sGeometry.')';
+        } else {
+            $sSQL = '(SELECT max(ai_p.importance * (ai_p.rank_address + 2))';
+            $sSQL .= '   FROM place_addressline ai_s, placex ai_p';
+            $sSQL .= '   WHERE ai_s.place_id = '.$sPlaceId;
+            $sSQL .= '     AND ai_p.place_id = ai_s.address_place_id ';
+            $sSQL .= '     AND ai_s.isaddress ';
+            $sSQL .= '     AND ai_p.importance is not null)';
+        }
+
+        return $sSQL.' AS addressimportance,';
     }
 
-    public function setPolygonSimplificationThreshold($f)
+    private function langAddressSql($sHousenumber)
     {
-        $this->fPolygonSimplificationThreshold = $f;
+        return 'get_address_by_language(place_id,'.$sHousenumber.','.$this->aLangPrefOrderSql.') AS langaddress,';
     }
 
     public function lookupOSMID($sType, $iID)
     {
-        $sSQL = "select place_id from placex where osm_type = '".pg_escape_string($sType)."' and osm_id = ".(int)$iID." order by type = 'postcode' asc";
+        $sSQL = "select place_id from placex where osm_type = '".$sType."' and osm_id = ".$iID;
         $iPlaceID = chksql($this->oDB->getOne($sSQL));
 
-        return $this->lookup((int)$iPlaceID);
+        if (!$iPlaceID) {
+            return null;
+        }
+
+        $aResults = $this->lookup(array($iPlaceID => new Result($iPlaceID)));
+
+        return sizeof($aResults) ? reset($aResults) : null;
     }
 
-    public function lookup($iPlaceID, $sType = '', $fInterpolFraction = 0.0)
+    public function lookup($aResults, $iMinRank = 0, $iMaxRank = 30)
     {
-        if (!$iPlaceID) return null;
-
-        $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]";
-        $bIsTiger = CONST_Use_US_Tiger_Data && $sType == 'tiger';
-        $bIsInterpolation = $sType == 'interpolation';
-
-        if ($bIsTiger) {
-            $sSQL = "select place_id,partition, 'T' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, housenumber, postcode,";
-            $sSQL .= " 'us' as country_code, parent_place_id, null as linked_place_id, 30 as rank_address, 30 as rank_search,";
-            $sSQL .= " coalesce(null,0.75-(30::float/40)) as importance, null as indexed_status, null as indexed_date, null as wikipedia, 'us' as country_code, ";
-            $sSQL .= " get_address_by_language(place_id, housenumber, $sLanguagePrefArraySQL) as langaddress,";
-            $sSQL .= " null as placename,";
-            $sSQL .= " null as ref,";
-            if ($this->bExtraTags) $sSQL .= " null as extra,";
-            if ($this->bNameDetails) $sSQL .= " null as names,";
-            $sSQL .= " ST_X(point) as lon, ST_Y(point) as lat from (select *, ST_LineInterpolatePoint(linegeo, (housenumber-startnumber::float)/(endnumber-startnumber)::float) as point from ";
-            $sSQL .= " (select *, ";
-            $sSQL .= " CASE WHEN interpolationtype='odd' THEN floor((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2+1";
-            $sSQL .= " WHEN interpolationtype='even' THEN ((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2";
-            $sSQL .= " WHEN interpolationtype='all' THEN (".$fInterpolFraction."*(endnumber-startnumber)+startnumber)::int";
-            $sSQL .= " END as housenumber";
-            $sSQL .= " from location_property_tiger where place_id = ".$iPlaceID.") as blub1) as blub2";
-        } elseif ($bIsInterpolation) {
-            $sSQL = "select place_id, partition, 'W' as osm_type, osm_id, 'place' as class, 'house' as type, null admin_level, housenumber, postcode,";
-            $sSQL .= " country_code, parent_place_id, null as linked_place_id, 30 as rank_address, 30 as rank_search,";
-            $sSQL .= " (0.75-(30::float/40)) as importance, null as indexed_status, null as indexed_date, null as wikipedia, country_code, ";
-            $sSQL .= " get_address_by_language(place_id, housenumber, $sLanguagePrefArraySQL) as langaddress,";
-            $sSQL .= " null as placename,";
-            $sSQL .= " null as ref,";
-            if ($this->bExtraTags) $sSQL .= " null as extra,";
-            if ($this->bNameDetails) $sSQL .= " null as names,";
-            $sSQL .= " ST_X(point) as lon, ST_Y(point) as lat from (select *, ST_LineInterpolatePoint(linegeo, (housenumber-startnumber::float)/(endnumber-startnumber)::float) as point from ";
-            $sSQL .= " (select *, ";
-            $sSQL .= " CASE WHEN interpolationtype='odd' THEN floor((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2+1";
-            $sSQL .= " WHEN interpolationtype='even' THEN ((".$fInterpolFraction."*(endnumber-startnumber)+startnumber)/2)::int*2";
-            $sSQL .= " WHEN interpolationtype='all' THEN (".$fInterpolFraction."*(endnumber-startnumber)+startnumber)::int";
-            $sSQL .= " END as housenumber";
-            $sSQL .= " from location_property_osmline where place_id = ".$iPlaceID.") as blub1) as blub2";
-            // testcase: interpolationtype=odd, startnumber=1000, endnumber=1006, fInterpolFraction=1 => housenumber=1007 => error in st_lineinterpolatepoint
-            // but this will never happen, because if the searched point is that close to the endnumber, the endnumber house will be directly taken from placex (in ReverseGeocode.php line 220)
-            // and not interpolated
-        } else {
-            $sSQL = "select placex.place_id, partition, osm_type, osm_id, class,";
-            $sSQL .= " type, admin_level, housenumber, postcode, country_code,";
-            $sSQL .= " parent_place_id, linked_place_id, rank_address, rank_search, ";
-            $sSQL .= " coalesce(importance,0.75-(rank_search::float/40)) as importance, indexed_status, indexed_date, wikipedia, country_code, ";
-            $sSQL .= " get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) as langaddress,";
-            $sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL) as placename,";
-            $sSQL .= " get_name_by_language(name, ARRAY['ref']) as ref,";
-            if ($this->bExtraTags) $sSQL .= " hstore_to_json(extratags) as extra,";
-            if ($this->bNameDetails) $sSQL .= " hstore_to_json(name) as names,";
-            $sSQL .= " (case when centroid is null then st_y(st_centroid(geometry)) else st_y(centroid) end) as lat,";
-            $sSQL .= " (case when centroid is null then st_x(st_centroid(geometry)) else st_x(centroid) end) as lon";
-            $sSQL .= " from placex where place_id = ".$iPlaceID;
+        if (!sizeof($aResults)) {
+            return array();
+        }
+        $aSubSelects = array();
+
+        $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
+        if (CONST_Debug) var_dump('PLACEX', $sPlaceIDs);
+        if ($sPlaceIDs) {
+            $sSQL  = 'SELECT ';
+            $sSQL .= '    osm_type,';
+            $sSQL .= '    osm_id,';
+            $sSQL .= '    class,';
+            $sSQL .= '    type,';
+            $sSQL .= '    admin_level,';
+            $sSQL .= '    rank_search,';
+            $sSQL .= '    rank_address,';
+            $sSQL .= '    min(place_id) AS place_id,';
+            $sSQL .= '    min(parent_place_id) AS parent_place_id,';
+            $sSQL .= '    housenumber,';
+            $sSQL .= '    country_code,';
+            $sSQL .= $this->langAddressSql('-1');
+            $sSQL .= '    get_name_by_language(name,'.$this->aLangPrefOrderSql.') AS placename,';
+            $sSQL .= "    get_name_by_language(name, ARRAY['ref']) AS ref,";
+            if ($this->bExtraTags) {
+                $sSQL .= 'hstore_to_json(extratags)::text AS extra,';
+            }
+            if ($this->bNameDetails) {
+                $sSQL .= 'hstore_to_json(name)::text AS names,';
+            }
+            $sSQL .= '    avg(ST_X(centroid)) AS lon, ';
+            $sSQL .= '    avg(ST_Y(centroid)) AS lat, ';
+            $sSQL .= '    COALESCE(importance,0.75-(rank_search::float/40)) AS importance, ';
+            $sSQL .= $this->addressImportanceSql(
+                'ST_Collect(centroid)',
+                'min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END)'
+            );
+            $sSQL .= "    (extratags->'place') AS extra_place ";
+            $sSQL .= ' FROM placex';
+            $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
+            $sSQL .= '   AND (';
+            $sSQL .= "        placex.rank_address between $iMinRank and $iMaxRank ";
+            if (14 >= $iMinRank && 14 <= $iMaxRank) {
+                $sSQL .= "    OR (extratags->'place') = 'city'";
+            }
+            if ($this->sAddressRankListSql) {
+                $sSQL .= '    OR placex.rank_address in '.$this->sAddressRankListSql;
+            }
+            $sSQL .= "       ) ";
+            if ($this->sAllowedTypesSQLList) {
+                $sSQL .= 'AND placex.class in '.$this->sAllowedTypesSQLList;
+            }
+            $sSQL .= '    AND linked_place_id is null ';
+            $sSQL .= ' GROUP BY ';
+            $sSQL .= '     osm_type, ';
+            $sSQL .= '     osm_id, ';
+            $sSQL .= '     class, ';
+            $sSQL .= '     type, ';
+            $sSQL .= '     admin_level, ';
+            $sSQL .= '     rank_search, ';
+            $sSQL .= '     rank_address, ';
+            $sSQL .= '     housenumber,';
+            $sSQL .= '     country_code, ';
+            $sSQL .= '     importance, ';
+            if (!$this->bDeDupe) $sSQL .= 'place_id,';
+            $sSQL .= '     langaddress, ';
+            $sSQL .= '     placename, ';
+            $sSQL .= '     ref, ';
+            if ($this->bExtraTags) $sSQL .= 'extratags, ';
+            if ($this->bNameDetails) $sSQL .= 'name, ';
+            $sSQL .= "     extratags->'place' ";
+
+            $aSubSelects[] = $sSQL;
         }
 
-        $aPlace = chksql($this->oDB->getRow($sSQL), "Could not lookup place");
+        // postcode table
+        $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_POSTCODE);
+        if ($sPlaceIDs) {
+            $sSQL = 'SELECT';
+            $sSQL .= "  'P' as osm_type,";
+            $sSQL .= "  (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id,";
+            $sSQL .= "  'place' as class, 'postcode' as type,";
+            $sSQL .= '  null as admin_level, rank_search, rank_address,';
+            $sSQL .= '  place_id, parent_place_id,';
+            $sSQL .= '  null as housenumber,';
+            $sSQL .= '  country_code,';
+            $sSQL .= $this->langAddressSql('-1');
+            $sSQL .= "  postcode as placename,";
+            $sSQL .= "  postcode as ref,";
+            if ($this->bExtraTags) $sSQL .= "null AS extra,";
+            if ($this->bNameDetails) $sSQL .= "null AS names,";
+            $sSQL .= "  ST_x(geometry) AS lon, ST_y(geometry) AS lat,";
+            $sSQL .= "  (0.75-(rank_search::float/40)) AS importance, ";
+            $sSQL .= $this->addressImportanceSql('geometry', 'lp.parent_place_id');
+            $sSQL .= "  null AS extra_place ";
+            $sSQL .= "FROM location_postcode lp";
+            $sSQL .= " WHERE place_id in ($sPlaceIDs) ";
+            $sSQL .= "   AND lp.rank_address between $iMinRank and $iMaxRank";
+
+            $aSubSelects[] = $sSQL;
+        }
 
-        if (!$aPlace['place_id']) return null;
+        // All other tables are rank 30 only.
+        if ($iMaxRank == 30) {
+            // TIGER table
+            if (CONST_Use_US_Tiger_Data) {
+                $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_TIGER);
+                if ($sPlaceIDs) {
+                    $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_TIGER);
+                    // Tiger search only if a housenumber was searched and if it was found
+                    // (realized through a join)
+                    $sSQL = " SELECT ";
+                    $sSQL .= "     'T' AS osm_type, ";
+                    $sSQL .= "     (SELECT osm_id from placex p WHERE p.place_id=blub.parent_place_id) as osm_id, ";
+                    $sSQL .= "     'place' AS class, ";
+                    $sSQL .= "     'house' AS type, ";
+                    $sSQL .= '     null AS admin_level, ';
+                    $sSQL .= '     30 AS rank_search, ';
+                    $sSQL .= '     30 AS rank_address, ';
+                    $sSQL .= '     place_id, ';
+                    $sSQL .= '     parent_place_id, ';
+                    $sSQL .= '     housenumber_for_place as housenumber,';
+                    $sSQL .= "     'us' AS country_code, ";
+                    $sSQL .= $this->langAddressSql('housenumber_for_place');
+                    $sSQL .= "     null AS placename, ";
+                    $sSQL .= "     null AS ref, ";
+                    if ($this->bExtraTags) $sSQL .= "null AS extra,";
+                    if ($this->bNameDetails) $sSQL .= "null AS names,";
+                    $sSQL .= "     st_x(centroid) AS lon, ";
+                    $sSQL .= "     st_y(centroid) AS lat,";
+                    $sSQL .= "     -1.15 AS importance, ";
+                    $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
+                    $sSQL .= "     null AS extra_place ";
+                    $sSQL .= " FROM (";
+                    $sSQL .= "     SELECT place_id, ";    // interpolate the Tiger housenumbers here
+                    $sSQL .= "         ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) AS centroid, ";
+                    $sSQL .= "         parent_place_id, ";
+                    $sSQL .= "         housenumber_for_place";
+                    $sSQL .= "     FROM (";
+                    $sSQL .= "            location_property_tiger ";
+                    $sSQL .= "            JOIN (values ".$sHousenumbers.") AS housenumbers(place_id, housenumber_for_place) USING(place_id)) ";
+                    $sSQL .= "     WHERE ";
+                    $sSQL .= "         housenumber_for_place >= startnumber";
+                    $sSQL .= "         AND housenumber_for_place <= endnumber";
+                    $sSQL .= " ) AS blub"; //postgres wants an alias here
+
+                    $aSubSelects[] = $sSQL;
+                }
+            }
 
-        if ($this->bAddressDetails) {
-            // to get addressdetails for tiger data, the housenumber is needed
-            $iHousenumber = ($bIsTiger || $bIsInterpolation) ? $aPlace['housenumber'] : -1;
-            $aPlace['aAddress'] = $this->getAddressNames($aPlace['place_id'], $iHousenumber);
-        }
+            // osmline - interpolated housenumbers
+            $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_OSMLINE);
+            if ($sPlaceIDs) {
+                $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_OSMLINE);
+                // interpolation line search only if a housenumber was searched
+                // (realized through a join)
+                $sSQL = "SELECT ";
+                $sSQL .= "  'W' AS osm_type, ";
+                $sSQL .= "  osm_id, ";
+                $sSQL .= "  'place' AS class, ";
+                $sSQL .= "  'house' AS type, ";
+                $sSQL .= '  15 AS admin_level, ';
+                $sSQL .= '  30 AS rank_search, ';
+                $sSQL .= '  30 AS rank_address, ';
+                $sSQL .= '  place_id, ';
+                $sSQL .= '  parent_place_id, ';
+                $sSQL .= '  housenumber_for_place as housenumber,';
+                $sSQL .= '  country_code, ';
+                $sSQL .= $this->langAddressSql('housenumber_for_place');
+                $sSQL .= '  null AS placename, ';
+                $sSQL .= '  null AS ref, ';
+                if ($this->bExtraTags) $sSQL .= 'null AS extra, ';
+                if ($this->bNameDetails) $sSQL .= 'null AS names, ';
+                $sSQL .= '  st_x(centroid) AS lon, ';
+                $sSQL .= '  st_y(centroid) AS lat, ';
+                // slightly smaller than the importance for normal houses
+                $sSQL .= "  -0.1 AS importance, ";
+                $sSQL .= $this->addressImportanceSql('centroid', 'blub.parent_place_id');
+                $sSQL .= "  null AS extra_place ";
+                $sSQL .= "  FROM (";
+                $sSQL .= "     SELECT ";
+                $sSQL .= "         osm_id, ";
+                $sSQL .= "         place_id, ";
+                $sSQL .= "         country_code, ";
+                $sSQL .= "         CASE ";             // interpolate the housenumbers here
+                $sSQL .= "           WHEN startnumber != endnumber ";
+                $sSQL .= "           THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) ";
+                $sSQL .= "           ELSE ST_LineInterpolatePoint(linegeo, 0.5) ";
+                $sSQL .= "         END as centroid, ";
+                $sSQL .= "         parent_place_id, ";
+                $sSQL .= "         housenumber_for_place ";
+                $sSQL .= "     FROM (";
+                $sSQL .= "            location_property_osmline ";
+                $sSQL .= "            JOIN (values ".$sHousenumbers.") AS housenumbers(place_id, housenumber_for_place) USING(place_id)";
+                $sSQL .= "          ) ";
+                $sSQL .= "     WHERE housenumber_for_place >= 0 ";
+                $sSQL .= "  ) as blub"; //postgres wants an alias here
+
+                $aSubSelects[] = $sSQL;
+            }
 
-        if ($this->bExtraTags) {
-            if ($aPlace['extra']) {
-                $aPlace['sExtraTags'] = json_decode($aPlace['extra']);
-            } else {
-                $aPlace['sExtraTags'] = (object) array();
+            if (CONST_Use_Aux_Location_data) {
+                $sPlaceIDs = Result::joinIdsByTable($aResults, Result::TABLE_AUX);
+                if ($sPlaceIDs) {
+                    $sHousenumbers = Result::sqlHouseNumberTable($aResults, Result::TABLE_AUX);
+                    $sSQL = "  SELECT ";
+                    $sSQL .= "     'L' AS osm_type, ";
+                    $sSQL .= "     place_id AS osm_id, ";
+                    $sSQL .= "     'place' AS class,";
+                    $sSQL .= "     'house' AS type, ";
+                    $sSQL .= '     null AS admin_level, ';
+                    $sSQL .= '     30 AS rank_search,';
+                    $sSQL .= '     30 AS rank_address, ';
+                    $sSQL .= '     place_id,';
+                    $sSQL .= '     parent_place_id, ';
+                    $sSQL .= '     housenumber,';
+                    $sSQL .= "     'us' AS country_code, ";
+                    $sSQL .= $this->langAddressSql('-1');
+                    $sSQL .= "     null AS placename, ";
+                    $sSQL .= "     null AS ref, ";
+                    if ($this->bExtraTags) $sSQL .= "null AS extra, ";
+                    if ($this->bNameDetails) $sSQL .= "null AS names, ";
+                    $sSQL .= "     ST_X(centroid) AS lon, ";
+                    $sSQL .= "     ST_Y(centroid) AS lat, ";
+                    $sSQL .= "     -1.10 AS importance, ";
+                    $sSQL .= $this->addressImportanceSql(
+                        'centroid',
+                        'location_property_aux.parent_place_id'
+                    );
+                    $sSQL .= "     null AS extra_place ";
+                    $sSQL .= "  FROM location_property_aux ";
+                    $sSQL .= "  WHERE place_id in ($sPlaceIDs) ";
+
+                    $aSubSelects[] = $sSQL;
+                }
             }
         }
 
-        if ($this->bNameDetails) {
-            if ($aPlace['names']) {
-                $aPlace['sNameDetails'] = json_decode($aPlace['names']);
-            } else {
-                $aPlace['sNameDetails'] = (object) array();
-            }
+        if (CONST_Debug) var_dump($aSubSelects);
+
+        if (!sizeof($aSubSelects)) {
+            return array();
         }
 
+        $aPlaces = chksql(
+            $this->oDB->getAll(join(' UNION ', $aSubSelects)),
+            "Could not lookup place"
+        );
+
         $aClassType = getClassTypes();
-        $sAddressType = '';
-        $sClassType = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
-        if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel'])) {
-            $sAddressType = $aClassType[$aClassType]['simplelabel'];
-        } else {
-            $sClassType = $aPlace['class'].':'.$aPlace['type'];
-            if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
-                $sAddressType = $aClassType[$sClassType]['simplelabel'];
-            else $sAddressType = $aPlace['class'];
+        foreach ($aPlaces as &$aPlace) {
+            if ($this->bAddressDetails) {
+                // to get addressdetails for tiger data, the housenumber is needed
+                $aPlace['aAddress'] = $this->getAddressNames(
+                    $aPlace['place_id'],
+                    $aPlace['housenumber']
+                );
+            }
+
+            if ($this->bExtraTags) {
+                if ($aPlace['extra']) {
+                    $aPlace['sExtraTags'] = json_decode($aPlace['extra']);
+                } else {
+                    $aPlace['sExtraTags'] = (object) array();
+                }
+            }
+
+            if ($this->bNameDetails) {
+                if ($aPlace['names']) {
+                    $aPlace['sNameDetails'] = json_decode($aPlace['names']);
+                } else {
+                    $aPlace['sNameDetails'] = (object) array();
+                }
+            }
+
+            $sAddressType = '';
+            $sClassType = $aPlace['class'].':'.$aPlace['type'].':'.$aPlace['admin_level'];
+            if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel'])) {
+                $sAddressType = $aClassType[$aClassType]['simplelabel'];
+            } else {
+                $sClassType = $aPlace['class'].':'.$aPlace['type'];
+                if (isset($aClassType[$sClassType]) && isset($aClassType[$sClassType]['simplelabel']))
+                    $sAddressType = $aClassType[$sClassType]['simplelabel'];
+                else $sAddressType = $aPlace['class'];
+            }
+
+            $aPlace['addresstype'] = $sAddressType;
         }
 
-        $aPlace['addresstype'] = $sAddressType;
+        if (CONST_Debug) var_dump($aPlaces);
 
-        return $aPlace;
+        return $aPlaces;
     }
 
-    public function getAddressDetails($iPlaceID, $bAll = false, $housenumber = -1)
+    private function getAddressDetails($iPlaceID, $bAll, $sHousenumber)
     {
-        $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]";
-
-        $sSQL = "select *,get_name_by_language(name,$sLanguagePrefArraySQL) as localname from get_addressdata(".$iPlaceID.",".$housenumber.")";
-        if (!$bAll) $sSQL .= " WHERE isaddress OR type = 'country_code'";
-        $sSQL .= " order by rank_address desc,isaddress desc";
+        $sSQL = 'SELECT *,';
+        $sSQL .= '  get_name_by_language(name,'.$this->aLangPrefOrderSql.') as localname';
+        $sSQL .= ' FROM get_addressdata('.$iPlaceID.','.$sHousenumber.')';
+        if (!$bAll) {
+            $sSQL .= " WHERE isaddress OR type = 'country_code'";
+        }
+        $sSQL .= ' ORDER BY rank_address desc,isaddress DESC';
 
         return chksql($this->oDB->getAll($sSQL));
     }
 
-    public function getAddressNames($iPlaceID, $housenumber = -1)
+    public function getAddressNames($iPlaceID, $sHousenumber = null)
     {
-        $aAddressLines = $this->getAddressDetails($iPlaceID, false, $housenumber);
+        $aAddressLines = $this->getAddressDetails(
+            $iPlaceID,
+            false,
+            $sHousenumber === null ? -1 : $sHousenumber
+        );
 
         $aAddress = array();
         $aFallback = array();
diff --git a/lib/Result.php b/lib/Result.php
new file mode 100644 (file)
index 0000000..30c5985
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+
+namespace Nominatim;
+
+/**
+ * A single result of a search operation or a reverse lookup.
+ *
+ * This object only contains the id of the result. It does not yet
+ * have any details needed to format the output document.
+ */
+class Result
+{
+    const TABLE_PLACEX = 0;
+    const TABLE_POSTCODE = 1;
+    const TABLE_OSMLINE = 2;
+    const TABLE_AUX = 3;
+    const TABLE_TIGER = 4;
+
+    /// Database table that contains the result.
+    public $iTable;
+    /// Id of the result.
+    public $iId;
+    /// House number (only for interpolation results).
+    public $iHouseNumber = -1;
+    /// Number of exact matches in address (address searches only).
+    public $iExactMatches = 0;
+    /// Subranking within the results (the higher the worse).
+    public $iResultRank = 0;
+
+
+    public function __construct($sId, $iTable = Result::TABLE_PLACEX)
+    {
+        $this->iTable = $iTable;
+        $this->iId = (int) $sId;
+    }
+
+    public static function joinIdsByTable($aResults, $iTable)
+    {
+        return join(',', array_keys(array_filter(
+            $aResults,
+            function ($aValue) use ($iTable) {
+                return $aValue->iTable == $iTable;
+            }
+        )));
+    }
+    public static function sqlHouseNumberTable($aResults, $iTable)
+    {
+        $sHousenumbers = '';
+        $sSep = '';
+        foreach ($aResults as $oResult) {
+            if ($oResult->iTable == $iTable) {
+                $sHousenumbers .= $sSep.'('.$oResult->iId.',';
+                $sHousenumbers .= $oResult->iHouseNumber.')';
+                $sSep = ',';
+            }
+        }
+
+        return $sHousenumbers;
+    }
+}
index 9b43a3e38cdd8d2aabd8c5391b0aeee90898631b..31ebe7174fa66e0ab76730c7d81fd2bb779c48bd 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace Nominatim;
 
+require_once(CONST_BasePath.'/lib/Result.php');
+
 class ReverseGeocode
 {
     protected $oDB;
@@ -53,12 +55,13 @@ class ReverseGeocode
     protected function lookupInterpolation($sPointSQL, $fSearchDiam)
     {
         $sSQL = 'SELECT place_id, parent_place_id, 30 as rank_search,';
-        $sSQL .= ' ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction';
-        $sSQL .= ' , ST_Distance(linegeo,'.$sPointSQL.') as distance';
+        $sSQL .= '  ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction,';
+        $sSQL .= '  startnumber, endnumber, interpolationtype,';
+        $sSQL .= '  ST_Distance(linegeo,'.$sPointSQL.') as distance';
         $sSQL .= ' FROM location_property_osmline';
         $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
         $sSQL .= ' and indexed_status = 0 and startnumber is not NULL ';
-        $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
+        $sSQL .= ' ORDER BY distance ASC limit 1';
 
         return chksql(
             $this->oDB->getRow($sSQL),
@@ -74,24 +77,17 @@ class ReverseGeocode
         );
     }
 
-    /* lookup()
-     * returns { place_id =>, type => '(osm|tiger)' }
-     * fails if no place was found
-     */
-
-
     public function lookupPoint($sPointSQL, $bDoInterpolation = true)
     {
         $iMaxRank = $this->iMaxRank;
 
         // Find the nearest point
         $fSearchDiam = 0.0004;
-        $iPlaceID = null;
+        $oResult = null;
+        $aPlace = null;
         $fMaxAreaDistance = 1;
-        $bIsInUnitedStates = false;
-        $bPlaceIsTiger = false;
-        $bPlaceIsLine = false;
-        while (!$iPlaceID && $fSearchDiam < $fMaxAreaDistance) {
+        $bIsTigerStreet = false;
+        while ($oResult === null && $fSearchDiam < $fMaxAreaDistance) {
             $fSearchDiam = $fSearchDiam * 2;
 
             // If we have to expand the search area by a large amount then we need a larger feature
@@ -106,16 +102,14 @@ class ReverseGeocode
             if ($fSearchDiam > 0.001 && $iMaxRank > 26) {
                 // try with interpolations before continuing
                 if ($bDoInterpolation) {
-                    // no house found, try with interpolations
-                    $aPlaceLine = $this->lookupInterpolation($sPointSQL, $fSearchDiam/2);
-
-                    if ($aPlaceLine) {
-                        // interpolation is closer to point than placex house
-                        $bPlaceIsLine = true;
-                        $aPlace = $aPlaceLine;
-                        $iPlaceID = $aPlaceLine['place_id'];
-                        $iParentPlaceID = $aPlaceLine['parent_place_id']; // the street
-                        $fFraction = $aPlaceLine['fraction'];
+                    $aHouse = $this->lookupInterpolation($sPointSQL, $fSearchDiam/2);
+
+                    if ($aHouse) {
+                        $oResult = new Result($aHouse['place_id'], Result::TABLE_OSMLINE);
+                        $oResult->iHouseNumber = closestHouseNumber($aHouse);
+
+                        $aPlace = $aHouse;
+                        $iParentPlaceID = $aHouse['parent_place_id']; // the street
                         $iMaxRank = 30;
 
                         break;
@@ -125,7 +119,8 @@ class ReverseGeocode
                 $iMaxRank = 26;
             }
 
-            $sSQL = 'select place_id,parent_place_id,rank_search,country_code';
+            $sSQL = 'select place_id,parent_place_id,rank_search,country_code,';
+            $sSQL .= '  ST_distance('.$sPointSQL.', geometry) as distance';
             $sSQL .= ' FROM placex';
             $sSQL .= ' WHERE ST_DWithin('.$sPointSQL.', geometry, '.$fSearchDiam.')';
             $sSQL .= ' and rank_search != 28 and rank_search >= '.$iMaxRank;
@@ -134,60 +129,52 @@ class ReverseGeocode
             $sSQL .= ' and indexed_status = 0 ';
             $sSQL .= ' and (ST_GeometryType(geometry) not in (\'ST_Polygon\',\'ST_MultiPolygon\') ';
             $sSQL .= ' OR ST_DWithin('.$sPointSQL.', centroid, '.$fSearchDiam.'))';
-            $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', geometry) ASC limit 1';
+            $sSQL .= ' ORDER BY distance ASC limit 1';
             if (CONST_Debug) var_dump($sSQL);
             $aPlace = chksql(
                 $this->oDB->getRow($sSQL),
                 "Could not determine closest place."
             );
-            $iPlaceID = $aPlace['place_id'];
-            $iParentPlaceID = $aPlace['parent_place_id'];
-            $bIsInUnitedStates = ($aPlace['country_code'] == 'us');
-        }
-
-        // If a house was found make sure there isn't an interpolation line
-        // that is closer
-        if ($bDoInterpolation && !$bPlaceIsLine && $aPlace && $aPlace['rank_search'] == 30) {
-            // get the distance of the house to the search point
-            $sSQL = 'SELECT ST_distance('.$sPointSQL.', house.geometry)';
-            $sSQL .= ' FROM placex as house WHERE house.place_id='.$iPlaceID;
-
-            $fDistancePlacex = chksql(
-                $this->oDB->getOne($sSQL),
-                "Could not determine distance between searched point and placex house."
-            );
-
-            // look for an interpolation that is closer
-            $aPlaceLine = $this->lookupInterpolation($sPointSQL, $fDistancePlacex);
-
-            if ($aPlaceLine && (float) $aPlaceLine['distance'] < (float) $fDistancePlacex) {
-                // interpolation is closer to point than placex house
-                $bPlaceIsLine = true;
-                $aPlace = $aPlaceLine;
-                $iPlaceID = $aPlaceLine['place_id'];
-                $iParentPlaceID = $aPlaceLine['parent_place_id']; // the street
-                $fFraction = $aPlaceLine['fraction'];
+            if ($aPlace) {
+                $oResult = new Result($aPlace['place_id']);
+                $iParentPlaceID = $aPlace['parent_place_id'];
+                if ($bDoInterpolation) {
+                    if ($aPlace['rank_search'] == 26 || $aPlace['rank_search'] == 27) {
+                        $bIsTigerStreet = ($aPlace['country_code'] == 'us');
+                    } elseif ($aPlace['rank_search'] == 30) {
+                        // If a house was found, make sure there isn't an
+                        // interpolation line that is closer.
+                        $aHouse = $this->lookupInterpolation(
+                            $sPointSQL,
+                            $aPlace['distance']
+                        );
+                        if ($aHouse && $aPlace['distance'] < $aHouse['distance']) {
+                            $oResult = new Result(
+                                $aHouse['place_id'],
+                                Result::TABLE_OSMLINE
+                            );
+                            $oResult->iHouseNumber = closestHouseNumber($aHouse);
+
+                            $aPlace = $aHouse;
+                            $iParentPlaceID = $aHouse['parent_place_id'];
+                        }
+                    }
+                }
             }
         }
 
-        // Only street found? If it's in the US we can check TIGER data for nearest housenumber
-        if (CONST_Use_US_Tiger_Data && $bDoInterpolation && $bIsInUnitedStates && $this->iMaxRank >= 28 && $iPlaceID && ($aPlace['rank_search'] == 26 || $aPlace['rank_search'] == 27 )) {
-            $fSearchDiam = 0.001;
-            $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search, ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction';
-            //if (CONST_Debug) { $sSQL .= ', housenumber, ST_distance('.$sPointSQL.', centroid) as distance, st_y(centroid) as lat, st_x(centroid) as lon'; }
-            $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$iPlaceID;
-            $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';  //no centroid anymore in Tiger data, now we have lines
-            $sSQL .= ' ORDER BY ST_distance('.$sPointSQL.', linegeo) ASC limit 1';
-
-            if (CONST_Debug) {
-                $sSQL = preg_replace('/limit 1/', 'limit 100', $sSQL);
-                var_dump($sSQL);
-
-                $aAllHouses = chksql($this->oDB->getAll($sSQL));
-                foreach ($aAllHouses as $i) {
-                    echo $i['housenumber'] . ' | ' . $i['distance'] * 1000 . ' | ' . $i['lat'] . ' | ' . $i['lon']. ' | '. "<br>\n";
-                }
-            }
+        // Only street found? In the US we can check TIGER data for nearest housenumber
+        if (CONST_Use_US_Tiger_Data && $bIsTigerStreet && $this->iMaxRank >= 28) {
+            $fSearchDiam = $aPlace['rank_search'] > 28 ? $aPlace['distance'] : 0.001;
+            $sSQL = 'SELECT place_id,parent_place_id,30 as rank_search,';
+            $sSQL .= 'ST_LineLocatePoint(linegeo,'.$sPointSQL.') as fraction,';
+            $sSQL .= 'ST_distance('.$sPointSQL.', linegeo) as distance,';
+            $sSQL .= 'startnumber,endnumber,interpolationtype';
+            $sSQL .= ' FROM location_property_tiger WHERE parent_place_id = '.$oResult->iId;
+            $sSQL .= ' AND ST_DWithin('.$sPointSQL.', linegeo, '.$fSearchDiam.')';
+            $sSQL .= ' ORDER BY distance ASC limit 1';
+
+            if (CONST_Debug) var_dump($sSQL);
 
             $aPlaceTiger = chksql(
                 $this->oDB->getRow($sSQL),
@@ -195,21 +182,20 @@ class ReverseGeocode
             );
             if ($aPlaceTiger) {
                 if (CONST_Debug) var_dump('found Tiger housenumber', $aPlaceTiger);
-                $bPlaceIsTiger = true;
                 $aPlace = $aPlaceTiger;
-                $iPlaceID = $aPlaceTiger['place_id'];
-                $iParentPlaceID = $aPlaceTiger['parent_place_id']; // the street
-                $fFraction = $aPlaceTiger['fraction'];
+                $oResult = new Result($aPlace['place_id'], Result::TABLE_TIGER);
+                $oResult->iHouseNumber = closestHouseNumber($aPlaceTiger);
+                $iParentPlaceID = $aPlace['parent_place_id'];
                 $iMaxRank = 30;
             }
         }
 
         // The point we found might be too small - use the address to find what it is a child of
-        if ($iPlaceID && $iMaxRank < 28) {
-            if (($aPlace['rank_search'] > 28 || $bPlaceIsTiger || $bPlaceIsLine) && $iParentPlaceID) {
+        if ($oResult !== null && $iMaxRank < 28) {
+            if ($aPlace['rank_search'] > 28 && $iParentPlaceID) {
                 $iPlaceID = $iParentPlaceID;
-                $bPlaceIsLine = false;
-                $bPlaceIsTiger = false;
+            } else {
+                $iPlaceID = $oResult->iId;
             }
             $sSQL  = 'select address_place_id';
             $sSQL .= ' FROM place_addressline';
@@ -217,14 +203,11 @@ class ReverseGeocode
             $sSQL .= " ORDER BY abs(cached_rank_address - $iMaxRank) asc,cached_rank_address desc,isaddress desc,distance desc";
             $sSQL .= ' LIMIT 1';
             $iPlaceID = chksql($this->oDB->getOne($sSQL), "Could not get parent for place.");
-            if (!$iPlaceID) {
-                $iPlaceID = $aPlace['place_id'];
+            if ($iPlaceID) {
+                $oResult = new Result($iPlaceID);
             }
         }
-        return array(
-                'place_id' => $iPlaceID,
-                'type' => $bPlaceIsTiger ? 'tiger' : ($bPlaceIsLine ? 'interpolation' : 'osm'),
-                'fraction' => ($bPlaceIsTiger || $bPlaceIsLine) ? $fFraction : -1
-               );
+
+        return $oResult;
     }
 }
index eba5f6a93262e4cf0f50d8a262832158935c5897..56d04478c2cd540564534e7859cd95db8d446266 100644 (file)
@@ -4,6 +4,7 @@ namespace Nominatim;
 
 require_once(CONST_BasePath.'/lib/SpecialSearchOperator.php');
 require_once(CONST_BasePath.'/lib/SearchContext.php');
+require_once(CONST_BasePath.'/lib/Result.php');
 
 /**
  * Description of a single interpretation of a search query.
@@ -405,7 +406,6 @@ class SearchDescription
      * @param object  $oDB                  Database connection to use.
      * @param mixed[] $aWordFrequencyScores Number of times tokens appears
      *                                      overall in a planet database.
-     * @param mixed[] $aExactMatchCache     Saves number of exact matches.
      * @param integer $iMinRank             Minimum address rank to restrict
      *                                      search to.
      * @param integer $iMaxRank             Maximum address rank to restrict
@@ -416,9 +416,9 @@ class SearchDescription
      *                 matching place IDs and houseNumber the houseNumber
      *                 if appicable or -1 if not.
      */
-    public function query(&$oDB, &$aWordFrequencyScores, &$aExactMatchCache, $iMinRank, $iMaxRank, $iLimit)
+    public function query(&$oDB, &$aWordFrequencyScores, $iMinRank, $iMaxRank, $iLimit)
     {
-        $aPlaceIDs = array();
+        $aResults = array();
         $iHousenumber = -1;
 
         if ($this->sCountryCode
@@ -429,21 +429,21 @@ class SearchDescription
         ) {
             // Just looking for a country - look it up
             if (4 >= $iMinRank && 4 <= $iMaxRank) {
-                $aPlaceIDs = $this->queryCountry($oDB);
+                $aResults = $this->queryCountry($oDB);
             }
         } elseif (!sizeof($this->aName) && !sizeof($this->aAddress)) {
             // Neither name nor address? Then we must be
             // looking for a POI in a geographic area.
             if ($this->oContext->isBoundedSearch()) {
-                $aPlaceIDs = $this->queryNearbyPoi($oDB, $iLimit);
+                $aResults = $this->queryNearbyPoi($oDB, $iLimit);
             }
         } elseif ($this->iOperator == Operator::POSTCODE) {
             // looking for postcode
-            $aPlaceIDs = $this->queryPostcode($oDB, $iLimit);
+            $aResults = $this->queryPostcode($oDB, $iLimit);
         } else {
             // Ordinary search:
             // First search for places according to name and address.
-            $aNamedPlaceIDs = $this->queryNamedPlace(
+            $aResults = $this->queryNamedPlace(
                 $oDB,
                 $aWordFrequencyScores,
                 $iMinRank,
@@ -451,52 +451,50 @@ class SearchDescription
                 $iLimit
             );
 
-            if (sizeof($aNamedPlaceIDs)) {
-                foreach ($aNamedPlaceIDs as $aRow) {
-                    $aPlaceIDs[] = $aRow['place_id'];
-                    $aExactMatchCache[$aRow['place_id']] = $aRow['exactmatch'];
-                }
-            }
-
             //now search for housenumber, if housenumber provided
-            if ($this->sHouseNumber && sizeof($aPlaceIDs)) {
-                $aResult = $this->queryHouseNumber($oDB, $aPlaceIDs, $iLimit);
-
-                if (sizeof($aResult)) {
-                    $iHousenumber = $aResult['iHouseNumber'];
-                    $aPlaceIDs = $aResult['aPlaceIDs'];
-                } elseif (!$this->looksLikeFullAddress()) {
-                    $aPlaceIDs = array();
+            if ($this->sHouseNumber && sizeof($aResults)) {
+                $aNamedPlaceIDs = $aResults;
+                $aResults = $this->queryHouseNumber($oDB, $aNamedPlaceIDs, $iLimit);
+
+                if (!sizeof($aResults) && $this->looksLikeFullAddress()) {
+                    $aResults = $aNamedPlaceIDs;
                 }
             }
 
             // finally get POIs if requested
-            if ($this->sClass && sizeof($aPlaceIDs)) {
-                $aPlaceIDs = $this->queryPoiByOperator($oDB, $aPlaceIDs, $iLimit);
+            if ($this->sClass && sizeof($aResults)) {
+                $aResults = $this->queryPoiByOperator($oDB, $aResults, $iLimit);
             }
         }
 
         if (CONST_Debug) {
             echo "<br><b>Place IDs:</b> ";
-            var_Dump($aPlaceIDs);
+            var_dump(array_keys($aResults));
         }
 
-        if (sizeof($aPlaceIDs) && $this->sPostcode) {
-            $sSQL = 'SELECT place_id FROM placex';
-            $sSQL .= ' WHERE place_id in ('.join(',', $aPlaceIDs).')';
-            $sSQL .= " AND postcode = '".$this->sPostcode."'";
-            if (CONST_Debug) var_dump($sSQL);
-            $aFilteredPlaceIDs = chksql($oDB->getCol($sSQL));
-            if ($aFilteredPlaceIDs) {
-                $aPlaceIDs = $aFilteredPlaceIDs;
-                if (CONST_Debug) {
-                    echo "<br><b>Place IDs after postcode filtering:</b> ";
-                    var_Dump($aPlaceIDs);
+        if (sizeof($aResults) && $this->sPostcode) {
+            $sPlaceIds = Result::joinIdsByTable($aResults, Result::TABLE_PLACEX);
+            if ($sPlaceIds) {
+                $sSQL = 'SELECT place_id FROM placex';
+                $sSQL .= ' WHERE place_id in ('.$sPlaceIds.')';
+                $sSQL .= " AND postcode = '".$this->sPostcode."'";
+                if (CONST_Debug) var_dump($sSQL);
+                $aFilteredPlaceIDs = chksql($oDB->getCol($sSQL));
+                if ($aFilteredPlaceIDs) {
+                    $aNewResults = array();
+                    foreach ($aFilteredPlaceIDs as $iPlaceId) {
+                        $aNewResults[$iPlaceId] = $aResults[$iPLaceId];
+                    }
+                    $aResults = $aNewResults;
+                    if (CONST_Debug) {
+                        echo "<br><b>Place IDs after postcode filtering:</b> ";
+                        var_dump(array_keys($aResults));
+                    }
                 }
             }
         }
 
-        return array('IDs' => $aPlaceIDs, 'houseNumber' => $iHousenumber);
+        return $aResults;
     }
 
 
@@ -512,7 +510,12 @@ class SearchDescription
 
         if (CONST_Debug) var_dump($sSQL);
 
-        return chksql($oDB->getCol($sSQL));
+        $aResults = array();
+        foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+            $aResults[$iPlaceId] = new Result($iPlaceId);
+        }
+
+        return $aResults;
     }
 
     private function queryNearbyPoi(&$oDB, $iLimit)
@@ -521,6 +524,7 @@ class SearchDescription
             return array();
         }
 
+        $aDBResults = array();
         $sPoiTable = $this->poiTable();
 
         $sSQL = 'SELECT count(*) FROM pg_tables WHERE tablename = \''.$sPoiTable."'";
@@ -546,7 +550,7 @@ class SearchDescription
             }
             $sSQL .= " limit $iLimit";
             if (CONST_Debug) var_dump($sSQL);
-            return chksql($oDB->getCol($sSQL));
+            $aDBResults = chksql($oDB->getCol($sSQL));
         }
 
         if ($this->oContext->hasNearPoint()) {
@@ -560,10 +564,15 @@ class SearchDescription
             $sSQL .= ' ORDER BY '.$this->oContext->distanceSQL('centroid')." ASC";
             $sSQL .= " LIMIT $iLimit";
             if (CONST_Debug) var_dump($sSQL);
-            return chksql($oDB->getCol($sSQL));
+            $aDBResults = chksql($oDB->getCol($sSQL));
         }
 
-        return array();
+        $aResults = array();
+        foreach ($aDBResults as $iPlaceId) {
+            $aResults[$iPlaceId] = new Result($iPlaceId);
+        }
+
+        return $aResults;
     }
 
     private function queryPostcode(&$oDB, $iLimit)
@@ -586,7 +595,12 @@ class SearchDescription
 
         if (CONST_Debug) var_dump($sSQL);
 
-        return chksql($oDB->getCol($sSQL));
+        $aResults = array();
+        foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+            $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_POSTCODE);
+        }
+
+        return $aResults;
     }
 
     private function queryNamedPlace(&$oDB, $aWordFrequencyScores, $iMinAddressRank, $iMaxAddressRank, $iLimit)
@@ -701,6 +715,8 @@ class SearchDescription
             $iLimit = 20;
         }
 
+        $aResults = array();
+
         if (sizeof($aTerms)) {
             $sSQL = 'SELECT place_id,'.$sExactMatchSQL;
             $sSQL .= ' FROM search_name';
@@ -710,18 +726,29 @@ class SearchDescription
 
             if (CONST_Debug) var_dump($sSQL);
 
-            return chksql(
+            $aDBResults = chksql(
                 $oDB->getAll($sSQL),
                 "Could not get places for search terms."
             );
+
+            foreach ($aDBResults as $aResult) {
+                $oResult = new Result($aResult['place_id']);
+                $oResult->iExactMatches = $aResult['exactmatch'];
+                $aResults[$aResult['place_id']] = $oResult;
+            }
         }
 
-        return array();
+        return $aResults;
     }
 
     private function queryHouseNumber(&$oDB, $aRoadPlaceIDs, $iLimit)
     {
-        $sPlaceIDs = join(',', $aRoadPlaceIDs);
+        $aResults = array();
+        $sPlaceIDs = Result::joinIdsByTable($aRoadPlaceIDs, Result::TABLE_PLACEX);
+
+        if (!$sPlaceIDs) {
+            return $aResults;
+        }
 
         $sHouseNumberRegex = '\\\\m'.$this->sHouseNumber.'\\\\M';
         $sSQL = 'SELECT place_id FROM placex ';
@@ -732,15 +759,14 @@ class SearchDescription
 
         if (CONST_Debug) var_dump($sSQL);
 
-        $aPlaceIDs = chksql($oDB->getCol($sSQL));
-
-        if (sizeof($aPlaceIDs)) {
-            return array('aPlaceIDs' => $aPlaceIDs, 'iHouseNumber' => -1);
+        // XXX should inherit the exactMatches from its parent
+        foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+            $aResults[$iPlaceId] = new Result($iPlaceId);
         }
 
         $bIsIntHouseNumber= (bool) preg_match('/[0-9]+/', $this->sHouseNumber);
         $iHousenumber = intval($this->sHouseNumber);
-        if ($bIsIntHouseNumber) {
+        if ($bIsIntHouseNumber && !sizeof($aResults)) {
             // if nothing found, search in the interpolation line table
             $sSQL = 'SELECT distinct place_id FROM location_property_osmline';
             $sSQL .= ' WHERE startnumber is not NULL';
@@ -761,15 +787,15 @@ class SearchDescription
 
             if (CONST_Debug) var_dump($sSQL);
 
-            $aPlaceIDs = chksql($oDB->getCol($sSQL, 0));
-
-            if (sizeof($aPlaceIDs)) {
-                return array('aPlaceIDs' => $aPlaceIDs, 'iHouseNumber' => $iHousenumber);
+            foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                $oResult = new Result($iPlaceId, Result::TABLE_OSMLINE);
+                $oResult->iHouseNumber = $iHousenumber;
+                $aResults[$iPlaceId] = $oResult;
             }
         }
 
         // If nothing found try the aux fallback table
-        if (CONST_Use_Aux_Location_data) {
+        if (CONST_Use_Aux_Location_data && !sizeof($aResults)) {
             $sSQL = 'SELECT place_id FROM location_property_aux';
             $sSQL .= ' WHERE parent_place_id in ('.$sPlaceIDs.')';
             $sSQL .= " AND housenumber = '".$this->sHouseNumber."'";
@@ -778,16 +804,14 @@ class SearchDescription
 
             if (CONST_Debug) var_dump($sSQL);
 
-            $aPlaceIDs = chksql($oDB->getCol($sSQL));
-
-            if (sizeof($aPlaceIDs)) {
-                return array('aPlaceIDs' => $aPlaceIDs, 'iHouseNumber' => -1);
+            foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                $aResults[$iPlaceId] = new Result($iPlaceId, Result::TABLE_AUX);
             }
         }
 
         // If nothing found then search in Tiger data (location_property_tiger)
-        if (CONST_Use_US_Tiger_Data && $bIsIntHouseNumber) {
-            $sSQL = 'SELECT distinct place_id FROM location_property_tiger';
+        if (CONST_Use_US_Tiger_Data && $bIsIntHouseNumber && !sizeof($aResults)) {
+            $sSQL = 'SELECT place_id FROM location_property_tiger';
             $sSQL .= ' WHERE parent_place_id in ('.$sPlaceIDs.') and (';
             if ($iHousenumber % 2 == 0) {
                 $sSQL .= "interpolationtype='even'";
@@ -802,21 +826,25 @@ class SearchDescription
 
             if (CONST_Debug) var_dump($sSQL);
 
-            $aPlaceIDs = chksql($oDB->getCol($sSQL, 0));
-
-            if (sizeof($aPlaceIDs)) {
-                return array('aPlaceIDs' => $aPlaceIDs, 'iHouseNumber' => $iHousenumber);
+            foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                $oResult = new Result($iPlaceId, Result::TABLE_TIGER);
+                $oResult->iHouseNumber = $iHousenumber;
+                $aResults[$iPlaceId] = $oResult;
             }
         }
 
-        return array();
+        return $aResults;
     }
 
 
     private function queryPoiByOperator(&$oDB, $aParentIDs, $iLimit)
     {
-        $sPlaceIDs = join(',', $aParentIDs);
-        $aClassPlaceIDs = array();
+        $aResults = array();
+        $sPlaceIDs = Result::joinIdsByTable($aParentIDs, Result::TABLE_PLACEX);
+
+        if (!$sPlaceIDs) {
+            return $aResults;
+        }
 
         if ($this->iOperator == Operator::TYPE || $this->iOperator == Operator::NAME) {
             // If they were searching for a named class (i.e. 'Kings Head pub')
@@ -832,7 +860,9 @@ class SearchDescription
 
             if (CONST_Debug) var_dump($sSQL);
 
-            $aClassPlaceIDs = chksql($oDB->getCol($sSQL));
+            foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                $aResults[$iPlaceId] = new Result($iPlaceId);
+            }
         }
 
         // NEAR and IN are handled the same
@@ -912,7 +942,9 @@ class SearchDescription
 
                     if (CONST_Debug) var_dump($sSQL);
 
-                    $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($oDB->getCol($sSQL)));
+                    foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                        $aResults[$iPlaceId] = new Result($iPlaceId);
+                    }
                 } else {
                     if ($this->oContext->hasNearPoint()) {
                         $fRange = $this->oContext->nearRadius();
@@ -942,12 +974,14 @@ class SearchDescription
 
                     if (CONST_Debug) var_dump($sSQL);
 
-                    $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($oDB->getCol($sSQL)));
+                    foreach (chksql($oDB->getCol($sSQL)) as $iPlaceId) {
+                        $aResults[$iPlaceId] = new Result($iPlaceId);
+                    }
                 }
             }
         }
 
-        return $aClassPlaceIDs;
+        return $aResults;
     }
 
     private function poiTable()
index 76775d6c8f6febac91f8b9aca990b3f446a75242..afcc3c7ffbbcaa22bfb255a947e5b1191fe29118 100644 (file)
@@ -615,3 +615,23 @@ function createPointsAroundCenter($fLon, $fLat, $fRadius)
     }
     return $aPolyPoints;
 }
+
+function closestHouseNumber($aRow)
+{
+    $fHouse = $aRow['startnumber']
+                + ($aRow['endnumber'] - $aRow['startnumber']) * $aRow['fraction'];
+
+    switch ($aRow['interpolationtype']) {
+        case 'odd':
+            $iHn = (int)($fHouse/2) * 2 + 1;
+            break;
+        case 'even':
+            $iHn = (int)(round($fHouse/2)) * 2;
+            break;
+        default:
+            $iHn = (int)(round($fHouse));
+            break;
+    }
+
+    return max(min($aRow['endnumber'], $iHn), $aRow['startnumber']);
+}
index ea709b8094d7e0845a6343232f95ccfce512e656..2549495261fa8f578f898adac3a6231df2520267 100644 (file)
@@ -102,7 +102,7 @@ Feature: Simple Tests
     Scenario: Empty XML search with viewbox
         When sending xml search query "xnznxvcx"
           | viewbox |
-          | 12,45.13,77,33 |
+          | 12,33,77,45.13 |
         Then result header contains
           | attr        | value |
           | querystring | xnznxvcx |
@@ -117,12 +117,12 @@ Feature: Simple Tests
           | attr        | value |
           | querystring | xnznxvcx |
           | polygon     | false |
-          | viewbox     | 12,45,77,34.13 |
+          | viewbox     | 12,34.13,77,45 |
 
     Scenario: Empty XML search with viewboxlbrt and viewbox
         When sending xml search query "pub"
           | viewbox        | viewboxblrt |
-          | 12,45.13,77,33 | 1,2,3,4 |
+          | 12,33,77,45.13 | 1,2,3,4 |
         Then result header contains
           | attr        | value |
           | querystring | pub |
index 667686d145bad20b49fc8f409f6b05b4f2849f58..578d4c37306d0ecebb545ab3518ac8e8d5017212 100755 (executable)
@@ -24,10 +24,7 @@ $aSearchResults = array();
 $aCleanedQueryParts = array();
 
 $oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->setLanguagePreference($aLangPrefOrder);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-$oPlaceLookup->setIncludeExtraTags($oParams->getBool('extratags', false));
-$oPlaceLookup->setIncludeNameDetails($oParams->getBool('namedetails', false));
+$oPlaceLookup->loadParamArray($oParams);
 
 $aOsmIds = explode(',', $oParams->getString('osm_ids', ''));
 
index 026fa85b434b4dcabc7af6dc2ecf6db1ea3650ba..85ca19816b196662b7864374c1d8b5863e264fdf 100755 (executable)
@@ -11,23 +11,6 @@ ini_set('memory_limit', '200M');
 
 $oParams = new Nominatim\ParameterParser();
 
-$bAsGeoJSON = $oParams->getBool('polygon_geojson');
-$bAsKML = $oParams->getBool('polygon_kml');
-$bAsSVG = $oParams->getBool('polygon_svg');
-$bAsText = $oParams->getBool('polygon_text');
-
-$iWantedTypes = ($bAsGeoJSON?1:0) + ($bAsKML?1:0) + ($bAsSVG?1:0) + ($bAsText?1:0);
-if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
-    if (CONST_PolygonOutput_MaximumTypes) {
-        userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
-    } else {
-        userError("Polygon output is disabled");
-    }
-}
-
-// Polygon simplification threshold (optional)
-$fThreshold = $oParams->getFloat('polygon_threshold', 0.0);
-
 // Format for output
 $sOutputFormat = $oParams->getSet('format', array('html', 'xml', 'json', 'jsonv2'), 'xml');
 
@@ -38,44 +21,35 @@ $oDB =& getDB();
 
 $hLog = logStart($oDB, 'reverse', $_SERVER['QUERY_STRING'], $aLangPrefOrder);
 
-
 $oPlaceLookup = new Nominatim\PlaceLookup($oDB);
-$oPlaceLookup->setLanguagePreference($aLangPrefOrder);
-$oPlaceLookup->setIncludeAddressDetails($oParams->getBool('addressdetails', true));
-$oPlaceLookup->setIncludeExtraTags($oParams->getBool('extratags', false));
-$oPlaceLookup->setIncludeNameDetails($oParams->getBool('namedetails', false));
+$oPlaceLookup->loadParamArray($oParams);
 
 $sOsmType = $oParams->getSet('osm_type', array('N', 'W', 'R'));
 $iOsmId = $oParams->getInt('osm_id', -1);
 $fLat = $oParams->getFloat('lat');
 $fLon = $oParams->getFloat('lon');
-$iZoom = $oParams->getInt('zoom');
+$iZoom = $oParams->getInt('zoom', 18);
+
 if ($sOsmType && $iOsmId > 0) {
     $aPlace = $oPlaceLookup->lookupOSMID($sOsmType, $iOsmId);
 } elseif ($fLat !== false && $fLon !== false) {
     $oReverseGeocode = new Nominatim\ReverseGeocode($oDB);
-    $oReverseGeocode->setZoom($iZoom !== false ? $iZoom : 18);
+    $oReverseGeocode->setZoom($iZoom);
 
-    $aLookup = $oReverseGeocode->lookup($fLat, $fLon);
-    if (CONST_Debug) var_dump($aLookup);
+    $oLookup = $oReverseGeocode->lookup($fLat, $fLon);
+    if (CONST_Debug) var_dump($oLookup);
 
-    $aPlace = $oPlaceLookup->lookup(
-        (int)$aLookup['place_id'],
-        $aLookup['type'],
-        $aLookup['fraction']
-    );
+    if ($oLookup) {
+        $aPlaces = $oPlaceLookup->lookup(array($oLookup->iId => $oLookup));
+        if (sizeof($aPlaces)) {
+            $aPlace = reset($aPlaces);
+        }
+    }
 } elseif ($sOutputFormat != 'html') {
     userError("Need coordinates or OSM object to lookup.");
 }
 
 if (isset($aPlace)) {
-    $oPlaceLookup->setIncludePolygonAsPoints(false);
-    $oPlaceLookup->setIncludePolygonAsText($bAsText);
-    $oPlaceLookup->setIncludePolygonAsGeoJSON($bAsGeoJSON);
-    $oPlaceLookup->setIncludePolygonAsKML($bAsKML);
-    $oPlaceLookup->setIncludePolygonAsSVG($bAsSVG);
-    $oPlaceLookup->setPolygonSimplificationThreshold($fThreshold);
-
     $fRadius = $fDiameter = getResultDiameter($aPlace);
     $aOutlineResult = $oPlaceLookup->getOutlines(
         $aPlace['place_id'],
index 4952465e68ce858789fde582f45c42f5b1341431..bf9695ab52a3a73673cb691332103ac0bf4c31ab 100755 (executable)
@@ -28,36 +28,8 @@ if (CONST_Search_ReversePlanForAll
 // Format for output
 $sOutputFormat = $oParams->getSet('format', array('html', 'xml', 'json', 'jsonv2'), 'html');
 
-// Show / use polygons
-if ($sOutputFormat == 'html') {
-    $oGeocode->setIncludePolygonAsGeoJSON($oParams->getBool('polygon_geojson'));
-    $bAsGeoJSON = false;
-} else {
-    $bAsPoints = $oParams->getBool('polygon');
-    $bAsGeoJSON = $oParams->getBool('polygon_geojson');
-    $bAsKML = $oParams->getBool('polygon_kml');
-    $bAsSVG = $oParams->getBool('polygon_svg');
-    $bAsText = $oParams->getBool('polygon_text');
-    $iWantedTypes = ($bAsGeoJSON?1:0) + ($bAsKML?1:0) + ($bAsSVG?1:0) + ($bAsText?1:0) + ($bAsPoints?1:0);
-    if ($iWantedTypes > CONST_PolygonOutput_MaximumTypes) {
-        if (CONST_PolygonOutput_MaximumTypes) {
-            userError("Select only ".CONST_PolygonOutput_MaximumTypes." polgyon output option");
-        } else {
-            userError("Polygon output is disabled");
-        }
-        exit;
-    }
-    $oGeocode->setIncludePolygonAsPoints($bAsPoints);
-    $oGeocode->setIncludePolygonAsText($bAsText);
-    $oGeocode->setIncludePolygonAsGeoJSON($bAsGeoJSON);
-    $oGeocode->setIncludePolygonAsKML($bAsKML);
-    $oGeocode->setIncludePolygonAsSVG($bAsSVG);
-}
-
-// Polygon simplification threshold (optional)
-$oGeocode->setPolygonSimplificationThreshold($oParams->getFloat('polygon_threshold', 0.0));
-
-$oGeocode->loadParamArray($oParams);
+$sForcedGeometry = ($sOutputFormat == 'html') ? "geojson" : null;
+$oGeocode->loadParamArray($oParams, $sForcedGeometry);
 
 if (CONST_Search_BatchMode && isset($_GET['batch'])) {
     $aBatch = json_decode($_GET['batch'], true);