4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
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'),
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)'),
18 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
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('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
43 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
45 $bDidSomething = false;
47 // Check if osm-file is set and points to a valid file if --all or --import-data is given
48 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
49 if (!isset($aCMDResult['osm-file'])) {
50 fail('missing --osm-file for data import');
53 if (!file_exists($aCMDResult['osm-file'])) {
54 fail('the path supplied to --osm-file does not exist');
57 if (!is_readable($aCMDResult['osm-file'])) {
58 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
63 // This is a pretty hard core default - the number of processors in the box - 1
64 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
65 if ($iInstances < 1) {
67 echo "WARNING: resetting threads to $iInstances\n";
69 if ($iInstances > getProcessorCount()) {
70 $iInstances = getProcessorCount();
71 echo "WARNING: resetting threads to $iInstances\n";
74 // Assume we can steal all the cache memory in the box (unless told otherwise)
75 if (isset($aCMDResult['osm2pgsql-cache'])) {
76 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
78 $iCacheMemory = getCacheMemoryMB();
81 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
82 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
84 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
86 $bDidSomething = true;
87 $oDB = DB::connect(CONST_Database_DSN, false);
88 if (!PEAR::isError($oDB)) {
89 fail('database already exists ('.CONST_Database_DSN.')');
91 passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
94 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
96 $bDidSomething = true;
97 // TODO: path detection, detection memory, etc.
101 $fPostgresVersion = getPostgresVersion($oDB);
102 echo 'Postgres version found: '.$fPostgresVersion."\n";
104 if ($fPostgresVersion < 9.1) {
105 fail("Minimum supported version of Postgresql is 9.1.");
108 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
109 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
111 // For extratags and namedetails the hstore_to_json converter is
112 // needed which is only available from Postgresql 9.3+. For older
113 // versions add a dummy function that returns nothing.
114 $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
116 if ($iNumFunc == 0) {
117 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
118 echo "WARNING: Postgresql is too old. extratags and namedetails API not available.";
121 $fPostgisVersion = getPostgisVersion($oDB);
122 echo 'Postgis version found: '.$fPostgisVersion."\n";
124 if ($fPostgisVersion < 2.1) {
125 // Function was renamed in 2.1 and throws an annoying deprecation warning
126 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
129 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
130 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
131 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql');
132 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
133 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
134 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
136 echo "WARNING: external UK postcode table not found.\n";
138 if (CONST_Use_Extra_US_Postcodes) {
139 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
142 if ($aCMDResult['no-partitions']) {
143 pgsqlRunScript('update country_name set partition = 0');
146 // the following will be needed by create_functions later but
147 // is only defined in the subsequently called create_tables.
148 // Create dummies here that will be overwritten by the proper
149 // versions in create-tables.
150 pgsqlRunScript('CREATE TABLE place_boundingbox ()');
151 pgsqlRunScript('create type wikipedia_article_match as ()');
154 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
156 $bDidSomething = true;
158 $osm2pgsql = CONST_Osm2pgsql_Binary;
159 if (!file_exists($osm2pgsql)) {
160 echo "Please download and build osm2pgsql.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
161 fail("osm2pgsql not found in '$osm2pgsql'");
164 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
165 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
167 if (CONST_Tablespace_Osm2pgsql_Data)
168 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
169 if (CONST_Tablespace_Osm2pgsql_Index)
170 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
171 if (CONST_Tablespace_Place_Data)
172 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
173 if (CONST_Tablespace_Place_Index)
174 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
175 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
176 $osm2pgsql .= ' -C '.$iCacheMemory;
177 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
178 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
179 passthruCheckReturn($osm2pgsql);
182 if (!chksql($oDB->getRow('select * from place limit 1'))) {
187 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
189 $bDidSomething = true;
190 if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) fail("nominatim module not built");
191 create_sql_functions($aCMDResult);
194 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
195 $bDidSomething = true;
198 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
199 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
200 $sTemplate = replace_tablespace('{ts:address-data}',
201 CONST_Tablespace_Address_Data, $sTemplate);
202 $sTemplate = replace_tablespace('{ts:address-index}',
203 CONST_Tablespace_Address_Index, $sTemplate);
204 $sTemplate = replace_tablespace('{ts:search-data}',
205 CONST_Tablespace_Search_Data, $sTemplate);
206 $sTemplate = replace_tablespace('{ts:search-index}',
207 CONST_Tablespace_Search_Index, $sTemplate);
208 $sTemplate = replace_tablespace('{ts:aux-data}',
209 CONST_Tablespace_Aux_Data, $sTemplate);
210 $sTemplate = replace_tablespace('{ts:aux-index}',
211 CONST_Tablespace_Aux_Index, $sTemplate);
212 pgsqlRunScript($sTemplate, false);
214 // re-run the functions
216 create_sql_functions($aCMDResult);
219 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
220 echo "Partition Tables\n";
221 $bDidSomething = true;
223 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
224 $sTemplate = replace_tablespace('{ts:address-data}',
225 CONST_Tablespace_Address_Data, $sTemplate);
226 $sTemplate = replace_tablespace('{ts:address-index}',
227 CONST_Tablespace_Address_Index, $sTemplate);
228 $sTemplate = replace_tablespace('{ts:search-data}',
229 CONST_Tablespace_Search_Data, $sTemplate);
230 $sTemplate = replace_tablespace('{ts:search-index}',
231 CONST_Tablespace_Search_Index, $sTemplate);
232 $sTemplate = replace_tablespace('{ts:aux-data}',
233 CONST_Tablespace_Aux_Data, $sTemplate);
234 $sTemplate = replace_tablespace('{ts:aux-index}',
235 CONST_Tablespace_Aux_Index, $sTemplate);
237 pgsqlRunPartitionScript($sTemplate);
241 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
242 echo "Partition Functions\n";
243 $bDidSomething = true;
245 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
247 pgsqlRunPartitionScript($sTemplate);
250 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
251 $bDidSomething = true;
252 $sWikiArticlesFile = CONST_BasePath.'/data/wikipedia_article.sql.bin';
253 $sWikiRedirectsFile = CONST_BasePath.'/data/wikipedia_redirect.sql.bin';
254 if (file_exists($sWikiArticlesFile)) {
255 echo "Importing wikipedia articles...";
256 pgsqlRunDropAndRestore($sWikiArticlesFile);
259 echo "WARNING: wikipedia article dump file not found - places will have default importance\n";
261 if (file_exists($sWikiRedirectsFile)) {
262 echo "Importing wikipedia redirects...";
263 pgsqlRunDropAndRestore($sWikiRedirectsFile);
266 echo "WARNING: wikipedia redirect dump file not found - some place importance values may be missing\n";
271 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
272 echo "Drop old Data\n";
273 $bDidSomething = true;
276 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
278 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
280 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
282 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
284 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
286 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
288 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
290 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
292 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
294 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
297 $sSQL = 'select distinct partition from country_name';
298 $aPartitions = chksql($oDB->getCol($sSQL));
299 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
300 foreach ($aPartitions as $sPartition) {
301 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
305 // used by getorcreate_word_id to ignore frequent partial words
306 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));
309 // pre-create the word list
310 if (!$aCMDResult['disable-token-precalc']) {
311 echo "Loading word list\n";
312 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
316 $aDBInstances = array();
317 $iLoadThreads = max(1, $iInstances - 1);
318 for ($i = 0; $i < $iLoadThreads; $i++) {
319 $aDBInstances[$i] =& getDB(true);
320 $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
321 $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
322 $sSQL .= 'geometry) select * from place where osm_id % '.$iLoadThreads.' = '.$i;
323 $sSQL .= " and not (class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString')";
324 if ($aCMDResult['verbose']) echo "$sSQL\n";
325 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
327 // last thread for interpolation lines
328 $aDBInstances[$iLoadThreads] =& getDB(true);
329 $sSQL = 'select insert_osmline (osm_id, housenumber, street, addr_place, postcode, country_code, ';
330 $sSQL .= 'geometry) from place where ';
331 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
332 if ($aCMDResult['verbose']) echo "$sSQL\n";
333 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
338 for ($i = 0; $i <= $iLoadThreads; $i++) {
339 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
345 echo "Reanalysing database...\n";
346 pgsqlRunScript('ANALYSE');
349 if ($aCMDResult['import-tiger-data']) {
350 $bDidSomething = true;
352 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
353 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
354 $sTemplate = replace_tablespace('{ts:aux-data}',
355 CONST_Tablespace_Aux_Data, $sTemplate);
356 $sTemplate = replace_tablespace('{ts:aux-index}',
357 CONST_Tablespace_Aux_Index, $sTemplate);
358 pgsqlRunScript($sTemplate, false);
360 $aDBInstances = array();
361 for ($i = 0; $i < $iInstances; $i++) {
362 $aDBInstances[$i] =& getDB(true);
365 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
367 $hFile = fopen($sFile, "r");
368 $sSQL = fgets($hFile, 100000);
372 for ($i = 0; $i < $iInstances; $i++) {
373 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
374 while (pg_get_result($aDBInstances[$i]->connection));
375 $sSQL = fgets($hFile, 100000);
377 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
379 if ($iLines == 1000) {
393 for ($i = 0; $i < $iInstances; $i++) {
394 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
401 echo "Creating indexes\n";
402 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
403 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
404 $sTemplate = replace_tablespace('{ts:aux-data}',
405 CONST_Tablespace_Aux_Data, $sTemplate);
406 $sTemplate = replace_tablespace('{ts:aux-index}',
407 CONST_Tablespace_Aux_Index, $sTemplate);
408 pgsqlRunScript($sTemplate, false);
411 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
412 $bDidSomething = true;
414 if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
415 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
416 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
417 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
418 $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
419 $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
420 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
422 if (CONST_Use_Extra_US_Postcodes) {
423 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
424 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
425 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
426 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
430 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) { // no use doing osmosis-init when dropping update tables
431 $bDidSomething = true;
434 if (!file_exists(CONST_Osmosis_Binary)) {
435 echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
436 if (!$aCMDResult['all']) {
437 fail("osmosis not found in '".CONST_Osmosis_Binary."'");
440 if (file_exists(CONST_InstallPath.'/settings/configuration.txt')) {
441 echo "settings/configuration.txt already exists\n";
443 passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
444 // update osmosis configuration.txt with our settings
445 passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
446 passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
449 // Find the last node in the DB
450 $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
452 // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
453 $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
454 $sLastNodeXML = file_get_contents($sLastNodeURL);
455 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);
456 $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
458 // Search for the correct state file - uses file timestamps so need to sort by date descending
459 $sRepURL = CONST_Replication_Url."/";
460 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
461 // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
462 // planet.openstreetmap.org: <a href="273/">273/</a> 2013-03-11 07:41 -
463 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);
465 $aPrevRepMatch = false;
466 foreach ($aRepMatches as $aRepMatch) {
467 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
468 $aPrevRepMatch = $aRepMatch;
470 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
472 $sRepURL .= $aRepMatch[1];
473 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
474 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);
475 $aPrevRepMatch = false;
476 foreach ($aRepMatches as $aRepMatch) {
477 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
478 $aPrevRepMatch = $aRepMatch;
480 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
482 $sRepURL .= $aRepMatch[1];
483 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
484 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);
485 $aPrevRepMatch = false;
486 foreach ($aRepMatches as $aRepMatch) {
487 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
488 $aPrevRepMatch = $aRepMatch;
490 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
492 $sRepURL .= $aRepMatch[1].'.state.txt';
493 echo "Getting state file: $sRepURL\n";
494 $sStateFile = file_get_contents($sRepURL);
495 if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
496 file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
497 echo "Updating DB status\n";
498 pg_query($oDB->connection, 'TRUNCATE import_status');
499 $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
500 pg_query($oDB->connection, $sSQL);
502 if (!$aCMDResult['all']) {
503 fail("Cannot read state file directory.");
509 if ($aCMDResult['index'] || $aCMDResult['all']) {
510 $bDidSomething = true;
512 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
513 passthruCheckReturn($sBaseCmd.' -R 4');
514 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
515 passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
516 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
517 passthruCheckReturn($sBaseCmd.' -r 26');
520 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
521 echo "Search indices\n";
522 $bDidSomething = true;
524 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
525 $sTemplate = replace_tablespace('{ts:address-index}',
526 CONST_Tablespace_Address_Index, $sTemplate);
527 $sTemplate = replace_tablespace('{ts:search-index}',
528 CONST_Tablespace_Search_Index, $sTemplate);
529 $sTemplate = replace_tablespace('{ts:aux-index}',
530 CONST_Tablespace_Aux_Index, $sTemplate);
532 pgsqlRunScript($sTemplate);
535 if ($aCMDResult['drop']) {
536 // The implementation is potentially a bit dangerous because it uses
537 // a positive selection of tables to keep, and deletes everything else.
538 // Including any tables that the unsuspecting user might have manually
539 // created. USE AT YOUR OWN PERIL.
540 $bDidSomething = true;
542 // tables we want to keep. everything else goes.
543 $aKeepTables = array(
548 "location_property*",
562 $aDropTables = array();
563 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
565 foreach ($aHaveTables as $sTable) {
567 foreach ($aKeepTables as $sKeep) {
568 if (fnmatch($sKeep, $sTable)) {
573 if (!$bFound) array_push($aDropTables, $sTable);
576 foreach ($aDropTables as $sDrop) {
577 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
578 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
579 // ignore warnings/errors as they might be caused by a table having
580 // been deleted already by CASCADE
583 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
584 if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
585 unlink(CONST_Osm2pgsql_Flatnode_File);
589 if (!$bDidSomething) {
590 showUsage($aCMDOptions, true);
592 echo "Setup finished.\n";
595 function pgsqlRunScriptFile($sFilename)
597 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
599 // Convert database DSN to psql parameters
600 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
601 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
602 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
605 if (preg_match('/\\.gz$/', $sFilename)) {
606 $aDescriptors = array(
607 0 => array('pipe', 'r'),
608 1 => array('pipe', 'w'),
609 2 => array('file', '/dev/null', 'a')
611 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
612 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
613 $aReadPipe = $ahGzipPipes[1];
614 fclose($ahGzipPipes[0]);
616 $sCMD .= ' -f '.$sFilename;
617 $aReadPipe = array('pipe', 'r');
620 $aDescriptors = array(
622 1 => array('pipe', 'w'),
623 2 => array('file', '/dev/null', 'a')
626 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
627 if (!is_resource($hProcess)) fail('unable to start pgsql');
630 // TODO: error checking
631 while (!feof($ahPipes[1])) {
632 echo fread($ahPipes[1], 4096);
636 $iReturn = proc_close($hProcess);
638 fail("pgsql returned with error code ($iReturn)");
641 fclose($ahGzipPipes[1]);
642 proc_close($hGzipProcess);
647 function pgsqlRunScript($sScript, $bfatal = true)
650 // Convert database DSN to psql parameters
651 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
652 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
653 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
654 if ($bfatal && !$aCMDResult['ignore-errors'])
655 $sCMD .= ' -v ON_ERROR_STOP=1';
656 $aDescriptors = array(
657 0 => array('pipe', 'r'),
662 $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
663 if (!is_resource($hProcess)) fail('unable to start pgsql');
665 while (strlen($sScript)) {
666 $written = fwrite($ahPipes[0], $sScript);
667 if ($written <= 0) break;
668 $sScript = substr($sScript, $written);
671 $iReturn = proc_close($hProcess);
672 if ($bfatal && $iReturn > 0) {
673 fail("pgsql returned with error code ($iReturn)");
677 function pgsqlRunPartitionScript($sTemplate)
682 $sSQL = 'select distinct partition from country_name';
683 $aPartitions = chksql($oDB->getCol($sSQL));
684 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
686 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
687 foreach ($aMatches as $aMatch) {
689 foreach ($aPartitions as $sPartitionName) {
690 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
692 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
695 pgsqlRunScript($sTemplate);
698 function pgsqlRunRestoreData($sDumpFile)
700 // Convert database DSN to psql parameters
701 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
702 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
703 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
705 $aDescriptors = array(
706 0 => array('pipe', 'r'),
707 1 => array('pipe', 'w'),
708 2 => array('file', '/dev/null', 'a')
711 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
712 if (!is_resource($hProcess)) fail('unable to start pg_restore');
716 // TODO: error checking
717 while (!feof($ahPipes[1])) {
718 echo fread($ahPipes[1], 4096);
722 $iReturn = proc_close($hProcess);
725 function pgsqlRunDropAndRestore($sDumpFile)
727 // Convert database DSN to psql parameters
728 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
729 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
730 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
732 $aDescriptors = array(
733 0 => array('pipe', 'r'),
734 1 => array('pipe', 'w'),
735 2 => array('file', '/dev/null', 'a')
738 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
739 if (!is_resource($hProcess)) fail('unable to start pg_restore');
743 // TODO: error checking
744 while (!feof($ahPipes[1])) {
745 echo fread($ahPipes[1], 4096);
749 $iReturn = proc_close($hProcess);
752 function passthruCheckReturn($cmd)
755 passthru($cmd, $result);
756 if ($result != 0) fail('Error executing external command: '.$cmd);
759 function replace_tablespace($sTemplate, $sTablespace, $sSql)
762 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
765 $sSql = str_replace($sTemplate, '', $sSql);
771 function create_sql_functions($aCMDResult)
773 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
774 $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
775 if ($aCMDResult['enable-diff-updates']) {
776 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
778 if ($aCMDResult['enable-debug-statements']) {
779 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
781 if (CONST_Limit_Reindexing) {
782 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
784 if (!CONST_Use_US_Tiger_Data) {
785 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
787 if (!CONST_Use_Aux_Location_data) {
788 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
790 pgsqlRunScript($sTemplate);