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 if (!checkModulePresence()) {
163 fail('error loading nominatim.so module');
166 if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
167 echo 'Error: you need to download the country_osm_grid first:';
168 echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
172 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
173 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
174 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
175 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
176 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
177 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
179 warn('external UK postcode table not found.');
181 if (CONST_Use_Extra_US_Postcodes) {
182 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
185 if ($aCMDResult['no-partitions']) {
186 pgsqlRunScript('update country_name set partition = 0');
189 // the following will be needed by create_functions later but
190 // is only defined in the subsequently called create_tables.
191 // Create dummies here that will be overwritten by the proper
192 // versions in create-tables.
193 pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
194 pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
197 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
199 $bDidSomething = true;
201 $osm2pgsql = CONST_Osm2pgsql_Binary;
202 if (!file_exists($osm2pgsql)) {
203 echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
204 echo "Normally you should not need to set this manually.\n";
205 fail("osm2pgsql not found in '$osm2pgsql'");
208 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
209 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
211 if (CONST_Tablespace_Osm2pgsql_Data)
212 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
213 if (CONST_Tablespace_Osm2pgsql_Index)
214 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
215 if (CONST_Tablespace_Place_Data)
216 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
217 if (CONST_Tablespace_Place_Index)
218 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
219 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
220 $osm2pgsql .= ' -C '.$iCacheMemory;
221 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
222 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
223 $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
225 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
226 $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
230 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
231 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
234 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
235 runWithEnv($osm2pgsql, $aProcEnv);
238 if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
243 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
244 info('Create Functions');
245 $bDidSomething = true;
247 if (!checkModulePresence()) {
248 fail('error loading nominatim.so module');
251 create_sql_functions($aCMDResult);
254 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
255 info('Create Tables');
256 $bDidSomething = true;
258 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
259 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
260 $sTemplate = replace_tablespace(
262 CONST_Tablespace_Address_Data,
265 $sTemplate = replace_tablespace(
266 '{ts:address-index}',
267 CONST_Tablespace_Address_Index,
270 $sTemplate = replace_tablespace(
272 CONST_Tablespace_Search_Data,
275 $sTemplate = replace_tablespace(
277 CONST_Tablespace_Search_Index,
280 $sTemplate = replace_tablespace(
282 CONST_Tablespace_Aux_Data,
285 $sTemplate = replace_tablespace(
287 CONST_Tablespace_Aux_Index,
290 pgsqlRunScript($sTemplate, false);
292 // re-run the functions
293 info('Recreate Functions');
294 create_sql_functions($aCMDResult);
297 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
298 info('Create Partition Tables');
299 $bDidSomething = true;
301 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
302 $sTemplate = replace_tablespace(
304 CONST_Tablespace_Address_Data,
307 $sTemplate = replace_tablespace(
308 '{ts:address-index}',
309 CONST_Tablespace_Address_Index,
312 $sTemplate = replace_tablespace(
314 CONST_Tablespace_Search_Data,
317 $sTemplate = replace_tablespace(
319 CONST_Tablespace_Search_Index,
322 $sTemplate = replace_tablespace(
324 CONST_Tablespace_Aux_Data,
327 $sTemplate = replace_tablespace(
329 CONST_Tablespace_Aux_Index,
333 pgsqlRunPartitionScript($sTemplate);
337 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
338 info('Create Partition Functions');
339 $bDidSomething = true;
341 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
343 pgsqlRunPartitionScript($sTemplate);
346 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
347 $bDidSomething = true;
348 $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
349 $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
350 if (file_exists($sWikiArticlesFile)) {
351 info('Importing wikipedia articles');
352 pgsqlRunDropAndRestore($sWikiArticlesFile);
354 warn('wikipedia article dump file not found - places will have default importance');
356 if (file_exists($sWikiRedirectsFile)) {
357 info('Importing wikipedia redirects');
358 pgsqlRunDropAndRestore($sWikiRedirectsFile);
360 warn('wikipedia redirect dump file not found - some place importance values may be missing');
365 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
366 info('Drop old Data');
367 $bDidSomething = true;
370 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
372 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
374 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
376 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
378 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
380 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
382 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
384 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
386 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
388 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
391 $sSQL = 'select distinct partition from country_name';
392 $aPartitions = chksql($oDB->getCol($sSQL));
393 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
394 foreach ($aPartitions as $sPartition) {
395 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
399 // used by getorcreate_word_id to ignore frequent partial words
400 $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
401 $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
402 if (!pg_query($oDB->connection, $sSQL)) {
403 fail(pg_last_error($oDB->connection));
407 // pre-create the word list
408 if (!$aCMDResult['disable-token-precalc']) {
409 info('Loading word list');
410 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
414 $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
416 $aDBInstances = array();
417 $iLoadThreads = max(1, $iInstances - 1);
418 for ($i = 0; $i < $iLoadThreads; $i++) {
419 $aDBInstances[$i] =& getDB(true);
420 $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
421 $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
422 $sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
423 $sSQL .= ' and ST_IsValid(geometry)';
424 if ($aCMDResult['verbose']) echo "$sSQL\n";
425 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
426 fail(pg_last_error($aDBInstances[$i]->connection));
429 // last thread for interpolation lines
430 $aDBInstances[$iLoadThreads] =& getDB(true);
431 $sSQL = 'insert into location_property_osmline';
432 $sSQL .= ' (osm_id, address, linegeo)';
433 $sSQL .= ' SELECT osm_id, address, geometry from place where ';
434 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
435 if ($aCMDResult['verbose']) echo "$sSQL\n";
436 if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
437 fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
441 for ($i = 0; $i <= $iLoadThreads; $i++) {
442 while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
443 $resultStatus = pg_result_status($hPGresult);
444 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
445 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
446 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
447 echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
448 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
449 $resultError = pg_result_error($hPGresult);
450 echo '-- error text ' . $i . ': ' . $resultError . "\n";
456 fail('SQL errors loading placex and/or location_property_osmline tables');
459 info('Reanalysing database');
460 pgsqlRunScript('ANALYSE');
462 $sDatabaseDate = getDatabaseDate($oDB);
463 pg_query($oDB->connection, 'TRUNCATE import_status');
464 if ($sDatabaseDate === false) {
465 warn('could not determine database date.');
467 $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
468 pg_query($oDB->connection, $sSQL);
469 echo "Latest data imported from $sDatabaseDate.\n";
473 if ($aCMDResult['import-tiger-data']) {
474 info('Import Tiger data');
475 $bDidSomething = true;
477 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
478 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
479 $sTemplate = replace_tablespace(
481 CONST_Tablespace_Aux_Data,
484 $sTemplate = replace_tablespace(
486 CONST_Tablespace_Aux_Index,
489 pgsqlRunScript($sTemplate, false);
491 $aDBInstances = array();
492 for ($i = 0; $i < $iInstances; $i++) {
493 $aDBInstances[$i] =& getDB(true);
496 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
498 $hFile = fopen($sFile, 'r');
499 $sSQL = fgets($hFile, 100000);
503 for ($i = 0; $i < $iInstances; $i++) {
504 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
505 while (pg_get_result($aDBInstances[$i]->connection));
506 $sSQL = fgets($hFile, 100000);
508 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
510 if ($iLines == 1000) {
524 for ($i = 0; $i < $iInstances; $i++) {
525 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
532 info('Creating indexes on Tiger data');
533 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
534 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
535 $sTemplate = replace_tablespace(
537 CONST_Tablespace_Aux_Data,
540 $sTemplate = replace_tablespace(
542 CONST_Tablespace_Aux_Index,
545 pgsqlRunScript($sTemplate, false);
548 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
549 info('Calculate Postcodes');
550 $bDidSomething = true;
552 if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
553 fail(pg_last_error($oDB->connection));
556 $sSQL = 'INSERT INTO location_postcode';
557 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
558 $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
559 $sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
560 $sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
561 $sSQL .= ' FROM placex';
562 $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
563 $sSQL .= ' AND geometry IS NOT null';
564 $sSQL .= ' GROUP BY country_code, pc';
566 if (!pg_query($oDB->connection, $sSQL)) {
567 fail(pg_last_error($oDB->connection));
570 if (CONST_Use_Extra_US_Postcodes) {
571 // only add postcodes that are not yet available in OSM
572 $sSQL = 'INSERT INTO location_postcode';
573 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
574 $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
575 $sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
576 $sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
577 $sSQL .= ' (SELECT postcode FROM location_postcode';
578 $sSQL .= " WHERE country_code = 'us')";
579 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
582 // add missing postcodes for GB (if available)
583 $sSQL = 'INSERT INTO location_postcode';
584 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
585 $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
586 $sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
587 $sSQL .= ' (SELECT postcode FROM location_postcode';
588 $sSQL .= " WHERE country_code = 'gb')";
589 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
591 if (!$aCMDResult['all']) {
592 $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
593 $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
594 if (!pg_query($oDB->connection, $sSQL)) {
595 fail(pg_last_error($oDB->connection));
598 $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
599 $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
601 if (!pg_query($oDB->connection, $sSQL)) {
602 fail(pg_last_error($oDB->connection));
606 if ($aCMDResult['osmosis-init']) {
607 $bDidSomething = true;
608 echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
611 if ($aCMDResult['index'] || $aCMDResult['all']) {
612 $bDidSomething = true;
614 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
615 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
616 $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
618 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
619 $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
622 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
623 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
626 info('Index ranks 0 - 4');
627 $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
629 fail('error status ' . $iStatus . ' running nominatim!');
631 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
632 info('Index ranks 5 - 25');
633 $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
635 fail('error status ' . $iStatus . ' running nominatim!');
637 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
638 info('Index ranks 26 - 30');
639 $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
641 fail('error status ' . $iStatus . ' running nominatim!');
644 info('Index postcodes');
646 $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
647 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
650 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
651 info('Create Search indices');
652 $bDidSomething = true;
654 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
655 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
656 $sTemplate = replace_tablespace(
657 '{ts:address-index}',
658 CONST_Tablespace_Address_Index,
661 $sTemplate = replace_tablespace(
663 CONST_Tablespace_Search_Index,
666 $sTemplate = replace_tablespace(
668 CONST_Tablespace_Aux_Index,
672 pgsqlRunScript($sTemplate);
675 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
676 info('Create search index for default country names');
677 $bDidSomething = true;
679 pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
680 pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
681 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');
682 pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
684 $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 ';
685 if (CONST_Languages) {
688 foreach (explode(',', CONST_Languages) as $sLang) {
689 $sSQL .= $sDelim."'name:$sLang'";
694 // all include all simple name tags
695 $sSQL .= "like 'name:%'";
698 pgsqlRunScript($sSQL);
701 if ($aCMDResult['drop']) {
702 info('Drop tables only required for updates');
703 // The implementation is potentially a bit dangerous because it uses
704 // a positive selection of tables to keep, and deletes everything else.
705 // Including any tables that the unsuspecting user might have manually
706 // created. USE AT YOUR OWN PERIL.
707 $bDidSomething = true;
709 // tables we want to keep. everything else goes.
710 $aKeepTables = array(
716 'location_property*',
729 $aDropTables = array();
730 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
732 foreach ($aHaveTables as $sTable) {
734 foreach ($aKeepTables as $sKeep) {
735 if (fnmatch($sKeep, $sTable)) {
740 if (!$bFound) array_push($aDropTables, $sTable);
743 foreach ($aDropTables as $sDrop) {
744 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
745 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
746 // ignore warnings/errors as they might be caused by a table having
747 // been deleted already by CASCADE
750 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
751 if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
752 unlink(CONST_Osm2pgsql_Flatnode_File);
756 if (!$bDidSomething) {
757 showUsage($aCMDOptions, true);
759 echo "Summary of warnings:\n\n";
762 info('Setup finished.');
766 function pgsqlRunScriptFile($sFilename)
769 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
771 // Convert database DSN to psql parameters
772 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
773 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
774 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
775 if (!$aCMDResult['verbose']) {
778 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
779 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
781 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
782 $sCMD .= ' -U ' . $aDSNInfo['username'];
785 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
786 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
790 if (preg_match('/\\.gz$/', $sFilename)) {
791 $aDescriptors = array(
792 0 => array('pipe', 'r'),
793 1 => array('pipe', 'w'),
794 2 => array('file', '/dev/null', 'a')
796 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
797 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
798 $aReadPipe = $ahGzipPipes[1];
799 fclose($ahGzipPipes[0]);
801 $sCMD .= ' -f '.$sFilename;
802 $aReadPipe = array('pipe', 'r');
805 $aDescriptors = array(
807 1 => array('pipe', 'w'),
808 2 => array('file', '/dev/null', 'a')
811 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
812 if (!is_resource($hProcess)) fail('unable to start pgsql');
814 // TODO: error checking
815 while (!feof($ahPipes[1])) {
816 echo fread($ahPipes[1], 4096);
820 $iReturn = proc_close($hProcess);
822 fail("pgsql returned with error code ($iReturn)");
825 fclose($ahGzipPipes[1]);
826 proc_close($hGzipProcess);
830 function pgsqlRunScript($sScript, $bfatal = true)
836 $aCMDResult['verbose'],
837 $aCMDResult['ignore-errors']
841 function pgsqlRunPartitionScript($sTemplate)
846 $sSQL = 'select distinct partition from country_name';
847 $aPartitions = chksql($oDB->getCol($sSQL));
848 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
850 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
851 foreach ($aMatches as $aMatch) {
853 foreach ($aPartitions as $sPartitionName) {
854 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
856 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
859 pgsqlRunScript($sTemplate);
862 function pgsqlRunRestoreData($sDumpFile)
864 // Convert database DSN to psql parameters
865 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
866 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
867 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
869 $aDescriptors = array(
870 0 => array('pipe', 'r'),
871 1 => array('pipe', 'w'),
872 2 => array('file', '/dev/null', 'a')
875 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
876 if (!is_resource($hProcess)) fail('unable to start pg_restore');
880 // TODO: error checking
881 while (!feof($ahPipes[1])) {
882 echo fread($ahPipes[1], 4096);
886 $iReturn = proc_close($hProcess);
889 function pgsqlRunDropAndRestore($sDumpFile)
891 // Convert database DSN to psql parameters
892 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
893 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
894 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
895 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
896 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
898 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
899 $sCMD .= ' -U ' . $aDSNInfo['username'];
902 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
903 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
906 $iReturn = runWithEnv($sCMD, $aProcEnv);
909 function passthruCheckReturn($sCmd)
912 passthru($sCmd, $iResult);
915 function replace_tablespace($sTemplate, $sTablespace, $sSql)
918 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
920 $sSql = str_replace($sTemplate, '', $sSql);
926 function create_sql_functions($aCMDResult)
929 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
930 $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
931 if ($aCMDResult['enable-diff-updates']) {
932 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
934 if ($aCMDResult['enable-debug-statements']) {
935 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
937 if (CONST_Limit_Reindexing) {
938 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
940 if (!CONST_Use_US_Tiger_Data) {
941 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
943 if (!CONST_Use_Aux_Location_data) {
944 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
946 pgsqlRunScript($sTemplate);
949 function checkModulePresence()
951 // Try accessing the C module, so we know early if something is wrong
952 // and can simply error out.
953 $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
954 $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
955 $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
956 $oResult = $oDB->query($sSQL);
960 if (PEAR::isError($oResult)) {
961 echo "\nERROR: Failed to load nominatim module. Reason:\n";
962 echo $oResult->userinfo."\n\n";