From 57583f09e3277c4297abf06d37e1f97982366564 Mon Sep 17 00:00:00 2001 From: Brian Quinion Date: Wed, 27 Oct 2010 14:05:42 +0000 Subject: [PATCH] hstore names / refactoring --- lib/db.php | 18 +- settings/settings.php | 5 +- sql/functions.sql | 89 ++-- sql/tables.sql | 3 +- utils/setup.php | 136 +++++- website/search.php | 1024 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1185 insertions(+), 90 deletions(-) create mode 100755 website/search.php diff --git a/lib/db.php b/lib/db.php index 4f7336c1..a2bba81f 100644 --- a/lib/db.php +++ b/lib/db.php @@ -1,15 +1,19 @@ getMessage(), 'Unable to connect to the database'); + // Get the database object + $oDB =& DB::connect(CONST_Database_DSN, false); + if (PEAR::IsError($oDB)) + { + fail($oDB->getMessage(), 'Unable to connect to the database'); + } + $oDB->setFetchMode(DB_FETCHMODE_ASSOC); + $oDB->query("SET DateStyle TO 'sql,european'"); + $oDB->query("SET client_encoding TO 'utf-8'"); + return $oDB; } - $oDB->setFetchMode(DB_FETCHMODE_ASSOC); - $oDB->query("SET DateStyle TO 'sql,european'"); - $oDB->query("SET client_encoding TO 'utf-8'"); function getDBQuoted($s) { diff --git a/settings/settings.php b/settings/settings.php index f8075cb1..ca90b3c5 100644 --- a/settings/settings.php +++ b/settings/settings.php @@ -1,5 +1,8 @@ NEW.rank_search and ST_geometrytype(placex.geometry) = 'ST_Point'; - update placex set indexed = false where indexed and (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) - AND rank_search > NEW.rank_search and ST_geometrytype(placex.geometry) != 'ST_Point'; + update placex set indexed_status = 2 where (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + AND rank_search > NEW.rank_search and indexed = 0 and ST_geometrytype(placex.geometry) = 'ST_Point'; + update placex set indexed_status = 2 where (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) + AND rank_search > NEW.rank_search and indexed = 0 and ST_geometrytype(placex.geometry) != 'ST_Point'; END IF; ELSE -- mark nearby items for re-indexing, where 'nearby' depends on the features rank_search and is a complete guess :( @@ -1103,7 +1105,7 @@ BEGIN END IF; IF diameter > 0 THEN -- RAISE WARNING 'placex point insert: % % % % %',NEW.osm_type,NEW.osm_id,NEW.class,NEW.type,diameter; - update placex set indexed = false where indexed and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter); + update placex set indexed = 2 where indexed and rank_search > NEW.rank_search and ST_DWithin(placex.geometry, NEW.geometry, diameter); END IF; END IF; @@ -1272,6 +1274,7 @@ BEGIN -- Try and find a way that is close roughly parellel to this line FOR relation IN SELECT place_id FROM placex WHERE ST_DWithin(location.geometry, placex.geometry, 0.001) and placex.rank_search = 26 + and st_geometrytype(location.geometry) in ('ST_LineString') ORDER BY (ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,0))+ ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,0.5))+ ST_distance(placex.geometry, ST_Line_Interpolate_Point(location.geometry,1))) ASC limit 1 @@ -1460,38 +1463,11 @@ BEGIN --END IF; -- mark everything linked to this place for re-indexing - UPDATE placex set indexed = false from place_addressline where address_place_id = OLD.place_id and placex.place_id = place_addressline.place_id and indexed; + UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = OLD.place_id + and placex.place_id = place_addressline.place_id and indexed_status = 0; -- do the actual delete DELETE FROM location_area where place_id = OLD.place_id; - DELETE FROM location_point where place_id = OLD.place_id; - DELETE FROM location_point_0 where place_id = OLD.place_id; - DELETE FROM location_point_1 where place_id = OLD.place_id; - DELETE FROM location_point_2 where place_id = OLD.place_id; - DELETE FROM location_point_3 where place_id = OLD.place_id; - DELETE FROM location_point_4 where place_id = OLD.place_id; - DELETE FROM location_point_5 where place_id = OLD.place_id; - DELETE FROM location_point_6 where place_id = OLD.place_id; - DELETE FROM location_point_7 where place_id = OLD.place_id; - DELETE FROM location_point_8 where place_id = OLD.place_id; - DELETE FROM location_point_9 where place_id = OLD.place_id; - DELETE FROM location_point_10 where place_id = OLD.place_id; - DELETE FROM location_point_11 where place_id = OLD.place_id; - DELETE FROM location_point_12 where place_id = OLD.place_id; - DELETE FROM location_point_13 where place_id = OLD.place_id; - DELETE FROM location_point_14 where place_id = OLD.place_id; - DELETE FROM location_point_15 where place_id = OLD.place_id; - DELETE FROM location_point_16 where place_id = OLD.place_id; - DELETE FROM location_point_17 where place_id = OLD.place_id; - DELETE FROM location_point_18 where place_id = OLD.place_id; - DELETE FROM location_point_19 where place_id = OLD.place_id; - DELETE FROM location_point_20 where place_id = OLD.place_id; - DELETE FROM location_point_21 where place_id = OLD.place_id; - DELETE FROM location_point_22 where place_id = OLD.place_id; - DELETE FROM location_point_23 where place_id = OLD.place_id; - DELETE FROM location_point_24 where place_id = OLD.place_id; - DELETE FROM location_point_25 where place_id = OLD.place_id; - DELETE FROM location_point_26 where place_id = OLD.place_id; DELETE FROM search_name where place_id = OLD.place_id; DELETE FROM place_addressline where place_id = OLD.place_id; DELETE FROM place_addressline where address_place_id = OLD.place_id; @@ -1663,12 +1639,12 @@ BEGIN IF st_area(NEW.geometry) < 1 AND st_area(existinggeometry) < 1 THEN -- re-index points that have moved in / out of the polygon, could be done as a single query but postgres gets the index usage wrong - update placex set indexed = false where indexed and + update placex set indexed_status = 2 where indexed_status = 0 and (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) AND NOT (ST_Contains(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) AND rank_search > NEW.rank_search; - update placex set indexed = false where indexed and + update placex set indexed_status = 2 where indexed_status = 0 and (ST_Contains(existinggeometry, placex.geometry) OR ST_Intersects(existinggeometry, placex.geometry)) AND NOT (ST_Contains(NEW.geometry, placex.geometry) OR ST_Intersects(NEW.geometry, placex.geometry)) AND rank_search > NEW.rank_search; @@ -1694,8 +1670,8 @@ BEGIN IF NOT update_location_nameonly(existingplacex.place_id, NEW.name) THEN IF st_area(NEW.geometry) < 0.5 THEN - UPDATE placex set indexed = false from place_addressline where address_place_id = existingplacex.place_id - and placex.place_id = place_addressline.place_id and indexed; + UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = existingplacex.place_id + and placex.place_id = place_addressline.place_id and indexed_status = 0; END IF; END IF; @@ -1716,8 +1692,8 @@ BEGIN -- performance, can't take the load of re-indexing a whole country / huge area IF st_area(NEW.geometry) < 0.5 THEN - UPDATE placex set indexed = false from place_addressline where address_place_id = existingplacex.place_id - and placex.place_id = place_addressline.place_id and indexed; + UPDATE placex set indexed_status = 2 from place_addressline where address_place_id = existingplacex.place_id + and placex.place_id = place_addressline.place_id and indexed_status = 0; END IF; END IF; @@ -1773,14 +1749,14 @@ DECLARE found BOOLEAN; BEGIN - IF (array_upper(name, 1) is null) THEN - return null; + IF name is null THEN + RETURN null; END IF; search := languagepref; FOR j IN 1..array_upper(search, 1) LOOP - IF name ? search[j] AND trim(name->search[j] != '') THEN + IF name ? search[j] AND trim(name->search[j]) != '' THEN return trim(name->search[j]); END IF; END LOOP; @@ -1869,28 +1845,24 @@ BEGIN search := languagepref; result := '{}'; --- UPDATE placex set indexed = false where indexed = true and place_id = for_place_id; - UPDATE placex set indexed = true where indexed = false and place_id = for_place_id; - select country_code,housenumber,rank_address from placex where place_id = for_place_id into searchcountrycode,searchhousenumber,searchrankaddress; FOR location IN select CASE WHEN address_place_id = for_place_id AND rank_address = 0 THEN 100 ELSE rank_address END as rank_address, - CASE WHEN type = 'postcode' THEN 'name'->postcode ELSE name END as name, + CASE WHEN type = 'postcode' THEN 'name' => postcode ELSE name END as name, distance,length(name::text) as namelength from place_addressline join placex on (address_place_id = placex.place_id) where place_addressline.place_id = for_place_id and ((rank_address > 0 AND rank_address < searchrankaddress) OR address_place_id = for_place_id) and (placex.country_code IS NULL OR searchcountrycode IS NULL OR placex.country_code = searchcountrycode OR rank_address < 4) order by rank_address desc,fromarea desc,distance asc,rank_search desc,namelength desc LOOP - IF array_upper(search, 1) IS NOT NULL AND array_upper(location.name, 1) IS NOT NULL THEN + IF array_upper(search, 1) IS NOT NULL AND location.name IS NOT NULL THEN FOR j IN 1..array_upper(search, 1) LOOP - FOR k IN 1..array_upper(location.name, 1) LOOP - IF (found > location.rank_address AND location.name[k].key = search[j] AND location.name[k].value != '') AND NOT result && ARRAY[trim(location.name[k].value)] THEN - result[(100 - location.rank_address)] := trim(location.name[k].value); - found := location.rank_address; - END IF; - END LOOP; + IF (found > location.rank_address AND location.name ? search[j] AND location.name -> search[j] != '' + AND NOT result && ARRAY[location.name -> search[j]]) THEN + result[(100 - location.rank_address)] := trim(location.name -> search[j]); + found := location.rank_address; + END IF; END LOOP; END IF; END LOOP; @@ -1931,8 +1903,7 @@ BEGIN search := languagepref; result := '{}'; --- UPDATE placex set indexed = false where indexed = true and place_id = for_place_id; - UPDATE placex set indexed = true where indexed = false and place_id = for_place_id; + UPDATE placex set indexed_status = 0 where indexed_status > 0 and place_id = for_place_id; select country_code,housenumber from placex where place_id = for_place_id into searchcountrycode,searchhousenumber; diff --git a/sql/tables.sql b/sql/tables.sql index a5b83c30..62a4eb36 100644 --- a/sql/tables.sql +++ b/sql/tables.sql @@ -63,7 +63,8 @@ CREATE TABLE word ( class text, type text, country_code varchar(2), - search_name_count INTEGER + search_name_count INTEGER, + operator TEXT ); SELECT AddGeometryColumn('word', 'location', 4326, 'GEOMETRY', 2); CREATE INDEX idx_word_word_id on word USING BTREE (word_id); diff --git a/utils/setup.php b/utils/setup.php index bc8f14f7..72dd9c9a 100755 --- a/utils/setup.php +++ b/utils/setup.php @@ -10,43 +10,75 @@ array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'), array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'), - array('create-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'), - array('load-data', '', 0, 1, 1, 1, 'realpath', 'Import a osm file'), + array('all', '', 0, 1, 1, 1, 'realpath', 'Do the complete process'), + + array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'), + array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'), + array('import-data', '', 0, 1, 1, 1, 'realpath', 'Import a osm file'), + array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'), + array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'), array('create-partitions', '', 0, 1, 0, 0, 'bool', 'Create required partition tables and triggers'), + array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'), ); getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true); $bDidSomething = false; - if ($aCMDResult['create-db']) + if ($aCMDResult['create-db'] || isset($aCMDResult['all'])) + { + $bDidSomething = true; + $oDB =& DB::connect(CONST_Database_DSN, false); + if (!PEAR::isError($oDB)) + { + fail('database already exists'); + } + passthru('createdb nominatim'); + } + + if ($aCMDResult['create-db'] || isset($aCMDResult['all'])) { $bDidSomething = true; // TODO: path detection, detection memory, etc. -// passthru('createdb nominatim'); + + $oDB =& getDB(); passthru('createlang plpgsql nominatim'); - passthru('psql -f '.CONST_Path_Postgresql_Contrib.'/_int.sql nominatim'); - passthru('psql -f '.CONST_Path_Postgresql_Contrib.'/hstore.sql nominatim'); - passthru('psql -f '.CONST_Path_Postgresql_Postgis.'/postgis.sql nominatim'); - passthru('psql -f '.CONST_Path_Postgresql_Postgis.'/spatial_ref_sys.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/country_name.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/country_osm_grid.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/gb_postcode.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/us_statecounty.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/us_state.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/data/worldboundaries.sql nominatim'); + pgsqlRunScriptFile(CONST_Path_Postgresql_Contrib.'/_int.sql'); + pgsqlRunScriptFile(CONST_Path_Postgresql_Contrib.'/hstore.sql'); + pgsqlRunScriptFile(CONST_Path_Postgresql_Postgis.'/postgis.sql'); + pgsqlRunScriptFile(CONST_Path_Postgresql_Postgis.'/spatial_ref_sys.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/us_statecounty.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/us_state.sql'); + pgsqlRunScriptFile(CONST_BasePath.'/data/worldboundaries.sql'); + } + + if (isset($aCMDResult['all']) && !isset($aCMDResult['import-data'])) $aCMDResult['import-data'] = $aCMDResult['all']; + if (isset($aCMDResult['import-data']) && $aCMDResult['import-data']) + { + $bDidSomething = true; + passthru(CONST_BasePath.'/osm2pgsql/osm2pgsql -lsc -O gazetteer -C 10000 --hstore -d nominatim '.$aCMDResult['import-data']); + } + + if ($aCMDResult['create-functions'] || isset($aCMDResult['all'])) + { + $bDidSomething = true; + $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql'); + $sTemplate = str_replace('{modulepath}',CONST_BasePath.'/module', $sTemplate); + pgsqlRunScript($sTemplate); } - if (isset($aCMDResult['load-data']) && $aCMDResult['load-data']) + if ($aCMDResult['create-tables'] || isset($aCMDResult['all'])) { $bDidSomething = true; - passthru(CONST_BasePath.'/osm2pgsql/osm2pgsql -lsc -O gazetteer -C 10000 --hstore -d nominatim '.$aCMDResult['load-data']); - passthru('psql -f '.CONST_BasePath.'/sql/functions.sql nominatim'); - passthru('psql -f '.CONST_BasePath.'/sql/tables.sql nominatim'); + pgsqlRunScriptFile(CONST_BasePath.'/sql/tables.sql'); } - if ($aCMDResult['create-partitions']) + if ($aCMDResult['create-partitions'] || isset($aCMDResult['all'])) { $bDidSomething = true; + $oDB =& getDB(); $sSQL = 'select distinct country_code from country_name order by country_code'; $aPartitions = $oDB->getCol($sSQL); if (PEAR::isError($aPartitions)) @@ -66,11 +98,73 @@ } $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate); } - echo $sTemplate; - exit; + pgsqlRunScript($sTemplate); + } + + if ($aCMDResult['load-data'] || isset($aCMDResult['all'])) + { + $bDidSomething = true; + pgsqlRunScriptFile(CONST_BasePath.'/sql/loaddata.sql'); } if (!$bDidSomething) { showUsage($aCMDOptions, true); } + + function pgsqlRunScriptFile($sFilename) + { + if (!file_exists($sFilename)) fail('unable to find '.$sFilename); + + // Convert database DSN to psql paramaters + $aDSNInfo = DB::parseDSN(CONST_Database_DSN); + $sCMD = 'psql -f '.$sFilename.' '.$aDSNInfo['database']; + + $aDescriptors = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('file', '/dev/null', 'a') + ); + $ahPipes = null; + $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes); + if (!is_resource($hProcess)) fail('unable to start pgsql'); + + fclose($ahPipes[0]); + + // TODO: error checking + while(!feof($ahPipes[1])) + { + echo fread($ahPipes[1], 4096); + } + fclose($ahPipes[1]); + + proc_close($hProcess); + } + + function pgsqlRunScript($sScript) + { + // Convert database DSN to psql paramaters + $aDSNInfo = DB::parseDSN(CONST_Database_DSN); + $sCMD = 'psql '.$aDSNInfo['database']; + + $aDescriptors = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('file', '/dev/null', 'a') + ); + $ahPipes = null; + $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes); + if (!is_resource($hProcess)) fail('unable to start pgsql'); + + fwrite($ahPipes[0], $sScript); + fclose($ahPipes[0]); + + // TODO: error checking + while(!feof($ahPipes[1])) + { + echo fread($ahPipes[1], 4096); + } + fclose($ahPipes[1]); + + proc_close($hProcess); + } diff --git a/website/search.php b/website/search.php new file mode 100755 index 00000000..a123e833 --- /dev/null +++ b/website/search.php @@ -0,0 +1,1024 @@ + 100) $iLimit = 100; + + // Format for output + if (isset($_GET['format']) && ($_GET['format'] == 'html' || $_GET['format'] == 'xml' || $_GET['format'] == 'json' || $_GET['format'] == 'jsonv2')) + { + $sOutputFormat = $_GET['format']; + } + + // Show / use polygons + $bShowPolygons = isset($_GET['polygon']) && $_GET['polygon']; + + // Show address breakdown + $bShowAddressDetails = isset($_GET['addressdetails']) && $_GET['addressdetails']; + + // Prefered language + $aLangPrefOrder = getPrefferedLangauges(); + if (isset($aLangPrefOrder['name:de'])) $bReverseInPlan = true; + $sLanguagePrefArraySQL = "ARRAY[".join(',',array_map("getDBQuoted",$aLangPrefOrder))."]"; + + if (isset($_GET['exclude_place_ids']) && $_GET['exclude_place_ids']) + { + foreach(explode(',',$_GET['exclude_place_ids']) as $iExcludedPlaceID) + { + $iExcludedPlaceID = (int)$iExcludedPlaceID; + if ($iExcludedPlaceID) $aExcludePlaceIDs[$iExcludedPlaceID] = $iExcludedPlaceID; + } + } + + // Search query + $sQuery = (isset($_GET['q'])?trim($_GET['q']):''); + if (!$sQuery && $_SERVER['PATH_INFO'] && $_SERVER['PATH_INFO'][0] == '/') + { + $sQuery = substr($_SERVER['PATH_INFO'], 1); + + // reverse order of '/' seperated string + $aPhrases = explode('/', $sQuery); + $aPhrases = array_reverse($aPhrases); + $sQuery = join(', ',$aPhrases); + } + + if ($sQuery) + { + $hLog = logStart($oDB, 'search', $sQuery, $aLangPrefOrder); + + // If we have a view box create the SQL + // Small is the actual view box, Large is double (on each axis) that + $sViewboxCentreSQL = $sViewboxSmallSQL = $sViewboxLargeSQL = false; + if (isset($_GET['viewboxlbrt']) && $_GET['viewboxlbrt']) + { + $aCoOrdinatesLBRT = explode(',',$_GET['viewboxlbrt']); + $_GET['viewbox'] = $aCoOrdinatesLBRT[0].','.$aCoOrdinatesLBRT[3].','.$aCoOrdinatesLBRT[2].','.$aCoOrdinatesLBRT[1]; + } + if (isset($_GET['viewbox']) && $_GET['viewbox']) + { + $aCoOrdinates = explode(',',$_GET['viewbox']); + $sViewboxSmallSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)"; + $fHeight = $aCoOrdinates[0]-$aCoOrdinates[2]; + $fWidth = $aCoOrdinates[1]-$aCoOrdinates[3]; + $aCoOrdinates[0] += $fHeight; + $aCoOrdinates[2] -= $fHeight; + $aCoOrdinates[1] += $fWidth; + $aCoOrdinates[3] -= $fWidth; + $sViewboxLargeSQL = "ST_SetSRID(ST_MakeBox2D(ST_Point(".(float)$aCoOrdinates[0].",".(float)$aCoOrdinates[1]."),ST_Point(".(float)$aCoOrdinates[2].",".(float)$aCoOrdinates[3].")),4326)"; + } + if (isset($_GET['route']) && $_GET['route'] && isset($_GET['routewidth']) && $_GET['routewidth']) + { + $aPoints = explode(',',$_GET['route']); + if (sizeof($aPoints) % 2 != 0) + { + echo "Uneven number of points"; + exit; + } + $sViewboxCentreSQL = "ST_SetSRID('LINESTRING("; + $fPrevCoord = false; + foreach($aPoints as $i => $fPoint) + { + if ($i%2) + { + if ($i != 1) $sViewboxCentreSQL .= ","; + $sViewboxCentreSQL .= ((float)$fPoint).' '.$fPrevCoord; + } + else + { + $fPrevCoord = (float)$fPoint; + } + } + $sViewboxCentreSQL .= ")'::geometry,4326)"; + + $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/69).")"; + $sViewboxSmallSQL = $oDB->getOne($sSQL); + if (PEAR::isError($sViewboxSmallSQL)) + { + var_dump($sViewboxSmallSQL); + exit; + } + $sViewboxSmallSQL = "'".$sViewboxSmallSQL."'::geometry"; + + $sSQL = "select st_buffer(".$sViewboxCentreSQL.",".(float)($_GET['routewidth']/30).")"; + $sViewboxLargeSQL = $oDB->getOne($sSQL); + if (PEAR::isError($sViewboxLargeSQL)) + { + var_dump($sViewboxLargeSQL); + exit; + } + $sViewboxLargeSQL = "'".$sViewboxLargeSQL."'::geometry"; + } + + // Do we have anything that looks like a lat/lon pair? + if (preg_match('/\\b([NS])[ ]+([0-9]+[0-9.]*)[ ]+([0-9.]+)?[, ]+([EW])[ ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?\\b/', $sQuery, $aData)) + { + $_GET['nearlat'] = ($aData[1]=='N'?1:-1) * ($aData[2] + $aData[3]/60); + $_GET['nearlon'] = ($aData[4]=='E'?1:-1) * ($aData[5] + $aData[6]/60); + $sQuery = trim(str_replace($aData[0], ' ', $sQuery)); + } + elseif (preg_match('/\\b([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([NS])[, ]+([0-9]+)[ ]+([0-9]+[0-9.]*)?[ ]+([EW])\\b/', $sQuery, $aData)) + { + $_GET['nearlat'] = ($aData[3]=='N'?1:-1) * ($aData[1] + $aData[2]/60); + $_GET['nearlon'] = ($aData[6]=='E'?1:-1) * ($aData[4] + $aData[5]/60); + $sQuery = trim(str_replace($aData[0], ' ', $sQuery)); + } + elseif (preg_match('/(\\[|\\b)(-?[0-9]+[0-9.]*)[, ]+(-?[0-9]+[0-9.]*)(\\]|\\b])/', $sQuery, $aData)) + { + $_GET['nearlat'] = $aData[2]; + $_GET['nearlon'] = $aData[3]; + $sQuery = trim(str_replace($aData[0], ' ', $sQuery)); + } + + if ($sQuery) + { + + // Start with a blank search + $aSearches = array( + array('iSearchRank' => 0, 'iNamePhrase' => 0, 'sCountryCode' => false, 'aName'=>array(), 'aAddress'=>array(), + 'sOperator'=>'', 'aFeatureName' => array(), 'sClass'=>'', 'sType'=>'', 'sHouseNumber'=>'', 'fLat'=>'', 'fLon'=>'', 'fRadius'=>'') + ); + + $sNearPointSQL = false; + if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) + { + $sNearPointSQL = "ST_SetSRID(ST_Point(".(float)$_GET['nearlon'].",".$_GET['nearlat']."),4326)"; + $aSearches[0]['fLat'] = (float)$_GET['nearlat']; + $aSearches[0]['fLon'] = (float)$_GET['nearlon']; + $aSearches[0]['fRadius'] = 0.1; + } + + $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('/\\[([a-zA-Z]*)\\]/', $sQuery, $aSpecialTermsRaw, PREG_SET_ORDER); + $aSpecialTerms = array(); + foreach($aSpecialTermsRaw as $aSpecialTerm) + { + $sQuery = str_replace($aSpecialTerm[0], ' ', $sQuery); + $sToken = $oDB->getOne("select make_standard_name('".$aSpecialTerm[1]."') as string"); + $sSQL = 'select * from (select word_id,word_token, word, class, type, location, country_code, operator'; + $sSQL .= ' from word where word_token in (\' '.$sToken.'\')) as x where (class is not null and class != \'place\') or country_code is not null'; + $aSearchWords = $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 + $aPhrases = explode(',',$sQuery); + + // 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 = $oDB->getRow("select make_standard_name('".pg_escape_string($sPhrase)."') as string"); + if (PEAR::isError($aPhrase)) + { + 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']); + $aTokens = array_merge($aTokens, getTokensFromSets($aPhrases[$iPhrase]['wordsets'])); + } + else + { + unset($aPhrases[$iPhrase]); + } + } + + // reindex phrases - we make assumptions later on + $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, location, country_code, operator'; + $sSQL .= ' from word where word_token in ('.join(',',array_map("getDBQuoted",$aTokens)).')'; +// $sSQL .= ' group by word_token, word, class, type, location,country_code'; + + if (CONST_Debug) var_Dump($sSQL); + + $aValidTokens = array(); + if (sizeof($aTokens)) + $aDatabaseWords = $oDB->getAll($sSQL); + else + $aDatabaseWords = array(); + if (PEAR::IsError($aDatabaseWords)) + { + var_dump($sSQL, $aDatabaseWords); + exit; + } + foreach($aDatabaseWords as $aToken) + { + if (isset($aValidTokens[$aToken['word_token']])) + { + $aValidTokens[$aToken['word_token']][] = $aToken; + } + else + { + $aValidTokens[$aToken['word_token']] = array($aToken); + } + } + if (CONST_Debug) var_Dump($aPhrases, $aValidTokens); + + $aSuggestion = array(); + $bSuggestion = false; + if (CONST_Suggestions_Enabled) + { + foreach($aPhrases as $iPhrase => $aPhrase) + { + if (!isset($aValidTokens[' '.$aPhrase['wordsets'][0][0]])) + { + $sQuotedPhrase = getDBQuoted(' '.$aPhrase['wordsets'][0][0]); + $aSuggestionWords = getWordSuggestions($oDB, $aPhrase['wordsets'][0][0]); + $aRow = $aSuggestionWords[0]; + if ($aRow && $aRow['word']) + { + $aSuggestion[] = $aRow['word']; + $bSuggestion = true; + } + else + { + $aSuggestion[] = $aPhrase['string']; + } + } + else + { + $aSuggestion[] = $aPhrase['string']; + } + } + } + if ($bSuggestion) $sSuggestion = join(', ',$aSuggestion); + + // Try and calculate GB postcodes we might be missing + foreach($aTokens as $sToken) + { + if (!isset($aValidTokens[$sToken]) && !isset($aValidTokens[' '.$sToken]) && preg_match('/^([A-Z][A-Z]?[0-9][0-9A-Z]? ?[0-9])([A-Z][A-Z])$/', strtoupper(trim($sToken)), $aData)) + { + if (substr($aData[1],-2,1) != ' ') + { + $aData[0] = substr($aData[0],0,strlen($aData[1]-1)).' '.substr($aData[0],strlen($aData[1]-1)); + $aData[1] = substr($aData[1],0,-1).' '.substr($aData[1],-1,1); + } + $aGBPostcodeLocation = gbPostcodeCalculate($aData[0], $aData[1], $aData[2], $oDB); + if ($aGBPostcodeLocation) + { + $aValidTokens[$sToken] = $aGBPostcodeLocation; + } + } + } + + // Any words that have failed completely? + // TODO: suggestions + + // Start the search process + $aResultPlaceIDs = array(); + + /* + 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 + */ + + foreach($aPhrases as $iPhrase => $sPhrase) + { + $aNewPhraseSearches = array(); + + foreach($aPhrases[$iPhrase]['wordsets'] as $iWordset => $aWordset) + { + $aWordsetSearches = $aSearches; + + // Add all words from this wordset + foreach($aWordset as $sToken) + { + $aNewWordsetSearches = array(); + + foreach($aWordsetSearches as $aCurrentSearch) + { + // If the token is valid + if (isset($aValidTokens[' '.$sToken])) + { + foreach($aValidTokens[' '.$sToken] as $aSearchTerm) + { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank']++; + if ($aSearchTerm['country_code'] !== null && $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 (opimisation) + if ($iWordset+1 != sizeof($aPhrases[$iPhrase]['wordsets']) || $iPhrase+1 != sizeof($aPhrases)) $aSearch['iSearchRank'] += 5; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + elseif ($aSearchTerm['lat'] !== '' && $aSearchTerm['lat'] !== null) + { + if ($aSearch['fLat'] === '') + { + $aSearch['fLat'] = $aSearchTerm['lat']; + $aSearch['fLon'] = $aSearchTerm['lon']; + $aSearch['fRadius'] = $aSearchTerm['radius']; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + elseif ($aSearchTerm['class'] == 'place' && $aSearchTerm['type'] == 'house') + { + if ($aSearch['sHouseNumber'] === '') + { + $aSearch['sHouseNumber'] = $sToken; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + + // Fall back to not searching for this item (better than nothing) + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank'] += 2; + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + elseif ($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 + + // Do we have a shortcut id? + if ($aSearch['sOperator'] == 'name') + { + $sSQL = "select get_tagpair('".$aSearch['sClass']."', '".$aSearch['sType']."')"; + if ($iAmenityID = $oDB->getOne($sSQL)) + { + $aValidTokens[$aSearch['sClass'].':'.$aSearch['sType']] = array('word_id' => $iAmenityID); + $aSearch['aName'][$iAmenityID] = $iAmenityID; + $aSearch['sClass'] = ''; + $aSearch['sType'] = ''; + } + } + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + else + { + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if (!sizeof($aSearch['aName'])) + { + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iNamePhrase'] = $iPhrase; + } + if ($aSearch['iSearchRank'] < $iMaxRank) $aNewWordsetSearches[] = $aSearch; + } + } + } + if (isset($aValidTokens[$sToken])) + { + // Allow searching for a word - but at extra cost + foreach($aValidTokens[$sToken] as $aSearchTerm) + { + $aSearch = $aCurrentSearch; + $aSearch['iSearchRank']+=5; + $aSearch['aAddress'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + if (!sizeof($aSearch['aName']) || $aSearch['iNamePhrase'] == $iPhrase) + { + $aSearch['aName'][$aSearchTerm['word_id']] = $aSearchTerm['word_id']; + $aSearch['iNamePhrase'] = $iPhrase; + } + if ($aSearch['iSearchRank'] < $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('
',sizeof($aWordsetSearches)); exit; + + $aNewPhraseSearches = array_merge($aNewPhraseSearches, $aNewWordsetSearches); + usort($aNewPhraseSearches, 'bySearchRank'); + $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'] < $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; + } + } + } + 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'] < $iMaxRank) + { + if (!isset($aGroupedSearches[$aSearch['iSearchRank']])) $aGroupedSearches[$aSearch['iSearchRank']] = array(); + $aGroupedSearches[$aSearch['iSearchRank']][] = $aSearch; + } + } + ksort($aGroupedSearches); + } + + if (CONST_Debug) var_Dump($aGroupedSearches); + + if ($bReverseInPlan) + { + foreach($aGroupedSearches as $iGroup => $aSearches) + { + foreach($aSearches as $iSearch => $aSearch) + { + if (sizeof($aSearch['aAddress'])) + { + $aReverseSearch = $aSearch; + $iReverseItem = array_pop($aSearch['aAddress']); + $aReverseSearch['aName'][$iReverseItem] = $iReverseItem; + $aGroupedSearches[$iGroup][] = $aReverseSearch; + } + } + } + } + +//var_Dump($aGroupedSearches); exit; + + // 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); + + if ($bReverseInPlan) + { + foreach($aGroupedSearches as $iGroup => $aSearches) + { + foreach($aSearches as $iSearch => $aSearch) + { + if (sizeof($aSearch['aAddress'])) + { + $aReverseSearch = $aSearch; + $iReverseItem = array_pop($aSearch['aAddress']); + $aReverseSearch['aName'][$iReverseItem] = $iReverseItem; + $aGroupedSearches[$iGroup][] = $aReverseSearch; + } + } + } + } + + // 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++; + // Must have a location term + if (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress'])) + { + if (!$bBoundingBoxSearch && !$aSearch['fLon']) continue; + if (!$aSearch['sClass']) continue; + if (CONST_Debug) var_dump('
',$aSearch); + if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens); + + $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + if ($oDB->getOne($sSQL)) + { + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']; + $sSQL .= " where st_contains($sViewboxSmallSQL, centroid)"; + if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + + if (!sizeof($aPlaceIDs)) + { + $sSQL = "select place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']; + $sSQL .= " where st_contains($sViewboxLargeSQL, centroid)"; + if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + } + else + { + $sSQL = "select place_id from placex where class='".$aSearch['sClass']."' and type='".$aSearch['sType']."'"; + $sSQL .= " and st_contains($sViewboxSmallSQL, centroid)"; + if ($sViewboxCentreSQL) $sSQL .= " order by st_distance($sViewboxCentreSQL, centroid) asc"; + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + } + else + { + if ($aSearch['aName'] == array(282=>'282')) continue; + + if (CONST_Debug) var_dump('
',$aSearch); + if (CONST_Debug) _debugDumpGroupedSearches(array($iGroupedRank => array($aSearch)), $aValidTokens); + $aPlaceIDs = array(); + + // First we need a position, either aName or fLat or both + $aTerms = array(); + $aOrder = array(); + if (sizeof($aSearch['aName'])) $aTerms[] = "name_vector @> ARRAY[".join($aSearch['aName'],",")."]"; + if (sizeof($aSearch['aAddress']) && $aSearch['aName'] != $aSearch['aAddress']) $aTerms[] = "nameaddress_vector @> ARRAY[".join($aSearch['aAddress'],",")."]"; + if ($aSearch['sCountryCode']) $aTerms[] = "country_code = '".pg_escape_string($aSearch['sCountryCode'])."'"; + if ($aSearch['sHouseNumber']) $aTerms[] = "address_rank in (26,27)"; + 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($aExcludePlaceIDs)) + { + $aTerms[] = "place_id not in (".join(',',$aExcludePlaceIDs).")"; + } + if ($bBoundingBoxSearch) $aTerms[] = "centroid && $sViewboxSmallSQL"; + if ($sNearPointSQL) $aOrder[] = "ST_Distance($sNearPointSQL, centroid) asc"; + if ($sViewboxSmallSQL) $aOrder[] = "ST_Contains($sViewboxSmallSQL, centroid) desc"; + if ($sViewboxLargeSQL) $aOrder[] = "ST_Contains($sViewboxLargeSQL, centroid) desc"; + $aOrder[] = "search_rank ASC"; + + if (sizeof($aTerms)) + { + $sSQL = "select place_id"; + if ($sViewboxSmallSQL) $sSQL .= ",ST_Contains($sViewboxSmallSQL, centroid) as in_small"; + else $sSQL .= ",false as in_small"; + if ($sViewboxLargeSQL) $sSQL .= ",ST_Contains($sViewboxLargeSQL, centroid) as in_large"; + else $sSQL .= ",false as in_large"; + $sSQL .= " from search_name"; + $sSQL .= " where ".join(' and ',$aTerms); + $sSQL .= " order by ".join(', ',$aOrder); + if ($aSearch['sHouseNumber']) + $sSQL .= " limit 50"; + elseif (!sizeof($aSearch['aName']) && !sizeof($aSearch['aAddress']) && $aSearch['sClass']) + $sSQL .= " limit 1"; + else + $sSQL .= " limit ".$iLimit; + + if (CONST_Debug) var_dump($sSQL); + $aViewBoxPlaceIDs = $oDB->getAll($sSQL); + if (PEAR::IsError($aViewBoxPlaceIDs)) + { + var_dump($sSQL, $aViewBoxPlaceIDs); + exit; + } + + // 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']; + } + } + + if ($aSearch['sHouseNumber'] && sizeof($aPlaceIDs)) + { + $sPlaceIDs = join(',',$aPlaceIDs); + + $sHouseNumberRegex = '\\\\m'.str_replace(' ','[-, ]',$aSearch['sHouseNumber']).'\\\\M'; + + // Make sure everything nearby is indexed (if we pre-indexed houses this wouldn't be needed!) + $sSQL = "update placex set indexed = true from placex as f where placex.indexed = false"; + $sSQL .= " and f.place_id in (".$sPlaceIDs.") and ST_DWithin(placex.geometry, f.geometry, 0.004)"; + $sSQL .= " and placex.housenumber ~* E'".$sHouseNumberRegex."'"; + $sSQL .= " and placex.class='place' and placex.type='house'"; + if (CONST_Debug) var_dump($sSQL); + $oDB->query($sSQL); + + // Now they are indexed look for a house attached to a street we found + $sSQL = "select place_id from placex where street_place_id in (".$sPlaceIDs.") and housenumber ~* E'".$sHouseNumberRegex."'"; + if (sizeof($aExcludePlaceIDs)) + { + $sSQL .= " and place_id not in (".join(',',$aExcludePlaceIDs).")"; + } + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + + if ($aSearch['sClass'] && sizeof($aPlaceIDs)) + { + $sPlaceIDs = join(',',$aPlaceIDs); + + 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 .= " order by rank_search asc limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + + if (!$aSearch['sOperator'] || $aSearch['sOperator'] == 'near') // & in + { + $sSQL = "select rank_search from placex where place_id in ($sPlaceIDs) order by rank_search asc limit 1"; + if (CONST_Debug) var_dump($sSQL); + $iMaxRank = ((int)$oDB->getOne($sSQL)) + 5; + + $sSQL = "select place_id from placex where place_id in ($sPlaceIDs) and rank_search < $iMaxRank"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + $sPlaceIDs = join(',',$aPlaceIDs); + + $fRange = 0.01; + $sSQL = "select count(*) from pg_tables where tablename = 'place_classtype_".$aSearch['sClass']."_".$aSearch['sType']."'"; + if ($oDB->getOne($sSQL)) + { + // More efficient - can make the range bigger + $fRange = 0.05; + $sSQL = "select l.place_id from place_classtype_".$aSearch['sClass']."_".$aSearch['sType']." as l"; + $sSQL .= ",placex as f where "; + $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.centroid, st_centroid(f.geometry), $fRange) "; + if (sizeof($aExcludePlaceIDs)) + { + $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")"; + } + if ($sNearPointSQL) $sSQL .= " order by ST_Distance($sNearPointSQL, l.geometry) ASC"; + else $sSQL .= " order by ST_Distance(l.centroid, f.geometry) asc"; + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + else + { + if (isset($aSearch['fRadius']) && $aSearch['fRadius']) $fRange = $aSearch['fRadius']; + $sSQL = "select l.place_id from placex as l,placex as f where "; + $sSQL .= "f.place_id in ($sPlaceIDs) and ST_DWithin(l.geometry, st_centroid(f.geometry), $fRange) "; + $sSQL .= "and l.class='".$aSearch['sClass']."' and l.type='".$aSearch['sType']."' "; + if (sizeof($aExcludePlaceIDs)) + { + $sSQL .= " and l.place_id not in (".join(',',$aExcludePlaceIDs).")"; + } + if ($sNearPointSQL) $sSQL .= " order by ST_Distance($sNearPointSQL, l.geometry) ASC"; + else $sSQL .= " order by ST_Distance(l.geometry, f.geometry) asc, l.rank_search ASC"; + $sSQL .= " limit $iLimit"; + if (CONST_Debug) var_dump($sSQL); + $aPlaceIDs = $oDB->getCol($sSQL); + } + } + } + + } + + if (PEAR::IsError($aPlaceIDs)) + { + var_dump($sSQL, $aPlaceIDs); + exit; + } + + if (CONST_Debug) var_Dump($aPlaceIDs); + + foreach($aPlaceIDs as $iPlaceID) + { + $aResultPlaceIDs[$iPlaceID] = $iPlaceID; + } + if ($iQueryLoop > 20) break; + } + //exit; + if (sizeof($aResultPlaceIDs)) break; + if ($iGroupLoop > 4) break; + if ($iQueryLoop > 30) break; + } +//exit; + // Did we find anything? + if (sizeof($aResultPlaceIDs)) + { +//var_Dump($aResultPlaceIDs);exit; + // Get the details for display (is this a redundant extra step?) + $sPlaceIDs = join(',',$aResultPlaceIDs); + $sOrderSQL = 'CASE '; + foreach(array_keys($aResultPlaceIDs) as $iOrder => $iPlaceID) + { + $sOrderSQL .= 'when min(place_id) = '.$iPlaceID.' then '.$iOrder.' '; + } + $sOrderSQL .= ' ELSE 10000000 END ASC'; + $sSQL = "select osm_type,osm_id,class,type,rank_search,rank_address,min(place_id) as place_id,country_code,"; + $sSQL .= "get_address_by_language(place_id, $sLanguagePrefArraySQL) as langaddress,"; + $sSQL .= "get_name_by_language(name, $sLanguagePrefArraySQL) as placename,"; + $sSQL .= "get_name_by_language(name, ARRAY['ref']) as ref,"; + $sSQL .= "avg(ST_X(ST_Centroid(geometry))) as lon,avg(ST_Y(ST_Centroid(geometry))) as lat "; + $sSQL .= "from placex where place_id in ($sPlaceIDs) "; + $sSQL .= "group by osm_type,osm_id,class,type,rank_search,rank_address,country_code"; + if (!$bDeDupe) $sSQL .= ",place_id"; + $sSQL .= ",get_address_by_language(place_id, $sLanguagePrefArraySQL) "; + $sSQL .= ",get_name_by_language(name, $sLanguagePrefArraySQL) "; + $sSQL .= ",get_name_by_language(name, ARRAY['ref']) "; + $sSQL .= "order by rank_search,rank_address,".$sOrderSQL; + if (CONST_Debug) var_dump('
',$sSQL); + $aSearchResults = $oDB->getAll($sSQL); +//var_dump($sSQL,$aSearchResults);exit; + + if (PEAR::IsError($aSearchResults)) + { + var_dump($sSQL, $aSearchResults); + exit; + } + } + } + } + + $sSearchResult = ''; + if (!sizeof($aSearchResults) && isset($_GET['q']) && $_GET['q']) + { + $sSearchResult = 'No Results Found'; + } + + $aClassType = getClassTypesWithImportance(); + + foreach($aSearchResults as $iResNum => $aResult) + { + if (CONST_Search_AreaPolygons || true) + { + // Get the bounding box and outline polygon + $sSQL = "select place_id,numfeatures,area,outline,"; + $sSQL .= "ST_Y(ST_PointN(ExteriorRing(ST_Box2D(outline)),4)) as minlat,ST_Y(ST_PointN(ExteriorRing(ST_Box2D(outline)),2)) as maxlat,"; + $sSQL .= "ST_X(ST_PointN(ExteriorRing(ST_Box2D(outline)),1)) as minlon,ST_X(ST_PointN(ExteriorRing(ST_Box2D(outline)),3)) as maxlon,"; + $sSQL .= "ST_AsText(outline) as outlinestring from get_place_boundingbox_quick(".$aResult['place_id'].")"; + $aPointPolygon = $oDB->getRow($sSQL); + if (PEAR::IsError($aPointPolygon)) + { + var_dump($sSQL, $aPointPolygon); + exit; + } + if ($aPointPolygon['place_id']) + { + // Translate geometary string to point array + if (preg_match('#POLYGON\\(\\(([- 0-9.,]+)#',$aPointPolygon['outlinestring'],$aMatch)) + { + preg_match_all('/(-?[0-9.]+) (-?[0-9.]+)/',$aMatch[1],$aPolyPoints,PREG_SET_ORDER); + } + elseif (preg_match('#POINT\\((-?[0-9.]+) (-?[0-9.]+)\\)#',$aPointPolygon['outlinestring'],$aMatch)) + { + $fRadius = 0.01; + $iSteps = ($fRadius * 40000)^2; + $fStepSize = (2*pi())/$iSteps; + $aPolyPoints = array(); + for($f = 0; $f < 2*pi(); $f += $fStepSize) + { + $aPolyPoints[] = array('',$aMatch[1]+($fRadius*sin($f)),$aMatch[2]+($fRadius*cos($f))); + } + $aPointPolygon['minlat'] = $aPointPolygon['minlat'] - $fRadius; + $aPointPolygon['maxlat'] = $aPointPolygon['maxlat'] + $fRadius; + $aPointPolygon['minlon'] = $aPointPolygon['minlon'] - $fRadius; + $aPointPolygon['maxlon'] = $aPointPolygon['maxlon'] + $fRadius; + } + + // Output data suitable for display (points and a bounding box) + if ($bShowPolygons) + { + $aResult['aPolyPoints'] = array(); + foreach($aPolyPoints as $aPoint) + { + $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]); + } + } + $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']); + } + } + + if (!isset($aResult['aBoundingBox'])) + { + // Default + $fDiameter = 0.0001; + + if (isset($aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']) + && $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defdiameter']) + { + $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type'].':'.$aResult['admin_level']]['defzoom']; + } + elseif (isset($aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']) + { + $fDiameter = $aClassType[$aResult['class'].':'.$aResult['type']]['defdiameter']; + } + $fRadius = $fDiameter / 2; + + $iSteps = max(8,min(100,$fRadius * 3.14 * 100000)); + $fStepSize = (2*pi())/$iSteps; + $aPolyPoints = array(); + for($f = 0; $f < 2*pi(); $f += $fStepSize) + { + $aPolyPoints[] = array('',$aResult['lon']+($fRadius*sin($f)),$aResult['lat']+($fRadius*cos($f))); + } + $aPointPolygon['minlat'] = $aResult['lat'] - $fRadius; + $aPointPolygon['maxlat'] = $aResult['lat'] + $fRadius; + $aPointPolygon['minlon'] = $aResult['lon'] - $fRadius; + $aPointPolygon['maxlon'] = $aResult['lon'] + $fRadius; + + // Output data suitable for display (points and a bounding box) + if ($bShowPolygons) + { + $aResult['aPolyPoints'] = array(); + foreach($aPolyPoints as $aPoint) + { + $aResult['aPolyPoints'][] = array($aPoint[1], $aPoint[2]); + } + } + $aResult['aBoundingBox'] = array($aPointPolygon['minlat'],$aPointPolygon['maxlat'],$aPointPolygon['minlon'],$aPointPolygon['maxlon']); + } + + // 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'; + } + + if ($bShowAddressDetails) + { + $aResult['address'] = getAddressDetails($oDB, $sLanguagePrefArraySQL, $aResult['place_id'], $aResult['country_code']); + } + + if (isset($aClassType[$aResult['class'].':'.$aResult['type']]['importance']) + && $aClassType[$aResult['class'].':'.$aResult['type']]['importance']) + { + $aResult['importance'] = $aClassType[$aResult['class'].':'.$aResult['type']]['importance']; + } + else + { + $aResult['importance'] = 1000000000000000; + } + + $aResult['name'] = $aResult['langaddress']; + $aResult['foundorder'] = $iResNum; + $aSearchResults[$iResNum] = $aResult; + } + + uasort($aSearchResults, 'byImportance'); + + $aOSMIDDone = array(); + $aClassTypeNameDone = array(); + $aToFilter = $aSearchResults; + $aSearchResults = array(); + + $bFirst = true; + foreach($aToFilter as $iResNum => $aResult) + { + if ($aResult['type'] == 'adminitrative') $aResult['type'] = 'administrative'; + $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 (!$bDeDupe || (!isset($aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']]) + && !isset($aClassTypeNameDone[$aResult['osm_type'].$aResult['osm_class'].$aResult['name']]))) + { + $aOSMIDDone[$aResult['osm_type'].$aResult['osm_id']] = true; + $aClassTypeNameDone[$aResult['osm_type'].$aResult['osm_class'].$aResult['name']] = true; + $aSearchResults[] = $aResult; + } + } + + $sDataDate = $oDB->getOne("select TO_CHAR(lastimportdate - '1 day'::interval,'YYYY/MM/DD') from import_status limit 1"); + + if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) + { + $sQuery .= ' ['.$_GET['nearlat'].','.$_GET['nearlon'].']'; + } + + if ($sQuery) + { + logEnd($oDB, $hLog, sizeof($aToFilter)); + } + $sMoreURL = CONST_Website_BaseURL.'search?format='.urlencode($sOutputFormat).'&exclude_place_ids='.join(',',$aExcludePlaceIDs); + $sMoreURL .= '&accept-language='.$_SERVER["HTTP_ACCEPT_LANGUAGE"]; + if ($bShowPolygons) $sMoreURL .= '&polygon=1'; + if ($bShowAddressDetails) $sMoreURL .= '&addressdetails=1'; + if (isset($_GET['viewbox']) && $_GET['viewbox']) $sMoreURL .= '&viewbox='.urlencode($_GET['viewbox']); + if (isset($_GET['nearlat']) && isset($_GET['nearlon'])) $sMoreURL .= '&nearlat='.(float)$_GET['nearlat'].'&nearlon='.(float)$_GET['nearlon']; + if ($sSuggestion) + { + $sSuggestionURL = $sMoreURL.'&q='.urlencode($sSuggestion); + } + $sMoreURL .= '&q='.urlencode($sQuery); + + if (CONST_Debug) exit; + + include('.htlib/output/search-'.$sOutputFormat.'.php'); -- 2.39.5