4 require_once(dirname(dirname(__FILE__)).'/settings/settings.php');
5 require_once(CONST_BasePath.'/lib/init-cmd.php');
6 ini_set('memory_limit', '800M');
8 # (long-opt, short-opt, min-occurs, max-occurs, num-arguments, num-arguments, type, help)
12 'Create and setup nominatim search system',
13 array('help', 'h', 0, 1, 0, 0, false, 'Show Help'),
14 array('quiet', 'q', 0, 1, 0, 0, 'bool', 'Quiet output'),
15 array('verbose', 'v', 0, 1, 0, 0, 'bool', 'Verbose output'),
17 array('osm-file', '', 0, 1, 1, 1, 'realpath', 'File to import'),
18 array('threads', '', 0, 1, 1, 1, 'int', 'Number of threads (where possible)'),
19 array('module-path', '', 0, 1, 1, 1, 'string', 'Directory on Postgres server containing Nominatim module'),
21 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
23 array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
24 array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
25 array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
26 array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
27 array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
28 array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
29 array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
30 array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
31 array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
32 array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
33 array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
34 array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
35 array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
36 array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
37 array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
38 array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
39 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
40 array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
41 array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
42 array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
43 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
44 array('create-country-names', '', 0, 1, 0, 0, 'bool', 'Create default list of searchable country names'),
45 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
47 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
49 $bDidSomething = false;
51 // Check if osm-file is set and points to a valid file if --all or --import-data is given
52 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
53 if (!isset($aCMDResult['osm-file'])) {
54 fail('missing --osm-file for data import');
57 if (!file_exists($aCMDResult['osm-file'])) {
58 fail('the path supplied to --osm-file does not exist');
61 if (!is_readable($aCMDResult['osm-file'])) {
62 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
66 // by default, use all but one processor, but never more than 15.
67 $iInstances = isset($aCMDResult['threads'])
68 ? $aCMDResult['threads']
69 : (min(16, getProcessorCount()) - 1);
71 if ($iInstances < 1) {
73 warn("resetting threads to $iInstances");
76 // Assume we can steal all the cache memory in the box (unless told otherwise)
77 if (isset($aCMDResult['osm2pgsql-cache'])) {
78 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
80 $iCacheMemory = getCacheMemoryMB();
83 $sModulePath = CONST_InstallPath . '/module';
84 if (isset($aCMDResult['module-path'])) {
85 $sModulePath = $aCMDResult['module-path'];
86 echo 'module path: ' . $sModulePath . '\n';
89 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
90 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
92 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
94 $bDidSomething = true;
95 $oDB = DB::connect(CONST_Database_DSN, false);
96 if (!PEAR::isError($oDB)) {
97 fail('database already exists ('.CONST_Database_DSN.')');
100 $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
101 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
102 $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
104 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
105 $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
109 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
110 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
113 $result = runWithEnv($sCreateDBCmd, $aProcEnv);
114 if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
117 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
119 $bDidSomething = true;
123 $fPostgresVersion = getPostgresVersion($oDB);
124 echo 'Postgres version found: '.$fPostgresVersion."\n";
126 if ($fPostgresVersion < 9.1) {
127 fail('Minimum supported version of Postgresql is 9.1.');
130 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
131 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
133 // For extratags and namedetails the hstore_to_json converter is
134 // needed which is only available from Postgresql 9.3+. For older
135 // versions add a dummy function that returns nothing.
136 $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
138 if ($iNumFunc == 0) {
139 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
140 warn('Postgresql is too old. extratags and namedetails API not available.');
143 $fPostgisVersion = getPostgisVersion($oDB);
144 echo 'Postgis version found: '.$fPostgisVersion."\n";
146 if ($fPostgisVersion < 2.1) {
147 // Functions were renamed in 2.1 and throw an annoying deprecation warning
148 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
149 pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
151 if ($fPostgisVersion < 2.2) {
152 pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
155 $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
157 echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
158 echo "\n createuser ".CONST_Database_Web_User."\n\n";
162 // Try accessing the C module, so we know early if something is wrong
163 // and can simply error out.
164 $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
165 $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
166 $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
167 $oResult = $oDB->query($sSQL);
169 if (PEAR::isError($oResult)) {
170 echo "\nERROR: Failed to load nominatim module. Reason:\n";
171 echo $oResult->userinfo."\n\n";
175 if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
176 echo 'Error: you need to download the country_osm_grid first:';
177 echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
181 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
182 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
183 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
184 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
185 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
186 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
188 warn('external UK postcode table not found.');
190 if (CONST_Use_Extra_US_Postcodes) {
191 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
194 if ($aCMDResult['no-partitions']) {
195 pgsqlRunScript('update country_name set partition = 0');
198 // the following will be needed by create_functions later but
199 // is only defined in the subsequently called create_tables.
200 // Create dummies here that will be overwritten by the proper
201 // versions in create-tables.
202 pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
203 pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
206 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
208 $bDidSomething = true;
210 $osm2pgsql = CONST_Osm2pgsql_Binary;
211 if (!file_exists($osm2pgsql)) {
212 echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
213 echo "Normally you should not need to set this manually.\n";
214 fail("osm2pgsql not found in '$osm2pgsql'");
217 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
218 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
220 if (CONST_Tablespace_Osm2pgsql_Data)
221 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
222 if (CONST_Tablespace_Osm2pgsql_Index)
223 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
224 if (CONST_Tablespace_Place_Data)
225 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
226 if (CONST_Tablespace_Place_Index)
227 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
228 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
229 $osm2pgsql .= ' -C '.$iCacheMemory;
230 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
231 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
232 $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
234 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
235 $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
239 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
240 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
243 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
244 runWithEnv($osm2pgsql, $aProcEnv);
247 if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
252 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
253 info('Create Functions');
254 $bDidSomething = true;
255 create_sql_functions($aCMDResult);
258 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
259 info('Create Tables');
260 $bDidSomething = true;
262 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
263 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
264 $sTemplate = replace_tablespace(
266 CONST_Tablespace_Address_Data,
269 $sTemplate = replace_tablespace(
270 '{ts:address-index}',
271 CONST_Tablespace_Address_Index,
274 $sTemplate = replace_tablespace(
276 CONST_Tablespace_Search_Data,
279 $sTemplate = replace_tablespace(
281 CONST_Tablespace_Search_Index,
284 $sTemplate = replace_tablespace(
286 CONST_Tablespace_Aux_Data,
289 $sTemplate = replace_tablespace(
291 CONST_Tablespace_Aux_Index,
294 pgsqlRunScript($sTemplate, false);
296 // re-run the functions
297 info('Recreate Functions');
298 create_sql_functions($aCMDResult);
301 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
302 info('Create Partition Tables');
303 $bDidSomething = true;
305 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
306 $sTemplate = replace_tablespace(
308 CONST_Tablespace_Address_Data,
311 $sTemplate = replace_tablespace(
312 '{ts:address-index}',
313 CONST_Tablespace_Address_Index,
316 $sTemplate = replace_tablespace(
318 CONST_Tablespace_Search_Data,
321 $sTemplate = replace_tablespace(
323 CONST_Tablespace_Search_Index,
326 $sTemplate = replace_tablespace(
328 CONST_Tablespace_Aux_Data,
331 $sTemplate = replace_tablespace(
333 CONST_Tablespace_Aux_Index,
337 pgsqlRunPartitionScript($sTemplate);
341 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
342 info('Create Partition Functions');
343 $bDidSomething = true;
345 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
347 pgsqlRunPartitionScript($sTemplate);
350 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
351 $bDidSomething = true;
352 $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
353 $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
354 if (file_exists($sWikiArticlesFile)) {
355 info('Importing wikipedia articles');
356 pgsqlRunDropAndRestore($sWikiArticlesFile);
358 warn('wikipedia article dump file not found - places will have default importance');
360 if (file_exists($sWikiRedirectsFile)) {
361 info('Importing wikipedia redirects');
362 pgsqlRunDropAndRestore($sWikiRedirectsFile);
364 warn('wikipedia redirect dump file not found - some place importance values may be missing');
369 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
370 info('Drop old Data');
371 $bDidSomething = true;
374 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
376 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
378 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
380 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
382 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
384 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
386 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
388 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
390 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
392 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
395 $sSQL = 'select distinct partition from country_name';
396 $aPartitions = chksql($oDB->getCol($sSQL));
397 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
398 foreach ($aPartitions as $sPartition) {
399 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
403 // used by getorcreate_word_id to ignore frequent partial words
404 $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
405 $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
406 if (!pg_query($oDB->connection, $sSQL)) {
407 fail(pg_last_error($oDB->connection));
411 // pre-create the word list
412 if (!$aCMDResult['disable-token-precalc']) {
413 info('Loading word list');
414 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
418 $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
420 $aDBInstances = array();
421 $iLoadThreads = max(1, $iInstances - 1);
422 for ($i = 0; $i < $iLoadThreads; $i++) {
423 $aDBInstances[$i] =& getDB(true);
424 $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
425 $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
426 $sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
427 $sSQL .= ' and ST_IsValid(geometry)';
428 if ($aCMDResult['verbose']) echo "$sSQL\n";
429 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
430 fail(pg_last_error($aDBInstances[$i]->connection));
433 // last thread for interpolation lines
434 $aDBInstances[$iLoadThreads] =& getDB(true);
435 $sSQL = 'insert into location_property_osmline';
436 $sSQL .= ' (osm_id, address, linegeo)';
437 $sSQL .= ' SELECT osm_id, address, geometry from place where ';
438 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
439 if ($aCMDResult['verbose']) echo "$sSQL\n";
440 if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
441 fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
445 for ($i = 0; $i <= $iLoadThreads; $i++) {
446 while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
447 $resultStatus = pg_result_status($hPGresult);
448 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
449 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
450 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
451 echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
452 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
453 $resultError = pg_result_error($hPGresult);
454 echo '-- error text ' . $i . ': ' . $resultError . "\n";
460 fail('SQL errors loading placex and/or location_property_osmline tables');
463 info('Reanalysing database');
464 pgsqlRunScript('ANALYSE');
466 $sDatabaseDate = getDatabaseDate($oDB);
467 pg_query($oDB->connection, 'TRUNCATE import_status');
468 if ($sDatabaseDate === false) {
469 warn('could not determine database date.');
471 $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
472 pg_query($oDB->connection, $sSQL);
473 echo "Latest data imported from $sDatabaseDate.\n";
477 if ($aCMDResult['import-tiger-data']) {
478 info('Import Tiger data');
479 $bDidSomething = true;
481 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
482 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
483 $sTemplate = replace_tablespace(
485 CONST_Tablespace_Aux_Data,
488 $sTemplate = replace_tablespace(
490 CONST_Tablespace_Aux_Index,
493 pgsqlRunScript($sTemplate, false);
495 $aDBInstances = array();
496 for ($i = 0; $i < $iInstances; $i++) {
497 $aDBInstances[$i] =& getDB(true);
500 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
502 $hFile = fopen($sFile, 'r');
503 $sSQL = fgets($hFile, 100000);
507 for ($i = 0; $i < $iInstances; $i++) {
508 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
509 while (pg_get_result($aDBInstances[$i]->connection));
510 $sSQL = fgets($hFile, 100000);
512 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
514 if ($iLines == 1000) {
528 for ($i = 0; $i < $iInstances; $i++) {
529 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
536 info('Creating indexes on Tiger data');
537 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
538 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
539 $sTemplate = replace_tablespace(
541 CONST_Tablespace_Aux_Data,
544 $sTemplate = replace_tablespace(
546 CONST_Tablespace_Aux_Index,
549 pgsqlRunScript($sTemplate, false);
552 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
553 info('Calculate Postcodes');
554 $bDidSomething = true;
556 if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
557 fail(pg_last_error($oDB->connection));
560 $sSQL = 'INSERT INTO location_postcode';
561 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
562 $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
563 $sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
564 $sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
565 $sSQL .= ' FROM placex';
566 $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
567 $sSQL .= ' AND geometry IS NOT null';
568 $sSQL .= ' GROUP BY country_code, pc';
570 if (!pg_query($oDB->connection, $sSQL)) {
571 fail(pg_last_error($oDB->connection));
574 if (CONST_Use_Extra_US_Postcodes) {
575 // only add postcodes that are not yet available in OSM
576 $sSQL = 'INSERT INTO location_postcode';
577 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
578 $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
579 $sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
580 $sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
581 $sSQL .= ' (SELECT postcode FROM location_postcode';
582 $sSQL .= " WHERE country_code = 'us')";
583 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
586 // add missing postcodes for GB (if available)
587 $sSQL = 'INSERT INTO location_postcode';
588 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
589 $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
590 $sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
591 $sSQL .= ' (SELECT postcode FROM location_postcode';
592 $sSQL .= " WHERE country_code = 'gb')";
593 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
595 if (!$aCMDResult['all']) {
596 $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
597 $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
598 if (!pg_query($oDB->connection, $sSQL)) {
599 fail(pg_last_error($oDB->connection));
602 $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
603 $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
605 if (!pg_query($oDB->connection, $sSQL)) {
606 fail(pg_last_error($oDB->connection));
610 if ($aCMDResult['osmosis-init']) {
611 $bDidSomething = true;
612 echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
615 if ($aCMDResult['index'] || $aCMDResult['all']) {
616 $bDidSomething = true;
618 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
619 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
620 $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
622 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
623 $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
626 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
627 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
630 info('Index ranks 0 - 4');
631 $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
633 fail('error status ' . $iStatus . ' running nominatim!');
635 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
636 info('Index ranks 5 - 25');
637 $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
639 fail('error status ' . $iStatus . ' running nominatim!');
641 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
642 info('Index ranks 26 - 30');
643 $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
645 fail('error status ' . $iStatus . ' running nominatim!');
648 info('Index postcodes');
650 $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
651 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
654 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
655 info('Create Search indices');
656 $bDidSomething = true;
658 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
659 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
660 $sTemplate = replace_tablespace(
661 '{ts:address-index}',
662 CONST_Tablespace_Address_Index,
665 $sTemplate = replace_tablespace(
667 CONST_Tablespace_Search_Index,
670 $sTemplate = replace_tablespace(
672 CONST_Tablespace_Aux_Index,
676 pgsqlRunScript($sTemplate);
679 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
680 info('Create search index for default country names');
681 $bDidSomething = true;
683 pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
684 pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
685 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');
686 pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
688 $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 ';
689 if (CONST_Languages) {
692 foreach (explode(',', CONST_Languages) as $sLang) {
693 $sSQL .= $sDelim."'name:$sLang'";
698 // all include all simple name tags
699 $sSQL .= "like 'name:%'";
702 pgsqlRunScript($sSQL);
705 if ($aCMDResult['drop']) {
706 info('Drop tables only required for updates');
707 // The implementation is potentially a bit dangerous because it uses
708 // a positive selection of tables to keep, and deletes everything else.
709 // Including any tables that the unsuspecting user might have manually
710 // created. USE AT YOUR OWN PERIL.
711 $bDidSomething = true;
713 // tables we want to keep. everything else goes.
714 $aKeepTables = array(
720 'location_property*',
733 $aDropTables = array();
734 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
736 foreach ($aHaveTables as $sTable) {
738 foreach ($aKeepTables as $sKeep) {
739 if (fnmatch($sKeep, $sTable)) {
744 if (!$bFound) array_push($aDropTables, $sTable);
747 foreach ($aDropTables as $sDrop) {
748 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
749 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
750 // ignore warnings/errors as they might be caused by a table having
751 // been deleted already by CASCADE
754 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
755 if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
756 unlink(CONST_Osm2pgsql_Flatnode_File);
760 if (!$bDidSomething) {
761 showUsage($aCMDOptions, true);
763 echo "Summary of warnings:\n\n";
766 info('Setup finished.');
770 function pgsqlRunScriptFile($sFilename)
773 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
775 // Convert database DSN to psql parameters
776 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
777 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
778 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
779 if (!$aCMDResult['verbose']) {
782 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
783 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
785 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
786 $sCMD .= ' -U ' . $aDSNInfo['username'];
789 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
790 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
794 if (preg_match('/\\.gz$/', $sFilename)) {
795 $aDescriptors = array(
796 0 => array('pipe', 'r'),
797 1 => array('pipe', 'w'),
798 2 => array('file', '/dev/null', 'a')
800 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
801 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
802 $aReadPipe = $ahGzipPipes[1];
803 fclose($ahGzipPipes[0]);
805 $sCMD .= ' -f '.$sFilename;
806 $aReadPipe = array('pipe', 'r');
809 $aDescriptors = array(
811 1 => array('pipe', 'w'),
812 2 => array('file', '/dev/null', 'a')
815 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
816 if (!is_resource($hProcess)) fail('unable to start pgsql');
818 // TODO: error checking
819 while (!feof($ahPipes[1])) {
820 echo fread($ahPipes[1], 4096);
824 $iReturn = proc_close($hProcess);
826 fail("pgsql returned with error code ($iReturn)");
829 fclose($ahGzipPipes[1]);
830 proc_close($hGzipProcess);
834 function pgsqlRunScript($sScript, $bfatal = true)
840 $aCMDResult['verbose'],
841 $aCMDResult['ignore-errors']
845 function pgsqlRunPartitionScript($sTemplate)
850 $sSQL = 'select distinct partition from country_name';
851 $aPartitions = chksql($oDB->getCol($sSQL));
852 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
854 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
855 foreach ($aMatches as $aMatch) {
857 foreach ($aPartitions as $sPartitionName) {
858 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
860 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
863 pgsqlRunScript($sTemplate);
866 function pgsqlRunRestoreData($sDumpFile)
868 // Convert database DSN to psql parameters
869 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
870 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
871 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
873 $aDescriptors = array(
874 0 => array('pipe', 'r'),
875 1 => array('pipe', 'w'),
876 2 => array('file', '/dev/null', 'a')
879 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
880 if (!is_resource($hProcess)) fail('unable to start pg_restore');
884 // TODO: error checking
885 while (!feof($ahPipes[1])) {
886 echo fread($ahPipes[1], 4096);
890 $iReturn = proc_close($hProcess);
893 function pgsqlRunDropAndRestore($sDumpFile)
895 // Convert database DSN to psql parameters
896 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
897 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
898 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
899 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
900 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
902 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
903 $sCMD .= ' -U ' . $aDSNInfo['username'];
906 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
907 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
910 $iReturn = runWithEnv($sCMD, $aProcEnv);
913 function passthruCheckReturn($sCmd)
916 passthru($sCmd, $iResult);
919 function replace_tablespace($sTemplate, $sTablespace, $sSql)
922 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
924 $sSql = str_replace($sTemplate, '', $sSql);
930 function create_sql_functions($aCMDResult)
933 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
934 $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
935 if ($aCMDResult['enable-diff-updates']) {
936 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
938 if ($aCMDResult['enable-debug-statements']) {
939 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
941 if (CONST_Limit_Reindexing) {
942 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
944 if (!CONST_Use_US_Tiger_Data) {
945 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
947 if (!CONST_Use_Aux_Location_data) {
948 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
950 pgsqlRunScript($sTemplate);