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