]> git.openstreetmap.org Git - nominatim.git/blob - utils/setup.php
44bdcdc72737cbc6ef8ef10a137ba47daef0514e
[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 $aCMDOptions
9 = array(
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'),
14
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)'),
17
18    array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
19
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)'),
43   );
44 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
45
46 $bDidSomething = false;
47
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');
52     }
53
54     if (!file_exists($aCMDResult['osm-file'])) {
55         fail('the path supplied to --osm-file does not exist');
56     }
57
58     if (!is_readable($aCMDResult['osm-file'])) {
59         fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
60     }
61 }
62
63
64 // This is a pretty hard core default - the number of processors in the box - 1
65 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
66 if ($iInstances < 1) {
67     $iInstances = 1;
68     warn("resetting threads to $iInstances");
69 }
70 if ($iInstances > getProcessorCount()) {
71     $iInstances = getProcessorCount();
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 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
83 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
84
85 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
86     info("Create DB");
87     $bDidSomething = true;
88     $oDB = DB::connect(CONST_Database_DSN, false);
89     if (!PEAR::isError($oDB)) {
90         fail('database already exists ('.CONST_Database_DSN.')');
91     }
92     passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
93 }
94
95 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
96     info("Setup DB");
97     $bDidSomething = true;
98
99     $oDB =& getDB();
100
101     $fPostgresVersion = getPostgresVersion($oDB);
102     echo 'Postgres version found: '.$fPostgresVersion."\n";
103
104     if ($fPostgresVersion < 9.1) {
105         fail("Minimum supported version of Postgresql is 9.1.");
106     }
107
108     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
109     pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
110
111     // For extratags and namedetails the hstore_to_json converter is
112     // needed which is only available from Postgresql 9.3+. For older
113     // versions add a dummy function that returns nothing.
114     $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
115
116     if ($iNumFunc == 0) {
117         pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
118         warn('Postgresql is too old. extratags and namedetails API not available.');
119     }
120
121     $fPostgisVersion = getPostgisVersion($oDB);
122     echo 'Postgis version found: '.$fPostgisVersion."\n";
123
124     if ($fPostgisVersion < 2.1) {
125         // Functions were renamed in 2.1 and throw an annoying deprecation warning
126         pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
127         pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
128     }
129     if ($fPostgisVersion < 2.2) {
130         pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
131     }
132
133     if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
134         echo "Error: you need to download the country_osm_grid first:";
135         echo "\n    wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz http://www.nominatim.org/data/country_grid.sql.gz\n";
136         exit(1);
137     }
138
139     pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
140     pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
141     pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
142     pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
143     if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
144         pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
145     } else {
146         warn('external UK postcode table not found.');
147     }
148     if (CONST_Use_Extra_US_Postcodes) {
149         pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
150     }
151
152     if ($aCMDResult['no-partitions']) {
153         pgsqlRunScript('update country_name set partition = 0');
154     }
155
156     // the following will be needed by create_functions later but
157     // is only defined in the subsequently called create_tables.
158     // Create dummies here that will be overwritten by the proper
159     // versions in create-tables.
160     pgsqlRunScript('CREATE TABLE place_boundingbox ()');
161     pgsqlRunScript('create type wikipedia_article_match as ()');
162 }
163
164 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
165     info('Import data');
166     $bDidSomething = true;
167
168     $osm2pgsql = CONST_Osm2pgsql_Binary;
169     if (!file_exists($osm2pgsql)) {
170         echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
171         echo "Normally you should not need to set this manually.\n";
172         fail("osm2pgsql not found in '$osm2pgsql'");
173     }
174
175     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
176         $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
177     }
178     if (CONST_Tablespace_Osm2pgsql_Data)
179         $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
180     if (CONST_Tablespace_Osm2pgsql_Index)
181         $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
182     if (CONST_Tablespace_Place_Data)
183         $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
184     if (CONST_Tablespace_Place_Index)
185         $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
186     $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
187     $osm2pgsql .= ' -C '.$iCacheMemory;
188     $osm2pgsql .= ' -P '.$aDSNInfo['port'];
189     $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
190     passthruCheckReturn($osm2pgsql);
191
192     $oDB =& getDB();
193     if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
194         fail('No Data');
195     }
196 }
197
198 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
199     info('Create Functions');
200     $bDidSomething = true;
201     if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) {
202         fail("nominatim module not built");
203     }
204     create_sql_functions($aCMDResult);
205 }
206
207 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
208     info('Create Tables');
209     $bDidSomething = true;
210
211     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
212     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
213     $sTemplate = replace_tablespace(
214         '{ts:address-data}',
215         CONST_Tablespace_Address_Data,
216         $sTemplate
217     );
218     $sTemplate = replace_tablespace(
219         '{ts:address-index}',
220         CONST_Tablespace_Address_Index,
221         $sTemplate
222     );
223     $sTemplate = replace_tablespace(
224         '{ts:search-data}',
225         CONST_Tablespace_Search_Data,
226         $sTemplate
227     );
228     $sTemplate = replace_tablespace(
229         '{ts:search-index}',
230         CONST_Tablespace_Search_Index,
231         $sTemplate
232     );
233     $sTemplate = replace_tablespace(
234         '{ts:aux-data}',
235         CONST_Tablespace_Aux_Data,
236         $sTemplate
237     );
238     $sTemplate = replace_tablespace(
239         '{ts:aux-index}',
240         CONST_Tablespace_Aux_Index,
241         $sTemplate
242     );
243     pgsqlRunScript($sTemplate, false);
244
245     // re-run the functions
246     info('Recreate Functions');
247     create_sql_functions($aCMDResult);
248 }
249
250 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
251     info('Create Partition Tables');
252     $bDidSomething = true;
253
254     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
255     $sTemplate = replace_tablespace(
256         '{ts:address-data}',
257         CONST_Tablespace_Address_Data,
258         $sTemplate
259     );
260     $sTemplate = replace_tablespace(
261         '{ts:address-index}',
262         CONST_Tablespace_Address_Index,
263         $sTemplate
264     );
265     $sTemplate = replace_tablespace(
266         '{ts:search-data}',
267         CONST_Tablespace_Search_Data,
268         $sTemplate
269     );
270     $sTemplate = replace_tablespace(
271         '{ts:search-index}',
272         CONST_Tablespace_Search_Index,
273         $sTemplate
274     );
275     $sTemplate = replace_tablespace(
276         '{ts:aux-data}',
277         CONST_Tablespace_Aux_Data,
278         $sTemplate
279     );
280     $sTemplate = replace_tablespace(
281         '{ts:aux-index}',
282         CONST_Tablespace_Aux_Index,
283         $sTemplate
284     );
285
286     pgsqlRunPartitionScript($sTemplate);
287 }
288
289
290 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
291     info('Create Partition Functions');
292     $bDidSomething = true;
293
294     $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
295
296     pgsqlRunPartitionScript($sTemplate);
297 }
298
299 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
300     $bDidSomething = true;
301     $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
302     $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
303     if (file_exists($sWikiArticlesFile)) {
304         info('Importing wikipedia articles');
305         pgsqlRunDropAndRestore($sWikiArticlesFile);
306     } else {
307         warn('wikipedia article dump file not found - places will have default importance');
308     }
309     if (file_exists($sWikiRedirectsFile)) {
310         info('Importing wikipedia redirects');
311         pgsqlRunDropAndRestore($sWikiRedirectsFile);
312     } else {
313         warn('wikipedia redirect dump file not found - some place importance values may be missing');
314     }
315 }
316
317
318 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
319     info('Drop old Data');
320     $bDidSomething = true;
321
322     $oDB =& getDB();
323     if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
324     echo '.';
325     if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
326     echo '.';
327     if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
328     echo '.';
329     if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
330     echo '.';
331     if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
332     echo '.';
333     if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
334     echo '.';
335     if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
336     echo '.';
337     if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
338     echo '.';
339     if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
340     echo '.';
341     if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
342     echo '.';
343
344     $sSQL = 'select distinct partition from country_name';
345     $aPartitions = chksql($oDB->getCol($sSQL));
346     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
347     foreach ($aPartitions as $sPartition) {
348         if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
349         echo '.';
350     }
351
352     // used by getorcreate_word_id to ignore frequent partial words
353     $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
354     $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
355     if (!pg_query($oDB->connection, $sSQL)) {
356         fail(pg_last_error($oDB->connection));
357     }
358     echo ".\n";
359
360     // pre-create the word list
361     if (!$aCMDResult['disable-token-precalc']) {
362         info('Loading word list');
363         pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
364     }
365
366     info('Load Data');
367     $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
368
369     $aDBInstances = array();
370     $iLoadThreads = max(1, $iInstances - 1);
371     for ($i = 0; $i < $iLoadThreads; $i++) {
372         $aDBInstances[$i] =& getDB(true);
373         $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
374         $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
375         $sSQL .= "          and ST_GeometryType(geometry) = 'ST_LineString')";
376         $sSQL .= " and ST_IsValid(geometry)";
377         if ($aCMDResult['verbose']) echo "$sSQL\n";
378         if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
379             fail(pg_last_error($aDBInstances[$i]->connection));
380         }
381     }
382     // last thread for interpolation lines
383     $aDBInstances[$iLoadThreads] =& getDB(true);
384     $sSQL = 'insert into location_property_osmline';
385     $sSQL .= ' (osm_id, address, linegeo)';
386     $sSQL .= ' SELECT osm_id, address, geometry from place where ';
387     $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
388     if ($aCMDResult['verbose']) echo "$sSQL\n";
389     if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
390         fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
391     }
392
393     $bAnyBusy = true;
394     while ($bAnyBusy) {
395         $bAnyBusy = false;
396         for ($i = 0; $i <= $iLoadThreads; $i++) {
397             if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
398         }
399         sleep(1);
400         echo '.';
401     }
402     echo "\n";
403     info('Reanalysing database');
404     pgsqlRunScript('ANALYSE');
405
406     $sDatabaseDate = getDatabaseDate($oDB);
407     pg_query($oDB->connection, 'TRUNCATE import_status');
408     if ($sDatabaseDate === false) {
409         warn('could not determine database date.');
410     } else {
411         $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
412         pg_query($oDB->connection, $sSQL);
413         echo "Latest data imported from $sDatabaseDate.\n";
414     }
415 }
416
417 if ($aCMDResult['import-tiger-data']) {
418     info('Import Tiger data');
419     $bDidSomething = true;
420
421     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
422     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
423     $sTemplate = replace_tablespace(
424         '{ts:aux-data}',
425         CONST_Tablespace_Aux_Data,
426         $sTemplate
427     );
428     $sTemplate = replace_tablespace(
429         '{ts:aux-index}',
430         CONST_Tablespace_Aux_Index,
431         $sTemplate
432     );
433     pgsqlRunScript($sTemplate, false);
434
435     $aDBInstances = array();
436     for ($i = 0; $i < $iInstances; $i++) {
437         $aDBInstances[$i] =& getDB(true);
438     }
439
440     foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
441         echo $sFile.': ';
442         $hFile = fopen($sFile, "r");
443         $sSQL = fgets($hFile, 100000);
444         $iLines = 0;
445
446         while (true) {
447             for ($i = 0; $i < $iInstances; $i++) {
448                 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
449                     while (pg_get_result($aDBInstances[$i]->connection));
450                     $sSQL = fgets($hFile, 100000);
451                     if (!$sSQL) break 2;
452                     if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
453                     $iLines++;
454                     if ($iLines == 1000) {
455                         echo ".";
456                         $iLines = 0;
457                     }
458                 }
459             }
460             usleep(10);
461         }
462
463         fclose($hFile);
464
465         $bAnyBusy = true;
466         while ($bAnyBusy) {
467             $bAnyBusy = false;
468             for ($i = 0; $i < $iInstances; $i++) {
469                 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
470             }
471             usleep(10);
472         }
473         echo "\n";
474     }
475
476     info('Creating indexes on Tiger data');
477     $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
478     $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
479     $sTemplate = replace_tablespace(
480         '{ts:aux-data}',
481         CONST_Tablespace_Aux_Data,
482         $sTemplate
483     );
484     $sTemplate = replace_tablespace(
485         '{ts:aux-index}',
486         CONST_Tablespace_Aux_Index,
487         $sTemplate
488     );
489     pgsqlRunScript($sTemplate, false);
490 }
491
492 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
493     info('Calculate Postcodes');
494     $bDidSomething = true;
495     $oDB =& getDB();
496     if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
497         fail(pg_last_error($oDB->connection));
498     }
499
500     $sSQL  = "INSERT INTO location_postcode";
501     $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
502     $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
503     $sSQL .= "       upper(trim (both ' ' from address->'postcode')) as pc,";
504     $sSQL .= "       ST_Centroid(ST_Collect(ST_Centroid(geometry)))";
505     $sSQL .= "  FROM placex";
506     $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
507     $sSQL .= "       AND geometry IS NOT null";
508     $sSQL .= " GROUP BY country_code, pc";
509
510     if (!pg_query($oDB->connection, $sSQL)) {
511         fail(pg_last_error($oDB->connection));
512     }
513
514     if (CONST_Use_Extra_US_Postcodes) {
515         // only add postcodes that are not yet available in OSM
516         $sSQL  = "INSERT INTO location_postcode";
517         $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
518         $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
519         $sSQL .= "       ST_SetSRID(ST_Point(x,y),4326)";
520         $sSQL .= "  FROM us_postcode WHERE postcode NOT IN";
521         $sSQL .= "        (SELECT postcode FROM location_postcode";
522         $sSQL .= "          WHERE country_code = 'us')";
523
524         if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
525     }
526
527     // add missing postcodes for GB (if available)
528     $sSQL  = "INSERT INTO location_postcode";
529     $sSQL .= " (place_id, indexed_status, country_code, postcode, geometry) ";
530     $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
531     $sSQL .= "  FROM gb_postcode WHERE postcode NOT IN";
532     $sSQL .= "           (SELECT postcode FROM location_postcode";
533     $sSQL .= "             WHERE country_code = 'gb')";
534     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
535
536     if (!$aCMDResult['all']) {
537         $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
538         $sSQL .= "and word NOT IN (SELECT postcode FROM location_postcode)";
539         if (!pg_query($oDB->connection, $sSQL)) {
540             fail(pg_last_error($oDB->connection));
541         }
542     }
543     $sSQL = "SELECT count(getorcreate_postcode_id(v)) FROM ";
544     $sSQL .= "(SELECT distinct(postcode) as v FROM location_postcode) p";
545
546     if (!pg_query($oDB->connection, $sSQL)) {
547         fail(pg_last_error($oDB->connection));
548     }
549 }
550
551 if ($aCMDResult['osmosis-init']) {
552     $bDidSomething = true;
553     echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
554 }
555
556 if ($aCMDResult['index'] || $aCMDResult['all']) {
557     $bDidSomething = true;
558     $sOutputFile = '';
559     $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
560     info('Index ranks 0 - 4');
561     passthruCheckReturn($sBaseCmd.' -R 4');
562     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
563     info('Index ranks 5 - 25');
564     passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
565     if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
566     info('Index ranks 26 - 30');
567     passthruCheckReturn($sBaseCmd.' -r 26');
568
569     info('Index postcodes');
570     $oDB =& getDB();
571     $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
572     if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
573 }
574
575 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
576     info('Create Search indices');
577     $bDidSomething = true;
578
579     $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
580     $sTemplate = replace_tablespace(
581         '{ts:address-index}',
582         CONST_Tablespace_Address_Index,
583         $sTemplate
584     );
585     $sTemplate = replace_tablespace(
586         '{ts:search-index}',
587         CONST_Tablespace_Search_Index,
588         $sTemplate
589     );
590     $sTemplate = replace_tablespace(
591         '{ts:aux-index}',
592         CONST_Tablespace_Aux_Index,
593         $sTemplate
594     );
595
596     pgsqlRunScript($sTemplate);
597 }
598
599 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
600     info('Create search index for default country names');
601     $bDidSomething = true;
602
603     pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
604     pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
605     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");
606     pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
607
608     $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 ';
609     if (CONST_Languages) {
610         $sSQL .= 'in ';
611         $sDelim = '(';
612         foreach (explode(',', CONST_Languages) as $sLang) {
613             $sSQL .= $sDelim."'name:$sLang'";
614             $sDelim = ',';
615         }
616         $sSQL .= ')';
617     } else {
618         // all include all simple name tags
619         $sSQL .= "like 'name:%'";
620     }
621     $sSQL .= ') v';
622     pgsqlRunScript($sSQL);
623 }
624
625 if ($aCMDResult['drop']) {
626     info('Drop tables only required for updates');
627     // The implementation is potentially a bit dangerous because it uses
628     // a positive selection of tables to keep, and deletes everything else.
629     // Including any tables that the unsuspecting user might have manually
630     // created. USE AT YOUR OWN PERIL.
631     $bDidSomething = true;
632
633     // tables we want to keep. everything else goes.
634     $aKeepTables = array(
635                     "*columns",
636                     "import_polygon_*",
637                     "import_status",
638                     "place_addressline",
639                     "location_property*",
640                     "placex",
641                     "search_name",
642                     "seq_*",
643                     "word",
644                     "query_log",
645                     "new_query_log",
646                     "gb_postcode",
647                     "spatial_ref_sys",
648                     "country_name",
649                     "place_classtype_*"
650                    );
651
652     $oDB =& getDB();
653     $aDropTables = array();
654     $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
655
656     foreach ($aHaveTables as $sTable) {
657         $bFound = false;
658         foreach ($aKeepTables as $sKeep) {
659             if (fnmatch($sKeep, $sTable)) {
660                 $bFound = true;
661                 break;
662             }
663         }
664         if (!$bFound) array_push($aDropTables, $sTable);
665     }
666
667     foreach ($aDropTables as $sDrop) {
668         if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
669         @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
670         // ignore warnings/errors as they might be caused by a table having
671         // been deleted already by CASCADE
672     }
673
674     if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
675         if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
676         unlink(CONST_Osm2pgsql_Flatnode_File);
677     }
678 }
679
680 if (!$bDidSomething) {
681     showUsage($aCMDOptions, true);
682 } else {
683     echo "Summary of warnings:\n\n";
684     repeatWarnings();
685     echo "\n";
686     info('Setup finished.');
687 }
688
689
690 function pgsqlRunScriptFile($sFilename)
691 {
692     global $aCMDResult;
693     if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
694
695     // Convert database DSN to psql parameters
696     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
697     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
698     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
699     if (!$aCMDResult['verbose']) {
700         $sCMD .= ' -q';
701     }
702
703     $ahGzipPipes = null;
704     if (preg_match('/\\.gz$/', $sFilename)) {
705         $aDescriptors = array(
706                          0 => array('pipe', 'r'),
707                          1 => array('pipe', 'w'),
708                          2 => array('file', '/dev/null', 'a')
709                         );
710         $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
711         if (!is_resource($hGzipProcess)) fail('unable to start zcat');
712         $aReadPipe = $ahGzipPipes[1];
713         fclose($ahGzipPipes[0]);
714     } else {
715         $sCMD .= ' -f '.$sFilename;
716         $aReadPipe = array('pipe', 'r');
717     }
718
719     $aDescriptors = array(
720                      0 => $aReadPipe,
721                      1 => array('pipe', 'w'),
722                      2 => array('file', '/dev/null', 'a')
723                     );
724     $ahPipes = null;
725     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
726     if (!is_resource($hProcess)) fail('unable to start pgsql');
727
728
729     // TODO: error checking
730     while (!feof($ahPipes[1])) {
731         echo fread($ahPipes[1], 4096);
732     }
733     fclose($ahPipes[1]);
734
735     $iReturn = proc_close($hProcess);
736     if ($iReturn > 0) {
737         fail("pgsql returned with error code ($iReturn)");
738     }
739     if ($ahGzipPipes) {
740         fclose($ahGzipPipes[1]);
741         proc_close($hGzipProcess);
742     }
743 }
744
745 function pgsqlRunScript($sScript, $bfatal = true)
746 {
747     global $aCMDResult;
748     // Convert database DSN to psql parameters
749     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
750     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
751     $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
752     if (!$aCMDResult['verbose']) {
753         $sCMD .= ' -q';
754     }
755     if ($bfatal && !$aCMDResult['ignore-errors'])
756         $sCMD .= ' -v ON_ERROR_STOP=1';
757     $aDescriptors = array(
758                      0 => array('pipe', 'r'),
759                      1 => STDOUT,
760                      2 => STDERR
761                     );
762     $ahPipes = null;
763     $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
764     if (!is_resource($hProcess)) fail('unable to start pgsql');
765
766     while (strlen($sScript)) {
767         $written = fwrite($ahPipes[0], $sScript);
768         if ($written <= 0) break;
769         $sScript = substr($sScript, $written);
770     }
771     fclose($ahPipes[0]);
772     $iReturn = proc_close($hProcess);
773     if ($bfatal && $iReturn > 0) {
774         fail("pgsql returned with error code ($iReturn)");
775     }
776 }
777
778 function pgsqlRunPartitionScript($sTemplate)
779 {
780     global $aCMDResult;
781     $oDB =& getDB();
782
783     $sSQL = 'select distinct partition from country_name';
784     $aPartitions = chksql($oDB->getCol($sSQL));
785     if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
786
787     preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
788     foreach ($aMatches as $aMatch) {
789         $sResult = '';
790         foreach ($aPartitions as $sPartitionName) {
791             $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
792         }
793         $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
794     }
795
796     pgsqlRunScript($sTemplate);
797 }
798
799 function pgsqlRunRestoreData($sDumpFile)
800 {
801     // Convert database DSN to psql parameters
802     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
803     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
804     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
805
806     $aDescriptors = array(
807                      0 => array('pipe', 'r'),
808                      1 => array('pipe', 'w'),
809                      2 => array('file', '/dev/null', 'a')
810                     );
811     $ahPipes = null;
812     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
813     if (!is_resource($hProcess)) fail('unable to start pg_restore');
814
815     fclose($ahPipes[0]);
816
817     // TODO: error checking
818     while (!feof($ahPipes[1])) {
819         echo fread($ahPipes[1], 4096);
820     }
821     fclose($ahPipes[1]);
822
823     $iReturn = proc_close($hProcess);
824 }
825
826 function pgsqlRunDropAndRestore($sDumpFile)
827 {
828     // Convert database DSN to psql parameters
829     $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
830     if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
831     $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
832
833     $aDescriptors = array(
834                      0 => array('pipe', 'r'),
835                      1 => array('pipe', 'w'),
836                      2 => array('file', '/dev/null', 'a')
837                     );
838     $ahPipes = null;
839     $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
840     if (!is_resource($hProcess)) fail('unable to start pg_restore');
841
842     fclose($ahPipes[0]);
843
844     // TODO: error checking
845     while (!feof($ahPipes[1])) {
846         echo fread($ahPipes[1], 4096);
847     }
848     fclose($ahPipes[1]);
849
850     $iReturn = proc_close($hProcess);
851 }
852
853 function passthruCheckReturn($cmd)
854 {
855     $result = -1;
856     passthru($cmd, $result);
857     if ($result != 0) fail('Error executing external command: '.$cmd);
858 }
859
860 function replace_tablespace($sTemplate, $sTablespace, $sSql)
861 {
862     if ($sTablespace) {
863         $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
864     } else {
865         $sSql = str_replace($sTemplate, '', $sSql);
866     }
867
868     return $sSql;
869 }
870
871 function create_sql_functions($aCMDResult)
872 {
873     $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
874     $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
875     if ($aCMDResult['enable-diff-updates']) {
876         $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
877     }
878     if ($aCMDResult['enable-debug-statements']) {
879         $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
880     }
881     if (CONST_Limit_Reindexing) {
882         $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
883     }
884     if (!CONST_Use_US_Tiger_Data) {
885         $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
886     }
887     if (!CONST_Use_Aux_Location_data) {
888         $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
889     }
890     pgsqlRunScript($sTemplate);
891 }