4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
9 "Create and setup nominatim search system",
10 array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
11 array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
12 array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
14 array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
15 array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
17 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
19 array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
20 array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
21 array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
22 array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
23 array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
24 array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
25 array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
26 array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
27 array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
28 array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
29 array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
30 array('no-partitions', '', 0, 1, 0, 0, 'bool', "Do not partition search indices (speeds up import of single country extracts)"),
31 array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
32 array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
33 array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
34 array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
35 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
36 array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
37 array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
38 array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
39 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
40 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
42 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
44 $bDidSomething = false;
46 // Check if osm-file is set and points to a valid file if --all or --import-data is given
47 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
48 if (!isset($aCMDResult['osm-file'])) {
49 fail('missing --osm-file for data import');
52 if (!file_exists($aCMDResult['osm-file'])) {
53 fail('the path supplied to --osm-file does not exist');
56 if (!is_readable($aCMDResult['osm-file'])) {
57 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
62 // This is a pretty hard core default - the number of processors in the box - 1
63 $iInstances = isset($aCMDResult['threads'])?$aCMDResult['threads']:(getProcessorCount()-1);
64 if ($iInstances < 1) {
66 echo "WARNING: resetting threads to $iInstances\n";
68 if ($iInstances > getProcessorCount()) {
69 $iInstances = getProcessorCount();
70 echo "WARNING: resetting threads to $iInstances\n";
73 // Assume we can steal all the cache memory in the box (unless told otherwise)
74 if (isset($aCMDResult['osm2pgsql-cache'])) {
75 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
77 $iCacheMemory = getCacheMemoryMB();
80 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
81 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
83 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
85 $bDidSomething = true;
86 $oDB = DB::connect(CONST_Database_DSN, false);
87 if (!PEAR::isError($oDB)) {
88 fail('database already exists ('.CONST_Database_DSN.')');
90 passthruCheckReturn('createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database']);
93 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
95 $bDidSomething = true;
96 // TODO: path detection, detection memory, etc.
100 $fPostgresVersion = getPostgresVersion($oDB);
101 echo 'Postgres version found: '.$fPostgresVersion."\n";
103 if ($fPostgresVersion < 9.1) {
104 fail("Minimum supported version of Postgresql is 9.1.");
107 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
108 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
110 // For extratags and namedetails the hstore_to_json converter is
111 // needed which is only available from Postgresql 9.3+. For older
112 // versions add a dummy function that returns nothing.
113 $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
115 if ($iNumFunc == 0) {
116 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
117 echo "WARNING: Postgresql is too old. extratags and namedetails API not available.";
120 $fPostgisVersion = getPostgisVersion($oDB);
121 echo 'Postgis version found: '.$fPostgisVersion."\n";
123 if ($fPostgisVersion < 2.1) {
124 // Function was renamed in 2.1 and throws an annoying deprecation warning
125 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
128 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
129 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
130 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql');
131 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
132 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
133 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
135 echo "WARNING: external UK postcode table not found.\n";
137 if (CONST_Use_Extra_US_Postcodes) {
138 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
141 if ($aCMDResult['no-partitions']) {
142 pgsqlRunScript('update country_name set partition = 0');
145 // the following will be needed by create_functions later but
146 // is only defined in the subsequently called create_tables.
147 // Create dummies here that will be overwritten by the proper
148 // versions in create-tables.
149 pgsqlRunScript('CREATE TABLE place_boundingbox ()');
150 pgsqlRunScript('create type wikipedia_article_match as ()');
153 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
155 $bDidSomething = true;
157 $osm2pgsql = CONST_Osm2pgsql_Binary;
158 if (!file_exists($osm2pgsql)) {
159 echo "Please download and build osm2pgsql.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
160 fail("osm2pgsql not found in '$osm2pgsql'");
163 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
164 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
166 if (CONST_Tablespace_Osm2pgsql_Data)
167 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
168 if (CONST_Tablespace_Osm2pgsql_Index)
169 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
170 if (CONST_Tablespace_Place_Data)
171 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
172 if (CONST_Tablespace_Place_Index)
173 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
174 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
175 $osm2pgsql .= ' -C '.$iCacheMemory;
176 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
177 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
178 passthruCheckReturn($osm2pgsql);
181 if (!chksql($oDB->getRow('select * from place limit 1'))) {
186 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
188 $bDidSomething = true;
189 if (!file_exists(CONST_InstallPath.'/module/nominatim.so')) fail("nominatim module not built");
190 create_sql_functions($aCMDResult);
193 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
194 $bDidSomething = true;
197 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
198 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
199 $sTemplate = replace_tablespace('{ts:address-data}',
200 CONST_Tablespace_Address_Data, $sTemplate);
201 $sTemplate = replace_tablespace('{ts:address-index}',
202 CONST_Tablespace_Address_Index, $sTemplate);
203 $sTemplate = replace_tablespace('{ts:search-data}',
204 CONST_Tablespace_Search_Data, $sTemplate);
205 $sTemplate = replace_tablespace('{ts:search-index}',
206 CONST_Tablespace_Search_Index, $sTemplate);
207 $sTemplate = replace_tablespace('{ts:aux-data}',
208 CONST_Tablespace_Aux_Data, $sTemplate);
209 $sTemplate = replace_tablespace('{ts:aux-index}',
210 CONST_Tablespace_Aux_Index, $sTemplate);
211 pgsqlRunScript($sTemplate, false);
213 // re-run the functions
215 create_sql_functions($aCMDResult);
218 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
219 echo "Partition Tables\n";
220 $bDidSomething = true;
222 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
223 $sTemplate = replace_tablespace('{ts:address-data}',
224 CONST_Tablespace_Address_Data, $sTemplate);
225 $sTemplate = replace_tablespace('{ts:address-index}',
226 CONST_Tablespace_Address_Index, $sTemplate);
227 $sTemplate = replace_tablespace('{ts:search-data}',
228 CONST_Tablespace_Search_Data, $sTemplate);
229 $sTemplate = replace_tablespace('{ts:search-index}',
230 CONST_Tablespace_Search_Index, $sTemplate);
231 $sTemplate = replace_tablespace('{ts:aux-data}',
232 CONST_Tablespace_Aux_Data, $sTemplate);
233 $sTemplate = replace_tablespace('{ts:aux-index}',
234 CONST_Tablespace_Aux_Index, $sTemplate);
236 pgsqlRunPartitionScript($sTemplate);
240 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
241 echo "Partition Functions\n";
242 $bDidSomething = true;
244 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
246 pgsqlRunPartitionScript($sTemplate);
249 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
250 $bDidSomething = true;
251 $sWikiArticlesFile = CONST_BasePath.'/data/wikipedia_article.sql.bin';
252 $sWikiRedirectsFile = CONST_BasePath.'/data/wikipedia_redirect.sql.bin';
253 if (file_exists($sWikiArticlesFile)) {
254 echo "Importing wikipedia articles...";
255 pgsqlRunDropAndRestore($sWikiArticlesFile);
258 echo "WARNING: wikipedia article dump file not found - places will have default importance\n";
260 if (file_exists($sWikiRedirectsFile)) {
261 echo "Importing wikipedia redirects...";
262 pgsqlRunDropAndRestore($sWikiRedirectsFile);
265 echo "WARNING: wikipedia redirect dump file not found - some place importance values may be missing\n";
270 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
271 echo "Drop old Data\n";
272 $bDidSomething = true;
275 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
277 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
279 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
281 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
283 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
285 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
287 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
289 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
291 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
293 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
296 $sSQL = 'select distinct partition from country_name';
297 $aPartitions = chksql($oDB->getCol($sSQL));
298 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
299 foreach ($aPartitions as $sPartition) {
300 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
304 // used by getorcreate_word_id to ignore frequent partial words
305 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));
308 // pre-create the word list
309 if (!$aCMDResult['disable-token-precalc']) {
310 echo "Loading word list\n";
311 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
315 $aDBInstances = array();
316 $iLoadThreads = max(1, $iInstances - 1);
317 for ($i = 0; $i < $iLoadThreads; $i++) {
318 $aDBInstances[$i] =& getDB(true);
319 $sSQL = 'insert into placex (osm_type, osm_id, class, type, name, admin_level, ';
320 $sSQL .= 'housenumber, street, addr_place, isin, postcode, country_code, extratags, ';
321 $sSQL .= 'geometry) select * from place where osm_id % '.$iLoadThreads.' = '.$i;
322 $sSQL .= " and not (class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString')";
323 if ($aCMDResult['verbose']) echo "$sSQL\n";
324 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
326 // last thread for interpolation lines
327 $aDBInstances[$iLoadThreads] =& getDB(true);
328 $sSQL = 'select insert_osmline (osm_id, housenumber, street, addr_place, postcode, country_code, ';
329 $sSQL .= 'geometry) from place where ';
330 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
331 if ($aCMDResult['verbose']) echo "$sSQL\n";
332 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
337 for ($i = 0; $i <= $iLoadThreads; $i++) {
338 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
344 echo "Reanalysing database...\n";
345 pgsqlRunScript('ANALYSE');
348 if ($aCMDResult['import-tiger-data']) {
349 $bDidSomething = true;
351 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
352 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
353 $sTemplate = replace_tablespace('{ts:aux-data}',
354 CONST_Tablespace_Aux_Data, $sTemplate);
355 $sTemplate = replace_tablespace('{ts:aux-index}',
356 CONST_Tablespace_Aux_Index, $sTemplate);
357 pgsqlRunScript($sTemplate, false);
359 $aDBInstances = array();
360 for ($i = 0; $i < $iInstances; $i++) {
361 $aDBInstances[$i] =& getDB(true);
364 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
366 $hFile = fopen($sFile, "r");
367 $sSQL = fgets($hFile, 100000);
371 for ($i = 0; $i < $iInstances; $i++) {
372 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
373 while (pg_get_result($aDBInstances[$i]->connection));
374 $sSQL = fgets($hFile, 100000);
376 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
378 if ($iLines == 1000) {
392 for ($i = 0; $i < $iInstances; $i++) {
393 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
400 echo "Creating indexes\n";
401 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
402 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
403 $sTemplate = replace_tablespace('{ts:aux-data}',
404 CONST_Tablespace_Aux_Data, $sTemplate);
405 $sTemplate = replace_tablespace('{ts:aux-index}',
406 CONST_Tablespace_Aux_Index, $sTemplate);
407 pgsqlRunScript($sTemplate, false);
410 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
411 $bDidSomething = true;
413 if (!pg_query($oDB->connection, 'DELETE from placex where osm_type=\'P\'')) fail(pg_last_error($oDB->connection));
414 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
415 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,calculated_country_code,";
416 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from (select calculated_country_code,postcode,";
417 $sSQL .= "avg(st_x(st_centroid(geometry))) as x,avg(st_y(st_centroid(geometry))) as y ";
418 $sSQL .= "from placex where postcode is not null group by calculated_country_code,postcode) as x";
419 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
421 if (CONST_Use_Extra_US_Postcodes) {
422 $sSQL = "insert into placex (osm_type,osm_id,class,type,postcode,calculated_country_code,geometry) ";
423 $sSQL .= "select 'P',nextval('seq_postcodes'),'place','postcode',postcode,'us',";
424 $sSQL .= "ST_SetSRID(ST_Point(x,y),4326) as geometry from us_postcode";
425 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
429 if ($aCMDResult['osmosis-init'] || ($aCMDResult['all'] && !$aCMDResult['drop'])) { // no use doing osmosis-init when dropping update tables
430 $bDidSomething = true;
433 if (!file_exists(CONST_Osmosis_Binary)) {
434 echo "Please download osmosis.\nIf it is already installed, check the path in your local settings (settings/local.php) file.\n";
435 if (!$aCMDResult['all']) {
436 fail("osmosis not found in '".CONST_Osmosis_Binary."'");
439 if (file_exists(CONST_InstallPath.'/settings/configuration.txt')) {
440 echo "settings/configuration.txt already exists\n";
442 passthru(CONST_Osmosis_Binary.' --read-replication-interval-init '.CONST_InstallPath.'/settings');
443 // update osmosis configuration.txt with our settings
444 passthru("sed -i 's!baseUrl=.*!baseUrl=".CONST_Replication_Url."!' ".CONST_InstallPath.'/settings/configuration.txt');
445 passthru("sed -i 's:maxInterval = .*:maxInterval = ".CONST_Replication_MaxInterval.":' ".CONST_InstallPath.'/settings/configuration.txt');
448 // Find the last node in the DB
449 $iLastOSMID = $oDB->getOne("select max(osm_id) from place where osm_type = 'N'");
451 // Lookup the timestamp that node was created (less 3 hours for margin for changsets to be closed)
452 $sLastNodeURL = 'http://www.openstreetmap.org/api/0.6/node/'.$iLastOSMID."/1";
453 $sLastNodeXML = file_get_contents($sLastNodeURL);
454 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);
455 $iLastNodeTimestamp = strtotime($aLastNodeDate[1]) - (3*60*60);
457 // Search for the correct state file - uses file timestamps so need to sort by date descending
458 $sRepURL = CONST_Replication_Url."/";
459 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
460 // download.geofabrik.de: <a href="000/">000/</a></td><td align="right">26-Feb-2013 11:53 </td>
461 // planet.openstreetmap.org: <a href="273/">273/</a> 2013-03-11 07:41 -
462 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);
464 $aPrevRepMatch = false;
465 foreach ($aRepMatches as $aRepMatch) {
466 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
467 $aPrevRepMatch = $aRepMatch;
469 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
471 $sRepURL .= $aRepMatch[1];
472 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
473 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);
474 $aPrevRepMatch = false;
475 foreach ($aRepMatches as $aRepMatch) {
476 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
477 $aPrevRepMatch = $aRepMatch;
479 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
481 $sRepURL .= $aRepMatch[1];
482 $sRep = file_get_contents($sRepURL."?C=M;O=D;F=1");
483 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);
484 $aPrevRepMatch = false;
485 foreach ($aRepMatches as $aRepMatch) {
486 if (strtotime($aRepMatch[2]) < $iLastNodeTimestamp) break;
487 $aPrevRepMatch = $aRepMatch;
489 if ($aPrevRepMatch) $aRepMatch = $aPrevRepMatch;
491 $sRepURL .= $aRepMatch[1].'.state.txt';
492 echo "Getting state file: $sRepURL\n";
493 $sStateFile = file_get_contents($sRepURL);
494 if (!$sStateFile || strlen($sStateFile) > 1000) fail("unable to obtain state file");
495 file_put_contents(CONST_InstallPath.'/settings/state.txt', $sStateFile);
496 echo "Updating DB status\n";
497 pg_query($oDB->connection, 'TRUNCATE import_status');
498 $sSQL = "INSERT INTO import_status VALUES('".$aRepMatch[2]."')";
499 pg_query($oDB->connection, $sSQL);
501 if (!$aCMDResult['all']) {
502 fail("Cannot read state file directory.");
508 if ($aCMDResult['index'] || $aCMDResult['all']) {
509 $bDidSomething = true;
511 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
512 passthruCheckReturn($sBaseCmd.' -R 4');
513 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
514 passthruCheckReturn($sBaseCmd.' -r 5 -R 25');
515 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
516 passthruCheckReturn($sBaseCmd.' -r 26');
519 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
520 echo "Search indices\n";
521 $bDidSomething = true;
523 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
524 $sTemplate = replace_tablespace('{ts:address-index}',
525 CONST_Tablespace_Address_Index, $sTemplate);
526 $sTemplate = replace_tablespace('{ts:search-index}',
527 CONST_Tablespace_Search_Index, $sTemplate);
528 $sTemplate = replace_tablespace('{ts:aux-index}',
529 CONST_Tablespace_Aux_Index, $sTemplate);
531 pgsqlRunScript($sTemplate);
534 if ($aCMDResult['drop']) {
535 // The implementation is potentially a bit dangerous because it uses
536 // a positive selection of tables to keep, and deletes everything else.
537 // Including any tables that the unsuspecting user might have manually
538 // created. USE AT YOUR OWN PERIL.
539 $bDidSomething = true;
541 // tables we want to keep. everything else goes.
542 $aKeepTables = array(
547 "location_property*",
561 $aDropTables = array();
562 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
564 foreach ($aHaveTables as $sTable) {
566 foreach ($aKeepTables as $sKeep) {
567 if (fnmatch($sKeep, $sTable)) {
572 if (!$bFound) array_push($aDropTables, $sTable);
575 foreach ($aDropTables as $sDrop) {
576 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
577 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
578 // ignore warnings/errors as they might be caused by a table having
579 // been deleted already by CASCADE
582 if (!is_null(CONST_Osm2pgsql_Flatnode_File)) {
583 if ($aCMDResult['verbose']) echo "deleting ".CONST_Osm2pgsql_Flatnode_File."\n";
584 unlink(CONST_Osm2pgsql_Flatnode_File);
588 if (!$bDidSomething) {
589 showUsage($aCMDOptions, true);
591 echo "Setup finished.\n";
594 function pgsqlRunScriptFile($sFilename)
596 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
598 // Convert database DSN to psql parameters
599 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
600 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
601 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
604 if (preg_match('/\\.gz$/', $sFilename)) {
605 $aDescriptors = array(
606 0 => array('pipe', 'r'),
607 1 => array('pipe', 'w'),
608 2 => array('file', '/dev/null', 'a')
610 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
611 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
612 $aReadPipe = $ahGzipPipes[1];
613 fclose($ahGzipPipes[0]);
615 $sCMD .= ' -f '.$sFilename;
616 $aReadPipe = array('pipe', 'r');
619 $aDescriptors = array(
621 1 => array('pipe', 'w'),
622 2 => array('file', '/dev/null', 'a')
625 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
626 if (!is_resource($hProcess)) fail('unable to start pgsql');
629 // TODO: error checking
630 while (!feof($ahPipes[1])) {
631 echo fread($ahPipes[1], 4096);
635 $iReturn = proc_close($hProcess);
637 fail("pgsql returned with error code ($iReturn)");
640 fclose($ahGzipPipes[1]);
641 proc_close($hGzipProcess);
646 function pgsqlRunScript($sScript, $bfatal = true)
649 // Convert database DSN to psql parameters
650 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
651 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
652 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
653 if ($bfatal && !$aCMDResult['ignore-errors'])
654 $sCMD .= ' -v ON_ERROR_STOP=1';
655 $aDescriptors = array(
656 0 => array('pipe', 'r'),
661 $hProcess = @proc_open($sCMD, $aDescriptors, $ahPipes);
662 if (!is_resource($hProcess)) fail('unable to start pgsql');
664 while (strlen($sScript)) {
665 $written = fwrite($ahPipes[0], $sScript);
666 if ($written <= 0) break;
667 $sScript = substr($sScript, $written);
670 $iReturn = proc_close($hProcess);
671 if ($bfatal && $iReturn > 0) {
672 fail("pgsql returned with error code ($iReturn)");
676 function pgsqlRunPartitionScript($sTemplate)
681 $sSQL = 'select distinct partition from country_name';
682 $aPartitions = chksql($oDB->getCol($sSQL));
683 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
685 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
686 foreach ($aMatches as $aMatch) {
688 foreach ($aPartitions as $sPartitionName) {
689 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
691 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
694 pgsqlRunScript($sTemplate);
697 function pgsqlRunRestoreData($sDumpFile)
699 // Convert database DSN to psql parameters
700 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
701 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
702 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
704 $aDescriptors = array(
705 0 => array('pipe', 'r'),
706 1 => array('pipe', 'w'),
707 2 => array('file', '/dev/null', 'a')
710 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
711 if (!is_resource($hProcess)) fail('unable to start pg_restore');
715 // TODO: error checking
716 while (!feof($ahPipes[1])) {
717 echo fread($ahPipes[1], 4096);
721 $iReturn = proc_close($hProcess);
724 function pgsqlRunDropAndRestore($sDumpFile)
726 // Convert database DSN to psql parameters
727 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
728 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
729 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
731 $aDescriptors = array(
732 0 => array('pipe', 'r'),
733 1 => array('pipe', 'w'),
734 2 => array('file', '/dev/null', 'a')
737 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
738 if (!is_resource($hProcess)) fail('unable to start pg_restore');
742 // TODO: error checking
743 while (!feof($ahPipes[1])) {
744 echo fread($ahPipes[1], 4096);
748 $iReturn = proc_close($hProcess);
751 function passthruCheckReturn($cmd)
754 passthru($cmd, $result);
755 if ($result != 0) fail('Error executing external command: '.$cmd);
758 function replace_tablespace($sTemplate, $sTablespace, $sSql)
761 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"',
764 $sSql = str_replace($sTemplate, '', $sSql);
770 function create_sql_functions($aCMDResult)
772 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
773 $sTemplate = str_replace('{modulepath}', CONST_InstallPath.'/module', $sTemplate);
774 if ($aCMDResult['enable-diff-updates']) {
775 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
777 if ($aCMDResult['enable-debug-statements']) {
778 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
780 if (CONST_Limit_Reindexing) {
781 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
783 if (!CONST_Use_US_Tiger_Data) {
784 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
786 if (!CONST_Use_Aux_Location_data) {
787 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
789 pgsqlRunScript($sTemplate);