2013-08-27 20:08:57 +04:00
< ? php
2016-03-04 22:29:09 +03:00
require_once ( CONST_BasePath . '/lib/PlaceLookup.php' );
2013-08-27 20:08:57 +04:00
class Geocode
{
protected $oDB ;
protected $aLangPrefOrder = array ();
protected $bIncludeAddressDetails = false ;
2015-08-10 22:42:40 +03:00
protected $bIncludeExtraTags = false ;
protected $bIncludeNameDetails = false ;
2013-08-27 20:08:57 +04:00
protected $bIncludePolygonAsPoints = false ;
protected $bIncludePolygonAsText = false ;
protected $bIncludePolygonAsGeoJSON = false ;
protected $bIncludePolygonAsKML = false ;
protected $bIncludePolygonAsSVG = false ;
2015-04-27 16:16:38 +03:00
protected $fPolygonSimplificationThreshold = 0.0 ;
2013-08-27 20:08:57 +04:00
protected $aExcludePlaceIDs = array ();
protected $bDeDupe = true ;
protected $bReverseInPlan = false ;
protected $iLimit = 20 ;
protected $iFinalLimit = 10 ;
protected $iOffset = 0 ;
2014-01-23 18:40:37 +04:00
protected $bFallback = false ;
2013-08-27 20:08:57 +04:00
protected $aCountryCodes = false ;
protected $aNearPoint = false ;
protected $bBoundedSearch = false ;
protected $aViewBox = false ;
2014-04-30 23:04:46 +04:00
protected $sViewboxSmallSQL = false ;
protected $sViewboxLargeSQL = false ;
2013-08-27 20:08:57 +04:00
protected $aRoutePoints = false ;
protected $iMaxRank = 20 ;
protected $iMinAddressRank = 0 ;
protected $iMaxAddressRank = 30 ;
protected $aAddressRankList = array ();
2013-12-04 10:53:53 +04:00
protected $exactMatchCache = array ();
2013-08-27 20:08:57 +04:00
protected $sAllowedTypesSQLList = false ;
protected $sQuery = false ;
protected $aStructuredQuery = false ;
function Geocode ( & $oDB )
{
$this -> oDB =& $oDB ;
}
2013-09-16 02:05:26 +04:00
function setReverseInPlan ( $bReverse )
{
$this -> bReverseInPlan = $bReverse ;
}
2013-08-27 20:08:57 +04:00
function setLanguagePreference ( $aLangPref )
{
$this -> aLangPrefOrder = $aLangPref ;
}
function setIncludeAddressDetails ( $bAddressDetails = true )
{
$this -> bIncludeAddressDetails = ( bool ) $bAddressDetails ;
}
function getIncludeAddressDetails ()
{
return $this -> bIncludeAddressDetails ;
}
2015-08-10 22:42:40 +03:00
function getIncludeExtraTags ()
{
return $this -> bIncludeExtraTags ;
}
function getIncludeNameDetails ()
{
return $this -> bIncludeNameDetails ;
}
2013-08-27 20:08:57 +04:00
function setIncludePolygonAsPoints ( $b = true )
{
$this -> bIncludePolygonAsPoints = $b ;
}
function getIncludePolygonAsPoints ()
{
return $this -> bIncludePolygonAsPoints ;
}
function setIncludePolygonAsText ( $b = true )
{
$this -> bIncludePolygonAsText = $b ;
}
function getIncludePolygonAsText ()
{
return $this -> bIncludePolygonAsText ;
}
function setIncludePolygonAsGeoJSON ( $b = true )
{
$this -> bIncludePolygonAsGeoJSON = $b ;
}
function setIncludePolygonAsKML ( $b = true )
{
$this -> bIncludePolygonAsKML = $b ;
}
function setIncludePolygonAsSVG ( $b = true )
{
$this -> bIncludePolygonAsSVG = $b ;
}
2015-04-27 16:16:38 +03:00
function setPolygonSimplificationThreshold ( $f )
{
$this -> fPolygonSimplificationThreshold = $f ;
}
2013-08-27 20:08:57 +04:00
function setDeDupe ( $bDeDupe = true )
{
$this -> bDeDupe = ( bool ) $bDeDupe ;
}
function setLimit ( $iLimit = 10 )
{
if ( $iLimit > 50 ) $iLimit = 50 ;
if ( $iLimit < 1 ) $iLimit = 1 ;
$this -> iFinalLimit = $iLimit ;
$this -> iLimit = $this -> iFinalLimit + min ( $this -> iFinalLimit , 10 );
}
function setOffset ( $iOffset = 0 )
{
$this -> iOffset = $iOffset ;
}
2014-01-23 18:40:37 +04:00
function setFallback ( $bFallback = true )
{
$this -> bFallback = ( bool ) $bFallback ;
}
2013-08-27 20:08:57 +04:00
function setExcludedPlaceIDs ( $a )
{
// TODO: force to int
$this -> aExcludePlaceIDs = $a ;
}
function getExcludedPlaceIDs ()
{
return $this -> aExcludePlaceIDs ;
}
function setBounded ( $bBoundedSearch = true )
{
$this -> bBoundedSearch = ( bool ) $bBoundedSearch ;
}
function setViewBox ( $fLeft , $fBottom , $fRight , $fTop )
{
$this -> aViewBox = array ( $fLeft , $fBottom , $fRight , $fTop );
}
2013-08-30 16:04:33 +04:00
function getViewBoxString ()
{
if ( ! $this -> aViewBox ) return null ;
return $this -> aViewBox [ 0 ] . ',' . $this -> aViewBox [ 3 ] . ',' . $this -> aViewBox [ 2 ] . ',' . $this -> aViewBox [ 1 ];
}
2013-08-27 20:08:57 +04:00
function setRoute ( $aRoutePoints )
{
$this -> aRoutePoints = $aRoutePoints ;
}
function setFeatureType ( $sFeatureType )
{
switch ( $sFeatureType )
{
case 'country' :
$this -> setRankRange ( 4 , 4 );
break ;
case 'state' :
$this -> setRankRange ( 8 , 8 );
break ;
case 'city' :
$this -> setRankRange ( 14 , 16 );
break ;
case 'settlement' :
$this -> setRankRange ( 8 , 20 );
break ;
}
}
function setRankRange ( $iMin , $iMax )
{
$this -> iMinAddressRank = ( int ) $iMin ;
$this -> iMaxAddressRank = ( int ) $iMax ;
}
function setNearPoint ( $aNearPoint , $fRadiusDeg = 0.1 )
{
$this -> aNearPoint = array (( float ) $aNearPoint [ 0 ], ( float ) $aNearPoint [ 1 ], ( float ) $fRadiusDeg );
}
function setCountryCodesList ( $aCountryCodes )
{
$this -> aCountryCodes = $aCountryCodes ;
}
function setQuery ( $sQueryString )
{
$this -> sQuery = $sQueryString ;
$this -> aStructuredQuery = false ;
}
function getQueryString ()
{
return $this -> sQuery ;
}
2014-02-28 02:48:12 +04:00
function loadParamArray ( $aParams )
{
if ( isset ( $aParams [ 'addressdetails' ])) $this -> bIncludeAddressDetails = ( bool ) $aParams [ 'addressdetails' ];
2016-03-30 23:48:18 +03:00
if ( isset ( $aParams [ 'extratags' ])) $this -> bIncludeExtraTags = ( bool ) $aParams [ 'extratags' ];
if ( isset ( $aParams [ 'namedetails' ])) $this -> bIncludeNameDetails = ( bool ) $aParams [ 'namedetails' ];
2014-02-28 02:48:12 +04:00
if ( isset ( $aParams [ 'bounded' ])) $this -> bBoundedSearch = ( bool ) $aParams [ 'bounded' ];
if ( isset ( $aParams [ 'dedupe' ])) $this -> bDeDupe = ( bool ) $aParams [ 'dedupe' ];
if ( isset ( $aParams [ 'limit' ])) $this -> setLimit (( int ) $aParams [ 'limit' ]);
if ( isset ( $aParams [ 'offset' ])) $this -> iOffset = ( int ) $aParams [ 'offset' ];
if ( isset ( $aParams [ 'fallback' ])) $this -> bFallback = ( bool ) $aParams [ 'fallback' ];
// List of excluded Place IDs - used for more acurate pageing
if ( isset ( $aParams [ 'exclude_place_ids' ]) && $aParams [ 'exclude_place_ids' ])
{
foreach ( explode ( ',' , $aParams [ 'exclude_place_ids' ]) as $iExcludedPlaceID )
{
$iExcludedPlaceID = ( int ) $iExcludedPlaceID ;
2015-03-29 19:17:52 +03:00
if ( $iExcludedPlaceID )
$aExcludePlaceIDs [ $iExcludedPlaceID ] = $iExcludedPlaceID ;
2014-02-28 02:48:12 +04:00
}
2015-03-29 19:17:52 +03:00
if ( isset ( $aExcludePlaceIDs ))
$this -> aExcludePlaceIDs = $aExcludePlaceIDs ;
2014-02-28 02:48:12 +04:00
}
// Only certain ranks of feature
if ( isset ( $aParams [ 'featureType' ])) $this -> setFeatureType ( $aParams [ 'featureType' ]);
if ( isset ( $aParams [ 'featuretype' ])) $this -> setFeatureType ( $aParams [ 'featuretype' ]);
// Country code list
if ( isset ( $aParams [ 'countrycodes' ]))
{
$aCountryCodes = array ();
foreach ( explode ( ',' , $aParams [ 'countrycodes' ]) as $sCountryCode )
{
if ( preg_match ( '/^[a-zA-Z][a-zA-Z]$/' , $sCountryCode ))
{
$aCountryCodes [] = strtolower ( $sCountryCode );
}
}
$this -> aCountryCodes = $aCountryCodes ;
}
if ( isset ( $aParams [ 'viewboxlbrt' ]) && $aParams [ 'viewboxlbrt' ])
{
$aCoOrdinatesLBRT = explode ( ',' , $aParams [ 'viewboxlbrt' ]);
$this -> setViewBox ( $aCoOrdinatesLBRT [ 0 ], $aCoOrdinatesLBRT [ 1 ], $aCoOrdinatesLBRT [ 2 ], $aCoOrdinatesLBRT [ 3 ]);
}
else if ( isset ( $aParams [ 'viewbox' ]) && $aParams [ 'viewbox' ])
{
$aCoOrdinatesLTRB = explode ( ',' , $aParams [ 'viewbox' ]);
$this -> setViewBox ( $aCoOrdinatesLTRB [ 0 ], $aCoOrdinatesLTRB [ 3 ], $aCoOrdinatesLTRB [ 2 ], $aCoOrdinatesLTRB [ 1 ]);
}
if ( isset ( $aParams [ 'route' ]) && $aParams [ 'route' ] && isset ( $aParams [ 'routewidth' ]) && $aParams [ 'routewidth' ])
{
$aPoints = explode ( ',' , $aParams [ 'route' ]);
if ( sizeof ( $aPoints ) % 2 != 0 )
{
userError ( " Uneven number of points " );
exit ;
}
$fPrevCoord = false ;
$aRoute = array ();
foreach ( $aPoints as $i => $fPoint )
{
if ( $i % 2 )
{
$aRoute [] = array (( float ) $fPoint , $fPrevCoord );
}
else
{
$fPrevCoord = ( float ) $fPoint ;
}
}
$this -> aRoutePoints = $aRoute ;
}
}
function setQueryFromParams ( $aParams )
{
// Search query
$sQuery = ( isset ( $aParams [ 'q' ]) ? trim ( $aParams [ 'q' ]) : '' );
if ( ! $sQuery )
{
$this -> setStructuredQuery ( @ $aParams [ 'amenity' ], @ $aParams [ 'street' ], @ $aParams [ 'city' ], @ $aParams [ 'county' ], @ $aParams [ 'state' ], @ $aParams [ 'country' ], @ $aParams [ 'postalcode' ]);
$this -> setReverseInPlan ( false );
}
else
{
$this -> setQuery ( $sQuery );
}
}
2013-09-17 20:33:44 +04:00
function loadStructuredAddressElement ( $sValue , $sKey , $iNewMinAddressRank , $iNewMaxAddressRank , $aItemListValues )
2013-09-12 19:16:00 +04:00
{
$sValue = trim ( $sValue );
if ( ! $sValue ) return false ;
2013-09-17 20:33:44 +04:00
$this -> aStructuredQuery [ $sKey ] = $sValue ;
if ( $this -> iMinAddressRank == 0 && $this -> iMaxAddressRank == 30 )
2013-09-12 19:16:00 +04:00
{
2013-09-17 20:33:44 +04:00
$this -> iMinAddressRank = $iNewMinAddressRank ;
$this -> iMaxAddressRank = $iNewMaxAddressRank ;
2013-09-12 19:16:00 +04:00
}
2013-09-17 20:33:44 +04:00
if ( $aItemListValues ) $this -> aAddressRankList = array_merge ( $this -> aAddressRankList , $aItemListValues );
2013-09-12 19:16:00 +04:00
return true ;
}
2013-08-27 20:08:57 +04:00
function setStructuredQuery ( $sAmentiy = false , $sStreet = false , $sCity = false , $sCounty = false , $sState = false , $sCountry = false , $sPostalCode = false )
{
$this -> sQuery = false ;
2014-01-23 18:40:37 +04:00
// Reset
$this -> iMinAddressRank = 0 ;
$this -> iMaxAddressRank = 30 ;
$this -> aAddressRankList = array ();
2013-08-27 20:08:57 +04:00
$this -> aStructuredQuery = array ();
$this -> sAllowedTypesSQLList = '' ;
2013-09-17 20:33:44 +04:00
$this -> loadStructuredAddressElement ( $sAmentiy , 'amenity' , 26 , 30 , false );
$this -> loadStructuredAddressElement ( $sStreet , 'street' , 26 , 30 , false );
$this -> loadStructuredAddressElement ( $sCity , 'city' , 14 , 24 , false );
$this -> loadStructuredAddressElement ( $sCounty , 'county' , 9 , 13 , false );
$this -> loadStructuredAddressElement ( $sState , 'state' , 8 , 8 , false );
$this -> loadStructuredAddressElement ( $sPostalCode , 'postalcode' , 5 , 11 , array ( 5 , 11 ));
$this -> loadStructuredAddressElement ( $sCountry , 'country' , 4 , 4 , false );
2013-08-27 20:08:57 +04:00
2016-03-21 14:42:38 +03:00
if ( sizeof ( $this -> aStructuredQuery ) > 0 )
2013-08-27 20:08:57 +04:00
{
$this -> sQuery = join ( ', ' , $this -> aStructuredQuery );
if ( $this -> iMaxAddressRank < 30 )
{
$sAllowedTypesSQLList = '(\'place\',\'boundary\')' ;
}
}
2014-01-23 18:40:37 +04:00
}
function fallbackStructuredQuery ()
{
if ( ! $this -> aStructuredQuery ) return false ;
$aParams = $this -> aStructuredQuery ;
if ( sizeof ( $aParams ) == 1 ) return false ;
$aOrderToFallback = array ( 'postalcode' , 'street' , 'city' , 'county' , 'state' );
foreach ( $aOrderToFallback as $sType )
{
if ( isset ( $aParams [ $sType ]))
{
unset ( $aParams [ $sType ]);
$this -> setStructuredQuery ( @ $aParams [ 'amenity' ], @ $aParams [ 'street' ], @ $aParams [ 'city' ], @ $aParams [ 'county' ], @ $aParams [ 'state' ], @ $aParams [ 'country' ], @ $aParams [ 'postalcode' ]);
return true ;
}
}
2013-08-27 20:08:57 +04:00
2014-01-23 18:40:37 +04:00
return false ;
2013-08-27 20:08:57 +04:00
}
2013-09-10 22:10:53 +04:00
function getDetails ( $aPlaceIDs )
2013-08-27 20:08:57 +04:00
{
2016-03-21 14:42:38 +03:00
//$aPlaceIDs is an array with key: placeID and value: tiger-housenumber, if found, else -1
2013-08-30 16:13:21 +04:00
if ( sizeof ( $aPlaceIDs ) == 0 ) return array ();
2013-08-27 20:08:57 +04:00
$sLanguagePrefArraySQL = " ARRAY[ " . join ( ',' , array_map ( " getDBQuoted " , $this -> aLangPrefOrder )) . " ] " ;
// Get the details for display (is this a redundant extra step?)
2016-03-23 11:34:36 +03:00
$sPlaceIDs = join ( ',' , array_keys ( $aPlaceIDs ));
2013-08-27 20:08:57 +04:00
2014-04-30 23:04:46 +04:00
$sImportanceSQL = '' ;
if ( $this -> sViewboxSmallSQL ) $sImportanceSQL .= " case when ST_Contains( $this->sViewboxSmallSQL , ST_Collect(centroid)) THEN 1 ELSE 0.75 END * " ;
if ( $this -> sViewboxLargeSQL ) $sImportanceSQL .= " case when ST_Contains( $this->sViewboxLargeSQL , ST_Collect(centroid)) THEN 1 ELSE 0.75 END * " ;
2013-12-04 10:53:53 +04:00
$sSQL = " select osm_type,osm_id,class,type,admin_level,rank_search,rank_address,min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code as country_code, " ;
2016-03-10 18:22:39 +03:00
$sSQL .= " get_address_by_language(place_id, -1, $sLanguagePrefArraySQL ) as langaddress, " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " get_name_by_language(name, $sLanguagePrefArraySQL ) as placename, " ;
$sSQL .= " get_name_by_language(name, ARRAY['ref']) as ref, " ;
2015-08-10 22:42:40 +03:00
if ( $this -> bIncludeExtraTags ) $sSQL .= " hstore_to_json(extratags)::text as extra, " ;
if ( $this -> bIncludeNameDetails ) $sSQL .= " hstore_to_json(name)::text as names, " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " avg(ST_X(centroid)) as lon,avg(ST_Y(centroid)) as lat, " ;
2014-04-30 23:04:46 +04:00
$sSQL .= $sImportanceSQL . " coalesce(importance,0.75-(rank_search::float/40)) as importance, " ;
2013-12-04 10:53:53 +04:00
$sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(CASE WHEN placex.rank_search < 28 THEN placex.place_id ELSE placex.parent_place_id END) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " (extratags->'place') as extra_place " ;
$sSQL .= " from placex where place_id in ( $sPlaceIDs ) " ;
2013-09-10 22:10:53 +04:00
$sSQL .= " and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank " ;
if ( 14 >= $this -> iMinAddressRank && 14 <= $this -> iMaxAddressRank ) $sSQL .= " OR (extratags->'place') = 'city' " ;
2013-09-13 20:45:23 +04:00
if ( $this -> aAddressRankList ) $sSQL .= " OR placex.rank_address in ( " . join ( ',' , $this -> aAddressRankList ) . " ) " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " ) " ;
2013-09-10 22:10:53 +04:00
if ( $this -> sAllowedTypesSQLList ) $sSQL .= " and placex.class in $this->sAllowedTypesSQLList " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " and linked_place_id is null " ;
$sSQL .= " group by osm_type,osm_id,class,type,admin_level,rank_search,rank_address,calculated_country_code,importance " ;
2013-09-10 22:10:53 +04:00
if ( ! $this -> bDeDupe ) $sSQL .= " ,place_id " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " ,langaddress " ;
$sSQL .= " ,placename " ;
$sSQL .= " ,ref " ;
2015-08-10 22:42:40 +03:00
if ( $this -> bIncludeExtraTags ) $sSQL .= " ,extratags " ;
if ( $this -> bIncludeNameDetails ) $sSQL .= " ,name " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " ,extratags->'place' " ;
2013-09-10 22:10:53 +04:00
if ( 30 >= $this -> iMinAddressRank && 30 <= $this -> iMaxAddressRank )
2013-08-27 20:08:57 +04:00
{
2016-04-25 14:58:28 +03:00
//only Tiger housenumbers and interpolation lines need to be interpolated, because they are saved as lines
// with start- and endnumber, the common osm housenumbers are usually saved as points
$sHousenumbers = " " ;
$i = 0 ;
$length = count ( $aPlaceIDs );
foreach ( $aPlaceIDs as $placeID => $housenumber )
{
$i ++ ;
$sHousenumbers .= " ( " . $placeID . " , " . $housenumber . " ) " ;
if ( $i < $length )
$sHousenumbers .= " , " ;
}
2016-04-07 22:47:51 +03:00
if ( CONST_Use_US_Tiger_Data )
2016-04-25 10:44:01 +03:00
{
2016-04-07 22:47:51 +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-04-25 14:58:28 +03:00
$sSQL .= " union " ;
$sSQL .= " select 'T' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 30 as rank_search, 30 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, 'us' as country_code " ;
2016-04-07 22:47:51 +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, avg(st_y(centroid)) as lat, " ;
$sSQL .= $sImportanceSQL . " -1.15 as importance " ;
$sSQL .= " , (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance " ;
$sSQL .= " , null as extra_place " ;
$sSQL .= " from (select place_id " ;
//interpolate the Tiger housenumbers here
2016-04-25 14:58:28 +03:00
$sSQL .= " , ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) as centroid, parent_place_id, housenumber_for_place " ;
$sSQL .= " from (location_property_tiger " ;
2016-04-07 22:47:51 +03:00
$sSQL .= " join (values " . $sHousenumbers . " ) as housenumbers(place_id, housenumber_for_place) using(place_id)) " ;
$sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank ) as blub " ; //postgres wants an alias here
$sSQL .= " group by place_id, housenumber_for_place " ; //is this group by really needed?, place_id + housenumber (in combination) are unique
if ( ! $this -> bDeDupe ) $sSQL .= " , place_id " ;
2016-03-21 14:42:38 +03:00
}
2016-04-25 10:44:01 +03:00
// osmline, osm_type is 'I' for Interpolation Line
2016-04-25 14:58:28 +03:00
// 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-04-25 10:44:01 +03:00
$sSQL .= " union " ;
$sSQL .= " select 'I' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 30 as rank_search, 30 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, calculated_country_code as country_code, " ;
$sSQL .= " get_address_by_language(place_id, housenumber_for_place, $sLanguagePrefArraySQL ) as langaddress, " ;
$sSQL .= " null as placename, " ;
$sSQL .= " null as ref, " ;
if ( $this -> bIncludeExtraTags ) $sSQL .= " null as extra, " ;
if ( $this -> bIncludeNameDetails ) $sSQL .= " null as names, " ;
$sSQL .= " avg(st_x(centroid)) as lon, avg(st_y(centroid)) as lat, " ;
$sSQL .= $sImportanceSQL . " -0.1 as importance, " ; // slightly smaller than the importance for normal houses with rank 30, which is 0
$sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(blub.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, " ;
$sSQL .= " null as extra_place " ;
$sSQL .= " from (select place_id, calculated_country_code " ;
//interpolate the housenumbers here
$sSQL .= " , CASE WHEN startnumber != endnumber THEN ST_LineInterpolatePoint(linegeo, (housenumber_for_place-startnumber::float)/(endnumber-startnumber)::float) " ;
$sSQL .= " ELSE ST_LineInterpolatePoint(linegeo, 0.5) END as centroid " ;
$sSQL .= " , parent_place_id, housenumber_for_place " ;
$sSQL .= " from (location_property_osmline " ;
$sSQL .= " join (values " . $sHousenumbers . " ) as housenumbers(place_id, housenumber_for_place) using(place_id)) " ;
$sSQL .= " where housenumber_for_place>=0 and 30 between $this->iMinAddressRank and $this->iMaxAddressRank ) as blub " ; //postgres wants an alias here
$sSQL .= " group by place_id, housenumber_for_place, calculated_country_code " ; //is this group by really needed?, place_id + housenumber (in combination) are unique
if ( ! $this -> bDeDupe ) $sSQL .= " , place_id " ;
2016-03-21 14:42:38 +03:00
2016-04-07 22:47:51 +03:00
if ( CONST_Use_Aux_Location_data )
{
$sSQL .= " union " ;
$sSQL .= " select 'L' as osm_type, place_id as osm_id, 'place' as class, 'house' as type, null as admin_level, 0 as rank_search, 0 as rank_address, min(place_id) as place_id, min(parent_place_id) as parent_place_id, 'us' as country_code, " ;
$sSQL .= " get_address_by_language(place_id, -1, $sLanguagePrefArraySQL ) as langaddress, " ;
$sSQL .= " null as placename, " ;
$sSQL .= " null as ref, " ;
if ( $this -> bIncludeExtraTags ) $sSQL .= " null as extra, " ;
if ( $this -> bIncludeNameDetails ) $sSQL .= " null as names, " ;
$sSQL .= " avg(ST_X(centroid)) as lon, avg(ST_Y(centroid)) as lat, " ;
$sSQL .= $sImportanceSQL . " -1.10 as importance, " ;
$sSQL .= " (select max(p.importance*(p.rank_address+2)) from place_addressline s, placex p where s.place_id = min(location_property_aux.parent_place_id) and p.place_id = s.address_place_id and s.isaddress and p.importance is not null) as addressimportance, " ;
$sSQL .= " null as extra_place " ;
$sSQL .= " from location_property_aux where place_id in ( $sPlaceIDs ) " ;
$sSQL .= " and 30 between $this->iMinAddressRank and $this->iMaxAddressRank " ;
$sSQL .= " group by place_id " ;
if ( ! $this -> bDeDupe ) $sSQL .= " , place_id " ;
$sSQL .= " , get_address_by_language(place_id, -1, $sLanguagePrefArraySQL ) " ;
}
2013-08-27 20:08:57 +04:00
}
2014-04-30 23:04:46 +04:00
$sSQL .= " order by importance desc " ;
2013-08-27 20:08:57 +04:00
if ( CONST_Debug ) { echo " <hr> " ; var_dump ( $sSQL ); }
$aSearchResults = $this -> oDB -> getAll ( $sSQL );
if ( PEAR :: IsError ( $aSearchResults ))
{
failInternalError ( " Could not get details for place. " , $sSQL , $aSearchResults );
}
return $aSearchResults ;
}
2015-01-26 23:34:21 +03:00
function getGroupedSearches ( $aSearches , $aPhraseTypes , $aPhrases , $aValidTokens , $aWordFrequencyScores , $bStructuredPhrases )
2014-10-31 01:00:47 +03:00
{
/*
2016-02-27 02:02:20 +03:00
Calculate all searches using aValidTokens i . e .
'Wodsworth Road, Sheffield' =>
2014-10-31 01:00:47 +03:00
2016-02-27 02:02:20 +03:00
Phrase Wordset
0 0 ( wodsworth road )
0 1 ( wodsworth )( road )
1 0 ( sheffield )
2014-10-31 01:00:47 +03:00
2016-02-27 02:02:20 +03:00
Score how good the search is so they can be ordered
2014-10-31 01:00:47 +03:00
*/
foreach ( $aPhrases as $iPhrase => $sPhrase )
{
$aNewPhraseSearches = array ();
if ( $bStructuredPhrases ) $sPhraseType = $aPhraseTypes [ $iPhrase ];
else $sPhraseType = '' ;
foreach ( $aPhrases [ $iPhrase ][ 'wordsets' ] as $iWordSet => $aWordset )
{
// Too many permutations - too expensive
if ( $iWordSet > 120 ) break ;
$aWordsetSearches = $aSearches ;
// Add all words from this wordset
foreach ( $aWordset as $iToken => $sToken )
{
//echo "<br><b>$sToken</b>";
$aNewWordsetSearches = array ();
foreach ( $aWordsetSearches as $aCurrentSearch )
{
//echo "<i>";
//var_dump($aCurrentSearch);
//echo "</i>";
// If the token is valid
if ( isset ( $aValidTokens [ ' ' . $sToken ]))
{
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTerm )
{
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] ++ ;
if (( $sPhraseType == '' || $sPhraseType == 'country' ) && ! empty ( $aSearchTerm [ 'country_code' ]) && $aSearchTerm [ 'country_code' ] != '0' )
{
if ( $aSearch [ 'sCountryCode' ] === false )
{
$aSearch [ 'sCountryCode' ] = strtolower ( $aSearchTerm [ 'country_code' ]);
// Country is almost always at the end of the string - increase score for finding it anywhere else (optimisation)
if (( $iToken + 1 != sizeof ( $aWordset ) || $iPhrase + 1 != sizeof ( $aPhrases )))
{
$aSearch [ 'iSearchRank' ] += 5 ;
}
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
elseif ( isset ( $aSearchTerm [ 'lat' ]) && $aSearchTerm [ 'lat' ] !== '' && $aSearchTerm [ 'lat' ] !== null )
{
if ( $aSearch [ 'fLat' ] === '' )
{
$aSearch [ 'fLat' ] = $aSearchTerm [ 'lat' ];
$aSearch [ 'fLon' ] = $aSearchTerm [ 'lon' ];
$aSearch [ 'fRadius' ] = $aSearchTerm [ 'radius' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
elseif ( $sPhraseType == 'postalcode' )
{
// We need to try the case where the postal code is the primary element (i.e. no way to tell if it is (postalcode, city) OR (city, postalcode) so try both
if ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ])
{
// If we already have a name try putting the postcode first
if ( sizeof ( $aSearch [ 'aName' ]))
{
$aNewSearch = $aSearch ;
$aNewSearch [ 'aAddress' ] = array_merge ( $aNewSearch [ 'aAddress' ], $aNewSearch [ 'aName' ]);
$aNewSearch [ 'aName' ] = array ();
$aNewSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aNewSearch ;
}
if ( sizeof ( $aSearch [ 'aName' ]))
{
2015-02-15 21:36:39 +03:00
if (( ! $bStructuredPhrases || $iPhrase > 0 ) && $sPhraseType != 'country' && ( ! isset ( $aValidTokens [ $sToken ]) || strpos ( $sToken , ' ' ) !== false ))
2014-10-31 01:00:47 +03:00
{
$aSearch [ 'aAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
}
else
{
$aCurrentSearch [ 'aFullNameAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iSearchRank' ] += 1000 ; // skip;
}
}
else
{
$aSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
//$aSearch['iNamePhrase'] = $iPhrase;
}
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
elseif (( $sPhraseType == '' || $sPhraseType == 'street' ) && $aSearchTerm [ 'class' ] == 'place' && $aSearchTerm [ 'type' ] == 'house' )
{
if ( $aSearch [ 'sHouseNumber' ] === '' )
{
$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' ] ++ ;
2015-02-02 09:41:28 +03:00
// also housenumbers should appear in the first or second phrase
if ( $iPhrase > 1 ) $aSearch [ 'iSearchRank' ] += 1 ;
2014-10-31 01:00:47 +03:00
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 ;
*/
}
}
elseif ( $sPhraseType == '' && $aSearchTerm [ 'class' ] !== '' && $aSearchTerm [ 'class' ] !== null )
{
if ( $aSearch [ 'sClass' ] === '' )
{
$aSearch [ 'sOperator' ] = $aSearchTerm [ 'operator' ];
$aSearch [ 'sClass' ] = $aSearchTerm [ 'class' ];
$aSearch [ 'sType' ] = $aSearchTerm [ 'type' ];
if ( sizeof ( $aSearch [ 'aName' ])) $aSearch [ 'sOperator' ] = 'name' ;
else $aSearch [ 'sOperator' ] = 'near' ; // near = in for the moment
if ( strlen ( $aSearchTerm [ 'operator' ]) == 0 ) $aSearch [ 'iSearchRank' ] += 1 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
elseif ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ])
{
if ( sizeof ( $aSearch [ 'aName' ]))
{
2015-02-15 21:36:39 +03:00
if (( ! $bStructuredPhrases || $iPhrase > 0 ) && $sPhraseType != 'country' && ( ! isset ( $aValidTokens [ $sToken ]) || strpos ( $sToken , ' ' ) !== false ))
2014-10-31 01:00:47 +03:00
{
$aSearch [ 'aAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
}
else
{
$aCurrentSearch [ 'aFullNameAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iSearchRank' ] += 1000 ; // skip;
}
}
else
{
$aSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
//$aSearch['iNamePhrase'] = $iPhrase;
}
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
}
2015-01-26 23:59:36 +03:00
// Look for partial matches.
// Note that there is no point in adding country terms here
// because country are omitted in the address.
if ( isset ( $aValidTokens [ $sToken ]) && $sPhraseType != 'country' )
2014-10-31 01:00:47 +03:00
{
// Allow searching for a word - but at extra cost
foreach ( $aValidTokens [ $sToken ] as $aSearchTerm )
{
if ( isset ( $aSearchTerm [ 'word_id' ]) && $aSearchTerm [ 'word_id' ])
{
if (( ! $bStructuredPhrases || $iPhrase > 0 ) && sizeof ( $aCurrentSearch [ 'aName' ]) && strpos ( $sToken , ' ' ) === false )
{
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
if ( $aWordFrequencyScores [ $aSearchTerm [ 'word_id' ]] < CONST_Max_Word_Frequency )
{
$aSearch [ 'aAddress' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
2015-02-15 21:36:39 +03:00
elseif ( isset ( $aValidTokens [ ' ' . $sToken ])) // revert to the token version?
2014-10-31 01:00:47 +03:00
{
$aSearch [ 'aAddressNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iSearchRank' ] += 1 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
foreach ( $aValidTokens [ ' ' . $sToken ] as $aSearchTermToken )
{
if ( empty ( $aSearchTermToken [ 'country_code' ])
&& empty ( $aSearchTermToken [ 'lat' ])
&& empty ( $aSearchTermToken [ 'class' ]))
{
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
$aSearch [ 'aAddress' ][ $aSearchTermToken [ 'word_id' ]] = $aSearchTermToken [ 'word_id' ];
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
}
else
{
$aSearch [ 'aAddressNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
if ( preg_match ( '#^[0-9]+$#' , $sToken )) $aSearch [ 'iSearchRank' ] += 2 ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
if ( ! sizeof ( $aCurrentSearch [ 'aName' ]) || $aCurrentSearch [ 'iNamePhrase' ] == $iPhrase )
{
$aSearch = $aCurrentSearch ;
$aSearch [ 'iSearchRank' ] += 1 ;
if ( ! sizeof ( $aCurrentSearch [ 'aName' ])) $aSearch [ 'iSearchRank' ] += 1 ;
if ( preg_match ( '#^[0-9]+$#' , $sToken )) $aSearch [ 'iSearchRank' ] += 2 ;
if ( $aWordFrequencyScores [ $aSearchTerm [ 'word_id' ]] < CONST_Max_Word_Frequency )
$aSearch [ 'aName' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
else
$aSearch [ 'aNameNonSearch' ][ $aSearchTerm [ 'word_id' ]] = $aSearchTerm [ 'word_id' ];
$aSearch [ 'iNamePhrase' ] = $iPhrase ;
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank ) $aNewWordsetSearches [] = $aSearch ;
}
}
}
}
else
{
// 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 ();
foreach ( $aNewPhraseSearches as $iSearch => $aSearch )
{
$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 ();
foreach ( $aNewPhraseSearches as $aSearch )
{
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank )
{
if ( ! isset ( $aGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
}
}
ksort ( $aGroupedSearches );
$iSearchCount = 0 ;
$aSearches = array ();
foreach ( $aGroupedSearches as $iScore => $aNewSearches )
{
$iSearchCount += sizeof ( $aNewSearches );
$aSearches = array_merge ( $aSearches , $aNewSearches );
if ( $iSearchCount > 50 ) break ;
}
//if (CONST_Debug) _debugDumpGroupedSearches($aGroupedSearches, $aValidTokens);
}
return $aGroupedSearches ;
}
2013-11-23 16:14:42 +04:00
/* Perform the actual query lookup .
Returns an ordered list of results , each with the following fields :
2016-02-27 02:02:20 +03:00
osm_type : type of corresponding OSM object
2013-11-23 16:14:42 +04:00
N - node
W - way
R - relation
P - postcode ( internally computed )
2016-02-27 02:02:20 +03:00
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
2013-11-23 16:14:42 +04:00
( see also http :// wiki . openstreetmap . org / wiki / Nominatim / Development_overview #Country_to_street_level)
2016-02-27 02:02:20 +03:00
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
2016-03-21 14:42:38 +03:00
label : short description of the object class / type ( English only )
2016-02-27 02:02:20 +03:00
name : full name ( currently the same as langaddress )
foundorder : secondary ordering for places with same importance
2013-11-23 16:14:42 +04:00
*/
2013-08-27 20:08:57 +04:00
function lookup ()
{
if ( ! $this -> sQuery && ! $this -> aStructuredQuery ) return false ;
$sLanguagePrefArraySQL = " ARRAY[ " . join ( ',' , array_map ( " getDBQuoted " , $this -> aLangPrefOrder )) . " ] " ;
$sCountryCodesSQL = false ;
if ( $this -> aCountryCodes && sizeof ( $this -> aCountryCodes ))
{
2013-08-30 15:18:57 +04:00
$sCountryCodesSQL = join ( ',' , array_map ( 'addQuotes' , $this -> aCountryCodes ));
2013-08-27 20:08:57 +04:00
}
2014-04-30 23:11:23 +04:00
$sQuery = $this -> sQuery ;
2013-08-27 20:08:57 +04:00
// Conflicts between US state abreviations and various words for 'the' in different languages
if ( isset ( $this -> aLangPrefOrder [ 'name:en' ]))
{
2014-03-11 03:32:54 +04: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 );
2013-08-27 20:08:57 +04:00
}
// View Box SQL
2014-04-30 23:04:46 +04:00
$sViewboxCentreSQL = false ;
2013-08-27 20:08:57 +04:00
$bBoundingBoxSearch = false ;
if ( $this -> aViewBox )
{
$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 ;
2014-04-30 23:04:46 +04:00
$this -> sViewboxSmallSQL = " ST_SetSRID(ST_MakeBox2D(ST_Point( " . ( float ) $this -> aViewBox [ 0 ] . " , " . ( float ) $this -> aViewBox [ 1 ] . " ),ST_Point( " . ( float ) $this -> aViewBox [ 2 ] . " , " . ( float ) $this -> aViewBox [ 3 ] . " )),4326) " ;
$this -> sViewboxLargeSQL = " ST_SetSRID(ST_MakeBox2D(ST_Point( " . ( float ) $aBigViewBox [ 0 ] . " , " . ( float ) $aBigViewBox [ 1 ] . " ),ST_Point( " . ( float ) $aBigViewBox [ 2 ] . " , " . ( float ) $aBigViewBox [ 3 ] . " )),4326) " ;
2013-08-30 15:18:57 +04:00
$bBoundingBoxSearch = $this -> bBoundedSearch ;
2013-08-27 20:08:57 +04:00
}
// Route SQL
if ( $this -> aRoutePoints )
{
$sViewboxCentreSQL = " ST_SetSRID('LINESTRING( " ;
2014-02-08 00:31:26 +04:00
$bFirst = true ;
foreach ( $this -> aRoutePoints as $aPoint )
2013-08-27 20:08:57 +04:00
{
if ( ! $bFirst ) $sViewboxCentreSQL .= " , " ;
2014-09-03 23:08:26 +04:00
$sViewboxCentreSQL .= $aPoint [ 0 ] . ' ' . $aPoint [ 1 ];
2014-02-08 00:31:26 +04:00
$bFirst = false ;
2013-08-27 20:08:57 +04:00
}
$sViewboxCentreSQL .= " )'::geometry,4326) " ;
$sSQL = " select st_buffer( " . $sViewboxCentreSQL . " , " . ( float )( $_GET [ 'routewidth' ] / 69 ) . " ) " ;
2014-04-30 23:04:46 +04:00
$this -> sViewboxSmallSQL = $this -> oDB -> getOne ( $sSQL );
if ( PEAR :: isError ( $this -> sViewboxSmallSQL ))
2013-08-27 20:08:57 +04:00
{
2014-04-30 23:04:46 +04:00
failInternalError ( " Could not get small viewbox. " , $sSQL , $this -> sViewboxSmallSQL );
2013-08-27 20:08:57 +04:00
}
2014-04-30 23:04:46 +04:00
$this -> sViewboxSmallSQL = " ' " . $this -> sViewboxSmallSQL . " '::geometry " ;
2013-08-27 20:08:57 +04:00
$sSQL = " select st_buffer( " . $sViewboxCentreSQL . " , " . ( float )( $_GET [ 'routewidth' ] / 30 ) . " ) " ;
2014-04-30 23:04:46 +04:00
$this -> sViewboxLargeSQL = $this -> oDB -> getOne ( $sSQL );
if ( PEAR :: isError ( $this -> sViewboxLargeSQL ))
2013-08-27 20:08:57 +04:00
{
2014-04-30 23:04:46 +04:00
failInternalError ( " Could not get large viewbox. " , $sSQL , $this -> sViewboxLargeSQL );
2013-08-27 20:08:57 +04:00
}
2014-04-30 23:04:46 +04:00
$this -> sViewboxLargeSQL = " ' " . $this -> sViewboxLargeSQL . " '::geometry " ;
2013-08-30 15:18:57 +04:00
$bBoundingBoxSearch = $this -> bBoundedSearch ;
2013-08-27 20:08:57 +04:00
}
// Do we have anything that looks like a lat/lon pair?
2016-03-23 11:34:36 +03:00
if ( $aLooksLike = looksLikeLatLonPair ( $sQuery ) )
2016-04-25 10:44:01 +03:00
{
2014-09-24 00:53:20 +04:00
$this -> setNearPoint ( array ( $aLooksLike [ 'lat' ], $aLooksLike [ 'lon' ]));
2016-03-21 14:42:38 +03:00
$sQuery = $aLooksLike [ 'query' ];
2013-08-27 20:08:57 +04:00
}
$aSearchResults = array ();
2013-08-30 16:13:21 +04:00
if ( $sQuery || $this -> aStructuredQuery )
2013-08-27 20:08:57 +04:00
{
// Start with a blank search
$aSearches = array (
2016-03-21 14:42:38 +03:00
array ( 'iSearchRank' => 0 ,
'iNamePhrase' => - 1 ,
'sCountryCode' => false ,
'aName' => array (),
'aAddress' => array (),
2016-02-28 19:06:52 +03:00
'aFullNameAddress' => array (),
2016-03-21 14:42:38 +03:00
'aNameNonSearch' => array (),
2016-02-28 19:06:52 +03:00
'aAddressNonSearch' => array (),
2016-03-21 14:42:38 +03:00
'sOperator' => '' ,
'aFeatureName' => array (),
'sClass' => '' ,
'sType' => '' ,
'sHouseNumber' => '' ,
'fLat' => '' ,
'fLon' => '' ,
2016-02-28 19:06:52 +03:00
'fRadius' => ''
)
2013-08-27 20:08:57 +04:00
);
// Do we have a radius search?
$sNearPointSQL = false ;
if ( $this -> aNearPoint )
{
$sNearPointSQL = " ST_SetSRID(ST_Point( " . ( float ) $this -> aNearPoint [ 1 ] . " , " . ( float ) $this -> aNearPoint [ 0 ] . " ),4326) " ;
$aSearches [ 0 ][ 'fLat' ] = ( float ) $this -> aNearPoint [ 0 ];
$aSearches [ 0 ][ 'fLon' ] = ( float ) $this -> aNearPoint [ 1 ];
$aSearches [ 0 ][ 'fRadius' ] = ( float ) $this -> aNearPoint [ 2 ];
}
// Any 'special' terms in the search?
$bSpecialTerms = false ;
preg_match_all ( '/\\[(.*)=(.*)\\]/' , $sQuery , $aSpecialTermsRaw , PREG_SET_ORDER );
$aSpecialTerms = array ();
foreach ( $aSpecialTermsRaw as $aSpecialTerm )
{
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
$aSpecialTerms [ strtolower ( $aSpecialTerm [ 1 ])] = $aSpecialTerm [ 2 ];
}
preg_match_all ( '/\\[([\\w ]*)\\]/u' , $sQuery , $aSpecialTermsRaw , PREG_SET_ORDER );
$aSpecialTerms = array ();
2015-04-07 16:04:08 +03:00
if ( isset ( $this -> aStructuredQuery [ 'amenity' ]) && $this -> aStructuredQuery [ 'amenity' ])
2013-08-27 20:08:57 +04:00
{
2015-04-07 16:04:08 +03:00
$aSpecialTermsRaw [] = array ( '[' . $this -> aStructuredQuery [ 'amenity' ] . ']' , $this -> aStructuredQuery [ 'amenity' ]);
unset ( $this -> aStructuredQuery [ 'amenity' ]);
2013-08-27 20:08:57 +04:00
}
foreach ( $aSpecialTermsRaw as $aSpecialTerm )
{
$sQuery = str_replace ( $aSpecialTerm [ 0 ], ' ' , $sQuery );
$sToken = $this -> oDB -> getOne ( " select make_standard_name(' " . $aSpecialTerm [ 1 ] . " ') as string " );
$sSQL = 'select * from (select word_id,word_token, word, class, type, country_code, operator' ;
$sSQL .= ' from word where word_token in (\' ' . $sToken . '\')) as x where (class is not null and class not in (\'place\')) or country_code is not null' ;
if ( CONST_Debug ) var_Dump ( $sSQL );
$aSearchWords = $this -> oDB -> getAll ( $sSQL );
$aNewSearches = array ();
foreach ( $aSearches as $aSearch )
{
foreach ( $aSearchWords as $aSearchTerm )
{
$aNewSearch = $aSearch ;
if ( $aSearchTerm [ 'country_code' ])
{
$aNewSearch [ 'sCountryCode' ] = strtolower ( $aSearchTerm [ 'country_code' ]);
$aNewSearches [] = $aNewSearch ;
$bSpecialTerms = true ;
}
if ( $aSearchTerm [ 'class' ])
{
$aNewSearch [ 'sClass' ] = $aSearchTerm [ 'class' ];
$aNewSearch [ 'sType' ] = $aSearchTerm [ 'type' ];
$aNewSearches [] = $aNewSearch ;
$bSpecialTerms = true ;
}
}
}
$aSearches = $aNewSearches ;
}
// Split query into phrases
// Commas are used to reduce the search space by indicating where phrases split
if ( $this -> aStructuredQuery )
{
2013-09-02 20:23:31 +04:00
$aPhrases = $this -> aStructuredQuery ;
2013-08-27 20:08:57 +04:00
$bStructuredPhrases = true ;
}
else
{
$aPhrases = explode ( ',' , $sQuery );
$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 ();
foreach ( $aPhrases as $iPhrase => $sPhrase )
{
$aPhrase = $this -> oDB -> getRow ( " select make_standard_name(' " . pg_escape_string ( $sPhrase ) . " ') as string " );
if ( PEAR :: isError ( $aPhrase ))
{
userError ( " Illegal query string (not an UTF-8 string): " . $sPhrase );
if ( CONST_Debug ) var_dump ( $aPhrase );
exit ;
}
if ( trim ( $aPhrase [ 'string' ]))
{
$aPhrases [ $iPhrase ] = $aPhrase ;
$aPhrases [ $iPhrase ][ 'words' ] = explode ( ' ' , $aPhrases [ $iPhrase ][ 'string' ]);
$aPhrases [ $iPhrase ][ 'wordsets' ] = getWordSets ( $aPhrases [ $iPhrase ][ 'words' ], 0 );
$aTokens = array_merge ( $aTokens , getTokensFromSets ( $aPhrases [ $iPhrase ][ 'wordsets' ]));
}
else
{
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 );
if ( sizeof ( $aTokens ))
{
// Check which tokens we have, get the ID numbers
$sSQL = 'select word_id,word_token, word, class, type, country_code, operator, search_name_count' ;
$sSQL .= ' from word where word_token in (' . join ( ',' , array_map ( " getDBQuoted " , $aTokens )) . ')' ;
if ( CONST_Debug ) var_Dump ( $sSQL );
$aValidTokens = array ();
if ( sizeof ( $aTokens )) $aDatabaseWords = $this -> oDB -> getAll ( $sSQL );
else $aDatabaseWords = array ();
if ( PEAR :: IsError ( $aDatabaseWords ))
{
failInternalError ( " Could not get word tokens. " , $sSQL , $aDatabaseWords );
}
$aPossibleMainWordIDs = array ();
$aWordFrequencyScores = array ();
foreach ( $aDatabaseWords as $aToken )
{
// Very special case - require 2 letter country param to match the country code found
2015-04-07 16:04:08 +03:00
if ( $bStructuredPhrases && $aToken [ 'country_code' ] && ! empty ( $this -> aStructuredQuery [ 'country' ])
&& strlen ( $this -> aStructuredQuery [ 'country' ]) == 2 && strtolower ( $this -> aStructuredQuery [ 'country' ]) != $aToken [ 'country_code' ])
2013-08-27 20:08:57 +04:00
{
continue ;
}
if ( isset ( $aValidTokens [ $aToken [ 'word_token' ]]))
{
$aValidTokens [ $aToken [ 'word_token' ]][] = $aToken ;
}
else
{
$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
foreach ( $aTokens as $sToken )
{
// Source of gb postcodes is now definitive - always use
if ( preg_match ( '/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/' , strtoupper ( trim ( $sToken )), $aData ))
{
if ( substr ( $aData [ 1 ], - 2 , 1 ) != ' ' )
{
2014-09-22 00:44:06 +04:00
$aData [ 0 ] = substr ( $aData [ 0 ], 0 , strlen ( $aData [ 1 ]) - 1 ) . ' ' . substr ( $aData [ 0 ], strlen ( $aData [ 1 ]) - 1 );
2013-08-27 20:08:57 +04:00
$aData [ 1 ] = substr ( $aData [ 1 ], 0 , - 1 ) . ' ' . substr ( $aData [ 1 ], - 1 , 1 );
}
$aGBPostcodeLocation = gbPostcodeCalculate ( $aData [ 0 ], $aData [ 1 ], $aData [ 2 ], $this -> oDB );
if ( $aGBPostcodeLocation )
{
$aValidTokens [ $sToken ] = $aGBPostcodeLocation ;
}
}
// US ZIP+4 codes - if there is no token,
// merge in the 5-digit ZIP code
else if ( ! isset ( $aValidTokens [ $sToken ]) && preg_match ( '/^([0-9]{5}) [0-9]{4}$/' , $sToken , $aData ))
{
if ( isset ( $aValidTokens [ $aData [ 1 ]]))
{
foreach ( $aValidTokens [ $aData [ 1 ]] as $aToken )
{
if ( ! $aToken [ 'class' ])
{
if ( isset ( $aValidTokens [ $sToken ]))
{
$aValidTokens [ $sToken ][] = $aToken ;
}
else
{
$aValidTokens [ $sToken ] = array ( $aToken );
}
}
}
}
}
}
foreach ( $aTokens as $sToken )
{
// Unknown single word token with a number - assume it is a house number
if ( ! isset ( $aValidTokens [ ' ' . $sToken ]) && strpos ( $sToken , ' ' ) === false && preg_match ( '/[0-9]/' , $sToken ))
{
$aValidTokens [ ' ' . $sToken ] = array ( array ( 'class' => 'place' , 'type' => 'house' ));
}
}
// Any words that have failed completely?
// TODO: suggestions
// Start the search process
2016-03-21 14:42:38 +03:00
// array with: placeid => -1 | tiger-housenumber
2013-08-27 20:08:57 +04:00
$aResultPlaceIDs = array ();
2015-01-26 23:34:21 +03:00
$aGroupedSearches = $this -> getGroupedSearches ( $aSearches , $aPhraseTypes , $aPhrases , $aValidTokens , $aWordFrequencyScores , $bStructuredPhrases );
2013-08-27 20:08:57 +04:00
2014-10-31 01:00:47 +03:00
if ( $this -> bReverseInPlan )
2013-08-27 20:08:57 +04:00
{
2014-10-31 01:00:47 +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 );
if ( sizeof ( $aPhrases ) > 1 )
2013-08-27 20:08:57 +04:00
{
2014-10-31 01:00:47 +03:00
$aFinalPhrase = end ( $aPhrases );
2015-02-04 22:29:38 +03:00
$aPhrases [ sizeof ( $aPhrases ) - 1 ][ 'wordsets' ] = getInverseWordSets ( $aFinalPhrase [ 'words' ], 0 );
2014-10-31 01:00:47 +03:00
}
2015-01-26 23:34:21 +03:00
$aReverseGroupedSearches = $this -> getGroupedSearches ( $aSearches , null , $aPhrases , $aValidTokens , $aWordFrequencyScores , false );
2013-08-27 20:08:57 +04:00
2014-10-31 01:00:47 +03:00
foreach ( $aGroupedSearches as $aSearches )
{
foreach ( $aSearches as $aSearch )
2013-08-27 20:08:57 +04:00
{
2014-10-31 01:00:47 +03:00
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank )
2013-08-27 20:08:57 +04:00
{
2014-10-31 01:00:47 +03:00
if ( ! isset ( $aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aReverseGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
2013-08-27 20:08:57 +04:00
}
}
}
2014-10-31 01:00:47 +03:00
$aGroupedSearches = $aReverseGroupedSearches ;
2013-08-27 20:08:57 +04:00
ksort ( $aGroupedSearches );
}
}
else
{
// Re-group the searches by their score, junk anything over 20 as just not worth trying
$aGroupedSearches = array ();
foreach ( $aSearches as $aSearch )
{
if ( $aSearch [ 'iSearchRank' ] < $this -> iMaxRank )
{
if ( ! isset ( $aGroupedSearches [ $aSearch [ 'iSearchRank' ]])) $aGroupedSearches [ $aSearch [ 'iSearchRank' ]] = array ();
$aGroupedSearches [ $aSearch [ 'iSearchRank' ]][] = $aSearch ;
}
}
ksort ( $aGroupedSearches );
}
if ( CONST_Debug ) var_Dump ( $aGroupedSearches );
2015-04-07 16:04:08 +03:00
if ( CONST_Search_TryDroppedAddressTerms && sizeof ( $this -> aStructuredQuery ) > 0 )
2013-08-27 20:08:57 +04:00
{
$aCopyGroupedSearches = $aGroupedSearches ;
foreach ( $aCopyGroupedSearches as $iGroup => $aSearches )
{
foreach ( $aSearches as $iSearch => $aSearch )
{
$aReductionsList = array ( $aSearch [ 'aAddress' ]);
$iSearchRank = $aSearch [ 'iSearchRank' ];
while ( sizeof ( $aReductionsList ) > 0 )
{
$iSearchRank += 5 ;
if ( $iSearchRank > iMaxRank ) break 3 ;
$aNewReductionsList = array ();
foreach ( $aReductionsList as $aReductionsWordList )
{
for ( $iReductionWord = 0 ; $iReductionWord < sizeof ( $aReductionsWordList ); $iReductionWord ++ )
{
$aReductionsWordListResult = array_merge ( array_slice ( $aReductionsWordList , 0 , $iReductionWord ), array_slice ( $aReductionsWordList , $iReductionWord + 1 ));
$aReverseSearch = $aSearch ;
$aSearch [ 'aAddress' ] = $aReductionsWordListResult ;
$aSearch [ 'iSearchRank' ] = $iSearchRank ;
$aGroupedSearches [ $iSearchRank ][] = $aReverseSearch ;
if ( sizeof ( $aReductionsWordListResult ) > 0 )
{
$aNewReductionsList [] = $aReductionsWordListResult ;
}
}
}
$aReductionsList = $aNewReductionsList ;
}
}
}
ksort ( $aGroupedSearches );
}
// Filter out duplicate searches
$aSearchHash = array ();
foreach ( $aGroupedSearches as $iGroup => $aSearches )
{
foreach ( $aSearches as $iSearch => $aSearch )
{
$sHash = serialize ( $aSearch );
if ( isset ( $aSearchHash [ $sHash ]))
{
unset ( $aGroupedSearches [ $iGroup ][ $iSearch ]);
if ( sizeof ( $aGroupedSearches [ $iGroup ]) == 0 ) unset ( $aGroupedSearches [ $iGroup ]);
}
else
{
$aSearchHash [ $sHash ] = 1 ;
}
}
}
if ( CONST_Debug ) _debugDumpGroupedSearches ( $aGroupedSearches , $aValidTokens );
$iGroupLoop = 0 ;
$iQueryLoop = 0 ;
foreach ( $aGroupedSearches as $iGroupedRank => $aSearches )
{
$iGroupLoop ++ ;
foreach ( $aSearches as $aSearch )
{
$iQueryLoop ++ ;
2016-03-23 11:34:36 +03:00
$searchedHousenumber = - 1 ;
2013-08-27 20:08:57 +04:00
if ( CONST_Debug ) { echo " <hr><b>Search Loop, group $iGroupLoop , loop $iQueryLoop </b> " ; }
if ( CONST_Debug ) _debugDumpGroupedSearches ( array ( $iGroupedRank => array ( $aSearch )), $aValidTokens );
2013-08-30 15:18:57 +04:00
// No location term?
2013-08-27 20:08:57 +04:00
if ( ! sizeof ( $aSearch [ 'aName' ]) && ! sizeof ( $aSearch [ 'aAddress' ]) && ! $aSearch [ 'fLon' ])
{
if ( $aSearch [ 'sCountryCode' ] && ! $aSearch [ 'sClass' ] && ! $aSearch [ 'sHouseNumber' ])
{
2013-08-30 15:18:57 +04:00
// Just looking for a country by code - look it up
2013-08-27 20:08:57 +04:00
if ( 4 >= $this -> iMinAddressRank && 4 <= $this -> iMaxAddressRank )
{
$sSQL = " select place_id from placex where calculated_country_code=' " . $aSearch [ 'sCountryCode' ] . " ' and rank_search = 4 " ;
if ( $sCountryCodesSQL ) $sSQL .= " and calculated_country_code in ( $sCountryCodesSQL ) " ;
2016-02-27 02:02:20 +03:00
if ( $bBoundingBoxSearch )
$sSQL .= " and _st_intersects( $this->sViewboxSmallSQL , geometry) " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " order by st_area(geometry) desc limit 1 " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
}
2014-05-01 17:38:31 +04:00
else
{
$aPlaceIDs = array ();
}
2013-08-27 20:08:57 +04:00
}
else
{
if ( ! $bBoundingBoxSearch && ! $aSearch [ 'fLon' ]) continue ;
if ( ! $aSearch [ 'sClass' ]) continue ;
$sSQL = " select count(*) from pg_tables where tablename = 'place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ' " ;
if ( $this -> oDB -> getOne ( $sSQL ))
{
$sSQL = " select place_id from place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ct " ;
if ( $sCountryCodesSQL ) $sSQL .= " join placex using (place_id) " ;
2014-04-30 23:04:46 +04:00
$sSQL .= " where st_contains( $this->sViewboxSmallSQL , ct.centroid) " ;
2013-08-27 20:08:57 +04:00
if ( $sCountryCodesSQL ) $sSQL .= " and calculated_country_code in ( $sCountryCodesSQL ) " ;
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$sSQL .= " and place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
if ( $sViewboxCentreSQL ) $sSQL .= " order by st_distance( $sViewboxCentreSQL , ct.centroid) asc " ;
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $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.
2014-05-01 16:20:23 +04:00
// Also don't expand if bounded results were requested.
if ( ! sizeof ( $aPlaceIDs ) && ! sizeof ( $this -> aExcludePlaceIDs ) && ! $this -> bBoundedSearch )
2013-08-27 20:08:57 +04:00
{
$sSQL = " select place_id from place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ct " ;
if ( $sCountryCodesSQL ) $sSQL .= " join placex using (place_id) " ;
2014-04-30 23:04:46 +04:00
$sSQL .= " where st_contains( $this->sViewboxLargeSQL , ct.centroid) " ;
2013-08-27 20:08:57 +04:00
if ( $sCountryCodesSQL ) $sSQL .= " and calculated_country_code in ( $sCountryCodesSQL ) " ;
if ( $sViewboxCentreSQL ) $sSQL .= " order by st_distance( $sViewboxCentreSQL , ct.centroid) asc " ;
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
}
}
else
{
$sSQL = " select place_id from placex where class=' " . $aSearch [ 'sClass' ] . " ' and type=' " . $aSearch [ 'sType' ] . " ' " ;
2014-04-30 23:04:46 +04:00
$sSQL .= " and st_contains( $this->sViewboxSmallSQL , geometry) and linked_place_id is null " ;
2013-08-27 20:08:57 +04:00
if ( $sCountryCodesSQL ) $sSQL .= " and calculated_country_code in ( $sCountryCodesSQL ) " ;
if ( $sViewboxCentreSQL ) $sSQL .= " order by st_distance( $sViewboxCentreSQL , centroid) asc " ;
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
}
}
}
else
{
$aPlaceIDs = array ();
// First we need a position, either aName or fLat or both
$aTerms = array ();
$aOrder = array ();
2015-02-05 23:13:33 +03:00
if ( $aSearch [ 'sHouseNumber' ] && sizeof ( $aSearch [ 'aAddress' ]))
2015-02-02 09:41:28 +03:00
{
$sHouseNumberRegex = '\\\\m' . $aSearch [ 'sHouseNumber' ] . '\\\\M' ;
2016-04-25 10:44:01 +03:00
$aOrder [] = " " ;
2016-04-25 14:58:28 +03:00
$aOrder [ 0 ] = " exists(select place_id from placex where parent_place_id = search_name.place_id " ;
$aOrder [ 0 ] .= " and transliteration(housenumber) ~* E' " . $sHouseNumberRegex . " ' limit 1) " ;
2016-04-25 10:44:01 +03:00
// also housenumbers from interpolation lines table are needed
2016-04-25 14:58:28 +03:00
$aOrder [ 0 ] .= " or exists(select place_id from location_property_osmline where parent_place_id = search_name.place_id " ;
$aOrder [ 0 ] .= " and " . $aSearch [ 'sHouseNumber' ] . " >=startnumber and " . $aSearch [ 'sHouseNumber' ] . " <=endnumber limit 1) " ;
2016-04-25 10:44:01 +03:00
$aOrder [ 0 ] .= " desc " ;
2015-02-02 09:41:28 +03:00
}
2013-08-27 20:08:57 +04: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
if ( sizeof ( $aSearch [ 'aName' ])) $aTerms [] = " name_vector @> ARRAY[ " . join ( $aSearch [ 'aName' ], " , " ) . " ] " ;
if ( sizeof ( $aSearch [ 'aNameNonSearch' ])) $aTerms [] = " array_cat(name_vector,ARRAY[]::integer[]) @> ARRAY[ " . join ( $aSearch [ 'aNameNonSearch' ], " , " ) . " ] " ;
if ( sizeof ( $aSearch [ 'aAddress' ]) && $aSearch [ 'aName' ] != $aSearch [ 'aAddress' ])
{
// For infrequent name terms disable index usage for address
if ( CONST_Search_NameOnlySearchFrequencyThreshold &&
sizeof ( $aSearch [ 'aName' ]) == 1 &&
$aWordFrequencyScores [ $aSearch [ 'aName' ][ reset ( $aSearch [ 'aName' ])]] < CONST_Search_NameOnlySearchFrequencyThreshold )
{
$aTerms [] = " array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[ " . join ( array_merge ( $aSearch [ 'aAddress' ], $aSearch [ 'aAddressNonSearch' ]), " , " ) . " ] " ;
}
else
{
$aTerms [] = " nameaddress_vector @> ARRAY[ " . join ( $aSearch [ 'aAddress' ], " , " ) . " ] " ;
if ( sizeof ( $aSearch [ 'aAddressNonSearch' ])) $aTerms [] = " array_cat(nameaddress_vector,ARRAY[]::integer[]) @> ARRAY[ " . join ( $aSearch [ 'aAddressNonSearch' ], " , " ) . " ] " ;
}
}
if ( $aSearch [ 'sCountryCode' ]) $aTerms [] = " country_code = ' " . pg_escape_string ( $aSearch [ 'sCountryCode' ]) . " ' " ;
2014-09-03 23:00:37 +04:00
if ( $aSearch [ 'sHouseNumber' ])
{
$aTerms [] = " address_rank between 16 and 27 " ;
}
else
{
if ( $this -> iMinAddressRank > 0 )
{
$aTerms [] = " address_rank >= " . $this -> iMinAddressRank ;
}
if ( $this -> iMaxAddressRank < 30 )
{
$aTerms [] = " address_rank <= " . $this -> iMaxAddressRank ;
}
}
2013-08-27 20:08:57 +04:00
if ( $aSearch [ 'fLon' ] && $aSearch [ 'fLat' ])
{
$aTerms [] = " ST_DWithin(centroid, ST_SetSRID(ST_Point( " . $aSearch [ 'fLon' ] . " , " . $aSearch [ 'fLat' ] . " ),4326), " . $aSearch [ 'fRadius' ] . " ) " ;
$aOrder [] = " ST_Distance(centroid, ST_SetSRID(ST_Point( " . $aSearch [ 'fLon' ] . " , " . $aSearch [ 'fLat' ] . " ),4326)) ASC " ;
}
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$aTerms [] = " place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
if ( $sCountryCodesSQL )
{
$aTerms [] = " country_code in ( $sCountryCodesSQL ) " ;
}
2014-04-30 23:04:46 +04:00
if ( $bBoundingBoxSearch ) $aTerms [] = " centroid && $this->sViewboxSmallSQL " ;
2013-08-27 20:08:57 +04:00
if ( $sNearPointSQL ) $aOrder [] = " ST_Distance( $sNearPointSQL , centroid) asc " ;
2014-03-13 23:34:31 +04:00
if ( $aSearch [ 'sHouseNumber' ])
{
$sImportanceSQL = '- abs(26 - address_rank) + 3' ;
}
else
{
$sImportanceSQL = '(case when importance = 0 OR importance IS NULL then 0.75-(search_rank::float/40) else importance end)' ;
}
2014-04-30 23:04:46 +04: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 " ;
2013-08-27 20:08:57 +04:00
$aOrder [] = " $sImportanceSQL DESC " ;
if ( sizeof ( $aSearch [ 'aFullNameAddress' ]))
{
2013-12-04 10:53:53 +04:00
$sExactMatchSQL = '(select count(*) from (select unnest(ARRAY[' . join ( $aSearch [ 'aFullNameAddress' ], " , " ) . ']) INTERSECT select unnest(nameaddress_vector))s) as exactmatch' ;
$aOrder [] = 'exactmatch DESC' ;
} else {
$sExactMatchSQL = '0::int as exactmatch' ;
2013-08-27 20:08:57 +04:00
}
if ( sizeof ( $aTerms ))
{
2013-12-04 10:53:53 +04:00
$sSQL = " select place_id, " ;
$sSQL .= $sExactMatchSQL ;
2013-08-27 20:08:57 +04:00
$sSQL .= " from search_name " ;
$sSQL .= " where " . join ( ' and ' , $aTerms );
$sSQL .= " order by " . join ( ', ' , $aOrder );
if ( $aSearch [ 'sHouseNumber' ] || $aSearch [ 'sClass' ])
2015-02-02 09:41:28 +03:00
$sSQL .= " limit 20 " ;
2013-08-27 20:08:57 +04:00
elseif ( ! sizeof ( $aSearch [ 'aName' ]) && ! sizeof ( $aSearch [ 'aAddress' ]) && $aSearch [ 'sClass' ])
$sSQL .= " limit 1 " ;
else
$sSQL .= " limit " . $this -> iLimit ;
if ( CONST_Debug ) { var_dump ( $sSQL ); }
$aViewBoxPlaceIDs = $this -> oDB -> getAll ( $sSQL );
if ( PEAR :: IsError ( $aViewBoxPlaceIDs ))
{
failInternalError ( " Could not get places for search terms. " , $sSQL , $aViewBoxPlaceIDs );
}
//var_dump($aViewBoxPlaceIDs);
// Did we have an viewbox matches?
$aPlaceIDs = array ();
$bViewBoxMatch = false ;
foreach ( $aViewBoxPlaceIDs as $aViewBoxRow )
{
//if ($bViewBoxMatch == 1 && $aViewBoxRow['in_small'] == 'f') break;
//if ($bViewBoxMatch == 2 && $aViewBoxRow['in_large'] == 'f') break;
//if ($aViewBoxRow['in_small'] == 't') $bViewBoxMatch = 1;
//else if ($aViewBoxRow['in_large'] == 't') $bViewBoxMatch = 2;
$aPlaceIDs [] = $aViewBoxRow [ 'place_id' ];
2013-12-04 10:53:53 +04:00
$this -> exactMatchCache [ $aViewBoxRow [ 'place_id' ]] = $aViewBoxRow [ 'exactmatch' ];
2013-08-27 20:08:57 +04:00
}
}
//var_Dump($aPlaceIDs);
//exit;
2016-03-07 13:07:33 +03:00
//now search for housenumber, if housenumber provided
2013-08-27 20:08:57 +04:00
if ( $aSearch [ 'sHouseNumber' ] && sizeof ( $aPlaceIDs ))
{
2016-04-25 10:44:01 +03:00
$searchedHousenumber = intval ( $aSearch [ 'sHouseNumber' ]);
2013-08-27 20:08:57 +04:00
$aRoadPlaceIDs = $aPlaceIDs ;
$sPlaceIDs = join ( ',' , $aPlaceIDs );
// Now they are indexed look for a house attached to a street we found
2014-05-10 21:41:11 +04:00
$sHouseNumberRegex = '\\\\m' . $aSearch [ 'sHouseNumber' ] . '\\\\M' ;
$sSQL = " select place_id from placex where parent_place_id in ( " . $sPlaceIDs . " ) and transliteration(housenumber) ~* E' " . $sHouseNumberRegex . " ' " ;
2013-08-27 20:08:57 +04:00
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$sSQL .= " and place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
2016-04-25 10:44:01 +03:00
// if nothing found, search in the interpolation line table
if ( ! sizeof ( $aPlaceIDs ))
{
// do we need to use transliteration and the regex for housenumbers???
//new query for lines, not housenumbers anymore
if ( $searchedHousenumber % 2 == 0 ){
//if housenumber is even, look for housenumber in streets with interpolationtype even or all
$sSQL = " select distinct place_id from location_property_osmline where parent_place_id in ( " . $sPlaceIDs . " ) and (interpolationtype='even' or interpolationtype='all') and " . $searchedHousenumber . " >=startnumber and " . $searchedHousenumber . " <=endnumber " ;
} else {
//look for housenumber in streets with interpolationtype odd or all
$sSQL = " select distinct place_id from location_property_osmline where parent_place_id in ( " . $sPlaceIDs . " ) and (interpolationtype='odd' or interpolationtype='all') and " . $searchedHousenumber . " >=startnumber and " . $searchedHousenumber . " <=endnumber " ;
}
2013-08-27 20:08:57 +04:00
2016-04-25 10:44:01 +03:00
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$sSQL .= " and parent_place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
//get place IDs
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL , 0 );
}
2016-03-07 13:07:33 +03:00
// If nothing found try the aux fallback table
2016-04-07 22:47:51 +03:00
if ( CONST_Use_Aux_Location_data && ! sizeof ( $aPlaceIDs ))
2013-08-27 20:08:57 +04:00
{
$sSQL = " select place_id from location_property_aux where parent_place_id in ( " . $sPlaceIDs . " ) and housenumber = ' " . pg_escape_string ( $aSearch [ 'sHouseNumber' ]) . " ' " ;
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
2016-04-25 10:44:01 +03:00
$sSQL .= " and parent_place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2013-08-27 20:08:57 +04:00
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
}
2016-04-25 12:01:04 +03:00
2016-03-21 14:42:38 +03:00
//if nothing was found in placex or location_property_aux, then search in Tiger data for this housenumber(location_property_tiger)
2016-04-07 22:47:51 +03:00
if ( CONST_Use_US_Tiger_Data && ! sizeof ( $aPlaceIDs ))
2013-08-27 20:08:57 +04:00
{
2016-03-07 13:07:33 +03:00
//new query for lines, not housenumbers anymore
2016-03-23 11:34:36 +03:00
if ( $searchedHousenumber % 2 == 0 ){
2016-03-21 14:42:38 +03:00
//if housenumber is even, look for housenumber in streets with interpolationtype even or all
$sSQL = " select distinct place_id from location_property_tiger where parent_place_id in ( " . $sPlaceIDs . " ) and (interpolationtype='even' or interpolationtype='all') and " . $searchedHousenumber . " >=startnumber and " . $searchedHousenumber . " <=endnumber " ;
} else {
//look for housenumber in streets with interpolationtype odd or all
$sSQL = " select distinct place_id from location_property_tiger where parent_place_id in ( " . $sPlaceIDs . " ) and (interpolationtype='odd' or interpolationtype='all') and " . $searchedHousenumber . " >=startnumber and " . $searchedHousenumber . " <=endnumber " ;
}
if ( sizeof ( $this -> aExcludePlaceIDs ))
2013-08-27 20:08:57 +04:00
{
2016-03-21 14:42:38 +03:00
$sSQL .= " and place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
2013-08-27 20:08:57 +04:00
}
//$sSQL .= " limit $this->iLimit";
if ( CONST_Debug ) var_dump ( $sSQL );
2016-03-21 14:42:38 +03:00
//get place IDs
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL , 0 );
2013-08-27 20:08:57 +04:00
}
2016-03-07 13:07:33 +03:00
// Fallback to the road (if no housenumber was found)
2013-08-27 20:08:57 +04:00
if ( ! sizeof ( $aPlaceIDs ) && preg_match ( '/[0-9]+/' , $aSearch [ 'sHouseNumber' ]))
{
$aPlaceIDs = $aRoadPlaceIDs ;
2016-03-21 14:42:38 +03:00
//set to -1, if no housenumbers were found
2016-03-23 11:34:36 +03:00
$searchedHousenumber = - 1 ;
2016-03-21 14:42:38 +03:00
}
2016-04-25 14:58:28 +03:00
//else: housenumber was found, remains saved in searchedHousenumber
2013-08-27 20:08:57 +04:00
}
2016-03-07 13:07:33 +03:00
2013-08-27 20:08:57 +04:00
if ( $aSearch [ 'sClass' ] && sizeof ( $aPlaceIDs ))
{
2016-03-21 16:26:39 +03:00
$sPlaceIDs = join ( ',' , $aPlaceIDs );
2013-08-27 20:08:57 +04:00
$aClassPlaceIDs = array ();
if ( ! $aSearch [ 'sOperator' ] || $aSearch [ 'sOperator' ] == 'name' )
{
// If they were searching for a named class (i.e. 'Kings Head pub') then we might have an extra match
$sSQL = " select place_id from placex where place_id in ( $sPlaceIDs ) and class=' " . $aSearch [ 'sClass' ] . " ' and type=' " . $aSearch [ 'sType' ] . " ' " ;
$sSQL .= " and linked_place_id is null " ;
if ( $sCountryCodesSQL ) $sSQL .= " and calculated_country_code in ( $sCountryCodesSQL ) " ;
$sSQL .= " order by rank_search asc limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aClassPlaceIDs = $this -> oDB -> getCol ( $sSQL );
}
if ( ! $aSearch [ 'sOperator' ] || $aSearch [ 'sOperator' ] == 'near' ) // & in
{
$sSQL = " select count(*) from pg_tables where tablename = 'place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " ' " ;
$bCacheTable = $this -> oDB -> getOne ( $sSQL );
$sSQL = " select min(rank_search) from placex where place_id in ( $sPlaceIDs ) " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$this -> iMaxRank = (( int ) $this -> oDB -> getOne ( $sSQL ));
// For state / country level searches the normal radius search doesn't work very well
$sPlaceGeom = false ;
if ( $this -> iMaxRank < 9 && $bCacheTable )
{
// Try and get a polygon to search in instead
$sSQL = " select geometry from placex where place_id in ( $sPlaceIDs ) and rank_search < $this->iMaxRank + 5 and st_geometrytype(geometry) in ('ST_Polygon','ST_MultiPolygon') order by rank_search asc limit 1 " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$sPlaceGeom = $this -> oDB -> getOne ( $sSQL );
}
if ( $sPlaceGeom )
{
$sPlaceIDs = false ;
}
else
{
$this -> iMaxRank += 5 ;
$sSQL = " select place_id from placex where place_id in ( $sPlaceIDs ) and rank_search < $this->iMaxRank " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aPlaceIDs = $this -> oDB -> getCol ( $sSQL );
$sPlaceIDs = join ( ',' , $aPlaceIDs );
}
if ( $sPlaceIDs || $sPlaceGeom )
{
$fRange = 0.01 ;
if ( $bCacheTable )
{
// More efficient - can make the range bigger
$fRange = 0.05 ;
$sOrderBySQL = '' ;
if ( $sNearPointSQL ) $sOrderBySQL = " ST_Distance( $sNearPointSQL , l.centroid) " ;
else if ( $sPlaceIDs ) $sOrderBySQL = " ST_Distance(l.centroid, f.geometry) " ;
else if ( $sPlaceGeom ) $sOrderBysSQL = " ST_Distance(st_centroid(' " . $sPlaceGeom . " '), l.centroid) " ;
$sSQL = " select distinct l.place_id " . ( $sOrderBySQL ? ',' . $sOrderBySQL : '' ) . " from place_classtype_ " . $aSearch [ 'sClass' ] . " _ " . $aSearch [ 'sType' ] . " as l " ;
if ( $sCountryCodesSQL ) $sSQL .= " join placex as lp using (place_id) " ;
if ( $sPlaceIDs )
{
$sSQL .= " ,placex as f where " ;
$sSQL .= " f.place_id in ( $sPlaceIDs ) and ST_DWithin(l.centroid, f.centroid, $fRange ) " ;
}
if ( $sPlaceGeom )
{
$sSQL .= " where " ;
$sSQL .= " ST_Contains(' " . $sPlaceGeom . " ', l.centroid) " ;
}
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$sSQL .= " and l.place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
if ( $sCountryCodesSQL ) $sSQL .= " and lp.calculated_country_code in ( $sCountryCodesSQL ) " ;
if ( $sOrderBySQL ) $sSQL .= " order by " . $sOrderBySQL . " asc " ;
2014-05-01 17:38:31 +04:00
if ( $this -> iOffset ) $sSQL .= " offset $this->iOffset " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aClassPlaceIDs = array_merge ( $aClassPlaceIDs , $this -> oDB -> getCol ( $sSQL ));
}
else
{
if ( isset ( $aSearch [ 'fRadius' ]) && $aSearch [ 'fRadius' ]) $fRange = $aSearch [ 'fRadius' ];
$sOrderBySQL = '' ;
if ( $sNearPointSQL ) $sOrderBySQL = " ST_Distance( $sNearPointSQL , l.geometry) " ;
else $sOrderBySQL = " ST_Distance(l.geometry, f.geometry) " ;
$sSQL = " select distinct l.place_id " . ( $sOrderBysSQL ? ',' . $sOrderBysSQL : '' ) . " from placex as l,placex as f where " ;
$sSQL .= " f.place_id in ( $sPlaceIDs ) and ST_DWithin(l.geometry, f.centroid, $fRange ) " ;
$sSQL .= " and l.class=' " . $aSearch [ 'sClass' ] . " ' and l.type=' " . $aSearch [ 'sType' ] . " ' " ;
if ( sizeof ( $this -> aExcludePlaceIDs ))
{
$sSQL .= " and l.place_id not in ( " . join ( ',' , $this -> aExcludePlaceIDs ) . " ) " ;
}
if ( $sCountryCodesSQL ) $sSQL .= " and l.calculated_country_code in ( $sCountryCodesSQL ) " ;
if ( $sOrderBy ) $sSQL .= " order by " . $OrderBysSQL . " asc " ;
2014-05-01 17:38:31 +04:00
if ( $this -> iOffset ) $sSQL .= " offset $this->iOffset " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " limit $this->iLimit " ;
if ( CONST_Debug ) var_dump ( $sSQL );
$aClassPlaceIDs = array_merge ( $aClassPlaceIDs , $this -> oDB -> getCol ( $sSQL ));
}
}
}
$aPlaceIDs = $aClassPlaceIDs ;
}
}
if ( PEAR :: IsError ( $aPlaceIDs ))
{
failInternalError ( " Could not get place IDs from tokens. " , $sSQL , $aPlaceIDs );
}
if ( CONST_Debug ) { echo " <br><b>Place IDs:</b> " ; var_Dump ( $aPlaceIDs ); }
foreach ( $aPlaceIDs as $iPlaceID )
{
2016-03-21 14:42:38 +03:00
// array for placeID => -1 | Tiger housenumber
2016-03-10 18:22:39 +03:00
$aResultPlaceIDs [ $iPlaceID ] = $searchedHousenumber ;
2013-08-27 20:08:57 +04:00
}
if ( $iQueryLoop > 20 ) break ;
}
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs ) && ( $this -> iMinAddressRank != 0 || $this -> iMaxAddressRank != 30 ))
{
// Need to verify passes rank limits before dropping out of the loop (yuk!)
2016-03-21 14:42:38 +03:00
// reduces the number of place ids, like a filter
2016-04-25 10:44:01 +03:00
// rank_address is 30 for interpolated housenumbers
2016-03-10 18:22:39 +03:00
$sSQL = " select place_id from placex where place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
2013-08-27 20:08:57 +04:00
$sSQL .= " and (placex.rank_address between $this->iMinAddressRank and $this->iMaxAddressRank " ;
if ( 14 >= $this -> iMinAddressRank && 14 <= $this -> iMaxAddressRank ) $sSQL .= " OR (extratags->'place') = 'city' " ;
2013-09-02 20:23:31 +04:00
if ( $this -> aAddressRankList ) $sSQL .= " OR placex.rank_address in ( " . join ( ',' , $this -> aAddressRankList ) . " ) " ;
2016-04-07 22:47:51 +03:00
if ( CONST_Use_US_Tiger_Data )
{
$sSQL .= " ) UNION select place_id from location_property_tiger where place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
$sSQL .= " and (30 between $this->iMinAddressRank and $this->iMaxAddressRank " ;
if ( $this -> aAddressRankList ) $sSQL .= " OR 30 in ( " . join ( ',' , $this -> aAddressRankList ) . " ) " ;
}
2016-04-25 18:44:34 +03:00
$sSQL .= " ) UNION select place_id from location_property_osmline where place_id in ( " . join ( ',' , array_keys ( $aResultPlaceIDs )) . " ) " ;
$sSQL .= " and (30 between $this->iMinAddressRank and $this->iMaxAddressRank ) " ;
2013-08-27 20:08:57 +04:00
if ( CONST_Debug ) var_dump ( $sSQL );
2016-03-10 18:22:39 +03:00
$aFilteredPlaceIDs = $this -> oDB -> getCol ( $sSQL );
2016-03-21 14:42:38 +03:00
$tempIDs = array ();
2016-03-23 11:34:36 +03:00
foreach ( $aFilteredPlaceIDs as $placeID )
2016-04-25 14:58:28 +03:00
{
2016-03-23 11:34:36 +03:00
$tempIDs [ $placeID ] = $aResultPlaceIDs [ $placeID ]; //assign housenumber to placeID
2016-03-21 14:42:38 +03:00
}
2016-03-23 11:34:36 +03:00
$aResultPlaceIDs = $tempIDs ;
2013-08-27 20:08:57 +04:00
}
//exit;
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs )) break ;
if ( $iGroupLoop > 4 ) break ;
if ( $iQueryLoop > 30 ) break ;
}
// Did we find anything?
if ( isset ( $aResultPlaceIDs ) && sizeof ( $aResultPlaceIDs ))
{
$aSearchResults = $this -> getDetails ( $aResultPlaceIDs );
}
}
else
{
// Just interpret as a reverse geocode
$iPlaceID = geocodeReverse (( float ) $this -> aNearPoint [ 0 ], ( float ) $this -> aNearPoint [ 1 ]);
2013-08-30 16:13:21 +04:00
if ( $iPlaceID )
$aSearchResults = $this -> getDetails ( array ( $iPlaceID ));
else
$aSearchResults = array ();
2013-08-27 20:08:57 +04:00
}
// No results? Done
if ( ! sizeof ( $aSearchResults ))
{
2014-01-23 18:40:37 +04:00
if ( $this -> bFallback )
{
if ( $this -> fallbackStructuredQuery ())
{
return $this -> lookup ();
}
}
2013-08-27 20:08:57 +04:00
return array ();
}
$aClassType = getClassTypesWithImportance ();
2014-05-01 17:19:24 +04:00
$aRecheckWords = preg_split ( '/\b[\s,\\-]*/u' , $sQuery );
2013-08-27 20:08:57 +04:00
foreach ( $aRecheckWords as $i => $sWord )
{
2015-03-29 18:23:06 +03:00
if ( ! preg_match ( '/\pL/' , $sWord )) unset ( $aRecheckWords [ $i ]);
2013-08-27 20:08:57 +04:00
}
2016-02-27 02:02:20 +03:00
if ( CONST_Debug ) { echo '<i>Recheck words:<\i>' ; var_dump ( $aRecheckWords ); }
2015-03-29 18:23:06 +03:00
2013-08-27 20:08:57 +04:00
foreach ( $aSearchResults as $iResNum => $aResult )
{
2015-05-21 23:33:05 +03:00
// Default
2016-03-04 22:29:09 +03:00
$fDiameter = getResultDiameter ( $aResult );
$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 );
$aOutlineResult = $oPlaceLookup -> getOutlines ( $aResult [ 'place_id' ], $aResult [ 'lon' ], $aResult [ 'lat' ], $fDiameter / 2 );
2016-03-13 05:01:19 +03:00
if ( $aOutlineResult )
{
$aResult = array_merge ( $aResult , $aOutlineResult );
}
2013-08-27 20:08:57 +04:00
if ( $aResult [ 'extra_place' ] == 'city' )
{
$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' ])
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ])
{
$aResult [ 'icon' ] = CONST_Website_BaseURL . 'images/mapicons/' . $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'icon' ] . '.p.20.png' ;
}
2013-09-09 19:18:40 +04:00
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ])
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ])
{
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ] . ':' . $aResult [ 'admin_level' ]][ 'label' ];
}
elseif ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ])
2013-08-27 20:08:57 +04:00
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ])
{
$aResult [ 'label' ] = $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'label' ];
}
2016-03-21 14:42:38 +03:00
// if tag '&addressdetails=1' is set in query
2013-08-27 20:08:57 +04:00
if ( $this -> bIncludeAddressDetails )
{
2016-03-21 14:42:38 +03:00
// getAddressDetails() is defined in lib.php and uses the SQL function get_addressdata in functions.sql
2016-03-10 18:22:39 +03:00
$aResult [ 'address' ] = getAddressDetails ( $this -> oDB , $sLanguagePrefArraySQL , $aResult [ 'place_id' ], $aResult [ 'country_code' ], $aResultPlaceIDs [ $aResult [ 'place_id' ]]);
2013-08-27 20:08:57 +04:00
if ( $aResult [ 'extra_place' ] == 'city' && ! isset ( $aResult [ 'address' ][ 'city' ]))
{
$aResult [ 'address' ] = array_merge ( array ( 'city' => array_shift ( array_values ( $aResult [ 'address' ]))), $aResult [ 'address' ]);
}
}
2015-08-10 22:42:40 +03:00
if ( $this -> bIncludeExtraTags )
{
if ( $aResult [ 'extra' ])
{
$aResult [ 'sExtraTags' ] = json_decode ( $aResult [ 'extra' ]);
}
else
{
2015-08-15 10:52:48 +03:00
$aResult [ 'sExtraTags' ] = ( object ) array ();
2015-08-10 22:42:40 +03:00
}
}
if ( $this -> bIncludeNameDetails )
{
if ( $aResult [ 'names' ])
{
$aResult [ 'sNameDetails' ] = json_decode ( $aResult [ 'names' ]);
}
else
{
2015-08-15 10:52:48 +03:00
$aResult [ 'sNameDetails' ] = ( object ) array ();
2015-08-10 22:42:40 +03:00
}
}
2013-08-27 20:08:57 +04:00
// Adjust importance for the number of exact string matches in the result
$aResult [ 'importance' ] = max ( 0.001 , $aResult [ 'importance' ]);
$iCountWords = 0 ;
$sAddress = $aResult [ 'langaddress' ];
foreach ( $aRecheckWords as $i => $sWord )
{
2014-05-01 17:19:24 +04:00
if ( stripos ( $sAddress , $sWord ) !== false )
{
$iCountWords ++ ;
2015-03-29 18:23:06 +03:00
if ( preg_match ( " /(^|,) \ s* " . preg_quote ( $sWord , '/' ) . " \ s*(,| $ )/ " , $sAddress )) $iCountWords += 0.1 ;
2014-05-01 17:19:24 +04:00
}
2013-08-27 20:08:57 +04:00
}
$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' ];
2013-12-04 10:53:53 +04:00
// secondary ordering (for results with same importance (the smaller the better):
// - approximate importance of address parts
$aResult [ 'foundorder' ] = - $aResult [ 'addressimportance' ] / 10 ;
// - number of exact matches from the query
if ( isset ( $this -> exactMatchCache [ $aResult [ 'place_id' ]]))
$aResult [ 'foundorder' ] -= $this -> exactMatchCache [ $aResult [ 'place_id' ]];
else if ( isset ( $this -> exactMatchCache [ $aResult [ 'parent_place_id' ]]))
$aResult [ 'foundorder' ] -= $this -> exactMatchCache [ $aResult [ 'parent_place_id' ]];
// - importance of the class/type
if ( isset ( $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ])
&& $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ])
{
2015-05-04 22:14:34 +03:00
$aResult [ 'foundorder' ] += 0.0001 * $aClassType [ $aResult [ 'class' ] . ':' . $aResult [ 'type' ]][ 'importance' ];
2013-12-04 10:53:53 +04:00
}
else
{
2015-05-04 22:14:34 +03:00
$aResult [ 'foundorder' ] += 0.01 ;
2013-12-04 10:53:53 +04:00
}
2015-08-10 22:42:40 +03:00
if ( CONST_Debug ) { var_dump ( $aResult ); }
2013-08-27 20:08:57 +04:00
$aSearchResults [ $iResNum ] = $aResult ;
}
uasort ( $aSearchResults , 'byImportance' );
$aOSMIDDone = array ();
$aClassTypeNameDone = array ();
$aToFilter = $aSearchResults ;
$aSearchResults = array ();
$bFirst = true ;
foreach ( $aToFilter as $iResNum => $aResult )
{
$this -> aExcludePlaceIDs [ $aResult [ 'place_id' ]] = $aResult [ 'place_id' ];
if ( $bFirst )
{
$fLat = $aResult [ 'lat' ];
$fLon = $aResult [ 'lon' ];
if ( isset ( $aResult [ 'zoom' ])) $iZoom = $aResult [ 'zoom' ];
$bFirst = false ;
}
if ( ! $this -> bDeDupe || ( ! isset ( $aOSMIDDone [ $aResult [ 'osm_type' ] . $aResult [ 'osm_id' ]])
&& ! isset ( $aClassTypeNameDone [ $aResult [ 'osm_type' ] . $aResult [ 'class' ] . $aResult [ 'type' ] . $aResult [ 'name' ] . $aResult [ 'admin_level' ]])))
{
$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