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