4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
 
   5 require_once(CONST_BasePath.'/lib/init-cmd.php');
 
   6 ini_set('memory_limit', '800M');
 
   8 # (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
 
  12    'Create and setup nominatim search system',
 
  13    array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
 
  14    array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
 
  15    array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
 
  17    array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
 
  18    array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
 
  20    array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
 
  22    array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
 
  23    array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
 
  24    array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
 
  25    array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
 
  26    array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
 
  27    array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
 
  28    array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
 
  29    array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
 
  30    array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
 
  31    array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
 
  32    array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
 
  33    array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
 
  34    array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
 
  35    array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
 
  36    array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
 
  37    array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
 
  38    array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
 
  39    array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
 
  40    array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
 
  41    array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
 
  42    array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
 
  43    array('create-country-names', '', 0, 1, 0, 0, 'bool', 'Create default list of searchable country names'),
 
  44    array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
 
  46 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
 
  48 $bDidSomething = false;
 
  50 // Check if osm-file is set and points to a valid file if --all or --import-data is given
 
  51 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
 
  52     if (!isset($aCMDResult['osm-file'])) {
 
  53         fail('missing --osm-file for data import');
 
  56     if (!file_exists($aCMDResult['osm-file'])) {
 
  57         fail('the path supplied to --osm-file does not exist');
 
  60     if (!is_readable($aCMDResult['osm-file'])) {
 
  61         fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
 
  65 // by default, use all but one processor, but never more than 15.
 
  66 $iInstances = isset($aCMDResult['threads'])
 
  67               ? $aCMDResult['threads']
 
  68               : (min(16, getProcessorCount()) - 1);
 
  70 if ($iInstances < 1) {
 
  72     warn("resetting threads to $iInstances");
 
  75 // Assume we can steal all the cache memory in the box (unless told otherwise)
 
  76 if (isset($aCMDResult['osm2pgsql-cache'])) {
 
  77     $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
 
  79     $iCacheMemory = getCacheMemoryMB();
 
  82 $sModulePath = CONST_Database_Module_Path;
 
  83 info('module path: ' . $sModulePath);
 
  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.')');
 
  96     $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
 
  97     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
 
  98         $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
 
 100     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
 
 101         $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
 
 105     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
 
 106         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
 
 109     $result = runWithEnv($sCreateDBCmd, $aProcEnv);
 
 110     if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
 
 113 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
 
 115     $bDidSomething = true;
 
 119     $fPostgresVersion = getPostgresVersion($oDB);
 
 120     echo 'Postgres version found: '.$fPostgresVersion."\n";
 
 122     if ($fPostgresVersion < 9.1) {
 
 123         fail('Minimum supported version of Postgresql is 9.1.');
 
 126     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
 
 127     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
 
 129     // For extratags and namedetails the hstore_to_json converter is
 
 130     // needed which is only available from Postgresql 9.3+. For older
 
 131     // versions add a dummy function that returns nothing.
 
 132     $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
 
 134     if ($iNumFunc == 0) {
 
 135         pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
 
 136         warn('Postgresql is too old. extratags and namedetails API not available.');
 
 139     $fPostgisVersion = getPostgisVersion($oDB);
 
 140     echo 'Postgis version found: '.$fPostgisVersion."\n";
 
 142     if ($fPostgisVersion < 2.1) {
 
 143         // Functions were renamed in 2.1 and throw an annoying deprecation warning
 
 144         pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
 
 145         pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
 
 147     if ($fPostgisVersion < 2.2) {
 
 148         pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
 
 151     $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
 
 153         echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
 
 154         echo "\n          createuser ".CONST_Database_Web_User."\n\n";
 
 158     if (!checkModulePresence()) {
 
 159         fail('error loading nominatim.so module');
 
 162     if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
 
 163         echo 'Error: you need to download the country_osm_grid first:';
 
 164         echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
 
 168     pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
 
 169     pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
 
 170     pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
 
 171     pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
 
 172     if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
 
 173         pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
 
 175         warn('external UK postcode table not found.');
 
 177     if (CONST_Use_Extra_US_Postcodes) {
 
 178         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
 
 181     if ($aCMDResult['no-partitions']) {
 
 182         pgsqlRunScript('update country_name set partition = 0');
 
 185     // the following will be needed by create_functions later but
 
 186     // is only defined in the subsequently called create_tables.
 
 187     // Create dummies here that will be overwritten by the proper
 
 188     // versions in create-tables.
 
 189     pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
 
 190     pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
 
 193 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
 
 195     $bDidSomething = true;
 
 197     $osm2pgsql = CONST_Osm2pgsql_Binary;
 
 198     if (!file_exists($osm2pgsql)) {
 
 199         echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
 
 200         echo "Normally you should not need to set this manually.\n";
 
 201         fail("osm2pgsql not found in '$osm2pgsql'");
 
 204     if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 205         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
 
 207     if (CONST_Tablespace_Osm2pgsql_Data)
 
 208         $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
 
 209     if (CONST_Tablespace_Osm2pgsql_Index)
 
 210         $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
 
 211     if (CONST_Tablespace_Place_Data)
 
 212         $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
 
 213     if (CONST_Tablespace_Place_Index)
 
 214         $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
 
 215     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
 
 216     $osm2pgsql .= ' -C '.$iCacheMemory;
 
 217     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
 
 218     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
 
 219         $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
 
 221     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
 
 222         $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
 
 226     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
 
 227         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
 
 230     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
 
 231     runWithEnv($osm2pgsql, $aProcEnv);
 
 234     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
 
 239 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
 
 240     info('Create Functions');
 
 241     $bDidSomething = true;
 
 243     if (!checkModulePresence()) {
 
 244         fail('error loading nominatim.so module');
 
 247     create_sql_functions($aCMDResult);
 
 250 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
 
 251     info('Create Tables');
 
 252     $bDidSomething = true;
 
 254     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
 
 255     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 256     $sTemplate = replace_tablespace(
 
 258         CONST_Tablespace_Address_Data,
 
 261     $sTemplate = replace_tablespace(
 
 262         '{ts:address-index}',
 
 263         CONST_Tablespace_Address_Index,
 
 266     $sTemplate = replace_tablespace(
 
 268         CONST_Tablespace_Search_Data,
 
 271     $sTemplate = replace_tablespace(
 
 273         CONST_Tablespace_Search_Index,
 
 276     $sTemplate = replace_tablespace(
 
 278         CONST_Tablespace_Aux_Data,
 
 281     $sTemplate = replace_tablespace(
 
 283         CONST_Tablespace_Aux_Index,
 
 286     pgsqlRunScript($sTemplate, false);
 
 288     // re-run the functions
 
 289     info('Recreate Functions');
 
 290     create_sql_functions($aCMDResult);
 
 293 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
 
 294     info('Create Partition Tables');
 
 295     $bDidSomething = true;
 
 297     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
 
 298     $sTemplate = replace_tablespace(
 
 300         CONST_Tablespace_Address_Data,
 
 303     $sTemplate = replace_tablespace(
 
 304         '{ts:address-index}',
 
 305         CONST_Tablespace_Address_Index,
 
 308     $sTemplate = replace_tablespace(
 
 310         CONST_Tablespace_Search_Data,
 
 313     $sTemplate = replace_tablespace(
 
 315         CONST_Tablespace_Search_Index,
 
 318     $sTemplate = replace_tablespace(
 
 320         CONST_Tablespace_Aux_Data,
 
 323     $sTemplate = replace_tablespace(
 
 325         CONST_Tablespace_Aux_Index,
 
 329     pgsqlRunPartitionScript($sTemplate);
 
 333 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
 
 334     info('Create Partition Functions');
 
 335     $bDidSomething = true;
 
 337     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
 
 339     pgsqlRunPartitionScript($sTemplate);
 
 342 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
 
 343     $bDidSomething = true;
 
 344     $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
 
 345     $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
 
 346     if (file_exists($sWikiArticlesFile)) {
 
 347         info('Importing wikipedia articles');
 
 348         pgsqlRunDropAndRestore($sWikiArticlesFile);
 
 350         warn('wikipedia article dump file not found - places will have default importance');
 
 352     if (file_exists($sWikiRedirectsFile)) {
 
 353         info('Importing wikipedia redirects');
 
 354         pgsqlRunDropAndRestore($sWikiRedirectsFile);
 
 356         warn('wikipedia redirect dump file not found - some place importance values may be missing');
 
 361 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
 
 362     info('Drop old Data');
 
 363     $bDidSomething = true;
 
 366     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
 
 368     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
 
 370     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
 
 372     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
 
 374     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
 
 376     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
 
 378     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
 
 380     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
 
 382     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
 
 384     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
 
 387     $sSQL = 'select distinct partition from country_name';
 
 388     $aPartitions = chksql($oDB->getCol($sSQL));
 
 389     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
 
 390     foreach ($aPartitions as $sPartition) {
 
 391         if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
 
 395     // used by getorcreate_word_id to ignore frequent partial words
 
 396     $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
 
 397     $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
 
 398     if (!pg_query($oDB->connection, $sSQL)) {
 
 399         fail(pg_last_error($oDB->connection));
 
 403     // pre-create the word list
 
 404     if (!$aCMDResult['disable-token-precalc']) {
 
 405         info('Loading word list');
 
 406         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
 
 410     $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
 
 412     $aDBInstances = array();
 
 413     $iLoadThreads = max(1, $iInstances - 1);
 
 414     for ($i = 0; $i < $iLoadThreads; $i++) {
 
 415         $aDBInstances[$i] =& getDB(true);
 
 416         $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
 
 417         $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
 
 418         $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
 
 419         $sSQL .= ' and ST_IsValid(geometry)';
 
 420         if ($aCMDResult['verbose']) echo "$sSQL\n";
 
 421         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
 
 422             fail(pg_last_error($aDBInstances[$i]->connection));
 
 425     // last thread for interpolation lines
 
 426     $aDBInstances[$iLoadThreads] =& getDB(true);
 
 427     $sSQL = 'insert into location_property_osmline';
 
 428     $sSQL .= ' (osm_id, address, linegeo)';
 
 429     $sSQL .= ' SELECT osm_id, address, geometry from place where ';
 
 430     $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
 
 431     if ($aCMDResult['verbose']) echo "$sSQL\n";
 
 432     if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
 
 433         fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
 
 437     for ($i = 0; $i <= $iLoadThreads; $i++) {
 
 438         while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
 
 439             $resultStatus = pg_result_status($hPGresult);
 
 440             // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
 
 441             // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
 
 442             // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
 
 443             echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
 
 444             if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
 
 445                 $resultError = pg_result_error($hPGresult);
 
 446                 echo '-- error text ' . $i . ': ' . $resultError . "\n";
 
 452         fail('SQL errors loading placex and/or location_property_osmline tables');
 
 455     info('Reanalysing database');
 
 456     pgsqlRunScript('ANALYSE');
 
 458     $sDatabaseDate = getDatabaseDate($oDB);
 
 459     pg_query($oDB->connection, 'TRUNCATE import_status');
 
 460     if ($sDatabaseDate === false) {
 
 461         warn('could not determine database date.');
 
 463         $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
 
 464         pg_query($oDB->connection, $sSQL);
 
 465         echo "Latest data imported from $sDatabaseDate.\n";
 
 469 if ($aCMDResult['import-tiger-data']) {
 
 470     info('Import Tiger data');
 
 471     $bDidSomething = true;
 
 473     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
 
 474     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 475     $sTemplate = replace_tablespace(
 
 477         CONST_Tablespace_Aux_Data,
 
 480     $sTemplate = replace_tablespace(
 
 482         CONST_Tablespace_Aux_Index,
 
 485     pgsqlRunScript($sTemplate, false);
 
 487     $aDBInstances = array();
 
 488     for ($i = 0; $i < $iInstances; $i++) {
 
 489         $aDBInstances[$i] =& getDB(true);
 
 492     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
 
 494         $hFile = fopen($sFile, 'r');
 
 495         $sSQL = fgets($hFile, 100000);
 
 499             for ($i = 0; $i < $iInstances; $i++) {
 
 500                 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
 
 501                     while (pg_get_result($aDBInstances[$i]->connection));
 
 502                     $sSQL = fgets($hFile, 100000);
 
 504                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 506                     if ($iLines == 1000) {
 
 520             for ($i = 0; $i < $iInstances; $i++) {
 
 521                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
 
 528     info('Creating indexes on Tiger data');
 
 529     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
 
 530     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 531     $sTemplate = replace_tablespace(
 
 533         CONST_Tablespace_Aux_Data,
 
 536     $sTemplate = replace_tablespace(
 
 538         CONST_Tablespace_Aux_Index,
 
 541     pgsqlRunScript($sTemplate, false);
 
 544 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
 
 545     info('Calculate Postcodes');
 
 546     $bDidSomething = true;
 
 548     if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
 
 549         fail(pg_last_error($oDB->connection));
 
 552     $sSQL  = 'INSERT INTO location_postcode';
 
 553     $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 554     $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
 
 555     $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
 
 556     $sSQL .= '       ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
 
 557     $sSQL .= '  FROM placex';
 
 558     $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
 
 559     $sSQL .= '       AND geometry IS NOT null';
 
 560     $sSQL .= ' GROUP BY country_code, pc';
 
 562     if (!pg_query($oDB->connection, $sSQL)) {
 
 563         fail(pg_last_error($oDB->connection));
 
 566     if (CONST_Use_Extra_US_Postcodes) {
 
 567         // only add postcodes that are not yet available in OSM
 
 568         $sSQL  = 'INSERT INTO location_postcode';
 
 569         $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 570         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
 
 571         $sSQL .= '       ST_SetSRID(ST_Point(x,y),4326)';
 
 572         $sSQL .= '  FROM us_postcode WHERE postcode NOT IN';
 
 573         $sSQL .= '        (SELECT postcode FROM location_postcode';
 
 574         $sSQL .= "          WHERE country_code = 'us')";
 
 575         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 578     // add missing postcodes for GB (if available)
 
 579     $sSQL  = 'INSERT INTO location_postcode';
 
 580     $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
 
 581     $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
 
 582     $sSQL .= '  FROM gb_postcode WHERE postcode NOT IN';
 
 583     $sSQL .= '           (SELECT postcode FROM location_postcode';
 
 584     $sSQL .= "             WHERE country_code = 'gb')";
 
 585     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 587     if (!$aCMDResult['all']) {
 
 588         $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
 
 589         $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
 
 590         if (!pg_query($oDB->connection, $sSQL)) {
 
 591             fail(pg_last_error($oDB->connection));
 
 594     $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
 
 595     $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
 
 597     if (!pg_query($oDB->connection, $sSQL)) {
 
 598         fail(pg_last_error($oDB->connection));
 
 602 if ($aCMDResult['osmosis-init']) {
 
 603     $bDidSomething = true;
 
 604     echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
 
 607 if ($aCMDResult['index'] || $aCMDResult['all']) {
 
 608     $bDidSomething = true;
 
 610     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
 
 611     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
 
 612         $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
 
 614     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
 
 615         $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
 
 618     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
 
 619         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
 
 622     info('Index ranks 0 - 4');
 
 623     $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
 
 625         fail('error status ' . $iStatus . ' running nominatim!');
 
 627     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
 
 628     info('Index ranks 5 - 25');
 
 629     $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
 
 631         fail('error status ' . $iStatus . ' running nominatim!');
 
 633     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
 
 634     info('Index ranks 26 - 30');
 
 635     $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
 
 637         fail('error status ' . $iStatus . ' running nominatim!');
 
 640     info('Index postcodes');
 
 642     $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
 
 643     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
 
 646 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
 
 647     info('Create Search indices');
 
 648     $bDidSomething = true;
 
 650     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
 
 651     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
 
 652     $sTemplate = replace_tablespace(
 
 653         '{ts:address-index}',
 
 654         CONST_Tablespace_Address_Index,
 
 657     $sTemplate = replace_tablespace(
 
 659         CONST_Tablespace_Search_Index,
 
 662     $sTemplate = replace_tablespace(
 
 664         CONST_Tablespace_Aux_Index,
 
 668     pgsqlRunScript($sTemplate);
 
 671 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
 
 672     info('Create search index for default country names');
 
 673     $bDidSomething = true;
 
 675     pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
 
 676     pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
 
 677     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');
 
 678     pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
 
 680     $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 ';
 
 681     if (CONST_Languages) {
 
 684         foreach (explode(',', CONST_Languages) as $sLang) {
 
 685             $sSQL .= $sDelim."'name:$sLang'";
 
 690         // all include all simple name tags
 
 691         $sSQL .= "like 'name:%'";
 
 694     pgsqlRunScript($sSQL);
 
 697 if ($aCMDResult['drop']) {
 
 698     info('Drop tables only required for updates');
 
 699     // The implementation is potentially a bit dangerous because it uses
 
 700     // a positive selection of tables to keep, and deletes everything else.
 
 701     // Including any tables that the unsuspecting user might have manually
 
 702     // created. USE AT YOUR OWN PERIL.
 
 703     $bDidSomething = true;
 
 705     // tables we want to keep. everything else goes.
 
 706     $aKeepTables = array(
 
 712                     'location_property*',
 
 725     $aDropTables = array();
 
 726     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
 
 728     foreach ($aHaveTables as $sTable) {
 
 730         foreach ($aKeepTables as $sKeep) {
 
 731             if (fnmatch($sKeep, $sTable)) {
 
 736         if (!$bFound) array_push($aDropTables, $sTable);
 
 739     foreach ($aDropTables as $sDrop) {
 
 740         if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
 
 741         @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
 
 742         // ignore warnings/errors as they might be caused by a table having
 
 743         // been deleted already by CASCADE
 
 746     if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
 
 747         if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
 
 748         unlink(CONST_Osm2pgsql_Flatnode_File);
 
 752 if (!$bDidSomething) {
 
 753     showUsage($aCMDOptions, true);
 
 755     echo "Summary of warnings:\n\n";
 
 758     info('Setup finished.');
 
 762 function pgsqlRunScriptFile($sFilename)
 
 765     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
 
 767     // Convert database DSN to psql parameters
 
 768     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 769     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 770     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
 
 771     if (!$aCMDResult['verbose']) {
 
 774     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
 
 775         $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
 
 777     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
 
 778         $sCMD .= ' -U ' . $aDSNInfo['username'];
 
 781     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
 
 782         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
 
 786     if (preg_match('/\\.gz$/', $sFilename)) {
 
 787         $aDescriptors = array(
 
 788                          0 => array('pipe', 'r'),
 
 789                          1 => array('pipe', 'w'),
 
 790                          2 => array('file', '/dev/null', 'a')
 
 792         $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
 
 793         if (!is_resource($hGzipProcess)) fail('unable to start zcat');
 
 794         $aReadPipe = $ahGzipPipes[1];
 
 795         fclose($ahGzipPipes[0]);
 
 797         $sCMD .= ' -f '.$sFilename;
 
 798         $aReadPipe = array('pipe', 'r');
 
 801     $aDescriptors = array(
 
 803                      1 => array('pipe', 'w'),
 
 804                      2 => array('file', '/dev/null', 'a')
 
 807     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
 
 808     if (!is_resource($hProcess)) fail('unable to start pgsql');
 
 810     // TODO: error checking
 
 811     while (!feof($ahPipes[1])) {
 
 812         echo fread($ahPipes[1], 4096);
 
 816     $iReturn = proc_close($hProcess);
 
 818         fail("pgsql returned with error code ($iReturn)");
 
 821         fclose($ahGzipPipes[1]);
 
 822         proc_close($hGzipProcess);
 
 826 function pgsqlRunScript($sScript, $bfatal = true)
 
 832         $aCMDResult['verbose'],
 
 833         $aCMDResult['ignore-errors']
 
 837 function pgsqlRunPartitionScript($sTemplate)
 
 842     $sSQL = 'select distinct partition from country_name';
 
 843     $aPartitions = chksql($oDB->getCol($sSQL));
 
 844     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
 
 846     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
 
 847     foreach ($aMatches as $aMatch) {
 
 849         foreach ($aPartitions as $sPartitionName) {
 
 850             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
 
 852         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
 
 855     pgsqlRunScript($sTemplate);
 
 858 function pgsqlRunRestoreData($sDumpFile)
 
 860     // Convert database DSN to psql parameters
 
 861     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 862     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 863     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
 
 865     $aDescriptors = array(
 
 866                      0 => array('pipe', 'r'),
 
 867                      1 => array('pipe', 'w'),
 
 868                      2 => array('file', '/dev/null', 'a')
 
 871     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
 
 872     if (!is_resource($hProcess)) fail('unable to start pg_restore');
 
 876     // TODO: error checking
 
 877     while (!feof($ahPipes[1])) {
 
 878         echo fread($ahPipes[1], 4096);
 
 882     $iReturn = proc_close($hProcess);
 
 885 function pgsqlRunDropAndRestore($sDumpFile)
 
 887     // Convert database DSN to psql parameters
 
 888     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
 
 889     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
 
 890     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
 
 891     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
 
 892         $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
 
 894     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
 
 895         $sCMD .= ' -U ' . $aDSNInfo['username'];
 
 898     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
 
 899         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
 
 902     $iReturn = runWithEnv($sCMD, $aProcEnv);
 
 905 function passthruCheckReturn($sCmd)
 
 908     passthru($sCmd, $iResult);
 
 911 function replace_tablespace($sTemplate, $sTablespace, $sSql)
 
 914         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
 
 916         $sSql = str_replace($sTemplate, '', $sSql);
 
 922 function create_sql_functions($aCMDResult)
 
 925     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
 
 926     $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
 
 927     if ($aCMDResult['enable-diff-updates']) {
 
 928         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
 
 930     if ($aCMDResult['enable-debug-statements']) {
 
 931         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
 
 933     if (CONST_Limit_Reindexing) {
 
 934         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
 
 936     if (!CONST_Use_US_Tiger_Data) {
 
 937         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
 
 939     if (!CONST_Use_Aux_Location_data) {
 
 940         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
 
 942     pgsqlRunScript($sTemplate);
 
 945 function checkModulePresence()
 
 947     // Try accessing the C module, so we know early if something is wrong
 
 948     // and can simply error out.
 
 950     $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
 
 951     $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
 
 952     $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
 
 955     $oResult = $oDB->query($sSQL);
 
 959     if (PEAR::isError($oResult)) {
 
 960         echo "\nERROR: Failed to load nominatim module. Reason:\n";
 
 961         echo $oResult->userinfo."\n\n";