X-Git-Url: https://git.openstreetmap.org./nominatim.git/blobdiff_plain/832547f192904a9ec92e173c27a91e0874fcc757..c2629bc08d081262a7fd1cbd9d52acb5f44bd8ec:/lib/Geocode.php diff --git a/lib/Geocode.php b/lib/Geocode.php index f228bf7f..f142731d 100644 --- a/lib/Geocode.php +++ b/lib/Geocode.php @@ -1,4 +1,8 @@ oDB =& $oDB; + $this->oNormalizer = \Transliterator::createFromRules(CONST_Term_Normalization_Rules); } - function setReverseInPlan($bReverse) + private function normTerm($sTerm) { - $this->bReverseInPlan = $bReverse; - } + if ($this->oNormalizer === null) { + return $sTerm; + } - function setLanguagePreference($aLangPref) - { - $this->aLangPrefOrder = $aLangPref; + return $this->oNormalizer->transliterate($sTerm); } - function getIncludeAddressDetails() + public function setReverseInPlan($bReverse) { - return $this->bIncludeAddressDetails; + $this->bReverseInPlan = $bReverse; } - function getIncludeExtraTags() + public function setLanguagePreference($aLangPref) { - return $this->bIncludeExtraTags; + $this->aLangPrefOrder = $aLangPref; } - function getIncludeNameDetails() + public function getMoreUrlParams() { - return $this->bIncludeNameDetails; + if ($this->aStructuredQuery) { + $aParams = $this->aStructuredQuery; + } else { + $aParams = array('q' => $this->sQuery); + } + + 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]; + } + + return $aParams; } - function setIncludePolygonAsPoints($b = true) + public function setIncludePolygonAsPoints($b = true) { $this->bIncludePolygonAsPoints = $b; } - function setIncludePolygonAsText($b = true) + public function setIncludePolygonAsText($b = true) { $this->bIncludePolygonAsText = $b; } - function setIncludePolygonAsGeoJSON($b = true) + public function setIncludePolygonAsGeoJSON($b = true) { $this->bIncludePolygonAsGeoJSON = $b; } - function setIncludePolygonAsKML($b = true) + public function setIncludePolygonAsKML($b = true) { $this->bIncludePolygonAsKML = $b; } - function setIncludePolygonAsSVG($b = true) + public function setIncludePolygonAsSVG($b = true) { $this->bIncludePolygonAsSVG = $b; } - function setPolygonSimplificationThreshold($f) + public function setPolygonSimplificationThreshold($f) { $this->fPolygonSimplificationThreshold = $f; } - function setLimit($iLimit = 10) + public function setLimit($iLimit = 10) { if ($iLimit > 50) $iLimit = 50; if ($iLimit < 1) $iLimit = 1; @@ -117,67 +159,65 @@ class Geocode $this->iLimit = $iLimit + min($iLimit, 10); } - function getExcludedPlaceIDs() - { - return $this->aExcludePlaceIDs; - } - - function getViewBoxString() + public function setFeatureType($sFeatureType) { - if (!$this->aViewBox) return null; - return $this->aViewBox[0].','.$this->aViewBox[3].','.$this->aViewBox[2].','.$this->aViewBox[1]; - } - - function setFeatureType($sFeatureType) - { - switch($sFeatureType) - { - case 'country': - $this->setRankRange(4, 4); - break; - case 'state': - $this->setRankRange(8, 8); - break; - case 'city': - $this->setRankRange(14, 16); - break; - case 'settlement': - $this->setRankRange(8, 20); - break; + switch ($sFeatureType) { + case 'country': + $this->setRankRange(4, 4); + break; + case 'state': + $this->setRankRange(8, 8); + break; + case 'city': + $this->setRankRange(14, 16); + break; + case 'settlement': + $this->setRankRange(8, 20); + break; } } - function setRankRange($iMin, $iMax) + public function setRankRange($iMin, $iMax) { $this->iMinAddressRank = $iMin; $this->iMaxAddressRank = $iMax; } - function setRoute($aRoutePoints, $fRouteWidth) + public function setRoute($aRoutePoints, $fRouteWidth) { $this->aViewBox = false; $this->sViewboxCentreSQL = "ST_SetSRID('LINESTRING("; $sSep = ''; - foreach($this->aRoutePoints as $aPoint) - { + foreach ($aRoutePoints as $aPoint) { $fPoint = (float)$aPoint; $this->sViewboxCentreSQL .= $sSep.$fPoint; $sSep = ($sSep == ' ') ? ',' : ' '; } $this->sViewboxCentreSQL .= ")'::geometry,4326)"; - $this->sViewboxSmallSQL = 'st_buffer('.$this->sViewboxCentreSQL; + $this->sViewboxSmallSQL = 'ST_BUFFER('.$this->sViewboxCentreSQL; $this->sViewboxSmallSQL .= ','.($fRouteWidth/69).')'; - $this->sViewboxLargeSQL = 'st_buffer('.$this->sViewboxCentreSQL; + $this->sViewboxLargeSQL = 'ST_BUFFER('.$this->sViewboxCentreSQL; $this->sViewboxLargeSQL .= ','.($fRouteWidth/30).')'; } - function setViewbox($aViewbox) + public function setViewbox($aViewbox) { $this->aViewBox = 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])); + + if (abs($this->aViewBox[0] - $this->aViewBox[2]) < 0.000000001 + || abs($this->aViewBox[1] - $this->aViewBox[3]) < 0.000000001 + ) { + userError("Bad parameter 'viewbox'. Not a box."); + } + $fHeight = $this->aViewBox[0] - $this->aViewBox[2]; $fWidth = $this->aViewBox[1] - $this->aViewBox[3]; $aBigViewBox[0] = $this->aViewBox[0] + $fHeight; @@ -186,35 +226,42 @@ class Geocode $aBigViewBox[3] = $this->aViewBox[3] - $fWidth; $this->sViewboxCentreSQL = false; - $this->sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".$this->aViewBox[0].",".$this->aViewBox[1]."),ST_Point(".$this->aViewBox[2].",".$this->aViewBox[3].")),4326)"; - $this->sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".$aBigViewBox[0].",".$aBigViewBox[1]."),ST_Point(".$aBigViewBox[2].",".$aBigViewBox[3].")),4326)"; + $this->sViewboxSmallSQL = sprintf( + 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)', + $this->aViewBox[0], + $this->aViewBox[1], + $this->aViewBox[2], + $this->aViewBox[3] + ); + $this->sViewboxLargeSQL = sprintf( + 'ST_SetSRID(ST_MakeBox2D(ST_Point(%F,%F),ST_Point(%F,%F)),4326)', + $aBigViewBox[0], + $aBigViewBox[1], + $aBigViewBox[2], + $aBigViewBox[3] + ); } - function setNearPoint($aNearPoint, $fRadiusDeg = 0.1) - { - $this->aNearPoint = array((float)$aNearPoint[0], (float)$aNearPoint[1], (float)$fRadiusDeg); - } - - function setQuery($sQueryString) + public function setQuery($sQueryString) { $this->sQuery = $sQueryString; $this->aStructuredQuery = false; } - function getQueryString() + public function getQueryString() { return $this->sQuery; } - function loadParamArray($oParams) + public function loadParamArray($oParams) { - $this->bIncludeAddressDetails = $oParams->getBool('addressdetails', - $this->bIncludeAddressDetails); - $this->bIncludeExtraTags = $oParams->getBool('extratags', - $this->bIncludeExtraTags); - $this->bIncludeNameDetails = $oParams->getBool('namedetails', - $this->bIncludeNameDetails); + $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); @@ -226,10 +273,8 @@ class Geocode // List of excluded Place IDs - used for more acurate pageing $sExcluded = $oParams->getStringList('exclude_place_ids'); - if ($sExcluded) - { - foreach($sExcluded as $iExcludedPlaceID) - { + if ($sExcluded) { + foreach ($sExcluded as $iExcludedPlaceID) { $iExcludedPlaceID = (int)$iExcludedPlaceID; if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID; @@ -246,72 +291,65 @@ class Geocode // Country code list $sCountries = $oParams->getStringList('countrycodes'); - if ($sCountries) - { - foreach($sCountries as $sCountryCode) - { - if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) - { + if ($sCountries) { + foreach ($sCountries as $sCountryCode) { + if (preg_match('/^[a-zA-Z][a-zA-Z]$/', $sCountryCode)) { $aCountries[] = strtolower($sCountryCode); } } - if (isset($aCountryCodes)) + if (isset($aCountries)) $this->aCountryCodes = $aCountries; } $aViewbox = $oParams->getStringList('viewboxlbrt'); - if ($aViewbox) - { + if ($aViewbox) { + if (count($aViewbox) != 4) { + userError("Bad parmater 'viewboxlbrt'. Expected 4 coordinates."); + } $this->setViewbox($aViewbox); - } - else - { + } else { $aViewbox = $oParams->getStringList('viewbox'); - if ($aViewbox) - { - $this->setViewBox(array($aViewbox[0], $aViewbox[3], - $aViewbox[2], $aViewbox[1])); - } - else - { + if ($aViewbox) { + if (count($aViewbox) != 4) { + userError("Bad parmater 'viewbox'. Expected 4 coordinates."); + } + $this->setViewBox($aViewbox); + } else { $aRoute = $oParams->getStringList('route'); $fRouteWidth = $oParams->getFloat('routewidth'); - if ($aRoute && $fRouteWidth) - { + if ($aRoute && $fRouteWidth) { $this->setRoute($aRoute, $fRouteWidth); } } } } - function setQueryFromParams($oParams) + public function setQueryFromParams($oParams) { // Search query $sQuery = $oParams->getString('q'); - if (!$sQuery) - { - $this->setStructuredQuery($oParams->getString('amenity'), - $oParams->getString('street'), - $oParams->getString('city'), - $oParams->getString('county'), - $oParams->getString('state'), - $oParams->getString('country'), - $oParams->getString('postalcode')); + if (!$sQuery) { + $this->setStructuredQuery( + $oParams->getString('amenity'), + $oParams->getString('street'), + $oParams->getString('city'), + $oParams->getString('county'), + $oParams->getString('state'), + $oParams->getString('country'), + $oParams->getString('postalcode') + ); $this->setReverseInPlan(false); - } - else - { + } else { $this->setQuery($sQuery); } } - function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues) + public function loadStructuredAddressElement($sValue, $sKey, $iNewMinAddressRank, $iNewMaxAddressRank, $aItemListValues) { $sValue = trim($sValue); if (!$sValue) return false; $this->aStructuredQuery[$sKey] = $sValue; - if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) - { + if ($this->iMinAddressRank == 0 && $this->iMaxAddressRank == 30) { $this->iMinAddressRank = $iNewMinAddressRank; $this->iMaxAddressRank = $iNewMaxAddressRank; } @@ -319,7 +357,7 @@ class Geocode return true; } - function setStructuredQuery($sAmentiy = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false) + public function setStructuredQuery($sAmenity = false, $sStreet = false, $sCity = false, $sCounty = false, $sState = false, $sCountry = false, $sPostalCode = false) { $this->sQuery = false; @@ -329,27 +367,25 @@ class Geocode $this->aAddressRankList = array(); $this->aStructuredQuery = array(); - $this->sAllowedTypesSQLList = ''; + $this->sAllowedTypesSQLList = False; - $this->loadStructuredAddressElement($sAmentiy, 'amenity', 26, 30, false); + $this->loadStructuredAddressElement($sAmenity, 'amenity', 26, 30, false); $this->loadStructuredAddressElement($sStreet, 'street', 26, 30, false); $this->loadStructuredAddressElement($sCity, 'city', 14, 24, false); $this->loadStructuredAddressElement($sCounty, 'county', 9, 13, false); $this->loadStructuredAddressElement($sState, 'state', 8, 8, false); - $this->loadStructuredAddressElement($sPostalCode, 'postalcode' , 5, 11, array(5, 11)); + $this->loadStructuredAddressElement($sPostalCode, 'postalcode', 5, 11, array(5, 11)); $this->loadStructuredAddressElement($sCountry, 'country', 4, 4, false); - if (sizeof($this->aStructuredQuery) > 0) - { + if (sizeof($this->aStructuredQuery) > 0) { $this->sQuery = join(', ', $this->aStructuredQuery); - if ($this->iMaxAddressRank < 30) - { - $sAllowedTypesSQLList = '(\'place\',\'boundary\')'; + if ($this->iMaxAddressRank < 30) { + $this->sAllowedTypesSQLList = '(\'place\',\'boundary\')'; } } } - function fallbackStructuredQuery() + public function fallbackStructuredQuery() { if (!$this->aStructuredQuery) return false; @@ -359,10 +395,8 @@ class Geocode $aOrderToFallback = array('postalcode', 'street', 'city', 'county', 'state'); - foreach($aOrderToFallback as $sType) - { - if (isset($aParams[$sType])) - { + foreach ($aOrderToFallback as $sType) { + if (isset($aParams[$sType])) { unset($aParams[$sType]); $this->setStructuredQuery(@$aParams['amenity'], @$aParams['street'], @$aParams['city'], @$aParams['county'], @$aParams['state'], @$aParams['country'], @$aParams['postalcode']); return true; @@ -372,138 +406,297 @@ class Geocode return false; } - function getDetails($aPlaceIDs) + public function getDetails($aPlaceIDs) { //$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1 - if (sizeof($aPlaceIDs) == 0) return array(); + if (sizeof($aPlaceIDs) == 0) return array(); - $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$this->aLangPrefOrder))."]"; + $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]"; // Get the details for display (is this a redundant extra step?) $sPlaceIDs = join(',', array_keys($aPlaceIDs)); $sImportanceSQL = ''; - if ($this->sViewboxSmallSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; - if ($this->sViewboxLargeSQL) $sImportanceSQL .= " case when ST_Contains($this->sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; - - $sSQL = "select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code as 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,avg(ST_Y(centroid)) as lat, "; - $sSQL .= $sImportanceSQL."coalesce(importance,0.75-(rank_search::float/40)) as importance, "; - $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; - $sSQL .= "(extratags->'place') as extra_place "; - $sSQL .= "from placex where place_id in ($sPlaceIDs) "; - $sSQL .= "and (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 osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,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' "; - - if (30 >= $this->iMinAddressRank && 30 <= $this->iMaxAddressRank) - { - //only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines + $sImportanceSQLGeom = ''; + if ($this->sViewboxSmallSQL) { + $sImportanceSQL .= " CASE WHEN ST_Contains($this->sViewboxSmallSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; + $sImportanceSQLGeom .= " CASE WHEN ST_Contains($this->sViewboxSmallSQL, geometry) THEN 1 ELSE 0.75 END * "; + } + if ($this->sViewboxLargeSQL) { + $sImportanceSQL .= " CASE WHEN ST_Contains($this->sViewboxLargeSQL, ST_Collect(centroid)) THEN 1 ELSE 0.75 END * "; + $sImportanceSQLGeom .= " CASE WHEN ST_Contains($this->sViewboxLargeSQL, geometry) THEN 1 ELSE 0.75 END * "; + } + + $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 .= " ".$sImportanceSQL."COALESCE(importance,0.75-(rank_search::float/40)) AS importance, "; + $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 .= $sImportanceSQLGeom."(0.75-(rank_search::float/40)) AS importance, "; + $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) - { + foreach ($aPlaceIDs as $placeID => $housenumber) { $i++; $sHousenumbers .= "(".$placeID.", ".$housenumber.")"; - if($i<$length) - $sHousenumbers .= ", "; + 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) + + 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 'T' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 30 as rank_search, 30 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, '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, avg(st_y(centroid)) as lat,"; - $sSQL .= $sImportanceSQL."-1.15 as importance "; - $sSQL .= ", (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance "; - $sSQL .= ", null as extra_place "; - $sSQL .= " from (select place_id"; - //interpolate the Tiger housenumbers here - $sSQL .= ", ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) as centroid, parent_place_id, housenumber_for_place"; - $sSQL .= " from (location_property_tiger "; - $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; - $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here - $sSQL .= " group by place_id, housenumber_for_place"; //is this group by really needed?, place_id + housenumber (in combination) are unique + $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 .= " ".$sImportanceSQL."-1.15 AS importance, "; + $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 'W' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 30 as rank_search, 30 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code 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, avg(st_y(centroid)) as lat,"; - $sSQL .= $sImportanceSQL."-0.1 as importance, "; // slightly smaller than the importance for normal houses with rank 30, which is 0 - $sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p"; - $sSQL .= " where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance,"; - $sSQL .= " null as extra_place "; - $sSQL .= " from (select place_id, calculated_country_code "; - //interpolate the housenumbers here - $sSQL .= ", CASE WHEN startnumber != endnumber THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) "; - $sSQL .= " ELSE ST_LineInterpolatePoint(linegeo, 0.5) END as centroid"; - $sSQL .= ", parent_place_id, housenumber_for_place "; - $sSQL .= " from (location_property_osmline "; - $sSQL .= " join (values ".$sHousenumbers.") as housenumbers(place_id, housenumber_for_place) using(place_id)) "; - $sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank) as blub"; //postgres wants an alias here - $sSQL .= " group by place_id, housenumber_for_place, calculated_country_code "; //is this group by really needed?, place_id + housenumber (in combination) are unique + $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 .= " ".$sImportanceSQL."-0.1 AS importance, "; // slightly smaller than the importance for normal houses with rank 30, which is 0 + $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 'L' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 0 as rank_search, 0 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, '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, avg(ST_Y(centroid)) as lat, "; - $sSQL .= $sImportanceSQL."-1.10 as importance, "; - $sSQL .= "(select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, "; - $sSQL .= "null as extra_place "; - $sSQL .= "from location_property_aux where place_id in ($sPlaceIDs) "; - $sSQL .= "and 30 between $this->iMinAddressRank and $this->iMaxAddressRank "; - $sSQL .= "group by place_id"; - if (!$this->bDeDupe) $sSQL .= ", place_id"; - $sSQL .= ", get_address_by_language(place_id, -1, $sLanguagePrefArraySQL) "; + 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 .= " ".$sImportanceSQL."-1.10 AS importance, "; + $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 "
"; var_dump($sSQL); } - $aSearchResults = chksql($this->oDB->getAll($sSQL), - "Could not get details for place."); + if (CONST_Debug) { + echo "
"; + var_dump($sSQL); + } + $aSearchResults = chksql( + $this->oDB->getAll($sSQL), + "Could not get details for place." + ); return $aSearchResults; } - function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases) + public function getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases, $sNormQuery) { /* Calculate all searches using aValidTokens i.e. @@ -516,101 +709,64 @@ class Geocode Score how good the search is so they can be ordered */ - foreach($aPhrases as $iPhrase => $sPhrase) - { + foreach ($aPhrases as $iPhrase => $aPhrase) { $aNewPhraseSearches = array(); if ($bStructuredPhrases) $sPhraseType = $aPhraseTypes[$iPhrase]; else $sPhraseType = ''; - foreach($aPhrases[$iPhrase]['wordsets'] as $iWordSet => $aWordset) - { + foreach ($aPhrase['wordsets'] as $iWordSet => $aWordset) { // Too many permutations - too expensive if ($iWordSet > 120) break; $aWordsetSearches = $aSearches; // Add all words from this wordset - foreach($aWordset as $iToken => $sToken) - { + foreach ($aWordset as $iToken => $sToken) { //echo "
$sToken"; $aNewWordsetSearches = array(); - foreach($aWordsetSearches as $aCurrentSearch) - { + foreach ($aWordsetSearches as $aCurrentSearch) { //echo ""; //var_dump($aCurrentSearch); //echo ""; // If the token is valid - if (isset($aValidTokens[' '.$sToken])) - { - foreach($aValidTokens[' '.$sToken] as $aSearchTerm) - { + if (isset($aValidTokens[' '.$sToken])) { + foreach ($aValidTokens[' '.$sToken] as $aSearchTerm) { $aSearch = $aCurrentSearch; $aSearch['iSearchRank']++; - if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0') - { - if ($aSearch['sCountryCode'] === false) - { + if (($sPhraseType == '' || $sPhraseType == 'country') && !empty($aSearchTerm['country_code']) && $aSearchTerm['country_code'] != '0') { + if ($aSearch['sCountryCode'] === false) { $aSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']); // Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation) - if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases))) - { + if (($iToken+1 != sizeof($aWordset) || $iPhrase+1 != sizeof($aPhrases))) { $aSearch['iSearchRank'] += 5; } if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } - } - elseif (isset($aSearchTerm['lat']) && $aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) - { - if ($aSearch['fLat'] === '') - { - $aSearch['fLat'] = $aSearchTerm['lat']; - $aSearch['fLon'] = $aSearchTerm['lon']; - $aSearch['fRadius'] = $aSearchTerm['radius']; - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - } - elseif ($sPhraseType == 'postalcode') - { + } elseif ($sPhraseType == 'postalcode' || ($aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'postcode')) { // We need to try the case where the postal code is the primary element (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) so try both - if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - // If we already have a name try putting the postcode first - if (sizeof($aSearch['aName'])) - { + if ($aSearch['sPostcode'] === '' && $aSearch['sHouseNumber'] === '' && + isset($aSearchTerm['word']) && $aSearchTerm['word'] && strpos($sNormQuery, $this->normTerm($aSearchTerm['word'])) !== false) { + // If we have structured search or this is the first term, + // make the postcode the primary search element. + if ($aSearch['sOperator'] === '' && ($sPhraseType == 'postalcode' || ($iToken == 0 && $iPhrase == 0))) { $aNewSearch = $aSearch; + $aNewSearch['sOperator'] = 'postcode'; $aNewSearch['aAddress'] = array_merge($aNewSearch['aAddress'], $aNewSearch['aName']); - $aNewSearch['aName'] = array(); - $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aNewSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word']; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aNewSearch; } - if (sizeof($aSearch['aName'])) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) - { - $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - } - else - { - $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - $aSearch['iSearchRank'] += 1000; // skip; - } - } - else - { - $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - //$aSearch['iNamePhrase'] = $iPhrase; + // If we have a structured search or this is not the first term, + // add the postcode as an addendum. + if ($aSearch['sOperator'] !== 'postcode' && ($sPhraseType == 'postalcode' || sizeof($aSearch['aName']))) { + $aSearch['sPostcode'] = $aSearchTerm['word']; + if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } - if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } - - } - elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') - { - if ($aSearch['sHouseNumber'] === '') - { + } elseif (($sPhraseType == '' || $sPhraseType == 'street') && $aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') { + if ($aSearch['sHouseNumber'] === '' && $aSearch['sOperator'] !== 'postcode') { $aSearch['sHouseNumber'] = $sToken; // sanity check: if the housenumber is not mainly made // up of numbers, add a penalty @@ -625,37 +781,32 @@ class Geocode if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; */ } - } - elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) - { - if ($aSearch['sClass'] === '') - { - $aSearch['sOperator'] = $aSearchTerm['operator']; + } elseif ($sPhraseType == '' && $aSearchTerm['class'] !== '' && $aSearchTerm['class'] !== null) { + // require a normalized exact match of the term + // if we have the normalizer version of the query + // available + if ($aSearch['sClass'] === '' + && ($sNormQuery === null || !($aSearchTerm['word'] && strpos($sNormQuery, $aSearchTerm['word']) === false))) { $aSearch['sClass'] = $aSearchTerm['class']; $aSearch['sType'] = $aSearchTerm['type']; - if (sizeof($aSearch['aName'])) $aSearch['sOperator'] = 'name'; - else $aSearch['sOperator'] = 'near'; // near = in for the moment - if (strlen($aSearchTerm['operator']) == 0) $aSearch['iSearchRank'] += 1; + if ($aSearchTerm['operator'] == '') { + $aSearch['sOperator'] = sizeof($aSearch['aName']) ? 'name' : 'near'; + $aSearch['iSearchRank'] += 2; + } else { + $aSearch['sOperator'] = 'near'; // near = in for the moment + } if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } - } - elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - if (sizeof($aSearch['aName'])) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) - { + } elseif (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + if (sizeof($aSearch['aName'])) { + if ((!$bStructuredPhrases || $iPhrase > 0) && $sPhraseType != 'country' && (!isset($aValidTokens[$sToken]) || strpos($sToken, ' ') !== false)) { $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - } - else - { + } else { $aCurrentSearch['aFullNameAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; $aSearch['iSearchRank'] += 1000; // skip; } - } - else - { + } else { $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; //$aSearch['iNamePhrase'] = $iPhrase; } @@ -666,66 +817,54 @@ class Geocode // Look for partial matches. // Note that there is no point in adding country terms here // because country are omitted in the address. - if (isset($aValidTokens[$sToken]) && $sPhraseType != 'country') - { + if (isset($aValidTokens[$sToken]) && $sPhraseType != 'country') { // Allow searching for a word - but at extra cost - foreach($aValidTokens[$sToken] as $aSearchTerm) - { - if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) - { - if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strpos($sToken, ' ') === false) - { + foreach ($aValidTokens[$sToken] as $aSearchTerm) { + if (isset($aSearchTerm['word_id']) && $aSearchTerm['word_id']) { + if ((!$bStructuredPhrases || $iPhrase > 0) && sizeof($aCurrentSearch['aName']) && strpos($sToken, ' ') === false) { $aSearch = $aCurrentSearch; $aSearch['iSearchRank'] += 1; - if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) - { + if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) { $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - } - elseif (isset($aValidTokens[' '.$sToken])) // revert to the token version? - { + } elseif (isset($aValidTokens[' '.$sToken])) { // revert to the token version? $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; $aSearch['iSearchRank'] += 1; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; - foreach($aValidTokens[' '.$sToken] as $aSearchTermToken) - { + foreach ($aValidTokens[' '.$sToken] as $aSearchTermToken) { if (empty($aSearchTermToken['country_code']) - && empty($aSearchTermToken['lat']) - && empty($aSearchTermToken['class'])) - { + && empty($aSearchTermToken['lat']) + && empty($aSearchTermToken['class']) + ) { $aSearch = $aCurrentSearch; $aSearch['iSearchRank'] += 1; $aSearch['aAddress'][$aSearchTermToken['word_id']] = $aSearchTermToken['word_id']; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } } - } - else - { + } else { $aSearch['aAddressNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } } - if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) - { + if (!sizeof($aCurrentSearch['aName']) || $aCurrentSearch['iNamePhrase'] == $iPhrase) { $aSearch = $aCurrentSearch; $aSearch['iSearchRank'] += 1; if (!sizeof($aCurrentSearch['aName'])) $aSearch['iSearchRank'] += 1; if (preg_match('#^[0-9]+$#', $sToken)) $aSearch['iSearchRank'] += 2; - if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) + if ($aWordFrequencyScores[$aSearchTerm['word_id']] < CONST_Max_Word_Frequency) { $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; - else + } else { $aSearch['aNameNonSearch'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + } $aSearch['iNamePhrase'] = $iPhrase; if ($aSearch['iSearchRank'] < $this->iMaxRank) $aNewWordsetSearches[] = $aSearch; } } } - } - else - { + } else { // Allow skipping a word - but at EXTREAM cost //$aSearch = $aCurrentSearch; //$aSearch['iSearchRank']+=100; @@ -742,8 +881,7 @@ class Geocode usort($aNewPhraseSearches, 'bySearchRank'); $aSearchHash = array(); - foreach($aNewPhraseSearches as $iSearch => $aSearch) - { + foreach ($aNewPhraseSearches as $iSearch => $aSearch) { $sHash = serialize($aSearch); if (isset($aSearchHash[$sHash])) unset($aNewPhraseSearches[$iSearch]); else $aSearchHash[$sHash] = 1; @@ -754,10 +892,8 @@ class Geocode // Re-group the searches by their score, junk anything over 20 as just not worth trying $aGroupedSearches = array(); - foreach($aNewPhraseSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { + foreach ($aNewPhraseSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; } @@ -766,18 +902,28 @@ class Geocode $iSearchCount = 0; $aSearches = array(); - foreach($aGroupedSearches as $iScore => $aNewSearches) - { + foreach ($aGroupedSearches as $iScore => $aNewSearches) { $iSearchCount += sizeof($aNewSearches); $aSearches = array_merge($aSearches, $aNewSearches); if ($iSearchCount > 50) break; } //if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens); + } + // Revisit searches, giving penalty to unlikely combinations + $aGroupedSearches = array(); + foreach ($aSearches as $aSearch) { + if (!$aSearch['aName']) { + if ($aSearch['sHouseNumber']) { + continue; + } + } + $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; } - return $aGroupedSearches; + ksort($aGroupedSearches); + return $aGroupedSearches; } /* Perform the actual query lookup. @@ -810,125 +956,126 @@ class Geocode name: full name (currently the same as langaddress) foundorder: secondary ordering for places with same importance */ - function lookup() + + + public function lookup() { - if (!$this->sQuery && !$this->aStructuredQuery) return false; + if (!$this->sQuery && !$this->aStructuredQuery) return array(); - $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$this->aLangPrefOrder))."]"; + $sNormQuery = $this->normTerm($this->sQuery); + $sLanguagePrefArraySQL = "ARRAY[".join(',', array_map("getDBQuoted", $this->aLangPrefOrder))."]"; $sCountryCodesSQL = false; - if ($this->aCountryCodes) - { + if ($this->aCountryCodes) { $sCountryCodesSQL = join(',', array_map('addQuotes', $this->aCountryCodes)); } $sQuery = $this->sQuery; + if (!preg_match('//u', $sQuery)) { + userError("Query string is not UTF-8 encoded."); + } // Conflicts between US state abreviations and various words for 'the' in different languages - if (isset($this->aLangPrefOrder['name:en'])) - { - $sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/','\1illinois\2', $sQuery); - $sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/','\1alabama\2', $sQuery); - $sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/','\1louisiana\2', $sQuery); + if (isset($this->aLangPrefOrder['name:en'])) { + $sQuery = preg_replace('/(^|,)\s*il\s*(,|$)/', '\1illinois\2', $sQuery); + $sQuery = preg_replace('/(^|,)\s*al\s*(,|$)/', '\1alabama\2', $sQuery); + $sQuery = preg_replace('/(^|,)\s*la\s*(,|$)/', '\1louisiana\2', $sQuery); } $bBoundingBoxSearch = $this->bBoundedSearch && $this->sViewboxSmallSQL; - if ($this->sViewboxCentreSQL) - { + if ($this->sViewboxCentreSQL) { // For complex viewboxes (routes) precompute the bounding geometry - $sGeom = chksql($this->oDB->getOne("select ".$this->sViewboxSmallSQL), - "Could not get small viewbox"); + $sGeom = chksql( + $this->oDB->getOne("select ".$this->sViewboxSmallSQL), + "Could not get small viewbox" + ); $this->sViewboxSmallSQL = "'".$sGeom."'::geometry"; - $sGeom = chksql($this->oDB->getOne("select ".$this->sViewboxLargeSQL), - "Could not get large viewbox"); + $sGeom = chksql( + $this->oDB->getOne("select ".$this->sViewboxLargeSQL), + "Could not get large viewbox" + ); $this->sViewboxLargeSQL = "'".$sGeom."'::geometry"; } // Do we have anything that looks like a lat/lon pair? - if ( $aLooksLike = looksLikeLatLonPair($sQuery) ) - { - $this->setNearPoint(array($aLooksLike['lat'], $aLooksLike['lon'])); + $oNearPoint = false; + if ($aLooksLike = NearPoint::extractFromQuery($sQuery)) { + $oNearPoint = $aLooksLike['pt']; $sQuery = $aLooksLike['query']; } $aSearchResults = array(); - if ($sQuery || $this->aStructuredQuery) - { + if ($sQuery || $this->aStructuredQuery) { // Start with a blank search $aSearches = array( - array('iSearchRank' => 0, - 'iNamePhrase' => -1, - 'sCountryCode' => false, - 'aName' => array(), - 'aAddress' => array(), - 'aFullNameAddress' => array(), - 'aNameNonSearch' => array(), - 'aAddressNonSearch' => array(), - 'sOperator' => '', - 'aFeatureName' => array(), - 'sClass' => '', - 'sType' => '', - 'sHouseNumber' => '', - 'fLat' => '', - 'fLon' => '', - 'fRadius' => '' - ) - ); - - // Do we have a radius search? - $sNearPointSQL = false; - if ($this->aNearPoint) - { - $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$this->aNearPoint[1].",".(float)$this->aNearPoint[0]."),4326)"; - $aSearches[0]['fLat'] = (float)$this->aNearPoint[0]; - $aSearches[0]['fLon'] = (float)$this->aNearPoint[1]; - $aSearches[0]['fRadius'] = (float)$this->aNearPoint[2]; - } + array( + 'iSearchRank' => 0, + 'iNamePhrase' => -1, + 'sCountryCode' => false, + 'aName' => array(), + 'aAddress' => array(), + 'aFullNameAddress' => array(), + 'aNameNonSearch' => array(), + 'aAddressNonSearch' => array(), + 'sOperator' => '', + 'aFeatureName' => array(), + 'sClass' => '', + 'sType' => '', + 'sHouseNumber' => '', + 'sPostcode' => '', + 'oNear' => $oNearPoint + ) + ); // Any 'special' terms in the search? $bSpecialTerms = false; - preg_match_all('/\\[(.*)=(.*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); - $aSpecialTerms = array(); - foreach($aSpecialTermsRaw as $aSpecialTerm) - { + preg_match_all('/\\[([\\w_]*)=([\\w_]*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); + foreach ($aSpecialTermsRaw as $aSpecialTerm) { $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); - $aSpecialTerms[strtolower($aSpecialTerm[1])] = $aSpecialTerm[2]; + if (!$bSpecialTerms) { + $aNewSearches = array(); + foreach ($aSearches as $aSearch) { + $aNewSearch = $aSearch; + $aNewSearch['sClass'] = $aSpecialTerm[1]; + $aNewSearch['sType'] = $aSpecialTerm[2]; + $aNewSearches[] = $aNewSearch; + } + + $aSearches = $aNewSearches; + $bSpecialTerms = true; + } } preg_match_all('/\\[([\\w ]*)\\]/u', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); - $aSpecialTerms = array(); - if (isset($this->aStructuredQuery['amenity']) && $this->aStructuredQuery['amenity']) - { + if (isset($this->aStructuredQuery['amenity']) && $this->aStructuredQuery['amenity']) { $aSpecialTermsRaw[] = array('['.$this->aStructuredQuery['amenity'].']', $this->aStructuredQuery['amenity']); unset($this->aStructuredQuery['amenity']); } - foreach($aSpecialTermsRaw as $aSpecialTerm) - { + + foreach ($aSpecialTermsRaw as $aSpecialTerm) { $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); - $sToken = chksql($this->oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string")); - $sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator'; - $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class not in (\'place\')) or country_code is not null'; + if ($bSpecialTerms) { + continue; + } + + $sToken = chksql($this->oDB->getOne("SELECT make_standard_name('".$aSpecialTerm[1]."') AS string")); + $sSQL = 'SELECT * '; + $sSQL .= 'FROM ( '; + $sSQL .= ' SELECT word_id, word_token, word, class, type, country_code, operator'; + $sSQL .= ' FROM word '; + $sSQL .= ' WHERE word_token in (\' '.$sToken.'\')'; + $sSQL .= ') AS x '; + $sSQL .= ' WHERE (class is not null AND class not in (\'place\'))'; if (CONST_Debug) var_Dump($sSQL); $aSearchWords = chksql($this->oDB->getAll($sSQL)); $aNewSearches = array(); - foreach($aSearches as $aSearch) - { - foreach($aSearchWords as $aSearchTerm) - { + foreach ($aSearches as $aSearch) { + foreach ($aSearchWords as $aSearchTerm) { $aNewSearch = $aSearch; - if ($aSearchTerm['country_code']) - { - $aNewSearch['sCountryCode'] = strtolower($aSearchTerm['country_code']); - $aNewSearches[] = $aNewSearch; - $bSpecialTerms = true; - } - if ($aSearchTerm['class']) - { - $aNewSearch['sClass'] = $aSearchTerm['class']; - $aNewSearch['sType'] = $aSearchTerm['type']; - $aNewSearches[] = $aNewSearch; - $bSpecialTerms = true; - } + $aNewSearch['sClass'] = $aSearchTerm['class']; + $aNewSearch['sType'] = $aSearchTerm['type']; + $aNewSearches[] = $aNewSearch; + $bSpecialTerms = true; } } $aSearches = $aNewSearches; @@ -936,14 +1083,11 @@ class Geocode // Split query into phrases // Commas are used to reduce the search space by indicating where phrases split - if ($this->aStructuredQuery) - { + if ($this->aStructuredQuery) { $aPhrases = $this->aStructuredQuery; $bStructuredPhrases = true; - } - else - { - $aPhrases = explode(',',$sQuery); + } else { + $aPhrases = explode(',', $sQuery); $bStructuredPhrases = false; } @@ -952,19 +1096,17 @@ class Geocode // Get all 'sets' of words // Generate a complete list of all $aTokens = array(); - foreach($aPhrases as $iPhrase => $sPhrase) - { - $aPhrase = chksql($this->oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string"), - "Cannot nomralize query string (is it an UTF-8 string?)"); - if (trim($aPhrase['string'])) - { + foreach ($aPhrases as $iPhrase => $sPhrase) { + $aPhrase = chksql( + $this->oDB->getRow("SELECT make_standard_name('".pg_escape_string($sPhrase)."') as string"), + "Cannot normalize query string (is it a UTF-8 string?)" + ); + if (trim($aPhrase['string'])) { $aPhrases[$iPhrase] = $aPhrase; - $aPhrases[$iPhrase]['words'] = explode(' ',$aPhrases[$iPhrase]['string']); + $aPhrases[$iPhrase]['words'] = explode(' ', $aPhrases[$iPhrase]['string']); $aPhrases[$iPhrase]['wordsets'] = getWordSets($aPhrases[$iPhrase]['words'], 0); $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets'])); - } - else - { + } else { unset($aPhrases[$iPhrase]); } } @@ -973,41 +1115,32 @@ class Geocode $aPhraseTypes = array_keys($aPhrases); $aPhrases = array_values($aPhrases); - if (sizeof($aTokens)) - { + if (sizeof($aTokens)) { // Check which tokens we have, get the ID numbers - $sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count'; - $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')'; + $sSQL = 'SELECT word_id, word_token, word, class, type, country_code, operator, search_name_count'; + $sSQL .= ' FROM word '; + $sSQL .= ' WHERE word_token in ('.join(',', array_map("getDBQuoted", $aTokens)).')'; if (CONST_Debug) var_Dump($sSQL); $aValidTokens = array(); - if (sizeof($aTokens)) - { - $aDatabaseWords = chksql($this->oDB->getAll($sSQL), - "Could not get word tokens."); - } - else - { - $aDatabaseWords = array(); - } + $aDatabaseWords = chksql( + $this->oDB->getAll($sSQL), + "Could not get word tokens." + ); $aPossibleMainWordIDs = array(); $aWordFrequencyScores = array(); - foreach($aDatabaseWords as $aToken) - { + foreach ($aDatabaseWords as $aToken) { // Very special case - require 2 letter country param to match the country code found if ($bStructuredPhrases && $aToken['country_code'] && !empty($this->aStructuredQuery['country']) - && strlen($this->aStructuredQuery['country']) == 2 && strtolower($this->aStructuredQuery['country']) != $aToken['country_code']) - { + && strlen($this->aStructuredQuery['country']) == 2 && strtolower($this->aStructuredQuery['country']) != $aToken['country_code'] + ) { continue; } - if (isset($aValidTokens[$aToken['word_token']])) - { + if (isset($aValidTokens[$aToken['word_token']])) { $aValidTokens[$aToken['word_token']][] = $aToken; - } - else - { + } else { $aValidTokens[$aToken['word_token']] = array($aToken); } if (!$aToken['class'] && !$aToken['country_code']) $aPossibleMainWordIDs[$aToken['word_id']] = 1; @@ -1015,39 +1148,15 @@ class Geocode } if (CONST_Debug) var_Dump($aPhrases, $aValidTokens); - // Try and calculate GB postcodes we might be missing - foreach($aTokens as $sToken) - { - // Source of gb postcodes is now definitive - always use - if (preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData)) - { - if (substr($aData[1],-2,1) != ' ') - { - $aData[0] = substr($aData[0],0,strlen($aData[1])-1).' '.substr($aData[0],strlen($aData[1])-1); - $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1); - } - $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $this->oDB); - if ($aGBPostcodeLocation) - { - $aValidTokens[$sToken] = $aGBPostcodeLocation; - } - } - // US ZIP+4 codes - if there is no token, - // merge in the 5-digit ZIP code - else if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) - { - if (isset($aValidTokens[$aData[1]])) - { - foreach($aValidTokens[$aData[1]] as $aToken) - { - if (!$aToken['class']) - { - if (isset($aValidTokens[$sToken])) - { + // US ZIP+4 codes - if there is no token, merge in the 5-digit ZIP code + foreach ($aTokens as $sToken) { + if (!isset($aValidTokens[$sToken]) && preg_match('/^([0-9]{5}) [0-9]{4}$/', $sToken, $aData)) { + if (isset($aValidTokens[$aData[1]])) { + foreach ($aValidTokens[$aData[1]] as $aToken) { + if (!$aToken['class']) { + if (isset($aValidTokens[$sToken])) { $aValidTokens[$sToken][] = $aToken; - } - else - { + } else { $aValidTokens[$sToken] = array($aToken); } } @@ -1056,12 +1165,10 @@ class Geocode } } - foreach($aTokens as $sToken) - { + foreach ($aTokens as $sToken) { // Unknown single word token with a number - assume it is a house number - if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken,' ') === false && preg_match('/[0-9]/', $sToken)) - { - $aValidTokens[' '.$sToken] = array(array('class'=>'place','type'=>'house')); + if (!isset($aValidTokens[' '.$sToken]) && strpos($sToken, ' ') === false && preg_match('/[0-9]/', $sToken)) { + $aValidTokens[' '.$sToken] = array(array('class' => 'place', 'type' => 'house')); } } @@ -1072,47 +1179,37 @@ class Geocode // array with: placeid => -1 | tiger-housenumber $aResultPlaceIDs = array(); - $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases); + $aGroupedSearches = $this->getGroupedSearches($aSearches, $aPhraseTypes, $aPhrases, $aValidTokens, $aWordFrequencyScores, $bStructuredPhrases, $sNormQuery); - if ($this->bReverseInPlan) - { + if ($this->bReverseInPlan) { // Reverse phrase array and also reverse the order of the wordsets in // the first and final phrase. Don't bother about phrases in the middle // because order in the address doesn't matter. $aPhrases = array_reverse($aPhrases); $aPhrases[0]['wordsets'] = getInverseWordSets($aPhrases[0]['words'], 0); - if (sizeof($aPhrases) > 1) - { + if (sizeof($aPhrases) > 1) { $aFinalPhrase = end($aPhrases); $aPhrases[sizeof($aPhrases)-1]['wordsets'] = getInverseWordSets($aFinalPhrase['words'], 0); } - $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false); - - foreach($aGroupedSearches as $aSearches) - { - foreach($aSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { + $aReverseGroupedSearches = $this->getGroupedSearches($aSearches, null, $aPhrases, $aValidTokens, $aWordFrequencyScores, false, $sNormQuery); + + foreach ($aGroupedSearches as $aSearches) { + foreach ($aSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { if (!isset($aReverseGroupedSearches[$aSearch['iSearchRank']])) $aReverseGroupedSearches[$aSearch['iSearchRank']] = array(); $aReverseGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; } - } } $aGroupedSearches = $aReverseGroupedSearches; ksort($aGroupedSearches); } - } - else - { + } else { // Re-group the searches by their score, junk anything over 20 as just not worth trying $aGroupedSearches = array(); - foreach($aSearches as $aSearch) - { - if ($aSearch['iSearchRank'] < $this->iMaxRank) - { + foreach ($aSearches as $aSearch) { + if ($aSearch['iSearchRank'] < $this->iMaxRank) { if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; } @@ -1120,33 +1217,24 @@ class Geocode ksort($aGroupedSearches); } - if (CONST_Debug) var_Dump($aGroupedSearches); - - if (CONST_Search_TryDroppedAddressTerms && sizeof($this->aStructuredQuery) > 0) - { + if (CONST_Search_TryDroppedAddressTerms && sizeof($this->aStructuredQuery) > 0) { $aCopyGroupedSearches = $aGroupedSearches; - foreach($aCopyGroupedSearches as $iGroup => $aSearches) - { - foreach($aSearches as $iSearch => $aSearch) - { + foreach ($aCopyGroupedSearches as $iGroup => $aSearches) { + foreach ($aSearches as $iSearch => $aSearch) { $aReductionsList = array($aSearch['aAddress']); $iSearchRank = $aSearch['iSearchRank']; - while(sizeof($aReductionsList) > 0) - { + while (sizeof($aReductionsList) > 0) { $iSearchRank += 5; if ($iSearchRank > iMaxRank) break 3; $aNewReductionsList = array(); - foreach($aReductionsList as $aReductionsWordList) - { - for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++) - { + foreach ($aReductionsList as $aReductionsWordList) { + for ($iReductionWord = 0; $iReductionWord < sizeof($aReductionsWordList); $iReductionWord++) { $aReductionsWordListResult = array_merge(array_slice($aReductionsWordList, 0, $iReductionWord), array_slice($aReductionsWordList, $iReductionWord+1)); $aReverseSearch = $aSearch; $aSearch['aAddress'] = $aReductionsWordListResult; $aSearch['iSearchRank'] = $iSearchRank; $aGroupedSearches[$iSearchRank][] = $aReverseSearch; - if (sizeof($aReductionsWordListResult) > 0) - { + if (sizeof($aReductionsWordListResult) > 0) { $aNewReductionsList[] = $aReductionsWordListResult; } } @@ -1160,18 +1248,13 @@ class Geocode // Filter out duplicate searches $aSearchHash = array(); - foreach($aGroupedSearches as $iGroup => $aSearches) - { - foreach($aSearches as $iSearch => $aSearch) - { + foreach ($aGroupedSearches as $iGroup => $aSearches) { + foreach ($aSearches as $iSearch => $aSearch) { $sHash = serialize($aSearch); - if (isset($aSearchHash[$sHash])) - { + if (isset($aSearchHash[$sHash])) { unset($aGroupedSearches[$iGroup][$iSearch]); if (sizeof($aGroupedSearches[$iGroup]) == 0) unset($aGroupedSearches[$iGroup]); - } - else - { + } else { $aSearchHash[$sHash] = 1; } } @@ -1181,207 +1264,225 @@ class Geocode $iGroupLoop = 0; $iQueryLoop = 0; - foreach($aGroupedSearches as $iGroupedRank => $aSearches) - { + foreach ($aGroupedSearches as $iGroupedRank => $aSearches) { $iGroupLoop++; - foreach($aSearches as $aSearch) - { + foreach ($aSearches as $aSearch) { $iQueryLoop++; $searchedHousenumber = -1; - if (CONST_Debug) { echo "
Search Loop, group $iGroupLoop, loop $iQueryLoop"; } + if (CONST_Debug) echo "
Search Loop, group $iGroupLoop, loop $iQueryLoop"; if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens); + if ($sCountryCodesSQL && $aSearch['sCountryCode'] && !in_array($aSearch['sCountryCode'], $this->aCountryCodes)) { + continue; + } + // No location term? - if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['fLon']) - { - if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber']) - { + if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress'])) { + if ($aSearch['sCountryCode'] && !$aSearch['sClass'] && !$aSearch['sHouseNumber'] && !$aSearch['oNear']) { // Just looking for a country by code - look it up - if (4 >= $this->iMinAddressRank && 4 <= $this->iMaxAddressRank) - { - $sSQL = "select place_id from placex where calculated_country_code='".$aSearch['sCountryCode']."' and rank_search = 4"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; + if (4 >= $this->iMinAddressRank && 4 <= $this->iMaxAddressRank) { + $sSQL = "SELECT place_id FROM placex WHERE country_code='".$aSearch['sCountryCode']."' AND rank_search = 4"; if ($bBoundingBoxSearch) - $sSQL .= " and _st_intersects($this->sViewboxSmallSQL, geometry)"; - $sSQL .= " order by st_area(geometry) desc limit 1"; + $sSQL .= " AND _st_intersects($this->sViewboxSmallSQL, geometry)"; + $sSQL .= " ORDER BY st_area(geometry) DESC LIMIT 1"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - else - { + } else { $aPlaceIDs = array(); } - } - else - { - if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue; + } else { + if (!$bBoundingBoxSearch && !$aSearch['oNear']) continue; if (!$aSearch['sClass']) continue; - $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; - if (chksql($this->oDB->getOne($sSQL))) - { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; - if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($this->sViewboxSmallSQL, ct.centroid)"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + + $sSQL = "SELECT COUNT(*) FROM pg_tables WHERE tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + if (chksql($this->oDB->getOne($sSQL))) { + $sSQL = "SELECT place_id FROM place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; + if ($sCountryCodesSQL) $sSQL .= " JOIN placex USING (place_id)"; + if ($aSearch['oNear']) { + $sSQL .= " WHERE ".$aSearch['oNear']->withinSQL('ct.centroid'); + } else { + $sSQL .= " WHERE st_contains($this->sViewboxSmallSQL, ct.centroid)"; + } + if ($sCountryCodesSQL) $sSQL .= " AND country_code in ($sCountryCodesSQL)"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($this->sViewboxCentreSQL) { + $sSQL .= " ORDER BY ST_Distance($this->sViewboxCentreSQL, ct.centroid) ASC"; + } elseif ($aSearch['oNear']) { + $sSQL .= " ORDER BY ".$aSearch['oNear']->distanceSQL('ct.centroid').' ASC'; } - if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, ct.centroid) asc"; $sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - - // If excluded place IDs are given, it is fair to assume that - // there have been results in the small box, so no further - // expansion in that case. - // Also don't expand if bounded results were requested. - if (!sizeof($aPlaceIDs) && !sizeof($this->aExcludePlaceIDs) && !$this->bBoundedSearch) - { - $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." ct"; - if ($sCountryCodesSQL) $sSQL .= " join placex using (place_id)"; - $sSQL .= " where st_contains($this->sViewboxLargeSQL, ct.centroid)"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, ct.centroid) asc"; - $sSQL .= " limit $this->iLimit"; - if (CONST_Debug) var_dump($sSQL); - $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - } - } - else - { - $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; - $sSQL .= " and st_contains($this->sViewboxSmallSQL, geometry) and linked_place_id is null"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - if ($this->sViewboxCentreSQL) $sSQL .= " order by st_distance($this->sViewboxCentreSQL, centroid) asc"; - $sSQL .= " limit $this->iLimit"; + } else if ($aSearch['oNear']) { + $sSQL = "SELECT place_id "; + $sSQL .= "FROM placex "; + $sSQL .= "WHERE class='".$aSearch['sClass']."' "; + $sSQL .= " AND type='".$aSearch['sType']."'"; + $sSQL .= " AND ".$aSearch['oNear']->withinSQL('geometry'); + $sSQL .= " AND linked_place_id is null"; + if ($sCountryCodesSQL) $sSQL .= " AND country_code in ($sCountryCodesSQL)"; + $sSQL .= " ORDER BY ".$aSearch['oNear']->distanceSQL('centroid')." ASC"; + $sSQL .= " LIMIT $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); } } - } - // If a coordinate is given, the search must either - // be for a name or a special search. Ignore everythin else. - else if ($aSearch['fLon'] && !sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['sClass']) - { + } elseif ($aSearch['oNear'] && !sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && !$aSearch['sClass']) { + // If a coordinate is given, the search must either + // be for a name or a special search. Ignore everythin else. $aPlaceIDs = array(); - } - else - { + } elseif ($aSearch['sOperator'] == 'postcode') { + $sSQL = "SELECT p.place_id FROM location_postcode p "; + if (sizeof($aSearch['aAddress'])) { + $sSQL .= ", search_name s "; + $sSQL .= "WHERE s.place_id = p.parent_place_id "; + $sSQL .= "AND array_cat(s.nameaddress_vector, s.name_vector) @> ARRAY[".join($aSearch['aAddress'], ",")."] AND "; + } else { + $sSQL .= " WHERE "; + } + $sSQL .= "p.postcode = '".pg_escape_string(reset($aSearch['aName']))."'"; + if ($aSearch['sCountryCode']) { + $sSQL .= " AND p.country_code = '".$aSearch['sCountryCode']."'"; + } elseif ($sCountryCodesSQL) { + $sSQL .= " AND p.country_code in ($sCountryCodesSQL)"; + } + $sSQL .= " LIMIT $this->iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); + } else { $aPlaceIDs = array(); // First we need a position, either aName or fLat or both $aTerms = array(); $aOrder = array(); - if ($aSearch['sHouseNumber'] && sizeof($aSearch['aAddress'])) - { + if ($aSearch['sHouseNumber'] && sizeof($aSearch['aAddress'])) { $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; $aOrder[] = ""; - $aOrder[0] = " (exists(select place_id from placex where parent_place_id = search_name.place_id"; - $aOrder[0] .= " and transliteration(housenumber) ~* E'".$sHouseNumberRegex."' limit 1) "; + $aOrder[0] = " ("; + $aOrder[0] .= " EXISTS("; + $aOrder[0] .= " SELECT place_id "; + $aOrder[0] .= " FROM placex "; + $aOrder[0] .= " WHERE parent_place_id = search_name.place_id"; + $aOrder[0] .= " AND transliteration(housenumber) ~* E'".$sHouseNumberRegex."' "; + $aOrder[0] .= " LIMIT 1"; + $aOrder[0] .= " ) "; // also housenumbers from interpolation lines table are needed - $aOrder[0] .= " or exists(select place_id from location_property_osmline where parent_place_id = search_name.place_id"; - $aOrder[0] .= " and ".intval($aSearch['sHouseNumber']).">=startnumber and ".intval($aSearch['sHouseNumber'])."<=endnumber limit 1))"; - $aOrder[0] .= " desc"; + $aOrder[0] .= " OR EXISTS("; + $aOrder[0] .= " SELECT place_id "; + $aOrder[0] .= " FROM location_property_osmline "; + $aOrder[0] .= " WHERE parent_place_id = search_name.place_id"; + $aOrder[0] .= " AND startnumber is not NULL"; + $aOrder[0] .= " AND ".intval($aSearch['sHouseNumber']).">=startnumber "; + $aOrder[0] .= " AND ".intval($aSearch['sHouseNumber'])."<=endnumber "; + $aOrder[0] .= " LIMIT 1"; + $aOrder[0] .= " )"; + $aOrder[0] .= " )"; + $aOrder[0] .= " DESC"; } // TODO: filter out the pointless search terms (2 letter name tokens and less) // they might be right - but they are just too darned expensive to run - if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]"; - if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'],",")."]"; - if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress']) - { + if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'], ",")."]"; + //if (sizeof($aSearch['aNameNonSearch'])) $aTerms[] = "array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aNameNonSearch'], ",")."]"; + if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress']) { // For infrequent name terms disable index usage for address - if (CONST_Search_NameOnlySearchFrequencyThreshold && - sizeof($aSearch['aName']) == 1 && - $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold) - { - $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'],$aSearch['aAddressNonSearch']),",")."]"; - } - else - { - $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]"; - if (sizeof($aSearch['aAddressNonSearch'])) $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'],",")."]"; + if (CONST_Search_NameOnlySearchFrequencyThreshold + && sizeof($aSearch['aName']) == 1 + && $aWordFrequencyScores[$aSearch['aName'][reset($aSearch['aName'])]] < CONST_Search_NameOnlySearchFrequencyThreshold + ) { + //$aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join(array_merge($aSearch['aAddress'], $aSearch['aAddressNonSearch']), ",")."]"; + $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddress'],",")."]"; + } else { + $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'], ",")."]"; + /*if (sizeof($aSearch['aAddressNonSearch'])) { + $aTerms[] = "array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[".join($aSearch['aAddressNonSearch'], ",")."]"; + }*/ } } if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'"; - if ($aSearch['sHouseNumber']) - { + if ($aSearch['sHouseNumber']) { $aTerms[] = "address_rank between 16 and 27"; - } - else - { - if ($this->iMinAddressRank > 0) - { + } elseif (!$aSearch['sClass'] || $aSearch['sOperator'] == 'name') { + if ($this->iMinAddressRank > 0) { $aTerms[] = "address_rank >= ".$this->iMinAddressRank; } - if ($this->iMaxAddressRank < 30) - { + if ($this->iMaxAddressRank < 30) { $aTerms[] = "address_rank <= ".$this->iMaxAddressRank; } } - if ($aSearch['fLon'] && $aSearch['fLat']) - { - $aTerms[] = "ST_DWithin(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326), ".$aSearch['fRadius'].")"; - $aOrder[] = "ST_Distance(centroid, ST_SetSRID(ST_Point(".$aSearch['fLon'].",".$aSearch['fLat']."),4326)) ASC"; + if ($aSearch['oNear']) { + $aTerms[] = $aSearch['oNear']->withinSQL('centroid'); + + $aOrder[] = $aSearch['oNear']->distanceSQL('centroid'); + } elseif ($aSearch['sPostcode']) { + if (!sizeof($aSearch['aAddress'])) { + $aTerms[] = "EXISTS(SELECT place_id FROM location_postcode p WHERE p.postcode = '".$aSearch['sPostcode']."' AND ST_DWithin(search_name.centroid, p.geometry, 0.1))"; + } else { + $aOrder[] = "(SELECT min(ST_Distance(search_name.centroid, p.geometry)) FROM location_postcode p WHERE p.postcode = '".$aSearch['sPostcode']."')"; + } } - if (sizeof($this->aExcludePlaceIDs)) - { - $aTerms[] = "place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + if (sizeof($this->aExcludePlaceIDs)) { + $aTerms[] = "place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } - if ($sCountryCodesSQL) - { + if ($sCountryCodesSQL) { $aTerms[] = "country_code in ($sCountryCodesSQL)"; } if ($bBoundingBoxSearch) $aTerms[] = "centroid && $this->sViewboxSmallSQL"; - if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc"; + if ($oNearPoint) { + $aOrder[] = $oNearPoint->distanceSQL('centroid'); + } - if ($aSearch['sHouseNumber']) - { + if ($aSearch['sHouseNumber']) { $sImportanceSQL = '- abs(26 - address_rank) + 3'; + } else { + $sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75-(search_rank::float/40) ELSE importance END)'; } - else - { - $sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)'; - } - if ($this->sViewboxSmallSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END"; - if ($this->sViewboxLargeSQL) $sImportanceSQL .= " * case when ST_Contains($this->sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END"; + if ($this->sViewboxSmallSQL) $sImportanceSQL .= " * CASE WHEN ST_Contains($this->sViewboxSmallSQL, centroid) THEN 1 ELSE 0.5 END"; + if ($this->sViewboxLargeSQL) $sImportanceSQL .= " * CASE WHEN ST_Contains($this->sViewboxLargeSQL, centroid) THEN 1 ELSE 0.5 END"; $aOrder[] = "$sImportanceSQL DESC"; - if (sizeof($aSearch['aFullNameAddress'])) - { - $sExactMatchSQL = '(select count(*) from (select unnest(ARRAY['.join($aSearch['aFullNameAddress'],",").']) INTERSECT select unnest(nameaddress_vector))s) as exactmatch'; + if (sizeof($aSearch['aFullNameAddress'])) { + $sExactMatchSQL = ' ( '; + $sExactMatchSQL .= ' SELECT count(*) FROM ( '; + $sExactMatchSQL .= ' SELECT unnest(ARRAY['.join($aSearch['aFullNameAddress'], ",").']) '; + $sExactMatchSQL .= ' INTERSECT '; + $sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)'; + $sExactMatchSQL .= ' ) s'; + $sExactMatchSQL .= ') as exactmatch'; $aOrder[] = 'exactmatch DESC'; } else { $sExactMatchSQL = '0::int as exactmatch'; } - if (sizeof($aTerms)) - { - $sSQL = "select place_id, "; + if (sizeof($aTerms)) { + $sSQL = "SELECT place_id, "; $sSQL .= $sExactMatchSQL; - $sSQL .= " from search_name"; - $sSQL .= " where ".join(' and ',$aTerms); - $sSQL .= " order by ".join(', ',$aOrder); - if ($aSearch['sHouseNumber'] || $aSearch['sClass']) - $sSQL .= " limit 20"; - elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass']) - $sSQL .= " limit 1"; - else - $sSQL .= " limit ".$this->iLimit; - - if (CONST_Debug) { var_dump($sSQL); } - $aViewBoxPlaceIDs = chksql($this->oDB->getAll($sSQL), - "Could not get places for search terms."); + $sSQL .= " FROM search_name"; + $sSQL .= " WHERE ".join(' and ', $aTerms); + $sSQL .= " ORDER BY ".join(', ', $aOrder); + if ($aSearch['sHouseNumber'] || $aSearch['sClass']) { + $sSQL .= " LIMIT 20"; + } elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass']) { + $sSQL .= " LIMIT 1"; + } else { + $sSQL .= " LIMIT ".$this->iLimit; + } + + if (CONST_Debug) var_dump($sSQL); + $aViewBoxPlaceIDs = chksql( + $this->oDB->getAll($sSQL), + "Could not get places for search terms." + ); //var_dump($aViewBoxPlaceIDs); // Did we have an viewbox matches? $aPlaceIDs = array(); $bViewBoxMatch = false; - foreach($aViewBoxPlaceIDs as $aViewBoxRow) - { + foreach ($aViewBoxPlaceIDs as $aViewBoxRow) { //if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break; //if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break; //if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1; @@ -1394,53 +1495,55 @@ class Geocode //exit; //now search for housenumber, if housenumber provided - if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs)) - { + if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs)) { $searchedHousenumber = intval($aSearch['sHouseNumber']); $aRoadPlaceIDs = $aPlaceIDs; - $sPlaceIDs = join(',',$aPlaceIDs); + $sPlaceIDs = join(',', $aPlaceIDs); // Now they are indexed, look for a house attached to a street we found $sHouseNumberRegex = '\\\\m'.$aSearch['sHouseNumber'].'\\\\M'; - $sSQL = "select place_id from placex where parent_place_id in (".$sPlaceIDs.") and transliteration(housenumber) ~* E'".$sHouseNumberRegex."'"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + $sSQL = "SELECT place_id FROM placex "; + $sSQL .= "WHERE parent_place_id in (".$sPlaceIDs.") and transliteration(housenumber) ~* E'".$sHouseNumberRegex."'"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } - $sSQL .= " limit $this->iLimit"; + $sSQL .= " LIMIT $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - + // if nothing found, search in the interpolation line table - if(!sizeof($aPlaceIDs)) - { + if (!sizeof($aPlaceIDs)) { // do we need to use transliteration and the regex for housenumbers??? //new query for lines, not housenumbers anymore - if($searchedHousenumber%2 == 0){ + $sSQL = "SELECT distinct place_id FROM location_property_osmline"; + $sSQL .= " WHERE startnumber is not NULL and parent_place_id in (".$sPlaceIDs.") and ("; + if ($searchedHousenumber%2 == 0) { //if housenumber is even, look for housenumber in streets with interpolationtype even or all - $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - }else{ + $sSQL .= "interpolationtype='even'"; + } else { //look for housenumber in streets with interpolationtype odd or all - $sSQL = "select distinct place_id from location_property_osmline where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + $sSQL .= "interpolationtype='odd'"; } + $sSQL .= " or interpolationtype='all') and "; + $sSQL .= $searchedHousenumber.">=startnumber and "; + $sSQL .= $searchedHousenumber."<=endnumber"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } //$sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); //get place IDs $aPlaceIDs = chksql($this->oDB->getCol($sSQL, 0)); } - + // If nothing found try the aux fallback table - if (CONST_Use_Aux_Location_data && !sizeof($aPlaceIDs)) - { - $sSQL = "select place_id from location_property_aux where parent_place_id in (".$sPlaceIDs.") and housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and parent_place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + if (CONST_Use_Aux_Location_data && !sizeof($aPlaceIDs)) { + $sSQL = "SELECT place_id FROM location_property_aux "; + $sSQL .= " WHERE parent_place_id in (".$sPlaceIDs.") "; + $sSQL .= " AND housenumber = '".pg_escape_string($aSearch['sHouseNumber'])."'"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND parent_place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } //$sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); @@ -1448,20 +1551,20 @@ class Geocode } //if nothing was found in placex or location_property_aux, then search in Tiger data for this housenumber(location_property_tiger) - if (CONST_Use_US_Tiger_Data && !sizeof($aPlaceIDs)) - { - //new query for lines, not housenumbers anymore - if($searchedHousenumber%2 == 0){ - //if housenumber is even, look for housenumber in streets with interpolationtype even or all - $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='even' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; - }else{ - //look for housenumber in streets with interpolationtype odd or all - $sSQL = "select distinct place_id from location_property_tiger where parent_place_id in (".$sPlaceIDs.") and (interpolationtype='odd' or interpolationtype='all') and ".$searchedHousenumber.">=startnumber and ".$searchedHousenumber."<=endnumber"; + if (CONST_Use_US_Tiger_Data && !sizeof($aPlaceIDs)) { + $sSQL = "SELECT distinct place_id FROM location_property_tiger"; + $sSQL .= " WHERE parent_place_id in (".$sPlaceIDs.") and ("; + if ($searchedHousenumber%2 == 0) { + $sSQL .= "interpolationtype='even'"; + } else { + $sSQL .= "interpolationtype='odd'"; } + $sSQL .= " or interpolationtype='all') and "; + $sSQL .= $searchedHousenumber.">=startnumber and "; + $sSQL .= $searchedHousenumber."<=endnumber"; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } //$sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); @@ -1470,8 +1573,8 @@ class Geocode } // Fallback to the road (if no housenumber was found) - if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber'])) - { + if (!sizeof($aPlaceIDs) && preg_match('/[0-9]+/', $aSearch['sHouseNumber']) + && ($aSearch['aAddress'] || $aSearch['sCountryCode'])) { $aPlaceIDs = $aRoadPlaceIDs; //set to -1, if no housenumbers were found $searchedHousenumber = -1; @@ -1480,155 +1583,196 @@ class Geocode } - if ($aSearch['sClass'] && sizeof($aPlaceIDs)) - { + if ($aSearch['sClass'] && sizeof($aPlaceIDs)) { $sPlaceIDs = join(',', $aPlaceIDs); $aClassPlaceIDs = array(); - if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name') - { + if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'name') { // If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match - $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; - $sSQL .= " and linked_place_id is null"; - if ($sCountryCodesSQL) $sSQL .= " and calculated_country_code in ($sCountryCodesSQL)"; - $sSQL .= " order by rank_search asc limit $this->iLimit"; + $sSQL = "SELECT place_id "; + $sSQL .= " FROM placex "; + $sSQL .= " WHERE place_id in ($sPlaceIDs) "; + $sSQL .= " AND class='".$aSearch['sClass']."' "; + $sSQL .= " AND type='".$aSearch['sType']."'"; + $sSQL .= " AND linked_place_id is null"; + if ($sCountryCodesSQL) $sSQL .= " AND country_code in ($sCountryCodesSQL)"; + $sSQL .= " ORDER BY rank_search ASC "; + $sSQL .= " LIMIT $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aClassPlaceIDs = chksql($this->oDB->getCol($sSQL)); } - if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in - { - $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') { // & in + $sClassTable = 'place_classtype_'.$aSearch['sClass'].'_'.$aSearch['sType']; + $sSQL = "SELECT count(*) FROM pg_tables "; + $sSQL .= "WHERE tablename = '$sClassTable'"; $bCacheTable = chksql($this->oDB->getOne($sSQL)); - $sSQL = "select min(rank_search) from placex where place_id in ($sPlaceIDs)"; + $sSQL = "SELECT min(rank_search) FROM placex WHERE place_id in ($sPlaceIDs)"; if (CONST_Debug) var_dump($sSQL); $this->iMaxRank = ((int)chksql($this->oDB->getOne($sSQL))); // For state / country level searches the normal radius search doesn't work very well $sPlaceGeom = false; - if ($this->iMaxRank < 9 && $bCacheTable) - { + if ($this->iMaxRank < 9 && $bCacheTable) { // Try and get a polygon to search in instead - $sSQL = "select geometry from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1"; + $sSQL = "SELECT geometry "; + $sSQL .= " FROM placex"; + $sSQL .= " WHERE place_id in ($sPlaceIDs)"; + $sSQL .= " AND rank_search < $this->iMaxRank + 5"; + $sSQL .= " AND ST_Geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon')"; + $sSQL .= " ORDER BY rank_search ASC "; + $sSQL .= " LIMIT 1"; if (CONST_Debug) var_dump($sSQL); $sPlaceGeom = chksql($this->oDB->getOne($sSQL)); } - if ($sPlaceGeom) - { + if ($sPlaceGeom) { $sPlaceIDs = false; - } - else - { + } else { $this->iMaxRank += 5; - $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank"; + $sSQL = "SELECT place_id FROM placex WHERE place_id in ($sPlaceIDs) and rank_search < $this->iMaxRank"; if (CONST_Debug) var_dump($sSQL); $aPlaceIDs = chksql($this->oDB->getCol($sSQL)); - $sPlaceIDs = join(',',$aPlaceIDs); + $sPlaceIDs = join(',', $aPlaceIDs); } - if ($sPlaceIDs || $sPlaceGeom) - { - + if ($sPlaceIDs || $sPlaceGeom) { $fRange = 0.01; - if ($bCacheTable) - { + if ($bCacheTable) { // More efficient - can make the range bigger $fRange = 0.05; $sOrderBySQL = ''; - if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.centroid)"; - else if ($sPlaceIDs) $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)"; - else if ($sPlaceGeom) $sOrderBysSQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)"; + if ($oNearPoint) { + $sOrderBySQL = $oNearPoint->distanceSQL('l.centroid'); + } elseif ($sPlaceIDs) { + $sOrderBySQL = "ST_Distance(l.centroid, f.geometry)"; + } elseif ($sPlaceGeom) { + $sOrderBySQL = "ST_Distance(st_centroid('".$sPlaceGeom."'), l.centroid)"; + } - $sSQL = "select distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:'')." from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l"; + $sSQL = "select distinct i.place_id".($sOrderBySQL?', i.order_term':'')." from ("; + $sSQL .= "select l.place_id".($sOrderBySQL?','.$sOrderBySQL.' as order_term':'')." from ".$sClassTable." as l"; if ($sCountryCodesSQL) $sSQL .= " join placex as lp using (place_id)"; - if ($sPlaceIDs) - { + if ($sPlaceIDs) { $sSQL .= ",placex as f where "; $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, f.centroid, $fRange) "; } - if ($sPlaceGeom) - { + if ($sPlaceGeom) { $sSQL .= " where "; $sSQL .= "ST_Contains('".$sPlaceGeom."', l.centroid) "; } - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and l.place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " and l.place_id not in (".join(',', $this->aExcludePlaceIDs).")"; } - if ($sCountryCodesSQL) $sSQL .= " and lp.calculated_country_code in ($sCountryCodesSQL)"; - if ($sOrderBySQL) $sSQL .= "order by ".$sOrderBySQL." asc"; + if ($sCountryCodesSQL) $sSQL .= " and lp.country_code in ($sCountryCodesSQL)"; + $sSQL .= 'limit 300) i '; + if ($sOrderBySQL) $sSQL .= "order by order_term asc"; if ($this->iOffset) $sSQL .= " offset $this->iOffset"; $sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); - } - else - { - if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius']; + } else { + if ($aSearch['oNear']) { + $fRange = $aSearch['oNear']->radius(); + } $sOrderBySQL = ''; - if ($sNearPointSQL) $sOrderBySQL = "ST_Distance($sNearPointSQL, l.geometry)"; - else $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)"; - - $sSQL = "select distinct l.place_id".($sOrderBysSQL?','.$sOrderBysSQL:'')." from placex as l,placex as f where "; - $sSQL .= "f.place_id in ( $sPlaceIDs) and ST_DWithin(l.geometry, f.centroid, $fRange) "; - $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' "; - if (sizeof($this->aExcludePlaceIDs)) - { - $sSQL .= " and l.place_id not in (".join(',',$this->aExcludePlaceIDs).")"; + if ($oNearPoint) { + $sOrderBySQL = $oNearPoint->distanceSQL('l.geometry'); + } else { + $sOrderBySQL = "ST_Distance(l.geometry, f.geometry)"; } - if ($sCountryCodesSQL) $sSQL .= " and l.calculated_country_code in ($sCountryCodesSQL)"; - if ($sOrderBy) $sSQL .= "order by ".$OrderBysSQL." asc"; - if ($this->iOffset) $sSQL .= " offset $this->iOffset"; + + $sSQL = "SELECT distinct l.place_id".($sOrderBySQL?','.$sOrderBySQL:''); + $sSQL .= " FROM placex as l, placex as f "; + $sSQL .= " WHERE f.place_id in ($sPlaceIDs) "; + $sSQL .= " AND ST_DWithin(l.geometry, f.centroid, $fRange) "; + $sSQL .= " AND l.class='".$aSearch['sClass']."' "; + $sSQL .= " AND l.type='".$aSearch['sType']."' "; + if (sizeof($this->aExcludePlaceIDs)) { + $sSQL .= " AND l.place_id not in (".join(',', $this->aExcludePlaceIDs).")"; + } + if ($sCountryCodesSQL) $sSQL .= " AND l.country_code in ($sCountryCodesSQL)"; + if ($sOrderBySQL) $sSQL .= "ORDER BY ".$sOrderBySQL." ASC"; + if ($this->iOffset) $sSQL .= " OFFSET $this->iOffset"; $sSQL .= " limit $this->iLimit"; if (CONST_Debug) var_dump($sSQL); $aClassPlaceIDs = array_merge($aClassPlaceIDs, chksql($this->oDB->getCol($sSQL))); } } } - $aPlaceIDs = $aClassPlaceIDs; - } + } + if (CONST_Debug) { + echo "
Place IDs: "; + var_Dump($aPlaceIDs); } - if (CONST_Debug) { echo "
Place IDs: "; var_Dump($aPlaceIDs); } + if (sizeof($aPlaceIDs) && $aSearch['sPostcode']) { + $sSQL = 'SELECT place_id FROM placex'; + $sSQL .= ' WHERE place_id in ('.join(',', $aPlaceIDs).')'; + $sSQL .= " AND postcode = '".pg_escape_string($aSearch['sPostcode'])."'"; + if (CONST_Debug) var_dump($sSQL); + $aFilteredPlaceIDs = chksql($this->oDB->getCol($sSQL)); + if ($aFilteredPlaceIDs) { + $aPlaceIDs = $aFilteredPlaceIDs; + if (CONST_Debug) { + echo "
Place IDs after postcode filtering: "; + var_Dump($aPlaceIDs); + } + } + } - foreach($aPlaceIDs as $iPlaceID) - { + foreach ($aPlaceIDs as $iPlaceID) { // array for placeID => -1 | Tiger housenumber $aResultPlaceIDs[$iPlaceID] = $searchedHousenumber; } if ($iQueryLoop > 20) break; } - if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($this->iMinAddressRank != 0 || $this->iMaxAddressRank != 30)) - { + if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs) && ($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 - $sSQL = "select place_id from placex where place_id in (".join(',',array_keys($aResultPlaceIDs)).") "; - $sSQL .= "and (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).")"; - if (CONST_Use_US_Tiger_Data) - { - $sSQL .= ") UNION select place_id from location_property_tiger where place_id in (".join(',',array_keys($aResultPlaceIDs)).") "; - $sSQL .= "and (30 between $this->iMinAddressRank and $this->iMaxAddressRank "; - if ($this->aAddressRankList) $sSQL .= " OR 30 in (".join(',',$this->aAddressRankList).")"; + $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).")"; + } + $sSQL .= ") "; + if (CONST_Use_US_Tiger_Data && $this->iMaxAddressRank == 30) { + $sSQL .= "UNION "; + $sSQL .= " SELECT place_id "; + $sSQL .= " FROM location_property_tiger ".$sWherePlaceId; + } + if ($this->iMaxAddressRank == 30) { + $sSQL .= "UNION "; + $sSQL .= " SELECT place_id "; + $sSQL .= " FROM location_property_osmline ".$sWherePlaceId; } - $sSQL .= ") UNION select place_id from location_property_osmline where place_id in (".join(',',array_keys($aResultPlaceIDs)).")"; - $sSQL .= " and (30 between $this->iMinAddressRank and $this->iMaxAddressRank)"; if (CONST_Debug) var_dump($sSQL); $aFilteredPlaceIDs = chksql($this->oDB->getCol($sSQL)); $tempIDs = array(); - foreach($aFilteredPlaceIDs as $placeID) - { + foreach ($aFilteredPlaceIDs as $placeID) { $tempIDs[$placeID] = $aResultPlaceIDs[$placeID]; //assign housenumber to placeID } $aResultPlaceIDs = $tempIDs; @@ -1641,37 +1785,34 @@ class Geocode } // Did we find anything? - if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) - { + if (isset($aResultPlaceIDs) && sizeof($aResultPlaceIDs)) { $aSearchResults = $this->getDetails($aResultPlaceIDs); } - - } - else - { + } else { // Just interpret as a reverse geocode $oReverse = new ReverseGeocode($this->oDB); $oReverse->setZoom(18); - $aLookup = $oReverse->lookup((float)$this->aNearPoint[0], - (float)$this->aNearPoint[1], - false); + $aLookup = $oReverse->lookup( + $oNearPoint->lat(), + $oNearPoint->lon(), + false + ); if (CONST_Debug) var_dump("Reverse search", $aLookup); - if ($aLookup['place_id']) + if ($aLookup['place_id']) { $aSearchResults = $this->getDetails(array($aLookup['place_id'] => -1)); - else + $aResultPlaceIDs[$aLookup['place_id']] = -1; + } else { $aSearchResults = array(); + } } // No results? Done - if (!sizeof($aSearchResults)) - { - if ($this->bFallback) - { - if ($this->fallbackStructuredQuery()) - { + if (!sizeof($aSearchResults)) { + if ($this->bFallback) { + if ($this->fallbackStructuredQuery()) { return $this->lookup(); } } @@ -1680,13 +1821,15 @@ class Geocode } $aClassType = getClassTypesWithImportance(); - $aRecheckWords = preg_split('/\b[\s,\\-]*/u',$sQuery); - foreach($aRecheckWords as $i => $sWord) - { - if (!preg_match('/\pL/', $sWord)) unset($aRecheckWords[$i]); + $aRecheckWords = preg_split('/\b[\s,\\-]*/u', $sQuery); + foreach ($aRecheckWords as $i => $sWord) { + if (!preg_match('/[\pL\pN]/', $sWord)) unset($aRecheckWords[$i]); } - if (CONST_Debug) { echo 'Recheck words:<\i>'; var_dump($aRecheckWords); } + if (CONST_Debug) { + echo 'Recheck words:<\i>'; + var_dump($aRecheckWords); + } $oPlaceLookup = new PlaceLookup($this->oDB); $oPlaceLookup->setIncludePolygonAsPoints($this->bIncludePolygonAsPoints); @@ -1696,19 +1839,16 @@ class Geocode $oPlaceLookup->setIncludePolygonAsSVG($this->bIncludePolygonAsSVG); $oPlaceLookup->setPolygonSimplificationThreshold($this->fPolygonSimplificationThreshold); - foreach($aSearchResults as $iResNum => $aResult) - { + foreach ($aSearchResults as $iResNum => $aResult) { // Default $fDiameter = getResultDiameter($aResult); $aOutlineResult = $oPlaceLookup->getOutlines($aResult['place_id'], $aResult['lon'], $aResult['lat'], $fDiameter/2); - if ($aOutlineResult) - { + if ($aOutlineResult) { $aResult = array_merge($aResult, $aOutlineResult); } - if ($aResult['extra_place'] == 'city') - { + if ($aResult['extra_place'] == 'city') { $aResult['class'] = 'place'; $aResult['type'] = 'city'; $aResult['rank_search'] = 16; @@ -1716,63 +1856,51 @@ class Geocode // Is there an icon set for this type of result? if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['icon']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['icon']) - { + && $aClassType[$aResult['class'].':'.$aResult['type']]['icon'] + ) { $aResult['icon'] = CONST_Website_BaseURL.'images/mapicons/'.$aClassType[$aResult['class'].':'.$aResult['type']]['icon'].'.p.20.png'; } if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']) - && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']) - { + && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label'] + ) { $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['label']; - } - elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['label']) - { + } elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['label']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['label'] + ) { $aResult['label'] = $aClassType[$aResult['class'].':'.$aResult['type']]['label']; } // if tag '&addressdetails=1' is set in query - if ($this->bIncludeAddressDetails) - { + 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']]); - if ($aResult['extra_place'] == 'city' && !isset($aResult['address']['city'])) - { - $aResult['address'] = array_merge(array('city' => array_shift(array_values($aResult['address']))), $aResult['address']); + 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']) - { + + if ($this->bIncludeExtraTags) { + if ($aResult['extra']) { $aResult['sExtraTags'] = json_decode($aResult['extra']); - } - else - { + } else { $aResult['sExtraTags'] = (object) array(); } } - if ($this->bIncludeNameDetails) - { - if ($aResult['names']) - { + if ($this->bIncludeNameDetails) { + if ($aResult['names']) { $aResult['sNameDetails'] = json_decode($aResult['names']); - } - else - { + } else { $aResult['sNameDetails'] = (object) array(); } } // Adjust importance for the number of exact string matches in the result - $aResult['importance'] = max(0.001,$aResult['importance']); + $aResult['importance'] = max(0.001, $aResult['importance']); $iCountWords = 0; $sAddress = $aResult['langaddress']; - foreach($aRecheckWords as $i => $sWord) - { - if (stripos($sAddress, $sWord)!==false) - { + foreach ($aRecheckWords as $i => $sWord) { + if (stripos($sAddress, $sWord)!==false) { $iCountWords++; if (preg_match("/(^|,)\s*".preg_quote($sWord, '/')."\s*(,|$)/", $sAddress)) $iCountWords += 0.1; } @@ -1782,24 +1910,23 @@ class Geocode $aResult['name'] = $aResult['langaddress']; // secondary ordering (for results with same importance (the smaller the better): - // - approximate importance of address parts + // - approximate importance of address parts $aResult['foundorder'] = -$aResult['addressimportance']/10; - // - number of exact matches from the query - if (isset($this->exactMatchCache[$aResult['place_id']])) + // - number of exact matches from the query + if (isset($this->exactMatchCache[$aResult['place_id']])) { $aResult['foundorder'] -= $this->exactMatchCache[$aResult['place_id']]; - else if (isset($this->exactMatchCache[$aResult['parent_place_id']])) + } elseif (isset($this->exactMatchCache[$aResult['parent_place_id']])) { $aResult['foundorder'] -= $this->exactMatchCache[$aResult['parent_place_id']]; - // - importance of the class/type + } + // - importance of the class/type if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance']) - && $aClassType[$aResult['class'].':'.$aResult['type']]['importance']) - { + && $aClassType[$aResult['class'].':'.$aResult['type']]['importance'] + ) { $aResult['foundorder'] += 0.0001 * $aClassType[$aResult['class'].':'.$aResult['type']]['importance']; - } - else - { + } else { $aResult['foundorder'] += 0.01; } - if (CONST_Debug) { var_dump($aResult); } + if (CONST_Debug) var_dump($aResult); $aSearchResults[$iResNum] = $aResult; } uasort($aSearchResults, 'byImportance'); @@ -1810,19 +1937,17 @@ class Geocode $aSearchResults = array(); $bFirst = true; - foreach($aToFilter as $iResNum => $aResult) - { + foreach ($aToFilter as $iResNum => $aResult) { $this->aExcludePlaceIDs[$aResult['place_id']] = $aResult['place_id']; - if ($bFirst) - { + if ($bFirst) { $fLat = $aResult['lat']; $fLon = $aResult['lon']; if (isset($aResult['zoom'])) $iZoom = $aResult['zoom']; $bFirst = false; } if (!$this->bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) - && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']]))) - { + && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']])) + ) { $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; $aClassTypeNameDone[$aResult['osm_type'].$aResult['class'].$aResult['type'].$aResult['name'].$aResult['admin_level']] = true; $aSearchResults[] = $aResult; @@ -1833,9 +1958,5 @@ class Geocode } return $aSearchResults; - } // end lookup() - - } // end class -