4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
 
   5 require_once(CONST_BasePath.'/lib/init-cmd.php');
 
   6 ini_set('memory_limit', '800M');
 
  10    'Create and setup nominatim search system',
 
  11    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
 
  12    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
 
  13    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
 
  15    array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
 
  16    array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
 
  18    array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
 
  20    array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
 
  21    array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
 
  22    array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
 
  23    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
 
  24    array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
 
  25    array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
 
  26    array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
 
  27    array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
 
  28    array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
 
  29    array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
 
  30    array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
 
  31    array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
 
  32    array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
 
  33    array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
 
  34    array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
 
  35    array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
 
  36    array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
 
  37    array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
 
  38    array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
 
  39    array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
 
  40    array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
 
  41    array('create-country-names', '', 0, 1, 0, 0, 'bool', 'Create default list of searchable country names'),
 
  42    array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
 
  44 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
 
  46 $bDidSomething = false;
 
  48 // Check if osm-file is set and points to a valid file if --all or --import-data is given
 
  49 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
 
  50     if (!isset($aCMDResult['osm-file'])) {
 
  51         fail('missing --osm-file for data import');
 
  54     if (!file_exists($aCMDResult['osm-file'])) {
 
  55         fail('the path supplied to --osm-file does not exist');
 
  58     if (!is_readable($aCMDResult['osm-file'])) {
 
  59         fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
 
  64 // by default, use all but one processor, but never more than 15.
 
  65 $iInstances = isset($aCMDResult['threads'])
 
  66               ? $aCMDResult['threads']
 
  67               : (min(16, getProcessorCount()) - 1);
 
  69 if ($iInstances < 1) {
 
  71     warn("resetting threads to $iInstances");
 
  73 if ($iInstances > getProcessorCount()) {
 
  74     $iInstances = getProcessorCount();
 
  75     warn("resetting threads to $iInstances");
 
  78 // Assume we can steal all the cache memory in the box (unless told otherwise)
 
  79 if (isset($aCMDResult['osm2pgsql-cache'])) {
 
  80     $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
 
  82     $iCacheMemory = getCacheMemoryMB();
 
  85 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
  86 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
  88 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
 
  90     $bDidSomething = true;
 
  91     $oDB = DB::connect(CONST_Database_DSN, false);
 
  92     if (!PEAR::isError($oDB)) {
 
  93         fail('database already exists ('.CONST_Database_DSN.')');
 
  95     passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
 
  98 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
 
 100     $bDidSomething = true;
 
 104     $fPostgresVersion = getPostgresVersion($oDB);
 
 105     echo 'Postgres version found: '.$fPostgresVersion."\n";
 
 107     if ($fPostgresVersion < 9.1) {
 
 108         fail('Minimum supported version of Postgresql is 9.1.');
 
 111     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
 
 112     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
 
 114     // For extratags and namedetails the hstore_to_json converter is
 
 115     // needed which is only available from Postgresql 9.3+. For older
 
 116     // versions add a dummy function that returns nothing.
 
 117     $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
 
 119     if ($iNumFunc == 0) {
 
 120         pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
 
 121         warn('Postgresql is too old. extratags and namedetails API not available.');
 
 124     $fPostgisVersion = getPostgisVersion($oDB);
 
 125     echo 'Postgis version found: '.$fPostgisVersion."\n";
 
 127     if ($fPostgisVersion < 2.1) {
 
 128         // Functions were renamed in 2.1 and throw an annoying deprecation warning
 
 129         pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
 
 130         pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
 
 132     if ($fPostgisVersion < 2.2) {
 
 133         pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
 
 136     $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
 
 138         echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
 
 139         echo "\n          createuser ".CONST_Database_Web_User."\n\n";
 
 143     // Try accessing the C module, so we know early if something is wrong
 
 144     // and can simply error out.
 
 145     $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
 
 146     $sSQL .= CONST_InstallPath."/module/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
 
 147     $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
 
 148     $oResult = $oDB->query($sSQL);
 
 150     if (PEAR::isError($oResult)) {
 
 151         echo "\nERROR: Failed to load nominatim module. Reason:\n";
 
 152         echo $oResult->userinfo."\n\n";
 
 156     if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
 
 157         echo 'Error: you need to download the country_osm_grid first:';
 
 158         echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
 
 162     pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
 
 163     pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
 
 164     pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
 
 165     pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
 
 166     if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
 
 167         pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
 
 169         warn('external UK postcode table not found.');
 
 171     if (CONST_Use_Extra_US_Postcodes) {
 
 172         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
 
 175     if ($aCMDResult['no-partitions']) {
 
 176         pgsqlRunScript('update country_name set partition = 0');
 
 179     // the following will be needed by create_functions later but
 
 180     // is only defined in the subsequently called create_tables.
 
 181     // Create dummies here that will be overwritten by the proper
 
 182     // versions in create-tables.
 
 183     pgsqlRunScript('CREATE TABLE place_boundingbox ()');
 
 184     pgsqlRunScript('create type wikipedia_article_match as ()');
 
 187 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
 
 189     $bDidSomething = true;
 
 191     $osm2pgsql = CONST_Osm2pgsql_Binary;
 
 192     if (!file_exists($osm2pgsql)) {
 
 193         echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
 
 194         echo "Normally you should not need to set this manually.\n";
 
 195         fail("osm2pgsql not found in '$osm2pgsql'");
 
 198     if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 199         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
 
 201     if (CONST_Tablespace_Osm2pgsql_Data)
 
 202         $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
 
 203     if (CONST_Tablespace_Osm2pgsql_Index)
 
 204         $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
 
 205     if (CONST_Tablespace_Place_Data)
 
 206         $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
 
 207     if (CONST_Tablespace_Place_Index)
 
 208         $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
 
 209     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
 
 210     $osm2pgsql .= ' -C '.$iCacheMemory;
 
 211     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
 
 212     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
 
 213     passthruCheckReturn($osm2pgsql);
 
 216     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
 
 221 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
 
 222     info('Create Functions');
 
 223     $bDidSomething = true;
 
 224     if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) {
 
 225         fail('nominatim module not built');
 
 227     create_sql_functions($aCMDResult);
 
 230 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
 
 231     info('Create Tables');
 
 232     $bDidSomething = true;
 
 234     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
 
 235     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 236     $sTemplate = replace_tablespace(
 
 238         CONST_Tablespace_Address_Data,
 
 241     $sTemplate = replace_tablespace(
 
 242         '{ts:address-index}',
 
 243         CONST_Tablespace_Address_Index,
 
 246     $sTemplate = replace_tablespace(
 
 248         CONST_Tablespace_Search_Data,
 
 251     $sTemplate = replace_tablespace(
 
 253         CONST_Tablespace_Search_Index,
 
 256     $sTemplate = replace_tablespace(
 
 258         CONST_Tablespace_Aux_Data,
 
 261     $sTemplate = replace_tablespace(
 
 263         CONST_Tablespace_Aux_Index,
 
 266     pgsqlRunScript($sTemplate, false);
 
 268     // re-run the functions
 
 269     info('Recreate Functions');
 
 270     create_sql_functions($aCMDResult);
 
 273 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
 
 274     info('Create Partition Tables');
 
 275     $bDidSomething = true;
 
 277     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
 
 278     $sTemplate = replace_tablespace(
 
 280         CONST_Tablespace_Address_Data,
 
 283     $sTemplate = replace_tablespace(
 
 284         '{ts:address-index}',
 
 285         CONST_Tablespace_Address_Index,
 
 288     $sTemplate = replace_tablespace(
 
 290         CONST_Tablespace_Search_Data,
 
 293     $sTemplate = replace_tablespace(
 
 295         CONST_Tablespace_Search_Index,
 
 298     $sTemplate = replace_tablespace(
 
 300         CONST_Tablespace_Aux_Data,
 
 303     $sTemplate = replace_tablespace(
 
 305         CONST_Tablespace_Aux_Index,
 
 309     pgsqlRunPartitionScript($sTemplate);
 
 313 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
 
 314     info('Create Partition Functions');
 
 315     $bDidSomething = true;
 
 317     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
 
 319     pgsqlRunPartitionScript($sTemplate);
 
 322 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
 
 323     $bDidSomething = true;
 
 324     $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
 
 325     $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
 
 326     if (file_exists($sWikiArticlesFile)) {
 
 327         info('Importing wikipedia articles');
 
 328         pgsqlRunDropAndRestore($sWikiArticlesFile);
 
 330         warn('wikipedia article dump file not found - places will have default importance');
 
 332     if (file_exists($sWikiRedirectsFile)) {
 
 333         info('Importing wikipedia redirects');
 
 334         pgsqlRunDropAndRestore($sWikiRedirectsFile);
 
 336         warn('wikipedia redirect dump file not found - some place importance values may be missing');
 
 341 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
 
 342     info('Drop old Data');
 
 343     $bDidSomething = true;
 
 346     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
 
 348     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
 
 350     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
 
 352     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
 
 354     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
 
 356     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
 
 358     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
 
 360     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
 
 362     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
 
 364     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
 
 367     $sSQL = 'select distinct partition from country_name';
 
 368     $aPartitions = chksql($oDB->getCol($sSQL));
 
 369     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
 
 370     foreach ($aPartitions as $sPartition) {
 
 371         if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
 
 375     // used by getorcreate_word_id to ignore frequent partial words
 
 376     $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
 
 377     $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
 
 378     if (!pg_query($oDB->connection, $sSQL)) {
 
 379         fail(pg_last_error($oDB->connection));
 
 383     // pre-create the word list
 
 384     if (!$aCMDResult['disable-token-precalc']) {
 
 385         info('Loading word list');
 
 386         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
 
 390     $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
 
 392     $aDBInstances = array();
 
 393     $iLoadThreads = max(1, $iInstances - 1);
 
 394     for ($i = 0; $i < $iLoadThreads; $i++) {
 
 395         $aDBInstances[$i] =& getDB(true);
 
 396         $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
 
 397         $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
 
 398         $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
 
 399         $sSQL .= ' and ST_IsValid(geometry)';
 
 400         if ($aCMDResult['verbose']) echo "$sSQL\n";
 
 401         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
 
 402             fail(pg_last_error($aDBInstances[$i]->connection));
 
 405     // last thread for interpolation lines
 
 406     $aDBInstances[$iLoadThreads] =& getDB(true);
 
 407     $sSQL = 'insert into location_property_osmline';
 
 408     $sSQL .= ' (osm_id, address, linegeo)';
 
 409     $sSQL .= ' SELECT osm_id, address, geometry from place where ';
 
 410     $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
 
 411     if ($aCMDResult['verbose']) echo "$sSQL\n";
 
 412     if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
 
 413         fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
 
 419         for ($i = 0; $i <= $iLoadThreads; $i++) {
 
 420             if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
 
 426     info('Reanalysing database');
 
 427     pgsqlRunScript('ANALYSE');
 
 429     $sDatabaseDate = getDatabaseDate($oDB);
 
 430     pg_query($oDB->connection, 'TRUNCATE import_status');
 
 431     if ($sDatabaseDate === false) {
 
 432         warn('could not determine database date.');
 
 434         $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
 
 435         pg_query($oDB->connection, $sSQL);
 
 436         echo "Latest data imported from $sDatabaseDate.\n";
 
 440 if ($aCMDResult['import-tiger-data']) {
 
 441     info('Import Tiger data');
 
 442     $bDidSomething = true;
 
 444     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
 
 445     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 446     $sTemplate = replace_tablespace(
 
 448         CONST_Tablespace_Aux_Data,
 
 451     $sTemplate = replace_tablespace(
 
 453         CONST_Tablespace_Aux_Index,
 
 456     pgsqlRunScript($sTemplate, false);
 
 458     $aDBInstances = array();
 
 459     for ($i = 0; $i < $iInstances; $i++) {
 
 460         $aDBInstances[$i] =& getDB(true);
 
 463     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
 
 465         $hFile = fopen($sFile, 'r');
 
 466         $sSQL = fgets($hFile, 100000);
 
 470             for ($i = 0; $i < $iInstances; $i++) {
 
 471                 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
 
 472                     while (pg_get_result($aDBInstances[$i]->connection));
 
 473                     $sSQL = fgets($hFile, 100000);
 
 475                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 477                     if ($iLines == 1000) {
 
 491             for ($i = 0; $i < $iInstances; $i++) {
 
 492                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
 
 499     info('Creating indexes on Tiger data');
 
 500     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
 
 501     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 502     $sTemplate = replace_tablespace(
 
 504         CONST_Tablespace_Aux_Data,
 
 507     $sTemplate = replace_tablespace(
 
 509         CONST_Tablespace_Aux_Index,
 
 512     pgsqlRunScript($sTemplate, false);
 
 515 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
 
 516     info('Calculate Postcodes');
 
 517     $bDidSomething = true;
 
 519     if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
 
 520         fail(pg_last_error($oDB->connection));
 
 523     $sSQL  = 'INSERT INTO location_postcode';
 
 524     $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 525     $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
 
 526     $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
 
 527     $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
 
 528     $sSQL .= '  FROM placex';
 
 529     $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
 
 530     $sSQL .= '       AND geometry IS NOT null';
 
 531     $sSQL .= ' GROUP BY country_code, pc';
 
 533     if (!pg_query($oDB->connection, $sSQL)) {
 
 534         fail(pg_last_error($oDB->connection));
 
 537     if (CONST_Use_Extra_US_Postcodes) {
 
 538         // only add postcodes that are not yet available in OSM
 
 539         $sSQL  = 'INSERT INTO location_postcode';
 
 540         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 541         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
 
 542         $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
 
 543         $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
 
 544         $sSQL .= '        (SELECT postcode FROM location_postcode';
 
 545         $sSQL .= "          WHERE country_code = 'us')";
 
 546         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 549     // add missing postcodes for GB (if available)
 
 550     $sSQL  = 'INSERT INTO location_postcode';
 
 551     $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 552     $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
 
 553     $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
 
 554     $sSQL .= '           (SELECT postcode FROM location_postcode';
 
 555     $sSQL .= "             WHERE country_code = 'gb')";
 
 556     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 558     if (!$aCMDResult['all']) {
 
 559         $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
 
 560         $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
 
 561         if (!pg_query($oDB->connection, $sSQL)) {
 
 562             fail(pg_last_error($oDB->connection));
 
 565     $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
 
 566     $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
 
 568     if (!pg_query($oDB->connection, $sSQL)) {
 
 569         fail(pg_last_error($oDB->connection));
 
 573 if ($aCMDResult['osmosis-init']) {
 
 574     $bDidSomething = true;
 
 575     echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
 
 578 if ($aCMDResult['index'] || $aCMDResult['all']) {
 
 579     $bDidSomething = true;
 
 581     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
 
 582     info('Index ranks 0 - 4');
 
 583     passthruCheckReturn($sBaseCmd.' -R 4');
 
 584     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
 
 585     info('Index ranks 5 - 25');
 
 586     passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
 
 587     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
 
 588     info('Index ranks 26 - 30');
 
 589     passthruCheckReturn($sBaseCmd.' -r 26');
 
 591     info('Index postcodes');
 
 593     $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
 
 594     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 597 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
 
 598     info('Create Search indices');
 
 599     $bDidSomething = true;
 
 601     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
 
 602     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 603     $sTemplate = replace_tablespace(
 
 604         '{ts:address-index}',
 
 605         CONST_Tablespace_Address_Index,
 
 608     $sTemplate = replace_tablespace(
 
 610         CONST_Tablespace_Search_Index,
 
 613     $sTemplate = replace_tablespace(
 
 615         CONST_Tablespace_Aux_Index,
 
 619     pgsqlRunScript($sTemplate);
 
 622 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
 
 623     info('Create search index for default country names');
 
 624     $bDidSomething = true;
 
 626     pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
 
 627     pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
 
 628     pgsqlRunScript('select count(*) from (select getorcreate_country(make_standard_name(country_code), country_code) from country_name where country_code is not null) as x');
 
 629     pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
 
 631     $sSQL = 'select count(*) from (select getorcreate_country(make_standard_name(v), country_code) from (select country_code, skeys(name) as k, svals(name) as v from country_name) x where k ';
 
 632     if (CONST_Languages) {
 
 635         foreach (explode(',', CONST_Languages) as $sLang) {
 
 636             $sSQL .= $sDelim."'name:$sLang'";
 
 641         // all include all simple name tags
 
 642         $sSQL .= "like 'name:%'";
 
 645     pgsqlRunScript($sSQL);
 
 648 if ($aCMDResult['drop']) {
 
 649     info('Drop tables only required for updates');
 
 650     // The implementation is potentially a bit dangerous because it uses
 
 651     // a positive selection of tables to keep, and deletes everything else.
 
 652     // Including any tables that the unsuspecting user might have manually
 
 653     // created. USE AT YOUR OWN PERIL.
 
 654     $bDidSomething = true;
 
 656     // tables we want to keep. everything else goes.
 
 657     $aKeepTables = array(
 
 663                     'location_property*',
 
 676     $aDropTables = array();
 
 677     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
 
 679     foreach ($aHaveTables as $sTable) {
 
 681         foreach ($aKeepTables as $sKeep) {
 
 682             if (fnmatch($sKeep, $sTable)) {
 
 687         if (!$bFound) array_push($aDropTables, $sTable);
 
 690     foreach ($aDropTables as $sDrop) {
 
 691         if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
 
 692         @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
 
 693         // ignore warnings/errors as they might be caused by a table having
 
 694         // been deleted already by CASCADE
 
 697     if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 698         if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
 
 699         unlink(CONST_Osm2pgsql_Flatnode_File);
 
 703 if (!$bDidSomething) {
 
 704     showUsage($aCMDOptions, true);
 
 706     echo "Summary of warnings:\n\n";
 
 709     info('Setup finished.');
 
 713 function pgsqlRunScriptFile($sFilename)
 
 716     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
 
 718     // Convert database DSN to psql parameters
 
 719     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 720     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 721     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
 
 722     if (!$aCMDResult['verbose']) {
 
 727     if (preg_match('/\\.gz$/', $sFilename)) {
 
 728         $aDescriptors = array(
 
 729                          0 => array('pipe', 'r'),
 
 730                          1 => array('pipe', 'w'),
 
 731                          2 => array('file', '/dev/null', 'a')
 
 733         $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
 
 734         if (!is_resource($hGzipProcess)) fail('unable to start zcat');
 
 735         $aReadPipe = $ahGzipPipes[1];
 
 736         fclose($ahGzipPipes[0]);
 
 738         $sCMD .= ' -f '.$sFilename;
 
 739         $aReadPipe = array('pipe', 'r');
 
 742     $aDescriptors = array(
 
 744                      1 => array('pipe', 'w'),
 
 745                      2 => array('file', '/dev/null', 'a')
 
 748     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
 
 749     if (!is_resource($hProcess)) fail('unable to start pgsql');
 
 752     // TODO: error checking
 
 753     while (!feof($ahPipes[1])) {
 
 754         echo fread($ahPipes[1], 4096);
 
 758     $iReturn = proc_close($hProcess);
 
 760         fail("pgsql returned with error code ($iReturn)");
 
 763         fclose($ahGzipPipes[1]);
 
 764         proc_close($hGzipProcess);
 
 768 function pgsqlRunScript($sScript, $bfatal = true)
 
 774         $aCMDResult['verbose'],
 
 775         $aCMDResult['ignore-errors']
 
 779 function pgsqlRunPartitionScript($sTemplate)
 
 784     $sSQL = 'select distinct partition from country_name';
 
 785     $aPartitions = chksql($oDB->getCol($sSQL));
 
 786     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
 
 788     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
 
 789     foreach ($aMatches as $aMatch) {
 
 791         foreach ($aPartitions as $sPartitionName) {
 
 792             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
 
 794         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
 
 797     pgsqlRunScript($sTemplate);
 
 800 function pgsqlRunRestoreData($sDumpFile)
 
 802     // Convert database DSN to psql parameters
 
 803     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 804     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 805     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
 
 807     $aDescriptors = array(
 
 808                      0 => array('pipe', 'r'),
 
 809                      1 => array('pipe', 'w'),
 
 810                      2 => array('file', '/dev/null', 'a')
 
 813     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
 
 814     if (!is_resource($hProcess)) fail('unable to start pg_restore');
 
 818     // TODO: error checking
 
 819     while (!feof($ahPipes[1])) {
 
 820         echo fread($ahPipes[1], 4096);
 
 824     $iReturn = proc_close($hProcess);
 
 827 function pgsqlRunDropAndRestore($sDumpFile)
 
 829     // Convert database DSN to psql parameters
 
 830     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 831     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 832     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
 
 834     $aDescriptors = array(
 
 835                      0 => array('pipe', 'r'),
 
 836                      1 => array('pipe', 'w'),
 
 837                      2 => array('file', '/dev/null', 'a')
 
 840     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
 
 841     if (!is_resource($hProcess)) fail('unable to start pg_restore');
 
 845     // TODO: error checking
 
 846     while (!feof($ahPipes[1])) {
 
 847         echo fread($ahPipes[1], 4096);
 
 851     $iReturn = proc_close($hProcess);
 
 854 function passthruCheckReturn($cmd)
 
 857     passthru($cmd, $result);
 
 858     if ($result != 0) fail('Error executing external command: '.$cmd);
 
 861 function replace_tablespace($sTemplate, $sTablespace, $sSql)
 
 864         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
 
 866         $sSql = str_replace($sTemplate, '', $sSql);
 
 872 function create_sql_functions($aCMDResult)
 
 874     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
 
 875     $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
 
 876     if ($aCMDResult['enable-diff-updates']) {
 
 877         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
 
 879     if ($aCMDResult['enable-debug-statements']) {
 
 880         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
 
 882     if (CONST_Limit_Reindexing) {
 
 883         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
 
 885     if (!CONST_Use_US_Tiger_Data) {
 
 886         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
 
 888     if (!CONST_Use_Aux_Location_data) {
 
 889         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
 
 891     pgsqlRunScript($sTemplate);