2013-08-27 20:08:57 +04:00
< ? php
2016-09-16 03:27:36 +03:00
namespace Nominatim ;
2017-03-15 22:43:08 +03:00
require_once ( CONST_BasePath . '/lib/NearPoint.php' );
2016-09-04 04:19:48 +03:00
require_once ( CONST_BasePath . '/lib/PlaceLookup.php' );
require_once ( CONST_BasePath . '/lib/ReverseGeocode.php' );
2017-10-08 16:26:14 +03:00
require_once ( CONST_BasePath . '/lib/SearchDescription.php' );
2016-09-04 04:19:48 +03:00
class Geocode
{
protected $oDB ;
protected $aLangPrefOrder = array ();
protected $bIncludeAddressDetails = false ;
protected $bIncludeExtraTags = false ;
protected $bIncludeNameDetails = false ;
protected $bIncludePolygonAsPoints = false ;
protected $bIncludePolygonAsText = false ;
protected $bIncludePolygonAsGeoJSON = false ;
protected $bIncludePolygonAsKML = false ;
protected $bIncludePolygonAsSVG = false ;
protected $fPolygonSimplificationThreshold = 0.0 ;
protected $aExcludePlaceIDs = array ();
protected $bDeDupe = true ;
protected $bReverseInPlan = false ;
protected $iLimit = 20 ;
protected $iFinalLimit = 10 ;
protected $iOffset = 0 ;
protected $bFallback = false ;
protected $aCountryCodes = false ;
protected $bBoundedSearch = false ;
protected $aViewBox = false ;
protected $sViewboxCentreSQL = false ;
protected $sViewboxSmallSQL = false ;
protected $sViewboxLargeSQL = false ;
protected $iMaxRank = 20 ;
protected $iMinAddressRank = 0 ;
protected $iMaxAddressRank = 30 ;
protected $aAddressRankList = array ();
protected $exactMatchCache = array ();
protected $sAllowedTypesSQLList = false ;
protected $sQuery = false ;
protected $aStructuredQuery = false ;
2017-07-07 00:06:13 +03:00
protected $oNormalizer = null ;
2016-09-14 04:16:46 +03:00
2016-09-16 03:27:36 +03:00
public function __construct ( & $oDB )
2016-09-04 04:19:48 +03:00
{
$this -> oDB =& $oDB ;
2017-07-07 00:06:13 +03:00
$this -> oNormalizer = \Transliterator :: createFromRules ( CONST_Term_Normalization_Rules );
}
private function normTerm ( $sTerm )
{
if ( $this -> oNormalizer === null ) {
2017-09-30 10:39:47 +03:00
return $sTerm ;
2017-07-07 00:06:13 +03:00
}
return $this -> oNormalizer -> transliterate ( $sTerm );
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setReverseInPlan ( $bReverse )
2016-09-04 04:19:48 +03:00
{
$this -> bReverseInPlan = $bReverse ;
}
2016-09-16 03:27:36 +03:00
public function setLanguagePreference ( $aLangPref )
2016-09-04 04:19:48 +03:00
{
$this -> aLangPrefOrder = $aLangPref ;
}
2017-03-23 02:16:58 +03:00
public function getMoreUrlParams ()
2016-09-04 04:19:48 +03:00
{
2017-03-23 02:16:58 +03:00
if ( $this -> aStructuredQuery ) {
$aParams = $this -> aStructuredQuery ;
} else {
$aParams = array ( 'q' => $this -> sQuery );
}
2016-09-04 04:19:48 +03:00
2017-03-23 02:16:58 +03:00
if ( $this -> aExcludePlaceIDs ) {
$aParams [ 'exclude_place_ids' ] = implode ( ',' , $this -> aExcludePlaceIDs );
}
2016-09-04 04:19:48 +03:00
2017-03-23 02:16:58 +03:00
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 ;
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setIncludePolygonAsPoints ( $b = true )
2016-09-04 04:19:48 +03:00
{
$this -> bIncludePolygonAsPoints = $b ;
}
2016-09-16 03:27:36 +03:00
public function setIncludePolygonAsText ( $b = true )
2016-09-04 04:19:48 +03:00
{
$this -> bIncludePolygonAsText = $b ;
}
2016-09-16 03:27:36 +03:00
public function setIncludePolygonAsGeoJSON ( $b = true )
2016-09-04 04:19:48 +03:00
{
$this -> bIncludePolygonAsGeoJSON = $b ;
}
2016-09-16 03:27:36 +03:00
public function setIncludePolygonAsKML ( $b = true )
2016-09-04 04:19:48 +03:00
{
$this -> bIncludePolygonAsKML = $b ;
}
2016-09-16 03:27:36 +03:00
public function setIncludePolygonAsSVG ( $b = true )
2016-09-04 04:19:48 +03:00
{
$this -> bIncludePolygonAsSVG = $b ;
}
2016-09-16 03:27:36 +03:00
public function setPolygonSimplificationThreshold ( $f )
2016-09-04 04:19:48 +03:00
{
$this -> fPolygonSimplificationThreshold = $f ;
}
2016-09-16 03:27:36 +03:00
public function setLimit ( $iLimit = 10 )
2016-09-04 04:19:48 +03:00
{
if ( $iLimit > 50 ) $iLimit = 50 ;
if ( $iLimit < 1 ) $iLimit = 1 ;
$this -> iFinalLimit = $iLimit ;
$this -> iLimit = $iLimit + min ( $iLimit , 10 );
}
2016-09-16 03:27:36 +03:00
public function setFeatureType ( $sFeatureType )
2016-09-04 04:19:48 +03:00
{
2016-09-08 04:16:22 +03:00
switch ( $sFeatureType ) {
2016-09-14 04:16:46 +03:00
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 ;
2016-09-04 04:19:48 +03:00
}
}
2016-09-16 03:27:36 +03:00
public function setRankRange ( $iMin , $iMax )
2016-09-04 04:19:48 +03:00
{
$this -> iMinAddressRank = $iMin ;
$this -> iMaxAddressRank = $iMax ;
}
2016-09-16 03:27:36 +03:00
public function setRoute ( $aRoutePoints , $fRouteWidth )
2016-09-04 04:19:48 +03:00
{
$this -> aViewBox = false ;
$this -> sViewboxCentreSQL = " ST_SetSRID('LINESTRING( " ;
$sSep = '' ;
2016-10-09 22:41:23 +03:00
foreach ( $aRoutePoints as $aPoint ) {
2016-09-04 04:19:48 +03:00
$fPoint = ( float ) $aPoint ;
$this -> sViewboxCentreSQL .= $sSep . $fPoint ;
$sSep = ( $sSep == ' ' ) ? ',' : ' ' ;
}
$this -> sViewboxCentreSQL .= " )'::geometry,4326) " ;
2016-10-29 19:49:21 +03:00
$this -> sViewboxSmallSQL = 'ST_BUFFER(' . $this -> sViewboxCentreSQL ;
2016-09-04 04:19:48 +03:00
$this -> sViewboxSmallSQL .= ',' . ( $fRouteWidth / 69 ) . ')' ;
2016-10-29 19:49:21 +03:00
$this -> sViewboxLargeSQL = 'ST_BUFFER(' . $this -> sViewboxCentreSQL ;
2016-09-04 04:19:48 +03:00
$this -> sViewboxLargeSQL .= ',' . ( $fRouteWidth / 30 ) . ')' ;
}
2016-09-16 03:27:36 +03:00
public function setViewbox ( $aViewbox )
2016-09-04 04:19:48 +03:00
{
$this -> aViewBox = array_map ( 'floatval' , $aViewbox );
2016-10-13 09:03:28 +03:00
$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
2016-10-12 23:13:07 +03:00
) {
2016-10-13 09:03:28 +03:00
userError ( " Bad parameter 'viewbox'. Not a box. " );
2016-10-12 23:13:07 +03:00
}
2016-09-04 04:19:48 +03:00
$fHeight = $this -> aViewBox [ 0 ] - $this -> aViewBox [ 2 ];
$fWidth = $this -> aViewBox [ 1 ] - $this -> aViewBox [ 3 ];
$aBigViewBox [ 0 ] = $this -> aViewBox [ 0 ] + $fHeight ;
$aBigViewBox [ 2 ] = $this -> aViewBox [ 2 ] - $fHeight ;
$aBigViewBox [ 1 ] = $this -> aViewBox [ 1 ] + $fWidth ;
$aBigViewBox [ 3 ] = $this -> aViewBox [ 3 ] - $fWidth ;
$this -> sViewboxCentreSQL = false ;
2016-10-29 19:49:21 +03:00
$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 ]
);
2016-09-04 04:19:48 +03:00
}
2016-09-16 03:27:36 +03:00
public function setQuery ( $sQueryString )
2016-09-04 04:19:48 +03:00
{
$this -> sQuery = $sQueryString ;
$this -> aStructuredQuery = false ;
}
2016-09-16 03:27:36 +03:00
public function getQueryString ()
2016-09-04 04:19:48 +03:00
{
return $this -> sQuery ;
}
2016-09-16 03:27:36 +03:00
public function loadParamArray ( $oParams )
2016-09-04 04:19:48 +03:00
{
2016-09-11 06:22:51 +03:00
$this -> bIncludeAddressDetails
= $oParams -> getBool ( 'addressdetails' , $this -> bIncludeAddressDetails );
$this -> bIncludeExtraTags
= $oParams -> getBool ( 'extratags' , $this -> bIncludeExtraTags );
$this -> bIncludeNameDetails
= $oParams -> getBool ( 'namedetails' , $this -> bIncludeNameDetails );
2016-09-04 04:19:48 +03:00
$this -> bBoundedSearch = $oParams -> getBool ( 'bounded' , $this -> bBoundedSearch );
$this -> bDeDupe = $oParams -> getBool ( 'dedupe' , $this -> bDeDupe );
$this -> setLimit ( $oParams -> getInt ( 'limit' , $this -> iFinalLimit ));
$this -> iOffset = $oParams -> getInt ( 'offset' , $this -> iOffset );
$this -> bFallback = $oParams -> getBool ( 'fallback' , $this -> bFallback );
// List of excluded Place IDs - used for more acurate pageing
$sExcluded = $oParams -> getStringList ( 'exclude_place_ids' );
2016-09-08 04:16:22 +03:00
if ( $sExcluded ) {
foreach ( $sExcluded as $iExcludedPlaceID ) {
2016-09-04 04:19:48 +03:00
$iExcludedPlaceID = ( int ) $iExcludedPlaceID ;
if ( $iExcludedPlaceID )
$aExcludePlaceIDs [ $iExcludedPlaceID ] = $iExcludedPlaceID ;
}
if ( isset ( $aExcludePlaceIDs ))
$this -> aExcludePlaceIDs = $aExcludePlaceIDs ;
}
// Only certain ranks of feature
$sFeatureType = $oParams -> getString ( 'featureType' );
if ( ! $sFeatureType ) $sFeatureType = $oParams -> getString ( 'featuretype' );
if ( $sFeatureType ) $this -> setFeatureType ( $sFeatureType );
// Country code list
$sCountries = $oParams -> getStringList ( 'countrycodes' );
2016-09-08 04:16:22 +03:00
if ( $sCountries ) {
foreach ( $sCountries as $sCountryCode ) {
if ( preg_match ( '/^[a-zA-Z][a-zA-Z]$/' , $sCountryCode )) {
2016-09-04 04:19:48 +03:00
$aCountries [] = strtolower ( $sCountryCode );
}
}
2016-10-04 21:32:22 +03:00
if ( isset ( $aCountries ))
2016-09-04 04:19:48 +03:00
$this -> aCountryCodes = $aCountries ;
}
$aViewbox = $oParams -> getStringList ( 'viewboxlbrt' );
2016-09-08 04:16:22 +03:00
if ( $aViewbox ) {
2016-10-12 23:13:07 +03:00
if ( count ( $aViewbox ) != 4 ) {
2017-09-18 00:30:08 +03:00
userError ( " Bad parmater 'viewboxlbrt'. Expected 4 coordinates. " );
2016-10-12 23:13:07 +03:00
}
2016-09-04 04:19:48 +03:00
$this -> setViewbox ( $aViewbox );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aViewbox = $oParams -> getStringList ( 'viewbox' );
2016-09-08 04:16:22 +03:00
if ( $aViewbox ) {
2016-10-12 23:13:07 +03:00
if ( count ( $aViewbox ) != 4 ) {
userError ( " Bad parmater 'viewbox'. Expected 4 coordinates. " );
}
2017-09-30 15:16:07 +03:00
$this -> setViewBox ( $aViewbox );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aRoute = $oParams -> getStringList ( 'route' );
$fRouteWidth = $oParams -> getFloat ( 'routewidth' );
2016-09-08 04:16:22 +03:00
if ( $aRoute && $fRouteWidth ) {
2016-09-04 04:19:48 +03:00
$this -> setRoute ( $aRoute , $fRouteWidth );
}
}
}
}
2016-09-16 03:27:36 +03:00
public function setQueryFromParams ( $oParams )
2016-09-04 04:19:48 +03:00
{
// Search query
$sQuery = $oParams -> getString ( 'q' );
2016-09-08 04:16:22 +03:00
if ( ! $sQuery ) {
2016-09-11 06:22:51 +03:00
$this -> setStructuredQuery (
$oParams -> getString ( 'amenity' ),
$oParams -> getString ( 'street' ),
$oParams -> getString ( 'city' ),
$oParams -> getString ( 'county' ),
$oParams -> getString ( 'state' ),
$oParams -> getString ( 'country' ),
$oParams -> getString ( 'postalcode' )
);
2016-09-04 04:19:48 +03:00
$this -> setReverseInPlan ( false );
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$this -> setQuery ( $sQuery );
}
}
2016-09-16 03:27:36 +03:00
public function loadStructuredAddressElement ( $sValue , $sKey , $iNewMinAddressRank , $iNewMaxAddressRank , $aItemListValues )
2016-09-04 04:19:48 +03:00
{
$sValue = trim ( $sValue );
if ( ! $sValue ) return false ;
$this -> aStructuredQuery [ $sKey ] = $sValue ;
2016-09-08 04:16:22 +03:00
if ( $this -> iMinAddressRank == 0 && $this -> iMaxAddressRank == 30 ) {
2016-09-04 04:19:48 +03:00
$this -> iMinAddressRank = $iNewMinAddressRank ;
$this -> iMaxAddressRank = $iNewMaxAddressRank ;
}
if ( $aItemListValues ) $this -> aAddressRankList = array_merge ( $this -> aAddressRankList , $aItemListValues );
return true ;
}
2017-03-23 02:16:58 +03:00
public function setStructuredQuery ( $sAmenity = false , $sStreet = false , $sCity = false , $sCounty = false , $sState = false , $sCountry = false , $sPostalCode = false )
2016-09-04 04:19:48 +03:00
{
$this -> sQuery = false ;
// Reset
$this -> iMinAddressRank = 0 ;
$this -> iMaxAddressRank = 30 ;
$this -> aAddressRankList = array ();
$this -> aStructuredQuery = array ();
2017-10-08 18:00:59 +03:00
$this -> sAllowedTypesSQLList = false ;
2016-09-04 04:19:48 +03:00
2017-03-23 02:16:58 +03:00
$this -> loadStructuredAddressElement ( $sAmenity , 'amenity' , 26 , 30 , false );
2016-09-04 04:19:48 +03:00
$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 );
2016-09-11 06:22:51 +03:00
$this -> loadStructuredAddressElement ( $sPostalCode , 'postalcode' , 5 , 11 , array ( 5 , 11 ));
2016-09-04 04:19:48 +03:00
$this -> loadStructuredAddressElement ( $sCountry , 'country' , 4 , 4 , false );
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aStructuredQuery ) > 0 ) {
2016-09-04 04:19:48 +03:00
$this -> sQuery = join ( ', ' , $this -> aStructuredQuery );
2016-09-08 04:16:22 +03:00
if ( $this -> iMaxAddressRank < 30 ) {
2017-09-18 00:30:08 +03:00
$this -> sAllowedTypesSQLList = '(\'place\',\'boundary\')' ;
2016-09-04 04:19:48 +03:00
}
}
}
2016-09-16 03:27:36 +03:00
public function fallbackStructuredQuery ()
2016-09-04 04:19:48 +03:00
{
if ( ! $this -> aStructuredQuery ) return false ;
$aParams = $this -> aStructuredQuery ;
if ( sizeof ( $aParams ) == 1 ) return false ;
$aOrderToFallback = array ( 'postalcode' , 'street' , 'city' , 'county' , 'state' );
2016-09-08 04:16:22 +03:00
foreach ( $aOrderToFallback as $sType ) {
if ( isset ( $aParams [ $sType ])) {
2016-09-04 04:19:48 +03:00
unset ( $aParams [ $sType ]);
$this -> setStructuredQuery ( @ $aParams [ 'amenity' ], @ $aParams [ 'street' ], @ $aParams [ 'city' ], @ $aParams [ 'county' ], @ $aParams [ 'state' ], @ $aParams [ 'country' ], @ $aParams [ 'postalcode' ]);
return true ;
}
}
return false ;
}
2016-09-16 03:27:36 +03:00
public function getDetails ( $aPlaceIDs )
2016-09-04 04:19:48 +03:00
{
//$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aPlaceIDs ) == 0 ) return array ();
2016-09-04 04:19:48 +03:00
2017-10-08 11:06:17 +03:00
$sLanguagePrefArraySQL = getArraySQL (
2017-10-08 18:00:59 +03:00
array_map ( " getDBQuoted " , $this -> aLangPrefOrder )
2017-10-08 11:06:17 +03:00
);
2016-09-04 04:19:48 +03:00
// Get the details for display (is this a redundant extra step?)
$sPlaceIDs = join ( ',' , array_keys ( $aPlaceIDs ));
$sImportanceSQL = '' ;
2017-07-09 10:37:10 +03:00
$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 * " ;
}
2016-10-29 19:49:21 +03:00
$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, " ;
2017-03-26 01:11:09 +03:00
$sSQL .= " country_code, " ;
2016-10-29 19:49:21 +03:00
$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, " ;
2017-03-26 01:11:09 +03:00
$sSQL .= " country_code, " ;
2016-10-29 19:49:21 +03:00
$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' " ;
2016-09-04 04:19:48 +03:00
2017-07-05 00:25:48 +03:00
// postcode table
$sSQL .= " UNION " ;
$sSQL .= " SELECT " ;
$sSQL .= " 'P' as osm_type, " ;
2017-08-19 00:07:53 +03:00
$sSQL .= " (SELECT osm_id from placex p WHERE p.place_id = lp.parent_place_id) as osm_id, " ;
2017-07-05 00:25:48 +03:00
$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, " ;
2017-07-09 10:37:10 +03:00
$sSQL .= $sImportanceSQLGeom . " (0.75-(rank_search::float/40)) AS importance, " ;
2017-07-05 00:25:48 +03:00
$sSQL .= " ( " ;
$sSQL .= " SELECT max(p.importance*(p.rank_address+2)) " ;
$sSQL .= " FROM " ;
$sSQL .= " place_addressline s, " ;
$sSQL .= " placex p " ;
2017-08-19 00:07:53 +03:00
$sSQL .= " WHERE s.place_id = lp.parent_place_id " ;
2017-07-05 00:25:48 +03:00
$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 " ;
2017-08-19 00:07:53 +03:00
$sSQL .= " FROM location_postcode lp " ;
2017-07-05 00:25:48 +03:00
$sSQL .= " WHERE place_id in ( $sPlaceIDs ) " ;
2016-09-08 04:16:22 +03:00
if ( 30 >= $this -> iMinAddressRank && 30 <= $this -> iMaxAddressRank ) {
2016-09-14 04:16:46 +03:00
// only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines
2016-09-04 04:19:48 +03:00
// with start- and endnumber, the common osm housenumbers are usually saved as points
$sHousenumbers = " " ;
$i = 0 ;
$length = count ( $aPlaceIDs );
2016-09-08 04:16:22 +03:00
foreach ( $aPlaceIDs as $placeID => $housenumber ) {
2016-09-04 04:19:48 +03:00
$i ++ ;
$sHousenumbers .= " ( " . $placeID . " , " . $housenumber . " ) " ;
2016-09-08 04:16:22 +03:00
if ( $i < $length ) $sHousenumbers .= " , " ;
2016-09-04 04:19:48 +03:00
}
2016-10-30 19:07:43 +03:00
2016-09-08 04:16:22 +03:00
if ( CONST_Use_US_Tiger_Data ) {
2016-09-14 04:16:46 +03:00
// Tiger search only if a housenumber was searched and if it was found (i.e. aPlaceIDs[placeID] = housenumber != -1) (realized through a join)
2016-09-04 04:19:48 +03:00
$sSQL .= " union " ;
2016-10-29 19:49:21 +03:00
$sSQL .= " SELECT " ;
$sSQL .= " 'T' AS osm_type, " ;
2016-10-30 19:07:43 +03:00
$sSQL .= " (SELECT osm_id from placex p WHERE p.place_id=min(blub.parent_place_id)) as osm_id, " ;
2016-10-29 19:49:21 +03:00
$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
2016-09-04 04:19:48 +03:00
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)
2016-10-29 19:49:21 +03:00
$sSQL .= " UNION " ;
$sSQL .= " SELECT " ;
$sSQL .= " 'W' AS osm_type, " ;
2016-10-30 15:57:48 +03:00
$sSQL .= " osm_id, " ;
2016-10-29 19:49:21 +03:00
$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, " ;
2017-03-26 01:11:09 +03:00
$sSQL .= " country_code, " ;
2016-10-29 19:49:21 +03:00
$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 " ;
2016-10-30 15:57:48 +03:00
$sSQL .= " osm_id, " ;
2016-10-29 19:49:21 +03:00
$sSQL .= " place_id, " ;
2017-03-26 01:11:09 +03:00
$sSQL .= " country_code, " ;
2016-10-29 19:49:21 +03:00
$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, " ;
2017-03-26 01:11:09 +03:00
$sSQL .= " country_code " ; //is this group by really needed?, place_id + housenumber (in combination) are unique
2016-09-04 04:19:48 +03:00
if ( ! $this -> bDeDupe ) $sSQL .= " , place_id " ;
2016-09-08 04:16:22 +03:00
if ( CONST_Use_Aux_Location_data ) {
2016-10-29 19:49:21 +03:00
$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 ) " ;
2016-09-04 04:19:48 +03:00
}
}
$sSQL .= " order by importance desc " ;
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) {
2016-09-14 04:16:46 +03:00
echo " <hr> " ;
var_dump ( $sSQL );
2016-09-08 04:16:22 +03:00
}
2016-09-11 06:22:51 +03:00
$aSearchResults = chksql (
$this -> oDB -> getAll ( $sSQL ),
" Could not get details for place. "
);
2016-09-04 04:19:48 +03:00
return $aSearchResults ;
}
2017-06-01 22:40:23 +03:00
public function getGroupedSearches ( $aSearches , $aPhraseTypes , $aPhrases , $aValidTokens , $aWordFrequencyScores , $bStructuredPhrases , $sNormQuery )
2016-09-04 04:19:48 +03:00
{
/*
Calculate all searches using aValidTokens i . e .
'Wodsworth Road, Sheffield' =>
Phrase Wordset
0 0 ( wodsworth road )
0 1 ( wodsworth )( road )
1 0 ( sheffield )
Score how good the search is so they can be ordered
*/
2017-10-06 22:07:33 +03:00
$iGlobalRank = 0 ;
2017-07-04 01:02:11 +03:00
foreach ( $aPhrases as $iPhrase => $aPhrase ) {
2016-09-04 04:19:48 +03:00
$aNewPhraseSearches = array ();
2017-10-08 13:57:22 +03:00
if ( $bStructuredPhrases ) {
$sPhraseType = $aPhraseTypes [ $iPhrase ];
} else {
$sPhraseType = '' ;
}
2016-09-04 04:19:48 +03:00
2017-07-04 01:02:11 +03:00
foreach ( $aPhrase [ 'wordsets' ] as $iWordSet => $aWordset ) {
2016-09-04 04:19:48 +03:00
// Too many permutations - too expensive
if ( $iWordSet > 120 ) break ;
$aWordsetSearches = $aSearches ;
// Add all words from this wordset
2016-09-08 04:16:22 +03:00
foreach ( $aWordset as $iToken => $sToken ) {
2016-09-04 04:19:48 +03:00
//echo "<br><b>$sToken</b>";
$aNewWordsetSearches = array ();
2017-10-08 13:57:22 +03:00
foreach ( $aWordsetSearches as $oCurrentSearch ) {
2016-09-04 04:19:48 +03:00
//echo "<i>";
2017-10-08 13:57:22 +03:00
//var_dump($oCurrentSearch);
2016-09-04 04:19:48 +03:00
//echo "</i>";
// If the token is valid
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ ' ' . $sToken ])) {
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTerm ) {
2017-10-08 17:42:04 +03:00
// Recheck if the original word shows up in the query.
$bWordInQuery = false ;
if ( isset ( $aSearchTerm [ 'word' ]) && $aSearchTerm [ 'word' ]) {
$bWordInQuery = strpos (
$sNormQuery ,
$this -> normTerm ( $aSearchTerm [ 'word' ])
) !== false ;
}
2017-10-08 13:57:22 +03:00
$aNewSearches = $oCurrentSearch -> extendWithFullTerm (
$aSearchTerm ,
$bWordInQuery ,
isset ( $aValidTokens [ $sToken ])
&& strpos ( $sToken , ' ' ) === false ,
$sPhraseType ,
$iToken == 0 && $iPhrase == 0 ,
$iPhrase == 0 ,
$iToken + 1 == sizeof ( $aWordset )
&& $iPhrase + 1 == sizeof ( $aPhrases ),
$iGlobalRank
);
foreach ( $aNewSearches as $oSearch ) {
if ( $oSearch -> getRank () < $this -> iMaxRank ) {
$aNewWordsetSearches [] = $oSearch ;
2016-09-04 04:19:48 +03:00
}
}
}
}
// Look for partial matches.
// Note that there is no point in adding country terms here
2017-10-08 13:57:22 +03:00
// because country is omitted in the address.
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $sToken ]) && $sPhraseType != 'country' ) {
2016-09-04 04:19:48 +03:00
// Allow searching for a word - but at extra cost
2016-09-08 04:16:22 +03:00
foreach ( $aValidTokens [ $sToken ] as $aSearchTerm ) {
2017-10-08 13:57:22 +03:00
$aNewSearches = $oCurrentSearch -> extendWithPartialTerm (
$aSearchTerm ,
$bStructuredPhrases ,
$iPhrase ,
$aWordFrequencyScores ,
isset ( $aValidTokens [ ' ' . $sToken ]) ? $aValidTokens [ ' ' . $sToken ] : array ()
);
foreach ( $aNewSearches as $oSearch ) {
if ( $oSearch -> getRank () < $this -> iMaxRank ) {
$aNewWordsetSearches [] = $oSearch ;
2016-09-04 04:19:48 +03:00
}
}
}
}
}
// Sort and cut
2017-10-08 16:26:14 +03:00
usort ( $aNewWordsetSearches , array ( 'Nominatim\SearchDescription' , 'bySearchRank' ));
2016-09-04 04:19:48 +03:00
$aWordsetSearches = array_slice ( $aNewWordsetSearches , 0 , 50 );
}
//var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
$aNewPhraseSearches = array_merge ( $aNewPhraseSearches , $aNewWordsetSearches );
2017-10-08 16:26:14 +03:00
usort ( $aNewPhraseSearches , array ( 'Nominatim\SearchDescription' , 'bySearchRank' ));
2016-09-04 04:19:48 +03:00
$aSearchHash = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aNewPhraseSearches as $iSearch => $aSearch ) {
2016-09-04 04:19:48 +03:00
$sHash = serialize ( $aSearch );
if ( isset ( $aSearchHash [ $sHash ])) unset ( $aNewPhraseSearches [ $iSearch ]);
else $aSearchHash [ $sHash ] = 1 ;
}
$aNewPhraseSearches = array_slice ( $aNewPhraseSearches , 0 , 50 );
}
// Re-group the searches by their score, junk anything over 20 as just not worth trying
$aGroupedSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aNewPhraseSearches as $aSearch ) {
2017-10-08 13:57:22 +03:00
$iRank = $aSearch -> getRank ();
if ( $iRank < $this -> iMaxRank ) {
if ( ! isset ( $aGroupedSearches [ $iRank ])) {
$aGroupedSearches [ $iRank ] = array ();
}
$aGroupedSearches [ $iRank ][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
ksort ( $aGroupedSearches );
$iSearchCount = 0 ;
$aSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iScore => $aNewSearches ) {
2016-09-04 04:19:48 +03:00
$iSearchCount += sizeof ( $aNewSearches );
$aSearches = array_merge ( $aSearches , $aNewSearches );
if ( $iSearchCount > 50 ) break ;
}
//if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
}
2017-10-02 22:57:44 +03:00
2017-10-07 13:23:46 +03:00
// Revisit searches, drop bad searches and give penalty to unlikely combinations.
2017-10-02 22:57:44 +03:00
$aGroupedSearches = array ();
2017-10-08 13:57:22 +03:00
foreach ( $aSearches as $oSearch ) {
2017-10-08 16:26:14 +03:00
if ( ! $oSearch -> isValidSearch ( $this -> aCountryCodes )) {
2017-10-07 13:23:46 +03:00
continue ;
}
2017-10-08 13:57:22 +03:00
$iRank = $oSearch -> addToRank ( $iGlobalRank );
2017-10-08 16:26:14 +03:00
if ( ! isset ( $aGroupedSearches [ $iRank ])) {
2017-10-08 13:57:22 +03:00
$aGroupedSearches [ $iRank ] = array ();
}
$aGroupedSearches [ $iRank ][] = $oSearch ;
2017-10-02 22:57:44 +03:00
}
ksort ( $aGroupedSearches );
2016-09-04 04:19:48 +03:00
return $aGroupedSearches ;
}
/* Perform the actual query lookup .
Returns an ordered list of results , each with the following fields :
osm_type : type of corresponding OSM object
N - node
W - way
R - relation
P - postcode ( internally computed )
osm_id : id of corresponding OSM object
class : general object class ( corresponds to tag key of primary OSM tag )
type : subclass of object ( corresponds to tag value of primary OSM tag )
admin_level : see http :// wiki . openstreetmap . org / wiki / Admin_level
rank_search : rank in search hierarchy
( see also http :// wiki . openstreetmap . org / wiki / Nominatim / Development_overview #Country_to_street_level)
rank_address : rank in address hierarchy ( determines orer in address )
place_id : internal key ( may differ between different instances )
country_code : ISO country code
langaddress : localized full address
placename : localized name of object
ref : content of ref tag ( if available )
lon : longitude
lat : latitude
importance : importance of place based on Wikipedia link count
addressimportance : cumulated importance of address elements
extra_place : type of place ( for admin boundaries , if there is a place tag )
aBoundingBox : bounding Box
label : short description of the object class / type ( English only )
name : full name ( currently the same as langaddress )
foundorder : secondary ordering for places with same importance
*/
2016-09-14 04:16:46 +03:00
2016-09-16 03:27:36 +03:00
public function lookup ()
2016-09-04 04:19:48 +03:00
{
2017-03-23 01:02:19 +03:00
if ( ! $this -> sQuery && ! $this -> aStructuredQuery ) return array ();
2016-09-04 04:19:48 +03:00
2017-07-07 00:06:13 +03:00
$sNormQuery = $this -> normTerm ( $this -> sQuery );
2017-10-08 11:06:17 +03:00
$sLanguagePrefArraySQL = getArraySQL (
2017-10-08 18:00:59 +03:00
array_map ( " getDBQuoted " , $this -> aLangPrefOrder )
2017-10-08 11:06:17 +03:00
);
2016-09-04 04:19:48 +03:00
$sCountryCodesSQL = false ;
2016-09-08 04:16:22 +03:00
if ( $this -> aCountryCodes ) {
2016-09-04 04:19:48 +03:00
$sCountryCodesSQL = join ( ',' , array_map ( 'addQuotes' , $this -> aCountryCodes ));
}
$sQuery = $this -> sQuery ;
2016-10-12 23:25:04 +03:00
if ( ! preg_match ( '//u' , $sQuery )) {
userError ( " Query string is not UTF-8 encoded. " );
}
2016-09-04 04:19:48 +03:00
// Conflicts between US state abreviations and various words for 'the' in different languages
2016-09-08 04:16:22 +03:00
if ( isset ( $this -> aLangPrefOrder [ 'name:en' ])) {
2016-09-11 06:22:51 +03:00
$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 );
2016-09-04 04:19:48 +03:00
}
$bBoundingBoxSearch = $this -> bBoundedSearch && $this -> sViewboxSmallSQL ;
2016-09-08 04:16:22 +03:00
if ( $this -> sViewboxCentreSQL ) {
2016-09-04 04:19:48 +03:00
// For complex viewboxes (routes) precompute the bounding geometry
2016-09-11 06:22:51 +03:00
$sGeom = chksql (
$this -> oDB -> getOne ( " select " . $this -> sViewboxSmallSQL ),
" Could not get small viewbox "
);
2016-09-04 04:19:48 +03:00
$this -> sViewboxSmallSQL = " ' " . $sGeom . " '::geometry " ;
2016-09-11 06:22:51 +03:00
$sGeom = chksql (
$this -> oDB -> getOne ( " select " . $this -> sViewboxLargeSQL ),
" Could not get large viewbox "
);
2016-09-04 04:19:48 +03:00
$this -> sViewboxLargeSQL = " ' " . $sGeom . " '::geometry " ;
}
// Do we have anything that looks like a lat/lon pair?
2017-03-15 22:43:08 +03:00
$oNearPoint = false ;
if ( $aLooksLike = NearPoint :: extractFromQuery ( $sQuery )) {
$oNearPoint = $aLooksLike [ 'pt' ];
2016-09-04 04:19:48 +03:00
$sQuery = $aLooksLike [ 'query' ];
}
$aSearchResults = array ();
2016-09-08 04:16:22 +03:00
if ( $sQuery || $this -> aStructuredQuery ) {
2017-10-06 01:14:48 +03:00
// Start with a single blank search
$aSearches = array ( new SearchDescription ());
2017-09-18 23:07:08 +03:00
2017-10-06 01:14:48 +03:00
if ( $oNearPoint ) {
$aSearches [ 0 ] -> setNear ( $oNearPoint );
2016-09-04 04:19:48 +03:00
}
2017-10-06 01:14:48 +03:00
if ( $sQuery ) {
$sQuery = $aSearches [ 0 ] -> extractKeyValuePairs ( $sQuery );
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
2017-10-06 01:14:48 +03:00
$sSpecialTerm = '' ;
if ( $sQuery ) {
preg_match_all (
'/\\[([\\w ]*)\\]/u' ,
$sQuery ,
$aSpecialTermsRaw ,
PREG_SET_ORDER
);
foreach ( $aSpecialTermsRaw as $aSpecialTerm ) {
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
if ( ! $sSpecialTerm ) {
$sSpecialTerm = $aSpecialTerm [ 1 ];
}
2017-09-18 23:07:08 +03:00
}
2017-10-06 01:14:48 +03:00
}
if ( ! $sSpecialTerm && $this -> aStructuredQuery
&& isset ( $this -> aStructuredQuery [ 'amenity' ])) {
$sSpecialTerm = $this -> aStructuredQuery [ 'amenity' ];
unset ( $this -> aStructuredQuery [ 'amenity' ]);
}
2017-09-18 23:07:08 +03:00
2017-10-06 01:14:48 +03:00
if ( $sSpecialTerm && ! $aSearches [ 0 ] -> hasOperator ()) {
$sSpecialTerm = pg_escape_string ( $sSpecialTerm );
$sToken = chksql (
$this -> oDB -> getOne ( " SELECT make_standard_name(' $sSpecialTerm ') " ),
" Cannot decode query. Wrong encoding? "
);
$sSQL = 'SELECT class, type FROM word ' ;
2016-10-29 19:49:21 +03:00
$sSQL .= ' WHERE word_token in (\' ' . $sToken . '\')' ;
2017-10-06 01:14:48 +03:00
$sSQL .= ' AND class is not null AND class not in (\'place\')' ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_Dump ( $sSQL );
$aSearchWords = chksql ( $this -> oDB -> getAll ( $sSQL ));
$aNewSearches = array ();
2017-10-06 01:14:48 +03:00
foreach ( $aSearches as $oSearch ) {
2016-09-08 04:16:22 +03:00
foreach ( $aSearchWords as $aSearchTerm ) {
2017-10-06 01:14:48 +03:00
$oNewSearch = clone $oSearch ;
$oNewSearch -> setPoiSearch (
Operator :: TYPE ,
$aSearchTerm [ 'class' ],
2017-10-08 16:26:14 +03:00
$aSearchTerm [ 'type' ]
2017-10-06 01:14:48 +03:00
);
$aNewSearches [] = $oNewSearch ;
2016-09-04 04:19:48 +03:00
}
}
$aSearches = $aNewSearches ;
}
// Split query into phrases
// Commas are used to reduce the search space by indicating where phrases split
2016-09-08 04:16:22 +03:00
if ( $this -> aStructuredQuery ) {
2016-09-04 04:19:48 +03:00
$aPhrases = $this -> aStructuredQuery ;
$bStructuredPhrases = true ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-11 06:22:51 +03:00
$aPhrases = explode ( ',' , $sQuery );
2016-09-04 04:19:48 +03:00
$bStructuredPhrases = false ;
}
// Convert each phrase to standard form
// Create a list of standard words
// Get all 'sets' of words
// Generate a complete list of all
$aTokens = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aPhrases as $iPhrase => $sPhrase ) {
2016-09-11 06:22:51 +03:00
$aPhrase = chksql (
2016-10-29 19:49:21 +03:00
$this -> oDB -> getRow ( " SELECT make_standard_name(' " . pg_escape_string ( $sPhrase ) . " ') as string " ),
2016-10-12 21:21:12 +03:00
" Cannot normalize query string (is it a UTF-8 string?) "
2016-09-11 06:22:51 +03:00
);
2016-09-08 04:16:22 +03:00
if ( trim ( $aPhrase [ 'string' ])) {
2016-09-04 04:19:48 +03:00
$aPhrases [ $iPhrase ] = $aPhrase ;
2016-09-11 06:22:51 +03:00
$aPhrases [ $iPhrase ][ 'words' ] = explode ( ' ' , $aPhrases [ $iPhrase ][ 'string' ]);
2016-09-04 04:19:48 +03:00
$aPhrases [ $iPhrase ][ 'wordsets' ] = getWordSets ( $aPhrases [ $iPhrase ][ 'words' ], 0 );
$aTokens = array_merge ( $aTokens , getTokensFromSets ( $aPhrases [ $iPhrase ][ 'wordsets' ]));
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
unset ( $aPhrases [ $iPhrase ]);
}
}
// Reindex phrases - we make assumptions later on that they are numerically keyed in order
$aPhraseTypes = array_keys ( $aPhrases );
$aPhrases = array_values ( $aPhrases );
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aTokens )) {
2016-09-04 04:19:48 +03:00
// Check which tokens we have, get the ID numbers
2016-10-29 19:49:21 +03:00
$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 )) . ')' ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_Dump ( $sSQL );
$aValidTokens = array ();
2017-10-02 23:31:52 +03:00
$aDatabaseWords = chksql (
$this -> oDB -> getAll ( $sSQL ),
" Could not get word tokens. "
);
2016-09-04 04:19:48 +03:00
$aPossibleMainWordIDs = array ();
$aWordFrequencyScores = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aDatabaseWords as $aToken ) {
2016-09-04 04:19:48 +03:00
// Very special case - require 2 letter country param to match the country code found
if ( $bStructuredPhrases && $aToken [ 'country_code' ] && ! empty ( $this -> aStructuredQuery [ 'country' ])
2016-09-08 04:16:22 +03:00
&& strlen ( $this -> aStructuredQuery [ 'country' ]) == 2 && strtolower ( $this -> aStructuredQuery [ 'country' ]) != $aToken [ 'country_code' ]
) {
2016-09-04 04:19:48 +03:00
continue ;
}
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $aToken [ 'word_token' ]])) {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $aToken [ 'word_token' ]][] = $aToken ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $aToken [ 'word_token' ]] = array ( $aToken );
}
if ( ! $aToken [ 'class' ] && ! $aToken [ 'country_code' ]) $aPossibleMainWordIDs [ $aToken [ 'word_id' ]] = 1 ;
$aWordFrequencyScores [ $aToken [ 'word_id' ]] = $aToken [ 'search_name_count' ] + 1 ;
}
if ( CONST_Debug ) var_Dump ( $aPhrases , $aValidTokens );
2017-07-06 23:48:09 +03:00
// US ZIP+4 codes - if there is no token, merge in the 5-digit ZIP code
2016-09-08 04:16:22 +03:00
foreach ( $aTokens as $sToken ) {
2017-07-06 23:48:09 +03:00
if ( ! isset ( $aValidTokens [ $sToken ]) && preg_match ( '/^([0-9]{5}) [0-9]{4}$/' , $sToken , $aData )) {
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ $aData [ 1 ]])) {
foreach ( $aValidTokens [ $aData [ 1 ]] as $aToken ) {
if ( ! $aToken [ 'class' ]) {
if ( isset ( $aValidTokens [ $sToken ])) {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $sToken ][] = $aToken ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $sToken ] = array ( $aToken );
}
}
}
}
}
}
2016-09-08 04:16:22 +03:00
foreach ( $aTokens as $sToken ) {
2016-09-04 04:19:48 +03:00
// Unknown single word token with a number - assume it is a house number
2017-10-08 16:26:14 +03:00
if ( ! isset ( $aValidTokens [ ' ' . $sToken ]) && strpos ( $sToken , ' ' ) === false && preg_match ( '/^[0-9]+$/' , $sToken )) {
$aValidTokens [ ' ' . $sToken ] = array ( array ( 'class' => 'place' , 'type' => 'house' , 'word_token' => ' ' . $sToken ));
2016-09-04 04:19:48 +03:00
}
}
// Any words that have failed completely?
// TODO: suggestions
// Start the search process
// array with: placeid => -1 | tiger-housenumber
$aResultPlaceIDs = array ();
2017-06-01 22:40:23 +03:00
$aGroupedSearches = $this -> getGroupedSearches ( $aSearches , $aPhraseTypes , $aPhrases , $aValidTokens , $aWordFrequencyScores , $bStructuredPhrases , $sNormQuery );
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( $this -> bReverseInPlan ) {
2016-09-04 04:19:48 +03:00
// 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 );
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aPhrases ) > 1 ) {
2016-09-04 04:19:48 +03:00
$aFinalPhrase = end ( $aPhrases );
$aPhrases [ sizeof ( $aPhrases ) - 1 ][ 'wordsets' ] = getInverseWordSets ( $aFinalPhrase [ 'words' ], 0 );
}
2017-06-01 22:40:23 +03:00
$aReverseGroupedSearches = $this -> getGroupedSearches ( $aSearches , null , $aPhrases , $aValidTokens , $aWordFrequencyScores , false , $sNormQuery );
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $aSearches ) {
foreach ( $aSearches as $aSearch ) {
2017-10-06 01:14:48 +03:00
if ( ! isset ( $aReverseGroupedSearches [ $aSearch -> getRank ()])) {
$aReverseGroupedSearches [ $aSearch -> getRank ()] = array ();
2016-09-04 04:19:48 +03:00
}
2017-10-06 01:14:48 +03:00
$aReverseGroupedSearches [ $aSearch -> getRank ()][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
$aGroupedSearches = $aReverseGroupedSearches ;
ksort ( $aGroupedSearches );
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
// Re-group the searches by their score, junk anything over 20 as just not worth trying
$aGroupedSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aSearches as $aSearch ) {
2017-10-06 01:14:48 +03:00
if ( $aSearch -> getRank () < $this -> iMaxRank ) {
if ( ! isset ( $aGroupedSearches [ $aSearch -> getRank ()])) $aGroupedSearches [ $aSearch -> getRank ()] = array ();
$aGroupedSearches [ $aSearch -> getRank ()][] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
ksort ( $aGroupedSearches );
}
// Filter out duplicate searches
$aSearchHash = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iGroup => $aSearches ) {
foreach ( $aSearches as $iSearch => $aSearch ) {
2016-09-04 04:19:48 +03:00
$sHash = serialize ( $aSearch );
2016-09-08 04:16:22 +03:00
if ( isset ( $aSearchHash [ $sHash ])) {
2016-09-04 04:19:48 +03:00
unset ( $aGroupedSearches [ $iGroup ][ $iSearch ]);
if ( sizeof ( $aGroupedSearches [ $iGroup ]) == 0 ) unset ( $aGroupedSearches [ $iGroup ]);
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearchHash [ $sHash ] = 1 ;
}
}
}
if ( CONST_Debug ) _debugDumpGroupedSearches ( $aGroupedSearches , $aValidTokens );
$iGroupLoop = 0 ;
$iQueryLoop = 0 ;
2016-09-08 04:16:22 +03:00
foreach ( $aGroupedSearches as $iGroupedRank => $aSearches ) {
2016-09-04 04:19:48 +03:00
$iGroupLoop ++ ;
2017-10-07 13:01:56 +03:00
foreach ( $aSearches as $oSearch ) {
2016-09-04 04:19:48 +03:00
$iQueryLoop ++ ;
$searchedHousenumber = - 1 ;
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) echo " <hr><b>Search Loop, group $iGroupLoop , loop $iQueryLoop </b> " ;
2017-10-07 13:01:56 +03:00
if ( CONST_Debug ) _debugDumpGroupedSearches ( array ( $iGroupedRank => array ( $oSearch )), $aValidTokens );
$aPlaceIDs = array ();
if ( $oSearch -> isCountrySearch ()) {
// Just looking for a country - look it up
if ( 4 >= $this -> iMinAddressRank && 4 <= $this -> iMaxAddressRank ) {
$aPlaceIDs = $oSearch -> queryCountry (
$this -> oDB ,
$bBoundingBoxSearch ? $this -> sViewboxSmallSQL : ''
);
2016-09-04 04:19:48 +03:00
}
2017-10-07 13:01:56 +03:00
} elseif ( ! $oSearch -> isNamedSearch ()) {
// looking for a POI in a geographic area
if ( ! $bBoundingBoxSearch && ! $oSearch -> isNearSearch ()) {
continue ;
2016-09-04 04:19:48 +03:00
}
2017-10-07 13:01:56 +03:00
$aPlaceIDs = $oSearch -> queryNearbyPoi (
$this -> oDB ,
$sCountryCodesSQL ,
$bBoundingBoxSearch ? $this -> sViewboxSmallSQL : '' ,
2017-10-08 16:26:14 +03:00
$this -> sViewboxCentreSQL ,
2017-10-07 13:01:56 +03:00
$this -> aExcludePlaceIDs ? join ( ',' , $this -> aExcludePlaceIDs ) : '' ,
$this -> iLimit
);
} elseif ( $oSearch -> isOperator ( Operator :: POSTCODE )) {
$aPlaceIDs = $oSearch -> queryPostcode (
2017-10-08 17:42:04 +03:00
$this -> oDB ,
2017-10-07 13:01:56 +03:00
$sCountryCodesSQL ,
$this -> iLimit
);
} else {
// Ordinary search:
// First search for places according to name and address.
$aNamedPlaceIDs = $oSearch -> queryNamedPlace (
$this -> oDB ,
$aWordFrequencyScores ,
$sCountryCodesSQL ,
$this -> iMinAddressRank ,
$this -> iMaxAddressRank ,
$this -> aExcludePlaceIDs ? join ( ',' , $this -> aExcludePlaceIDs ) : '' ,
$bBoundingBoxSearch ? $this -> sViewboxSmallSQL : '' ,
$bBoundingBoxSearch ? $this -> sViewboxLargeSQL : '' ,
$this -> iLimit
);
2016-09-04 04:19:48 +03:00
2017-10-07 13:01:56 +03:00
if ( sizeof ( $aNamedPlaceIDs )) {
foreach ( $aNamedPlaceIDs as $aRow ) {
$aPlaceIDs [] = $aRow [ 'place_id' ];
$this -> exactMatchCache [ $aRow [ 'place_id' ]] = $aRow [ 'exactmatch' ];
2016-09-04 04:19:48 +03:00
}
}
//now search for housenumber, if housenumber provided
2017-10-07 13:01:56 +03:00
if ( $oSearch -> hasHouseNumber () && sizeof ( $aPlaceIDs )) {
$aResult = $oSearch -> queryHouseNumber (
$this -> oDB ,
$aPlaceIDs ,
2017-10-08 16:26:14 +03:00
$this -> aExcludePlaceIDs ? join ( ',' , $this -> aExcludePlaceIDs ) : '' ,
2017-10-07 13:01:56 +03:00
$this -> iLimit
);
2016-09-04 04:19:48 +03:00
2017-10-07 13:01:56 +03:00
if ( sizeof ( $aResult )) {
$searchedHousenumber = $aResult [ 'iHouseNumber' ];
2017-10-08 16:26:14 +03:00
$aPlaceIDs = $aResult [ 'aPlaceIDs' ];
2017-10-07 13:01:56 +03:00
} elseif ( ! $oSearch -> looksLikeFullAddress ()) {
$aPlaceIDs = array ();
2016-09-04 04:19:48 +03:00
}
}
2017-10-07 13:01:56 +03:00
// finally get POIs if requested
if ( $oSearch -> isPoiSearch () && sizeof ( $aPlaceIDs )) {
$aPlaceIDs = $oSearch -> queryPoiByOperator (
$this -> oDB ,
$aPlaceIDs ,
2017-10-08 16:26:14 +03:00
$this -> aExcludePlaceIDs ? join ( ',' , $this -> aExcludePlaceIDs ) : '' ,
2017-10-07 13:01:56 +03:00
$this -> iLimit
);
2016-09-04 04:19:48 +03:00
}
}
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) {
2016-09-14 04:16:46 +03:00
echo " <br><b>Place IDs:</b> " ;
var_Dump ( $aPlaceIDs );
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
2017-10-07 13:01:56 +03:00
if ( sizeof ( $aPlaceIDs ) && $oSearch -> getPostcode ()) {
2017-07-06 00:35:22 +03:00
$sSQL = 'SELECT place_id FROM placex' ;
$sSQL .= ' WHERE place_id in (' . join ( ',' , $aPlaceIDs ) . ')' ;
2017-10-07 13:01:56 +03:00
$sSQL .= " AND postcode = ' " . $oSearch -> getPostcode () . " ' " ;
2017-07-06 00:35:22 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aFilteredPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
if ( $aFilteredPlaceIDs ) {
$aPlaceIDs = $aFilteredPlaceIDs ;
if ( CONST_Debug ) {
echo " <br><b>Place IDs after postcode filtering:</b> " ;
var_Dump ( $aPlaceIDs );
}
}
}
2016-09-08 04:16:22 +03:00
foreach ( $aPlaceIDs as $iPlaceID ) {
2016-09-04 04:19:48 +03:00
// array for placeID => -1 | Tiger housenumber
$aResultPlaceIDs [ $iPlaceID ] = $searchedHousenumber ;
}
if ( $iQueryLoop > 20 ) break ;
}
2016-09-08 04:16:22 +03:00
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs ) && ( $this -> iMinAddressRank != 0 || $this -> iMaxAddressRank != 30 )) {
2016-09-04 04:19:48 +03:00
// 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
2017-10-03 13:10:27 +03:00
$sWherePlaceId = 'WHERE place_id in (' ;
$sWherePlaceId .= join ( ',' , array_keys ( $aResultPlaceIDs )) . ') ' ;
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id " ;
2017-10-03 13:10:27 +03:00
$sSQL .= " FROM placex " . $sWherePlaceId ;
2016-10-29 19:49:21 +03:00
$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 ) . " ) " ;
}
2017-10-03 13:10:27 +03:00
$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 .= " ) " ;
2017-10-02 21:42:37 +03:00
if ( CONST_Use_US_Tiger_Data && $this -> iMaxAddressRank == 30 ) {
2016-10-29 19:49:21 +03:00
$sSQL .= " UNION " ;
$sSQL .= " SELECT place_id " ;
2017-10-03 13:10:27 +03:00
$sSQL .= " FROM location_property_tiger " . $sWherePlaceId ;
2016-09-04 04:19:48 +03:00
}
2017-10-02 21:42:37 +03:00
if ( $this -> iMaxAddressRank == 30 ) {
$sSQL .= " UNION " ;
$sSQL .= " SELECT place_id " ;
2017-10-03 13:10:27 +03:00
$sSQL .= " FROM location_property_osmline " . $sWherePlaceId ;
2017-10-02 21:42:37 +03:00
}
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aFilteredPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
$tempIDs = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aFilteredPlaceIDs as $placeID ) {
2016-09-04 04:19:48 +03:00
$tempIDs [ $placeID ] = $aResultPlaceIDs [ $placeID ]; //assign housenumber to placeID
}
$aResultPlaceIDs = $tempIDs ;
}
//exit;
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs )) break ;
if ( $iGroupLoop > 4 ) break ;
if ( $iQueryLoop > 30 ) break ;
}
// Did we find anything?
2016-09-08 04:16:22 +03:00
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs )) {
2016-09-04 04:19:48 +03:00
$aSearchResults = $this -> getDetails ( $aResultPlaceIDs );
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
// Just interpret as a reverse geocode
2016-09-30 23:42:12 +03:00
$oReverse = new ReverseGeocode ( $this -> oDB );
2016-09-04 04:19:48 +03:00
$oReverse -> setZoom ( 18 );
2016-09-11 06:22:51 +03:00
$aLookup = $oReverse -> lookup (
2017-03-15 22:43:08 +03:00
$oNearPoint -> lat (),
$oNearPoint -> lon (),
2016-09-11 06:22:51 +03:00
false
);
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( " Reverse search " , $aLookup );
2016-09-08 04:16:22 +03:00
if ( $aLookup [ 'place_id' ]) {
2016-09-04 04:19:48 +03:00
$aSearchResults = $this -> getDetails ( array ( $aLookup [ 'place_id' ] => - 1 ));
2016-10-01 14:03:31 +03:00
$aResultPlaceIDs [ $aLookup [ 'place_id' ]] = - 1 ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearchResults = array ();
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
}
// No results? Done
2016-09-08 04:16:22 +03:00
if ( ! sizeof ( $aSearchResults )) {
if ( $this -> bFallback ) {
if ( $this -> fallbackStructuredQuery ()) {
2016-09-04 04:19:48 +03:00
return $this -> lookup ();
}
}
return array ();
}
$aClassType = getClassTypesWithImportance ();
2016-09-11 06:22:51 +03:00
$aRecheckWords = preg_split ( '/\b[\s,\\-]*/u' , $sQuery );
2016-09-08 04:16:22 +03:00
foreach ( $aRecheckWords as $i => $sWord ) {
2017-02-28 23:40:05 +03:00
if ( ! preg_match ( '/[\pL\pN]/' , $sWord )) unset ( $aRecheckWords [ $i ]);
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) {
2016-09-14 04:16:46 +03:00
echo '<i>Recheck words:<\i>' ;
var_dump ( $aRecheckWords );
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
$oPlaceLookup = new PlaceLookup ( $this -> oDB );
$oPlaceLookup -> setIncludePolygonAsPoints ( $this -> bIncludePolygonAsPoints );
$oPlaceLookup -> setIncludePolygonAsText ( $this -> bIncludePolygonAsText );
$oPlaceLookup -> setIncludePolygonAsGeoJSON ( $this -> bIncludePolygonAsGeoJSON );
$oPlaceLookup -> setIncludePolygonAsKML ( $this -> bIncludePolygonAsKML );
$oPlaceLookup -> setIncludePolygonAsSVG ( $this -> bIncludePolygonAsSVG );
$oPlaceLookup -> setPolygonSimplificationThreshold ( $this -> fPolygonSimplificationThreshold );
2016-09-08 04:16:22 +03:00
foreach ( $aSearchResults as $iResNum => $aResult ) {
2016-09-04 04:19:48 +03:00
// Default
$fDiameter = getResultDiameter ( $aResult );
$aOutlineResult = $oPlaceLookup -> getOutlines ( $aResult [ 'place_id' ], $aResult [ 'lon' ], $aResult [ 'lat' ], $fDiameter / 2 );
2016-09-08 04:16:22 +03:00
if ( $aOutlineResult ) {
2016-09-04 04:19:48 +03:00
$aResult = array_merge ( $aResult , $aOutlineResult );
}
2016-09-08 04:16:22 +03:00
if ( $aResult [ 'extra_place' ] == 'city' ) {
2016-09-04 04:19:48 +03:00
$aResult [ 'class' ] = 'place' ;
$aResult [ 'type' ] = 'city' ;
$aResult [ 'rank_search' ] = 16 ;
}
// Is there an icon set for this type of result?
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$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' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ];
2016-09-08 04:16:22 +03:00
} elseif ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ])
2016-09-11 06:22:51 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ]
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ];
}
// if tag '&addressdetails=1' is set in query
2016-09-08 04:16:22 +03:00
if ( $this -> bIncludeAddressDetails ) {
2016-09-04 04:19:48 +03:00
// 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' ]]);
2016-09-08 04:16:22 +03:00
if ( $aResult [ 'extra_place' ] == 'city' && ! isset ( $aResult [ 'address' ][ 'city' ])) {
2016-10-09 22:14:59 +03:00
$aResult [ 'address' ] = array_merge ( array ( 'city' => array_values ( $aResult [ 'address' ])[ 0 ]), $aResult [ 'address' ]);
2016-09-04 04:19:48 +03:00
}
}
2016-09-08 04:16:22 +03:00
if ( $this -> bIncludeExtraTags ) {
if ( $aResult [ 'extra' ]) {
2016-09-04 04:19:48 +03:00
$aResult [ 'sExtraTags' ] = json_decode ( $aResult [ 'extra' ]);
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aResult [ 'sExtraTags' ] = ( object ) array ();
}
}
2016-09-08 04:16:22 +03:00
if ( $this -> bIncludeNameDetails ) {
if ( $aResult [ 'names' ]) {
2016-09-04 04:19:48 +03:00
$aResult [ 'sNameDetails' ] = json_decode ( $aResult [ 'names' ]);
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aResult [ 'sNameDetails' ] = ( object ) array ();
}
}
// Adjust importance for the number of exact string matches in the result
2016-09-11 06:22:51 +03:00
$aResult [ 'importance' ] = max ( 0.001 , $aResult [ 'importance' ]);
2016-09-04 04:19:48 +03:00
$iCountWords = 0 ;
$sAddress = $aResult [ 'langaddress' ];
2016-09-08 04:16:22 +03:00
foreach ( $aRecheckWords as $i => $sWord ) {
if ( stripos ( $sAddress , $sWord ) !== false ) {
2016-09-04 04:19:48 +03:00
$iCountWords ++ ;
if ( preg_match ( " /(^|,) \ s* " . preg_quote ( $sWord , '/' ) . " \ s*(,| $ )/ " , $sAddress )) $iCountWords += 0.1 ;
}
}
$aResult [ 'importance' ] = $aResult [ 'importance' ] + ( $iCountWords * 0.1 ); // 0.1 is a completely arbitrary number but something in the range 0.1 to 0.5 would seem right
$aResult [ 'name' ] = $aResult [ 'langaddress' ];
// secondary ordering (for results with same importance (the smaller the better):
2016-09-14 04:16:46 +03:00
// - approximate importance of address parts
2016-09-04 04:19:48 +03:00
$aResult [ 'foundorder' ] = - $aResult [ 'addressimportance' ] / 10 ;
2016-09-14 04:16:46 +03:00
// - number of exact matches from the query
2016-09-08 04:16:22 +03:00
if ( isset ( $this -> exactMatchCache [ $aResult [ 'place_id' ]])) {
2016-09-04 04:19:48 +03:00
$aResult [ 'foundorder' ] -= $this -> exactMatchCache [ $aResult [ 'place_id' ]];
2016-09-11 06:22:51 +03:00
} elseif ( isset ( $this -> exactMatchCache [ $aResult [ 'parent_place_id' ]])) {
2016-09-04 04:19:48 +03:00
$aResult [ 'foundorder' ] -= $this -> exactMatchCache [ $aResult [ 'parent_place_id' ]];
2016-09-08 04:16:22 +03:00
}
2016-09-14 04:16:46 +03:00
// - importance of the class/type
2016-09-04 04:19:48 +03:00
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ])
2016-09-08 04:16:22 +03:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ]
) {
2016-09-04 04:19:48 +03:00
$aResult [ 'foundorder' ] += 0.0001 * $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ];
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aResult [ 'foundorder' ] += 0.01 ;
}
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) var_dump ( $aResult );
2016-09-04 04:19:48 +03:00
$aSearchResults [ $iResNum ] = $aResult ;
}
uasort ( $aSearchResults , 'byImportance' );
$aOSMIDDone = array ();
$aClassTypeNameDone = array ();
$aToFilter = $aSearchResults ;
$aSearchResults = array ();
$bFirst = true ;
2016-09-08 04:16:22 +03:00
foreach ( $aToFilter as $iResNum => $aResult ) {
2016-09-04 04:19:48 +03:00
$this -> aExcludePlaceIDs [ $aResult [ 'place_id' ]] = $aResult [ 'place_id' ];
2016-09-08 04:16:22 +03:00
if ( $bFirst ) {
2016-09-04 04:19:48 +03:00
$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' ]])
2016-09-11 06:22:51 +03:00
&& ! isset ( $aClassTypeNameDone [ $aResult [ 'osm_type' ] . $aResult [ 'class' ] . $aResult [ 'type' ] . $aResult [ 'name' ] . $aResult [ 'admin_level' ]]))
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aOSMIDDone [ $aResult [ 'osm_type' ] . $aResult [ 'osm_id' ]] = true ;
$aClassTypeNameDone [ $aResult [ 'osm_type' ] . $aResult [ 'class' ] . $aResult [ 'type' ] . $aResult [ 'name' ] . $aResult [ 'admin_level' ]] = true ;
$aSearchResults [] = $aResult ;
}
// Absolute limit on number of results
if ( sizeof ( $aSearchResults ) >= $this -> iFinalLimit ) break ;
}
return $aSearchResults ;
} // end lookup()
} // end class