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