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' );
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 ;
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 ;
}
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 ) {
userError ( " Bad parmater 'viewbox'. Expected 4 coordinates. " );
}
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. " );
}
2016-09-10 22:10:52 +03:00
$this -> setViewBox ( array (
$aViewbox [ 0 ],
$aViewbox [ 3 ],
$aViewbox [ 2 ],
$aViewbox [ 1 ]
));
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 ();
$this -> sAllowedTypesSQLList = '' ;
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 ) {
2016-09-04 04:19:48 +03:00
$sAllowedTypesSQLList = '(\'place\',\'boundary\')' ;
}
}
}
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
2016-09-11 06:22:51 +03:00
$sLanguagePrefArraySQL = " ARRAY[ " . join ( ',' , array_map ( " getDBQuoted " , $this -> aLangPrefOrder )) . " ] " ;
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 = '' ;
2016-10-29 19:49:21 +03:00
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 " ;
$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, " ;
$sSQL .= " (SELECT osm_id from placex p WHERE p.place_id = 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 .= $sImportanceSQL . " (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 = 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 " ;
$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-07-04 01:02:11 +03:00
foreach ( $aPhrases as $iPhrase => $aPhrase ) {
2016-09-04 04:19:48 +03:00
$aNewPhraseSearches = array ();
if ( $bStructuredPhrases ) $sPhraseType = $aPhraseTypes [ $iPhrase ];
else $sPhraseType = '' ;
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 ();
2016-09-08 04:16:22 +03:00
foreach ( $aWordsetSearches as $aCurrentSearch ) {
2016-09-04 04:19:48 +03:00
//echo "<i>";
//var_dump($aCurrentSearch);
//echo "</i>";
// If the token is valid
2016-09-08 04:16:22 +03:00
if ( isset ( $aValidTokens [ ' ' . $sToken ])) {
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTerm ) {
2016-09-04 04:19:48 +03:00
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] ++ ;
2016-09-08 04:16:22 +03:00
if (( $sPhraseType == '' || $sPhraseType == 'country' ) && ! empty ( $aSearchTerm [ 'country_code' ]) && $aSearchTerm [ 'country_code' ] != '0' ) {
if ( $aSearch [ 'sCountryCode' ] === false ) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'sCountryCode' ] = strtolower ( $aSearchTerm [ 'country_code' ]);
// Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
2016-09-08 04:16:22 +03:00
if (( $iToken + 1 != sizeof ( $aWordset ) || $iPhrase + 1 != sizeof ( $aPhrases ))) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'iSearchRank' ] += 5 ;
}
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
2016-09-08 04:16:22 +03:00
} elseif ( isset ( $aSearchTerm [ 'lat' ]) && $aSearchTerm [ 'lat' ] !== '' && $aSearchTerm [ 'lat' ] !== null ) {
2017-03-17 00:04:30 +03:00
if ( $aSearch [ 'oNear' ] === false ) {
$aSearch [ 'oNear' ] = new NearPoint (
2017-03-18 00:36:22 +03:00
$aSearchTerm [ 'lat' ],
$aSearchTerm [ 'lon' ],
$aSearchTerm [ 'radius' ]
);
2016-09-04 04:19:48 +03:00
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
2017-07-04 01:02:11 +03:00
} elseif ( $sPhraseType == 'postalcode' || ( $aSearchTerm [ 'class' ] == 'place' && $aSearchTerm [ 'type' ] == 'postcode' )) {
2016-09-04 04:19:48 +03:00
// 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
2016-09-08 04:16:22 +03:00
if ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ]) {
2017-07-04 01:02:11 +03:00
// If we have structured search or this is the first term,
// make the postcode the primary search element.
2017-07-05 22:15:50 +03:00
if ( $aSearchTerm [ 'operator' ] == '' && ( $sPhraseType == 'postalcode' || sizeof ( $aSearch [ 'aName' ]) == 0 )) {
2016-09-04 04:19:48 +03:00
$aNewSearch = $aSearch ;
2017-07-04 01:02:11 +03:00
$aNewSearch [ 'sOperator' ] = 'postcode' ;
2016-09-04 04:19:48 +03:00
$aNewSearch [ 'aAddress' ] = array_merge ( $aNewSearch [ 'aAddress' ], $aNewSearch [ 'aName' ]);
2017-07-05 00:25:48 +03:00
$aNewSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = substr ( $aSearchTerm [ 'word_token' ], 1 );
2016-09-04 04:19:48 +03:00
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aNewSearch ;
}
2017-07-04 01:02:11 +03:00
// If we have a structured search or this is not the first term,
// add the postcode as an addendum.
if ( $sPhraseType == 'postalcode' || sizeof ( $aSearch [ 'aName' ])) {
$aSearch [ 'sPostcode' ] = $aSearchTerm [ 'word_token' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
2016-09-04 04:19:48 +03:00
}
}
2016-09-08 04:16:22 +03:00
} elseif (( $sPhraseType == '' || $sPhraseType == 'street' ) && $aSearchTerm [ 'class' ] == 'place' && $aSearchTerm [ 'type' ] == 'house' ) {
if ( $aSearch [ 'sHouseNumber' ] === '' ) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'sHouseNumber' ] = $sToken ;
// sanity check: if the housenumber is not mainly made
// up of numbers, add a penalty
if ( preg_match_all ( " /[^0-9]/ " , $sToken , $aMatches ) > 2 ) $aSearch [ 'iSearchRank' ] ++ ;
// also housenumbers should appear in the first or second phrase
if ( $iPhrase > 1 ) $aSearch [ 'iSearchRank' ] += 1 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
/*
// Fall back to not searching for this item (better than nothing)
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
*/
}
2016-09-08 04:16:22 +03:00
} elseif ( $sPhraseType == '' && $aSearchTerm [ 'class' ] !== '' && $aSearchTerm [ 'class' ] !== null ) {
2017-06-01 22:40:23 +03:00
// 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 ))) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'sClass' ] = $aSearchTerm [ 'class' ];
$aSearch [ 'sType' ] = $aSearchTerm [ 'type' ];
2017-06-01 22:40:23 +03:00
if ( $aSearchTerm [ 'operator' ] == '' ) {
$aSearch [ 'sOperator' ] = sizeof ( $aSearch [ 'aName' ]) ? 'name' : 'near' ;
$aSearch [ 'iSearchRank' ] += 2 ;
} else {
$aSearch [ 'sOperator' ] = 'near' ; // near = in for the moment
}
2016-09-04 04:19:48 +03:00
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
2016-09-08 04:16:22 +03:00
} elseif ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ]) {
if ( sizeof ( $aSearch [ 'aName' ])) {
if (( ! $bStructuredPhrases || $iPhrase > 0 ) && $sPhraseType != 'country' && ( ! isset ( $aValidTokens [ $sToken ]) || strpos ( $sToken , ' ' ) !== false )) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aCurrentSearch [ 'aFullNameAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iSearchRank' ] += 1000 ; // skip;
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
//$aSearch['iNamePhrase'] = $iPhrase;
}
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
}
// Look for partial matches.
// Note that there is no point in adding country terms here
// because country are 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 ) {
if ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ]) {
if (( ! $bStructuredPhrases || $iPhrase > 0 ) && sizeof ( $aCurrentSearch [ 'aName' ]) && strpos ( $sToken , ' ' ) === false ) {
2016-09-04 04:19:48 +03:00
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
2016-09-08 04:16:22 +03:00
if ( $aWordFrequencyScores [ $aSearchTerm [ 'word_id' ]] < CONST_Max_Word_Frequency ) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
2016-09-08 04:16:22 +03:00
} elseif ( isset ( $aValidTokens [ ' ' . $sToken ])) { // revert to the token version?
2016-09-04 04:19:48 +03:00
$aSearch [ 'aAddressNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iSearchRank' ] += 1 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
2016-09-08 04:16:22 +03:00
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTermToken ) {
2016-09-04 04:19:48 +03:00
if ( empty ( $aSearchTermToken [ 'country_code' ])
2016-09-11 06:22:51 +03:00
&& empty ( $aSearchTermToken [ 'lat' ])
&& empty ( $aSearchTermToken [ 'class' ])
2016-09-08 04:16:22 +03:00
) {
2016-09-04 04:19:48 +03:00
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
$aSearch [ 'aAddress' ][ $aSearchTermToken [ 'word_id' ]] = $aSearchTermToken [ 'word_id' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aAddressNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
if ( preg_match ( '#^[0-9]+$#' , $sToken )) $aSearch [ 'iSearchRank' ] += 2 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
2016-09-08 04:16:22 +03:00
if ( ! sizeof ( $aCurrentSearch [ 'aName' ]) || $aCurrentSearch [ 'iNamePhrase' ] == $iPhrase ) {
2016-09-04 04:19:48 +03:00
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
if ( ! sizeof ( $aCurrentSearch [ 'aName' ])) $aSearch [ 'iSearchRank' ] += 1 ;
if ( preg_match ( '#^[0-9]+$#' , $sToken )) $aSearch [ 'iSearchRank' ] += 2 ;
2016-09-08 04:16:22 +03:00
if ( $aWordFrequencyScores [ $aSearchTerm [ 'word_id' ]] < CONST_Max_Word_Frequency ) {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aSearch [ 'aNameNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
$aSearch [ 'iNamePhrase' ] = $iPhrase ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
}
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
// Allow skipping a word - but at EXTREAM cost
//$aSearch = $aCurrentSearch;
//$aSearch['iSearchRank']+=100;
//$aNewWordsetSearches[] = $aSearch;
}
}
// Sort and cut
usort ( $aNewWordsetSearches , 'bySearchRank' );
$aWordsetSearches = array_slice ( $aNewWordsetSearches , 0 , 50 );
}
//var_Dump('<hr>',sizeof($aWordsetSearches)); exit;
$aNewPhraseSearches = array_merge ( $aNewPhraseSearches , $aNewWordsetSearches );
usort ( $aNewPhraseSearches , 'bySearchRank' );
$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 ) {
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) {
2016-09-04 04:19:48 +03:00
if ( ! isset ( $aGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
}
}
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);
}
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-06-01 22:40:23 +03:00
$oNormalizer = \Transliterator :: createFromRules ( CONST_Term_Normalization_Rules );
if ( $oNormalizer !== null ) {
$sNormQuery = $oNormalizer -> transliterate ( $this -> sQuery );
} else {
$sNormQuery = null ;
}
2016-09-11 06:22:51 +03:00
$sLanguagePrefArraySQL = " ARRAY[ " . join ( ',' , array_map ( " getDBQuoted " , $this -> aLangPrefOrder )) . " ] " ;
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 ) {
2016-09-04 04:19:48 +03:00
// Start with a blank search
$aSearches = array (
2016-09-10 22:10:52 +03:00
array (
'iSearchRank' => 0 ,
'iNamePhrase' => - 1 ,
'sCountryCode' => false ,
'aName' => array (),
'aAddress' => array (),
'aFullNameAddress' => array (),
'aNameNonSearch' => array (),
'aAddressNonSearch' => array (),
'sOperator' => '' ,
'aFeatureName' => array (),
'sClass' => '' ,
'sType' => '' ,
'sHouseNumber' => '' ,
2017-07-04 01:02:11 +03:00
'sPostcode' => '' ,
2017-03-17 00:04:30 +03:00
'oNear' => $oNearPoint
2016-09-10 22:10:52 +03:00
)
);
2016-09-04 04:19:48 +03:00
// Any 'special' terms in the search?
$bSpecialTerms = false ;
preg_match_all ( '/\\[(.*)=(.*)\\]/' , $sQuery , $aSpecialTermsRaw , PREG_SET_ORDER );
$aSpecialTerms = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aSpecialTermsRaw as $aSpecialTerm ) {
2016-09-04 04:19:48 +03:00
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
$aSpecialTerms [ strtolower ( $aSpecialTerm [ 1 ])] = $aSpecialTerm [ 2 ];
}
preg_match_all ( '/\\[([\\w ]*)\\]/u' , $sQuery , $aSpecialTermsRaw , PREG_SET_ORDER );
$aSpecialTerms = array ();
2016-09-08 04:16:22 +03:00
if ( isset ( $this -> aStructuredQuery [ 'amenity' ]) && $this -> aStructuredQuery [ 'amenity' ]) {
2016-09-04 04:19:48 +03:00
$aSpecialTermsRaw [] = array ( '[' . $this -> aStructuredQuery [ 'amenity' ] . ']' , $this -> aStructuredQuery [ 'amenity' ]);
unset ( $this -> aStructuredQuery [ 'amenity' ]);
}
2016-09-08 04:16:22 +03:00
foreach ( $aSpecialTermsRaw as $aSpecialTerm ) {
2016-09-04 04:19:48 +03:00
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
2016-10-29 19:49:21 +03:00
$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\')) ' ;
$sSQL .= ' OR country_code is not null' ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_Dump ( $sSQL );
$aSearchWords = chksql ( $this -> oDB -> getAll ( $sSQL ));
$aNewSearches = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aSearches as $aSearch ) {
foreach ( $aSearchWords as $aSearchTerm ) {
2016-09-04 04:19:48 +03:00
$aNewSearch = $aSearch ;
2016-09-08 04:16:22 +03:00
if ( $aSearchTerm [ 'country_code' ]) {
2016-09-04 04:19:48 +03:00
$aNewSearch [ 'sCountryCode' ] = strtolower ( $aSearchTerm [ 'country_code' ]);
$aNewSearches [] = $aNewSearch ;
$bSpecialTerms = true ;
}
2016-09-08 04:16:22 +03:00
if ( $aSearchTerm [ 'class' ]) {
2016-09-04 04:19:48 +03:00
$aNewSearch [ 'sClass' ] = $aSearchTerm [ 'class' ];
$aNewSearch [ 'sType' ] = $aSearchTerm [ 'type' ];
$aNewSearches [] = $aNewSearch ;
$bSpecialTerms = true ;
}
}
}
$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 ();
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aTokens )) {
2016-09-11 06:22:51 +03:00
$aDatabaseWords = chksql (
$this -> oDB -> getAll ( $sSQL ),
" Could not get word tokens. "
);
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aDatabaseWords = array ();
}
$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 );
// Try and calculate GB postcodes we might be missing
2016-09-08 04:16:22 +03:00
foreach ( $aTokens as $sToken ) {
2016-09-04 04:19:48 +03:00
// Source of gb postcodes is now definitive - always use
2016-09-08 04:16:22 +03:00
if ( preg_match ( '/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/' , strtoupper ( trim ( $sToken )), $aData )) {
2016-09-11 06:22:51 +03:00
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 );
2016-09-04 04:19:48 +03:00
}
$aGBPostcodeLocation = gbPostcodeCalculate ( $aData [ 0 ], $aData [ 1 ], $aData [ 2 ], $this -> oDB );
2016-09-08 04:16:22 +03:00
if ( $aGBPostcodeLocation ) {
2016-09-04 04:19:48 +03:00
$aValidTokens [ $sToken ] = $aGBPostcodeLocation ;
}
2016-09-11 06:22:51 +03:00
} elseif ( ! isset ( $aValidTokens [ $sToken ]) && preg_match ( '/^([0-9]{5}) [0-9]{4}$/' , $sToken , $aData )) {
2016-09-08 04:16:22 +03:00
// US ZIP+4 codes - if there is no token,
2016-09-14 04:16:46 +03:00
// merge in the 5-digit ZIP code
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
2016-09-11 06:22:51 +03:00
if ( ! isset ( $aValidTokens [ ' ' . $sToken ]) && strpos ( $sToken , ' ' ) === false && preg_match ( '/[0-9]/' , $sToken )) {
2016-09-10 22:10:52 +03:00
$aValidTokens [ ' ' . $sToken ] = array ( array ( 'class' => 'place' , 'type' => 'house' ));
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 ) {
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) {
2016-09-04 04:19:48 +03:00
if ( ! isset ( $aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
}
}
}
$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 ) {
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) {
2016-09-04 04:19:48 +03:00
if ( ! isset ( $aGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
}
}
ksort ( $aGroupedSearches );
}
if ( CONST_Debug ) var_Dump ( $aGroupedSearches );
2016-09-08 04:16:22 +03:00
if ( CONST_Search_TryDroppedAddressTerms && sizeof ( $this -> aStructuredQuery ) > 0 ) {
2016-09-04 04:19:48 +03:00
$aCopyGroupedSearches = $aGroupedSearches ;
2016-09-08 04:16:22 +03:00
foreach ( $aCopyGroupedSearches as $iGroup => $aSearches ) {
foreach ( $aSearches as $iSearch => $aSearch ) {
2016-09-04 04:19:48 +03:00
$aReductionsList = array ( $aSearch [ 'aAddress' ]);
$iSearchRank = $aSearch [ 'iSearchRank' ];
2016-09-08 04:16:22 +03:00
while ( sizeof ( $aReductionsList ) > 0 ) {
2016-09-04 04:19:48 +03:00
$iSearchRank += 5 ;
if ( $iSearchRank > iMaxRank ) break 3 ;
$aNewReductionsList = array ();
2016-09-08 04:16:22 +03:00
foreach ( $aReductionsList as $aReductionsWordList ) {
for ( $iReductionWord = 0 ; $iReductionWord < sizeof ( $aReductionsWordList ); $iReductionWord ++ ) {
2016-09-04 04:19:48 +03:00
$aReductionsWordListResult = array_merge ( array_slice ( $aReductionsWordList , 0 , $iReductionWord ), array_slice ( $aReductionsWordList , $iReductionWord + 1 ));
$aReverseSearch = $aSearch ;
$aSearch [ 'aAddress' ] = $aReductionsWordListResult ;
$aSearch [ 'iSearchRank' ] = $iSearchRank ;
$aGroupedSearches [ $iSearchRank ][] = $aReverseSearch ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aReductionsWordListResult ) > 0 ) {
2016-09-04 04:19:48 +03:00
$aNewReductionsList [] = $aReductionsWordListResult ;
}
}
}
$aReductionsList = $aNewReductionsList ;
}
}
}
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 ++ ;
2016-09-08 04:16:22 +03:00
foreach ( $aSearches as $aSearch ) {
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> " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) _debugDumpGroupedSearches ( array ( $iGroupedRank => array ( $aSearch )), $aValidTokens );
// No location term?
2017-03-17 00:04:30 +03:00
if ( ! sizeof ( $aSearch [ 'aName' ]) && ! sizeof ( $aSearch [ 'aAddress' ]) && ! $aSearch [ 'oNear' ]) {
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sCountryCode' ] && ! $aSearch [ 'sClass' ] && ! $aSearch [ 'sHouseNumber' ]) {
2016-09-04 04:19:48 +03:00
// Just looking for a country by code - look it up
2016-09-08 04:16:22 +03:00
if ( 4 >= $this -> iMinAddressRank && 4 <= $this -> iMaxAddressRank ) {
2017-03-26 01:11:09 +03:00
$sSQL = " SELECT place_id FROM placex WHERE country_code=' " . $aSearch [ 'sCountryCode' ] . " ' AND rank_search = 4 " ;
if ( $sCountryCodesSQL ) $sSQL .= " AND country_code in ( $sCountryCodesSQL ) " ;
2016-09-04 04:19:48 +03:00
if ( $bBoundingBoxSearch )
2016-10-29 19:49:21 +03:00
$sSQL .= " AND _st_intersects( $this->sViewboxSmallSQL , geometry) " ;
$sSQL .= " ORDER BY st_area(geometry) DESC LIMIT 1 " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aPlaceIDs = array ();
}
2016-09-08 04:16:22 +03:00
} else {
2017-03-17 00:04:30 +03:00
if ( ! $bBoundingBoxSearch && ! $aSearch [ 'oNear' ]) continue ;
2016-09-04 04:19:48 +03:00
if ( ! $aSearch [ 'sClass' ]) continue ;
2016-09-08 04:16:22 +03:00
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT COUNT(*) FROM pg_tables WHERE tablename = 'place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ' " ;
2016-09-08 04:16:22 +03:00
if ( chksql ( $this -> oDB -> getOne ( $sSQL ))) {
2016-10-29 19:49:21 +03:00
$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) " ;
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " AND country_code in ( $sCountryCodesSQL ) " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2016-10-29 19:49:21 +03:00
if ( $this -> sViewboxCentreSQL ) $sSQL .= " ORDER BY ST_Distance( $this->sViewboxCentreSQL , ct.centroid) ASC " ;
2016-09-04 04:19:48 +03:00
$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.
2016-09-08 04:16:22 +03:00
if ( ! sizeof ( $aPlaceIDs ) && ! sizeof ( $this -> aExcludePlaceIDs ) && ! $this -> bBoundedSearch ) {
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id FROM place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ct " ;
2016-09-04 04:19:48 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " join placex using (place_id) " ;
2016-10-29 19:49:21 +03:00
$sSQL .= " WHERE ST_Contains( $this->sViewboxLargeSQL , ct.centroid) " ;
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " AND country_code in ( $sCountryCodesSQL ) " ;
2016-10-29 19:49:21 +03:00
if ( $this -> sViewboxCentreSQL ) $sSQL .= " ORDER BY ST_Distance( $this->sViewboxCentreSQL , ct.centroid) ASC " ;
$sSQL .= " LIMIT $this->iLimit " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
}
2016-09-08 04:16:22 +03:00
} else {
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id " ;
$sSQL .= " FROM placex " ;
$sSQL .= " WHERE class=' " . $aSearch [ 'sClass' ] . " ' " ;
$sSQL .= " AND type=' " . $aSearch [ 'sType' ] . " ' " ;
$sSQL .= " AND ST_Contains( $this->sViewboxSmallSQL , geometry) " ;
$sSQL .= " AND linked_place_id is null " ;
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " AND country_code in ( $sCountryCodesSQL ) " ;
2016-10-29 19:49:21 +03:00
if ( $this -> sViewboxCentreSQL ) $sSQL .= " ORDER BY ST_Distance( $this->sViewboxCentreSQL , centroid) ASC " ;
$sSQL .= " LIMIT $this->iLimit " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
}
}
2017-03-17 00:04:30 +03:00
} elseif ( $aSearch [ 'oNear' ] && ! sizeof ( $aSearch [ 'aName' ]) && ! sizeof ( $aSearch [ 'aAddress' ]) && ! $aSearch [ 'sClass' ]) {
2016-09-08 04:16:22 +03:00
// If a coordinate is given, the search must either
// be for a name or a special search. Ignore everythin else.
2016-09-04 04:19:48 +03:00
$aPlaceIDs = array ();
2017-07-05 00:25:48 +03:00
} elseif ( $aSearch [ 'sOperator' ] == 'postcode' ) {
2017-07-05 22:15:50 +03:00
$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' ])) . " ' " ;
2017-07-05 00:25:48 +03:00
if ( $aSearch [ 'sCountryCode' ]) {
2017-07-05 22:15:50 +03:00
$sSQL .= " AND p.country_code = ' " . $aSearch [ 'sCountryCode' ] . " ' " ;
2017-07-05 00:25:48 +03:00
}
if ( $sCountryCodesSQL ) {
2017-07-05 22:15:50 +03:00
$sSQL .= " AND p.country_code in ( $sCountryCodesSQL ) " ;
2017-07-05 00:25:48 +03:00
}
$sSQL .= " LIMIT $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$aPlaceIDs = array ();
// First we need a position, either aName or fLat or both
$aTerms = array ();
$aOrder = array ();
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sHouseNumber' ] && sizeof ( $aSearch [ 'aAddress' ])) {
2016-09-04 04:19:48 +03:00
$sHouseNumberRegex = '\\\\m' . $aSearch [ 'sHouseNumber' ] . '\\\\M' ;
$aOrder [] = " " ;
2016-10-29 19:49:21 +03:00
$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 ] .= " ) " ;
2016-09-04 04:19:48 +03:00
// also housenumbers from interpolation lines table are needed
2016-10-29 19:49:21 +03:00
$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 " ;
2017-02-26 14:58:07 +03:00
$aOrder [ 0 ] .= " AND startnumber is not NULL " ;
2016-10-29 19:49:21 +03:00
$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 " ;
2016-09-04 04:19:48 +03:00
}
// 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
2016-09-11 06:22:51 +03:00
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' ], " , " ) . " ] " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aSearch [ 'aAddress' ]) && $aSearch [ 'aName' ] != $aSearch [ 'aAddress' ]) {
2016-09-04 04:19:48 +03:00
// For infrequent name terms disable index usage for address
2016-09-11 06:22:51 +03:00
if ( CONST_Search_NameOnlySearchFrequencyThreshold
&& sizeof ( $aSearch [ 'aName' ]) == 1
&& $aWordFrequencyScores [ $aSearch [ 'aName' ][ reset ( $aSearch [ 'aName' ])]] < CONST_Search_NameOnlySearchFrequencyThreshold
2016-09-08 04:16:22 +03:00
) {
2016-09-11 06:22:51 +03:00
$aTerms [] = " array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[ " . join ( array_merge ( $aSearch [ 'aAddress' ], $aSearch [ 'aAddressNonSearch' ]), " , " ) . " ] " ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-11 06:22:51 +03:00
$aTerms [] = " nameaddress_vector @> ARRAY[ " . join ( $aSearch [ 'aAddress' ], " , " ) . " ] " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aSearch [ 'aAddressNonSearch' ])) {
2016-09-11 06:22:51 +03:00
$aTerms [] = " array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[ " . join ( $aSearch [ 'aAddressNonSearch' ], " , " ) . " ] " ;
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
}
}
if ( $aSearch [ 'sCountryCode' ]) $aTerms [] = " country_code = ' " . pg_escape_string ( $aSearch [ 'sCountryCode' ]) . " ' " ;
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sHouseNumber' ]) {
2016-09-04 04:19:48 +03:00
$aTerms [] = " address_rank between 16 and 27 " ;
2016-09-08 04:16:22 +03:00
} else {
if ( $this -> iMinAddressRank > 0 ) {
2016-09-04 04:19:48 +03:00
$aTerms [] = " address_rank >= " . $this -> iMinAddressRank ;
}
2016-09-08 04:16:22 +03:00
if ( $this -> iMaxAddressRank < 30 ) {
2016-09-04 04:19:48 +03:00
$aTerms [] = " address_rank <= " . $this -> iMaxAddressRank ;
}
}
2017-03-17 00:04:30 +03:00
if ( $aSearch [ 'oNear' ]) {
$aTerms [] = $aSearch [ 'oNear' ] -> withinSQL ( 'centroid' );
2016-10-29 19:49:21 +03:00
2017-03-17 00:04:30 +03:00
$aOrder [] = $aSearch [ 'oNear' ] -> distanceSQL ( 'centroid' );
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-09-11 06:22:51 +03:00
$aTerms [] = " place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( $sCountryCodesSQL ) {
2016-09-04 04:19:48 +03:00
$aTerms [] = " country_code in ( $sCountryCodesSQL ) " ;
}
if ( $bBoundingBoxSearch ) $aTerms [] = " centroid && $this->sViewboxSmallSQL " ;
2017-03-15 22:43:08 +03:00
if ( $oNearPoint ) {
$aOrder [] = $oNearPoint -> distanceSQL ( 'centroid' );
}
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sHouseNumber' ]) {
2016-09-04 04:19:48 +03:00
$sImportanceSQL = '- abs(26 - address_rank) + 3' ;
2016-09-08 04:16:22 +03:00
} else {
2016-10-29 19:49:21 +03:00
$sImportanceSQL = '(CASE WHEN importance = 0 OR importance IS NULL THEN 0.75-(search_rank::float/40) ELSE importance END)' ;
2016-09-04 04:19:48 +03:00
}
2016-10-29 19:49:21 +03:00
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 " ;
2016-09-04 04:19:48 +03:00
$aOrder [] = " $sImportanceSQL DESC " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aSearch [ 'aFullNameAddress' ])) {
2016-10-29 19:49:21 +03:00
$sExactMatchSQL = ' ( ' ;
$sExactMatchSQL .= ' SELECT count(*) FROM ( ' ;
$sExactMatchSQL .= ' SELECT unnest(ARRAY[' . join ( $aSearch [ 'aFullNameAddress' ], " , " ) . ']) ' ;
$sExactMatchSQL .= ' INTERSECT ' ;
$sExactMatchSQL .= ' SELECT unnest(nameaddress_vector)' ;
$sExactMatchSQL .= ' ) s' ;
$sExactMatchSQL .= ') as exactmatch' ;
2016-09-04 04:19:48 +03:00
$aOrder [] = 'exactmatch DESC' ;
} else {
$sExactMatchSQL = '0::int as exactmatch' ;
}
2016-09-08 04:16:22 +03:00
if ( sizeof ( $aTerms )) {
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id, " ;
2016-09-04 04:19:48 +03:00
$sSQL .= $sExactMatchSQL ;
2016-10-29 19:49:21 +03:00
$sSQL .= " FROM search_name " ;
$sSQL .= " WHERE " . join ( ' and ' , $aTerms );
$sSQL .= " ORDER BY " . join ( ', ' , $aOrder );
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sHouseNumber' ] || $aSearch [ 'sClass' ]) {
2016-10-29 19:49:21 +03:00
$sSQL .= " LIMIT 20 " ;
2016-09-08 04:16:22 +03:00
} elseif ( ! sizeof ( $aSearch [ 'aName' ]) && ! sizeof ( $aSearch [ 'aAddress' ]) && $aSearch [ 'sClass' ]) {
2016-10-29 19:49:21 +03:00
$sSQL .= " LIMIT 1 " ;
2016-09-08 04:16:22 +03:00
} else {
2016-10-29 19:49:21 +03:00
$sSQL .= " LIMIT " . $this -> iLimit ;
2016-09-08 04:16:22 +03:00
}
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
2016-09-11 06:22:51 +03:00
$aViewBoxPlaceIDs = chksql (
$this -> oDB -> getAll ( $sSQL ),
" Could not get places for search terms. "
);
2016-09-04 04:19:48 +03:00
//var_dump($aViewBoxPlaceIDs);
// Did we have an viewbox matches?
$aPlaceIDs = array ();
$bViewBoxMatch = false ;
2016-09-08 04:16:22 +03:00
foreach ( $aViewBoxPlaceIDs as $aViewBoxRow ) {
2016-09-04 04:19:48 +03:00
//if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
//if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
//if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
//else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
$aPlaceIDs [] = $aViewBoxRow [ 'place_id' ];
$this -> exactMatchCache [ $aViewBoxRow [ 'place_id' ]] = $aViewBoxRow [ 'exactmatch' ];
}
}
//var_Dump($aPlaceIDs);
//exit;
//now search for housenumber, if housenumber provided
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sHouseNumber' ] && sizeof ( $aPlaceIDs )) {
2016-09-04 04:19:48 +03:00
$searchedHousenumber = intval ( $aSearch [ 'sHouseNumber' ]);
$aRoadPlaceIDs = $aPlaceIDs ;
2016-09-11 06:22:51 +03:00
$sPlaceIDs = join ( ',' , $aPlaceIDs );
2016-09-04 04:19:48 +03:00
// Now they are indexed, look for a house attached to a street we found
$sHouseNumberRegex = '\\\\m' . $aSearch [ 'sHouseNumber' ] . '\\\\M' ;
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id FROM placex " ;
$sSQL .= " WHERE parent_place_id in ( " . $sPlaceIDs . " ) and transliteration(housenumber) ~* E' " . $sHouseNumberRegex . " ' " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2016-10-29 19:49:21 +03:00
$sSQL .= " LIMIT $this->iLimit " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
2016-10-13 00:30:24 +03:00
2016-09-04 04:19:48 +03:00
// if nothing found, search in the interpolation line table
2016-09-08 04:16:22 +03:00
if ( ! sizeof ( $aPlaceIDs )) {
2016-09-04 04:19:48 +03:00
// do we need to use transliteration and the regex for housenumbers???
//new query for lines, not housenumbers anymore
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT distinct place_id FROM location_property_osmline " ;
2017-02-26 14:58:07 +03:00
$sSQL .= " WHERE startnumber is not NULL and parent_place_id in ( " . $sPlaceIDs . " ) and ( " ;
2016-09-08 04:16:22 +03:00
if ( $searchedHousenumber % 2 == 0 ) {
2016-09-04 04:19:48 +03:00
//if housenumber is even, look for housenumber in streets with interpolationtype even or all
2016-10-13 00:30:24 +03:00
$sSQL .= " interpolationtype='even' " ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
//look for housenumber in streets with interpolationtype odd or all
2016-10-13 00:30:24 +03:00
$sSQL .= " interpolationtype='odd' " ;
2016-09-04 04:19:48 +03:00
}
2016-10-13 00:30:24 +03:00
$sSQL .= " or interpolationtype='all') and " ;
$sSQL .= $searchedHousenumber . " >=startnumber and " ;
$sSQL .= $searchedHousenumber . " <=endnumber " ;
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
//get place IDs
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL , 0 ));
}
2016-10-13 00:30:24 +03:00
2016-09-04 04:19:48 +03:00
// If nothing found try the aux fallback table
2016-09-08 04:16:22 +03:00
if ( CONST_Use_Aux_Location_data && ! sizeof ( $aPlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id FROM location_property_aux " ;
$sSQL .= " WHERE parent_place_id in ( " . $sPlaceIDs . " ) " ;
$sSQL .= " AND housenumber = ' " . pg_escape_string ( $aSearch [ 'sHouseNumber' ]) . " ' " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND parent_place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
}
//if nothing was found in placex or location_property_aux, then search in Tiger data for this housenumber(location_property_tiger)
2016-09-08 04:16:22 +03:00
if ( CONST_Use_US_Tiger_Data && ! sizeof ( $aPlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT distinct place_id FROM location_property_tiger " ;
$sSQL .= " WHERE parent_place_id in ( " . $sPlaceIDs . " ) and ( " ;
2016-09-08 04:16:22 +03:00
if ( $searchedHousenumber % 2 == 0 ) {
2016-10-13 00:30:24 +03:00
$sSQL .= " interpolationtype='even' " ;
2016-09-08 04:16:22 +03:00
} else {
2016-10-13 00:30:24 +03:00
$sSQL .= " interpolationtype='odd' " ;
2016-09-04 04:19:48 +03:00
}
2016-10-13 00:30:24 +03:00
$sSQL .= " or interpolationtype='all') and " ;
$sSQL .= $searchedHousenumber . " >=startnumber and " ;
$sSQL .= $searchedHousenumber . " <=endnumber " ;
2016-09-04 04:19:48 +03:00
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
//get place IDs
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL , 0 ));
}
// Fallback to the road (if no housenumber was found)
2016-09-08 04:16:22 +03:00
if ( ! sizeof ( $aPlaceIDs ) && preg_match ( '/[0-9]+/' , $aSearch [ 'sHouseNumber' ])) {
2016-09-04 04:19:48 +03:00
$aPlaceIDs = $aRoadPlaceIDs ;
//set to -1, if no housenumbers were found
$searchedHousenumber = - 1 ;
}
//else: housenumber was found, remains saved in searchedHousenumber
}
2016-09-08 04:16:22 +03:00
if ( $aSearch [ 'sClass' ] && sizeof ( $aPlaceIDs )) {
2016-09-04 04:19:48 +03:00
$sPlaceIDs = join ( ',' , $aPlaceIDs );
$aClassPlaceIDs = array ();
2016-09-08 04:16:22 +03:00
if ( ! $aSearch [ 'sOperator' ] || $aSearch [ 'sOperator' ] == 'name' ) {
2016-09-04 04:19:48 +03:00
// If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
2016-10-29 19:49:21 +03:00
$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 " ;
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " AND country_code in ( $sCountryCodesSQL ) " ;
2016-10-29 19:49:21 +03:00
$sSQL .= " ORDER BY rank_search ASC " ;
$sSQL .= " LIMIT $this->iLimit " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aClassPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
}
2016-09-08 04:16:22 +03:00
if ( ! $aSearch [ 'sOperator' ] || $aSearch [ 'sOperator' ] == 'near' ) { // & in
2017-06-05 22:33:50 +03:00
$sClassTable = 'place_classtype_' . $aSearch [ 'sClass' ] . '_' . $aSearch [ 'sType' ];
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT count(*) FROM pg_tables " ;
2017-06-05 22:33:50 +03:00
$sSQL .= " WHERE tablename = ' $sClassTable ' " ;
2016-09-04 04:19:48 +03:00
$bCacheTable = chksql ( $this -> oDB -> getOne ( $sSQL ));
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT min(rank_search) FROM placex WHERE place_id in ( $sPlaceIDs ) " ;
2016-09-04 04:19:48 +03:00
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 ;
2016-09-08 04:16:22 +03:00
if ( $this -> iMaxRank < 9 && $bCacheTable ) {
2016-09-04 04:19:48 +03:00
// Try and get a polygon to search in instead
2016-10-29 19:49:21 +03:00
$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 " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$sPlaceGeom = chksql ( $this -> oDB -> getOne ( $sSQL ));
}
2016-09-08 04:16:22 +03:00
if ( $sPlaceGeom ) {
2016-09-04 04:19:48 +03:00
$sPlaceIDs = false ;
2016-09-08 04:16:22 +03:00
} else {
2016-09-04 04:19:48 +03:00
$this -> iMaxRank += 5 ;
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id FROM placex WHERE place_id in ( $sPlaceIDs ) and rank_search < $this->iMaxRank " ;
2016-09-04 04:19:48 +03:00
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = chksql ( $this -> oDB -> getCol ( $sSQL ));
2016-09-11 06:22:51 +03:00
$sPlaceIDs = join ( ',' , $aPlaceIDs );
2016-09-04 04:19:48 +03:00
}
2016-09-08 04:16:22 +03:00
if ( $sPlaceIDs || $sPlaceGeom ) {
2016-09-04 04:19:48 +03:00
$fRange = 0.01 ;
2016-09-08 04:16:22 +03:00
if ( $bCacheTable ) {
2016-09-04 04:19:48 +03:00
// More efficient - can make the range bigger
$fRange = 0.05 ;
$sOrderBySQL = '' ;
2017-03-15 22:43:08 +03:00
if ( $oNearPoint ) {
2017-03-18 00:36:22 +03:00
$sOrderBySQL = $oNearPoint -> distanceSQL ( 'l.centroid' );
2017-03-15 22:43:08 +03:00
} elseif ( $sPlaceIDs ) {
$sOrderBySQL = " ST_Distance(l.centroid, f.geometry) " ;
} elseif ( $sPlaceGeom ) {
$sOrderBysSQL = " ST_Distance(st_centroid(' " . $sPlaceGeom . " '), l.centroid) " ;
}
2016-09-04 04:19:48 +03:00
2017-06-05 22:33:50 +03:00
$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 " ;
2016-09-04 04:19:48 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " join placex as lp using (place_id) " ;
2016-09-08 04:16:22 +03:00
if ( $sPlaceIDs ) {
2016-09-04 04:19:48 +03:00
$sSQL .= " ,placex as f where " ;
$sSQL .= " f.place_id in ( $sPlaceIDs ) and ST_DWithin(l.centroid, f.centroid, $fRange ) " ;
}
2016-09-08 04:16:22 +03:00
if ( $sPlaceGeom ) {
2016-09-04 04:19:48 +03:00
$sSQL .= " where " ;
$sSQL .= " ST_Contains(' " . $sPlaceGeom . " ', l.centroid) " ;
}
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-09-11 06:22:51 +03:00
$sSQL .= " and l.place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " and lp.country_code in ( $sCountryCodesSQL ) " ;
2017-06-05 22:33:50 +03:00
$sSQL .= 'limit 300) i ' ;
if ( $sOrderBySQL ) $sSQL .= " order by order_term asc " ;
2016-09-04 04:19:48 +03:00
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 )));
2016-09-08 04:16:22 +03:00
} else {
2017-03-17 00:04:30 +03:00
if ( $aSearch [ 'oNear' ]) {
$fRange = $aSearch [ 'oNear' ] -> radius ();
}
2016-09-04 04:19:48 +03:00
$sOrderBySQL = '' ;
2017-03-15 22:43:08 +03:00
if ( $oNearPoint ) {
$sOrderBySQL = $oNearPoint -> distanceSQL ( 'l.geometry' );
} else {
$sOrderBySQL = " ST_Distance(l.geometry, f.geometry) " ;
}
2016-09-04 04:19:48 +03:00
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT distinct l.place_id " . ( $sOrderBysSQL ? ',' . $sOrderBysSQL : '' );
$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' ] . " ' " ;
2016-09-08 04:16:22 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs )) {
2016-10-29 19:49:21 +03:00
$sSQL .= " AND l.place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2017-03-26 01:11:09 +03:00
if ( $sCountryCodesSQL ) $sSQL .= " AND l.country_code in ( $sCountryCodesSQL ) " ;
2016-10-29 19:49:21 +03:00
if ( $sOrderBy ) $sSQL .= " ORDER BY " . $OrderBysSQL . " ASC " ;
if ( $this -> iOffset ) $sSQL .= " OFFSET $this->iOffset " ;
2016-09-04 04:19:48 +03:00
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aClassPlaceIDs = array_merge ( $aClassPlaceIDs , chksql ( $this -> oDB -> getCol ( $sSQL )));
}
}
}
$aPlaceIDs = $aClassPlaceIDs ;
}
}
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
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
2016-10-29 19:49:21 +03:00
$sSQL = " SELECT place_id " ;
$sSQL .= " FROM placex " ;
$sSQL .= " WHERE place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
$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 ) . " ) " ;
}
2016-09-08 04:16:22 +03:00
if ( CONST_Use_US_Tiger_Data ) {
2016-10-29 19:49:21 +03:00
$sSQL .= " ) " ;
$sSQL .= " UNION " ;
$sSQL .= " SELECT place_id " ;
$sSQL .= " FROM location_property_tiger " ;
$sSQL .= " WHERE place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
$sSQL .= " AND (30 between $this->iMinAddressRank and $this->iMaxAddressRank " ;
2016-09-11 06:22:51 +03:00
if ( $this -> aAddressRankList ) $sSQL .= " OR 30 in ( " . join ( ',' , $this -> aAddressRankList ) . " ) " ;
2016-09-04 04:19:48 +03:00
}
2016-10-29 19:49:21 +03:00
$sSQL .= " ) UNION " ;
$sSQL .= " SELECT place_id " ;
$sSQL .= " FROM location_property_osmline " ;
$sSQL .= " WHERE place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
2017-02-26 14:58:07 +03:00
$sSQL .= " AND startnumber is not NULL AND (30 between $this->iMinAddressRank and $this->iMaxAddressRank ) " ;
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