]> git.openstreetmap.org Git - nominatim.git/blob - utils/setup.php
Merge pull request #1151 from mtmail/documentation-for-details-endpoint
[nominatim.git] / utils / setup.php
1 #!/usr/bin/php -Cq
2 <?php
3
4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
7
8 # (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
9
10 $aCMDOptions
11 = array(
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'),
16
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)'),
19
20    array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
21
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)'),
45   );
46 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
47
48 $bDidSomething = false;
49
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');
54     }
55
56     if (!file_exists($aCMDResult['osm-file'])) {
57         fail('the path supplied to --osm-file does not exist');
58     }
59
60     if (!is_readable($aCMDResult['osm-file'])) {
61         fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
62     }
63 }
64
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);
69
70 if ($iInstances < 1) {
71     $iInstances = 1;
72     warn("resetting threads to $iInstances");
73 }
74
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'];
78 } else {
79     $iCacheMemory = getCacheMemoryMB();
80 }
81
82 $sModulePath = CONST_Database_Module_Path;
83 info('module path: ' . $sModulePath);
84
85 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
86 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
87
88 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
89     info('Create DB');
90     $bDidSomething = true;
91     $oDB = DB::connect(CONST_Database_DSN, false);
92     if (!PEAR::isError($oDB)) {
93         fail('database already exists ('.CONST_Database_DSN.')');
94     }
95
96     $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
97     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
98         $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
99     }
100     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
101         $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
102     }
103
104     $aProcEnv = null;
105     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
106         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
107     }
108
109     $result = runWithEnv($sCreateDBCmd, $aProcEnv);
110     if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
111 }
112
113 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
114     info('Setup DB');
115     $bDidSomething = true;
116
117     $oDB =& getDB();
118
119     $fPostgresVersion = getPostgresVersion($oDB);
120     echo 'Postgres version found: '.$fPostgresVersion."\n";
121
122     if ($fPostgresVersion < 9.1) {
123         fail('Minimum supported version of Postgresql is 9.1.');
124     }
125
126     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
127     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
128
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'"));
133
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.');
137     }
138
139     $fPostgisVersion = getPostgisVersion($oDB);
140     echo 'Postgis version found: '.$fPostgisVersion."\n";
141
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');
146     }
147     if ($fPostgisVersion < 2.2) {
148         pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
149     }
150
151     $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
152     if ($i == 0) {
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";
155         exit(1);
156     }
157
158     if (!checkModulePresence()) {
159         fail('error loading nominatim.so module');
160     }
161
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";
165         exit(1);
166     }
167
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');
174     } else {
175         warn('external UK postcode table not found.');
176     }
177     if (CONST_Use_Extra_US_Postcodes) {
178         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
179     }
180
181     if ($aCMDResult['no-partitions']) {
182         pgsqlRunScript('update country_name set partition = 0');
183     }
184
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);
191 }
192
193 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
194     info('Import data');
195     $bDidSomething = true;
196
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'");
202     }
203
204     if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
205         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
206     }
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'];
220     }
221     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
222         $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
223     }
224
225     $aProcEnv = null;
226     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
227         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
228     }
229
230     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
231     runWithEnv($osm2pgsql, $aProcEnv);
232
233     $oDB =& getDB();
234     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
235         fail('No Data');
236     }
237 }
238
239 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
240     info('Create Functions');
241     $bDidSomething = true;
242
243     if (!checkModulePresence()) {
244         fail('error loading nominatim.so module');
245     }
246
247     create_sql_functions($aCMDResult);
248 }
249
250 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
251     info('Create Tables');
252     $bDidSomething = true;
253
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(
257         '{ts:address-data}',
258         CONST_Tablespace_Address_Data,
259         $sTemplate
260     );
261     $sTemplate = replace_tablespace(
262         '{ts:address-index}',
263         CONST_Tablespace_Address_Index,
264         $sTemplate
265     );
266     $sTemplate = replace_tablespace(
267         '{ts:search-data}',
268         CONST_Tablespace_Search_Data,
269         $sTemplate
270     );
271     $sTemplate = replace_tablespace(
272         '{ts:search-index}',
273         CONST_Tablespace_Search_Index,
274         $sTemplate
275     );
276     $sTemplate = replace_tablespace(
277         '{ts:aux-data}',
278         CONST_Tablespace_Aux_Data,
279         $sTemplate
280     );
281     $sTemplate = replace_tablespace(
282         '{ts:aux-index}',
283         CONST_Tablespace_Aux_Index,
284         $sTemplate
285     );
286     pgsqlRunScript($sTemplate, false);
287
288     // re-run the functions
289     info('Recreate Functions');
290     create_sql_functions($aCMDResult);
291 }
292
293 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
294     info('Create Partition Tables');
295     $bDidSomething = true;
296
297     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
298     $sTemplate = replace_tablespace(
299         '{ts:address-data}',
300         CONST_Tablespace_Address_Data,
301         $sTemplate
302     );
303     $sTemplate = replace_tablespace(
304         '{ts:address-index}',
305         CONST_Tablespace_Address_Index,
306         $sTemplate
307     );
308     $sTemplate = replace_tablespace(
309         '{ts:search-data}',
310         CONST_Tablespace_Search_Data,
311         $sTemplate
312     );
313     $sTemplate = replace_tablespace(
314         '{ts:search-index}',
315         CONST_Tablespace_Search_Index,
316         $sTemplate
317     );
318     $sTemplate = replace_tablespace(
319         '{ts:aux-data}',
320         CONST_Tablespace_Aux_Data,
321         $sTemplate
322     );
323     $sTemplate = replace_tablespace(
324         '{ts:aux-index}',
325         CONST_Tablespace_Aux_Index,
326         $sTemplate
327     );
328
329     pgsqlRunPartitionScript($sTemplate);
330 }
331
332
333 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
334     info('Create Partition Functions');
335     $bDidSomething = true;
336
337     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
338
339     pgsqlRunPartitionScript($sTemplate);
340 }
341
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);
349     } else {
350         warn('wikipedia article dump file not found - places will have default importance');
351     }
352     if (file_exists($sWikiRedirectsFile)) {
353         info('Importing wikipedia redirects');
354         pgsqlRunDropAndRestore($sWikiRedirectsFile);
355     } else {
356         warn('wikipedia redirect dump file not found - some place importance values may be missing');
357     }
358 }
359
360
361 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
362     info('Drop old Data');
363     $bDidSomething = true;
364
365     $oDB =& getDB();
366     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
367     echo '.';
368     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
369     echo '.';
370     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
371     echo '.';
372     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
373     echo '.';
374     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
375     echo '.';
376     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
377     echo '.';
378     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
379     echo '.';
380     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
381     echo '.';
382     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
383     echo '.';
384     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
385     echo '.';
386
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));
392         echo '.';
393     }
394
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));
400     }
401     echo ".\n";
402
403     // pre-create the word list
404     if (!$aCMDResult['disable-token-precalc']) {
405         info('Loading word list');
406         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
407     }
408
409     info('Load Data');
410     $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
411
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));
423         }
424     }
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));
434     }
435
436     $bFailed = false;
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";
447                 $bFailed = true;
448             }
449         }
450     }
451     if ($bFailed) {
452         fail('SQL errors loading placex and/or location_property_osmline tables');
453     }
454     echo "\n";
455     info('Reanalysing database');
456     pgsqlRunScript('ANALYSE');
457
458     $sDatabaseDate = getDatabaseDate($oDB);
459     pg_query($oDB->connection, 'TRUNCATE import_status');
460     if ($sDatabaseDate === false) {
461         warn('could not determine database date.');
462     } else {
463         $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
464         pg_query($oDB->connection, $sSQL);
465         echo "Latest data imported from $sDatabaseDate.\n";
466     }
467 }
468
469 if ($aCMDResult['import-tiger-data']) {
470     info('Import Tiger data');
471     $bDidSomething = true;
472
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(
476         '{ts:aux-data}',
477         CONST_Tablespace_Aux_Data,
478         $sTemplate
479     );
480     $sTemplate = replace_tablespace(
481         '{ts:aux-index}',
482         CONST_Tablespace_Aux_Index,
483         $sTemplate
484     );
485     pgsqlRunScript($sTemplate, false);
486
487     $aDBInstances = array();
488     for ($i = 0; $i < $iInstances; $i++) {
489         $aDBInstances[$i] =& getDB(true);
490     }
491
492     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
493         echo $sFile.': ';
494         $hFile = fopen($sFile, 'r');
495         $sSQL = fgets($hFile, 100000);
496         $iLines = 0;
497
498         while (true) {
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);
503                     if (!$sSQL) break 2;
504                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
505                     $iLines++;
506                     if ($iLines == 1000) {
507                         echo '.';
508                         $iLines = 0;
509                     }
510                 }
511             }
512             usleep(10);
513         }
514
515         fclose($hFile);
516
517         $bAnyBusy = true;
518         while ($bAnyBusy) {
519             $bAnyBusy = false;
520             for ($i = 0; $i < $iInstances; $i++) {
521                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
522             }
523             usleep(10);
524         }
525         echo "\n";
526     }
527
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(
532         '{ts:aux-data}',
533         CONST_Tablespace_Aux_Data,
534         $sTemplate
535     );
536     $sTemplate = replace_tablespace(
537         '{ts:aux-index}',
538         CONST_Tablespace_Aux_Index,
539         $sTemplate
540     );
541     pgsqlRunScript($sTemplate, false);
542 }
543
544 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
545     info('Calculate Postcodes');
546     $bDidSomething = true;
547     $oDB =& getDB();
548     if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
549         fail(pg_last_error($oDB->connection));
550     }
551
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';
561
562     if (!pg_query($oDB->connection, $sSQL)) {
563         fail(pg_last_error($oDB->connection));
564     }
565
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));
576     }
577
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));
586
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));
592         }
593     }
594     $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
595     $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
596
597     if (!pg_query($oDB->connection, $sSQL)) {
598         fail(pg_last_error($oDB->connection));
599     }
600 }
601
602 if ($aCMDResult['osmosis-init']) {
603     $bDidSomething = true;
604     echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
605 }
606
607 if ($aCMDResult['index'] || $aCMDResult['all']) {
608     $bDidSomething = true;
609     $sOutputFile = '';
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'];
613     }
614     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
615         $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
616     }
617     $aProcEnv = null;
618     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
619         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
620     }
621
622     info('Index ranks 0 - 4');
623     $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
624     if ($iStatus != 0) {
625         fail('error status ' . $iStatus . ' running nominatim!');
626     }
627     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
628     info('Index ranks 5 - 25');
629     $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
630     if ($iStatus != 0) {
631         fail('error status ' . $iStatus . ' running nominatim!');
632     }
633     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
634     info('Index ranks 26 - 30');
635     $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
636     if ($iStatus != 0) {
637         fail('error status ' . $iStatus . ' running nominatim!');
638     }
639
640     info('Index postcodes');
641     $oDB =& getDB();
642     $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
643     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
644 }
645
646 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
647     info('Create Search indices');
648     $bDidSomething = true;
649
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,
655         $sTemplate
656     );
657     $sTemplate = replace_tablespace(
658         '{ts:search-index}',
659         CONST_Tablespace_Search_Index,
660         $sTemplate
661     );
662     $sTemplate = replace_tablespace(
663         '{ts:aux-index}',
664         CONST_Tablespace_Aux_Index,
665         $sTemplate
666     );
667
668     pgsqlRunScript($sTemplate);
669 }
670
671 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
672     info('Create search index for default country names');
673     $bDidSomething = true;
674
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");
679
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) {
682         $sSQL .= 'in ';
683         $sDelim = '(';
684         foreach (explode(',', CONST_Languages) as $sLang) {
685             $sSQL .= $sDelim."'name:$sLang'";
686             $sDelim = ',';
687         }
688         $sSQL .= ')';
689     } else {
690         // all include all simple name tags
691         $sSQL .= "like 'name:%'";
692     }
693     $sSQL .= ') v';
694     pgsqlRunScript($sSQL);
695 }
696
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;
704
705     // tables we want to keep. everything else goes.
706     $aKeepTables = array(
707                     '*columns',
708                     'import_polygon_*',
709                     'import_status',
710                     'place_addressline',
711                     'location_postcode',
712                     'location_property*',
713                     'placex',
714                     'search_name',
715                     'seq_*',
716                     'word',
717                     'query_log',
718                     'new_query_log',
719                     'spatial_ref_sys',
720                     'country_name',
721                     'place_classtype_*'
722                    );
723
724     $oDB =& getDB();
725     $aDropTables = array();
726     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
727
728     foreach ($aHaveTables as $sTable) {
729         $bFound = false;
730         foreach ($aKeepTables as $sKeep) {
731             if (fnmatch($sKeep, $sTable)) {
732                 $bFound = true;
733                 break;
734             }
735         }
736         if (!$bFound) array_push($aDropTables, $sTable);
737     }
738
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
744     }
745
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);
749     }
750 }
751
752 if (!$bDidSomething) {
753     showUsage($aCMDOptions, true);
754 } else {
755     echo "Summary of warnings:\n\n";
756     repeatWarnings();
757     echo "\n";
758     info('Setup finished.');
759 }
760
761
762 function pgsqlRunScriptFile($sFilename)
763 {
764     global $aCMDResult;
765     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
766
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']) {
772         $sCMD .= ' -q';
773     }
774     if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
775         $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
776     }
777     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
778         $sCMD .= ' -U ' . $aDSNInfo['username'];
779     }
780     $aProcEnv = null;
781     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
782         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
783     }
784
785     $ahGzipPipes = null;
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')
791                         );
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]);
796     } else {
797         $sCMD .= ' -f '.$sFilename;
798         $aReadPipe = array('pipe', 'r');
799     }
800
801     $aDescriptors = array(
802                      0 => $aReadPipe,
803                      1 => array('pipe', 'w'),
804                      2 => array('file', '/dev/null', 'a')
805                     );
806     $ahPipes = null;
807     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
808     if (!is_resource($hProcess)) fail('unable to start pgsql');
809
810     // TODO: error checking
811     while (!feof($ahPipes[1])) {
812         echo fread($ahPipes[1], 4096);
813     }
814     fclose($ahPipes[1]);
815
816     $iReturn = proc_close($hProcess);
817     if ($iReturn > 0) {
818         fail("pgsql returned with error code ($iReturn)");
819     }
820     if ($ahGzipPipes) {
821         fclose($ahGzipPipes[1]);
822         proc_close($hGzipProcess);
823     }
824 }
825
826 function pgsqlRunScript($sScript, $bfatal = true)
827 {
828     global $aCMDResult;
829     runSQLScript(
830         $sScript,
831         $bfatal,
832         $aCMDResult['verbose'],
833         $aCMDResult['ignore-errors']
834     );
835 }
836
837 function pgsqlRunPartitionScript($sTemplate)
838 {
839     global $aCMDResult;
840     $oDB =& getDB();
841
842     $sSQL = 'select distinct partition from country_name';
843     $aPartitions = chksql($oDB->getCol($sSQL));
844     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
845
846     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
847     foreach ($aMatches as $aMatch) {
848         $sResult = '';
849         foreach ($aPartitions as $sPartitionName) {
850             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
851         }
852         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
853     }
854
855     pgsqlRunScript($sTemplate);
856 }
857
858 function pgsqlRunRestoreData($sDumpFile)
859 {
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;
864
865     $aDescriptors = array(
866                      0 => array('pipe', 'r'),
867                      1 => array('pipe', 'w'),
868                      2 => array('file', '/dev/null', 'a')
869                     );
870     $ahPipes = null;
871     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
872     if (!is_resource($hProcess)) fail('unable to start pg_restore');
873
874     fclose($ahPipes[0]);
875
876     // TODO: error checking
877     while (!feof($ahPipes[1])) {
878         echo fread($ahPipes[1], 4096);
879     }
880     fclose($ahPipes[1]);
881
882     $iReturn = proc_close($hProcess);
883 }
884
885 function pgsqlRunDropAndRestore($sDumpFile)
886 {
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'];
893     }
894     if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
895         $sCMD .= ' -U ' . $aDSNInfo['username'];
896     }
897     $aProcEnv = null;
898     if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
899         $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
900     }
901
902     $iReturn = runWithEnv($sCMD, $aProcEnv);
903 }
904
905 function passthruCheckReturn($sCmd)
906 {
907     $iResult = -1;
908     passthru($sCmd, $iResult);
909 }
910
911 function replace_tablespace($sTemplate, $sTablespace, $sSql)
912 {
913     if ($sTablespace) {
914         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
915     } else {
916         $sSql = str_replace($sTemplate, '', $sSql);
917     }
918
919     return $sSql;
920 }
921
922 function create_sql_functions($aCMDResult)
923 {
924     global $sModulePath;
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);
929     }
930     if ($aCMDResult['enable-debug-statements']) {
931         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
932     }
933     if (CONST_Limit_Reindexing) {
934         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
935     }
936     if (!CONST_Use_US_Tiger_Data) {
937         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
938     }
939     if (!CONST_Use_Aux_Location_data) {
940         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
941     }
942     pgsqlRunScript($sTemplate);
943 }
944
945 function checkModulePresence()
946 {
947     // Try accessing the C module, so we know early if something is wrong
948     // and can simply error out.
949     global $sModulePath;
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);';
953
954     $oDB =& getDB();
955     $oResult = $oDB->query($sSQL);
956
957     $bResult = true;
958
959     if (PEAR::isError($oResult)) {
960         echo "\nERROR: Failed to load nominatim module. Reason:\n";
961         echo $oResult->userinfo."\n\n";
962         $bResult = false;
963     }
964
965     return $bResult;
966 }