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)'),
20 array('all', '', 0, 1, 0, 0, 'bool', 'Do the complete process'),
22 array('create-db', '', 0, 1, 0, 0, 'bool', 'Create nominatim db'),
23 array('setup-db', '', 0, 1, 0, 0, 'bool', 'Build a blank nominatim db'),
24 array('import-data', '', 0, 1, 0, 0, 'bool', 'Import a osm file'),
25 array('osm2pgsql-cache', '', 0, 1, 1, 1, 'int', 'Cache size used by osm2pgsql'),
26 array('create-functions', '', 0, 1, 0, 0, 'bool', 'Create functions'),
27 array('enable-diff-updates', '', 0, 1, 0, 0, 'bool', 'Turn on the code required to make diff updates work'),
28 array('enable-debug-statements', '', 0, 1, 0, 0, 'bool', 'Include debug warning statements in pgsql commands'),
29 array('ignore-errors', '', 0, 1, 0, 0, 'bool', 'Continue import even when errors in SQL are present (EXPERT)'),
30 array('create-tables', '', 0, 1, 0, 0, 'bool', 'Create main tables'),
31 array('create-partition-tables', '', 0, 1, 0, 0, 'bool', 'Create required partition tables'),
32 array('create-partition-functions', '', 0, 1, 0, 0, 'bool', 'Create required partition triggers'),
33 array('no-partitions', '', 0, 1, 0, 0, 'bool', 'Do not partition search indices (speeds up import of single country extracts)'),
34 array('import-wikipedia-articles', '', 0, 1, 0, 0, 'bool', 'Import wikipedia article dump'),
35 array('load-data', '', 0, 1, 0, 0, 'bool', 'Copy data to live tables from import table'),
36 array('disable-token-precalc', '', 0, 1, 0, 0, 'bool', 'Disable name precalculation (EXPERT)'),
37 array('import-tiger-data', '', 0, 1, 0, 0, 'bool', 'Import tiger data (not included in \'all\')'),
38 array('calculate-postcodes', '', 0, 1, 0, 0, 'bool', 'Calculate postcode centroids'),
39 array('osmosis-init', '', 0, 1, 0, 0, 'bool', 'Generate default osmosis configuration'),
40 array('index', '', 0, 1, 0, 0, 'bool', 'Index the data'),
41 array('index-noanalyse', '', 0, 1, 0, 0, 'bool', 'Do not perform analyse operations during index (EXPERT)'),
42 array('create-search-indices', '', 0, 1, 0, 0, 'bool', 'Create additional indices required for search and update'),
43 array('create-country-names', '', 0, 1, 0, 0, 'bool', 'Create default list of searchable country names'),
44 array('drop', '', 0, 1, 0, 0, 'bool', 'Drop tables needed for updates, making the database readonly (EXPERIMENTAL)'),
46 getCmdOpt($_SERVER['argv'], $aCMDOptions, $aCMDResult, true, true);
48 $bDidSomething = false;
50 // Check if osm-file is set and points to a valid file if --all or --import-data is given
51 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
52 if (!isset($aCMDResult['osm-file'])) {
53 fail('missing --osm-file for data import');
56 if (!file_exists($aCMDResult['osm-file'])) {
57 fail('the path supplied to --osm-file does not exist');
60 if (!is_readable($aCMDResult['osm-file'])) {
61 fail('osm-file "'.$aCMDResult['osm-file'].'" not readable');
65 // by default, use all but one processor, but never more than 15.
66 $iInstances = isset($aCMDResult['threads'])
67 ? $aCMDResult['threads']
68 : (min(16, getProcessorCount()) - 1);
70 if ($iInstances < 1) {
72 warn("resetting threads to $iInstances");
75 // Assume we can steal all the cache memory in the box (unless told otherwise)
76 if (isset($aCMDResult['osm2pgsql-cache'])) {
77 $iCacheMemory = $aCMDResult['osm2pgsql-cache'];
79 $iCacheMemory = getCacheMemoryMB();
82 $sModulePath = CONST_Database_Module_Path;
83 info('module path: ' . $sModulePath);
85 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
86 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
88 if ($aCMDResult['create-db'] || $aCMDResult['all']) {
90 $bDidSomething = true;
91 $oDB = DB::connect(CONST_Database_DSN, false);
92 if (!PEAR::isError($oDB)) {
93 fail('database already exists ('.CONST_Database_DSN.')');
96 $sCreateDBCmd = 'createdb -E UTF-8 -p '.$aDSNInfo['port'].' '.$aDSNInfo['database'];
97 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
98 $sCreateDBCmd .= ' -U ' . $aDSNInfo['username'];
100 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
101 $sCreateDBCmd .= ' -h ' . $aDSNInfo['hostspec'];
105 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
106 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
109 $result = runWithEnv($sCreateDBCmd, $aProcEnv);
110 if ($result != 0) fail('Error executing external command: '.$sCreateDBCmd);
113 if ($aCMDResult['setup-db'] || $aCMDResult['all']) {
115 $bDidSomething = true;
119 $fPostgresVersion = getPostgresVersion($oDB);
120 echo 'Postgres version found: '.$fPostgresVersion."\n";
122 if ($fPostgresVersion < 9.1) {
123 fail('Minimum supported version of Postgresql is 9.1.');
126 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS hstore');
127 pgsqlRunScript('CREATE EXTENSION IF NOT EXISTS postgis');
129 // For extratags and namedetails the hstore_to_json converter is
130 // needed which is only available from Postgresql 9.3+. For older
131 // versions add a dummy function that returns nothing.
132 $iNumFunc = chksql($oDB->getOne("select count(*) from pg_proc where proname = 'hstore_to_json'"));
134 if ($iNumFunc == 0) {
135 pgsqlRunScript("create function hstore_to_json(dummy hstore) returns text AS 'select null::text' language sql immutable");
136 warn('Postgresql is too old. extratags and namedetails API not available.');
139 $fPostgisVersion = getPostgisVersion($oDB);
140 echo 'Postgis version found: '.$fPostgisVersion."\n";
142 if ($fPostgisVersion < 2.1) {
143 // Functions were renamed in 2.1 and throw an annoying deprecation warning
144 pgsqlRunScript('ALTER FUNCTION st_line_interpolate_point(geometry, double precision) RENAME TO ST_LineInterpolatePoint');
145 pgsqlRunScript('ALTER FUNCTION ST_Line_Locate_Point(geometry, geometry) RENAME TO ST_LineLocatePoint');
147 if ($fPostgisVersion < 2.2) {
148 pgsqlRunScript('ALTER FUNCTION ST_Distance_Spheroid(geometry, geometry, spheroid) RENAME TO ST_DistanceSpheroid');
151 $i = chksql($oDB->getOne("select count(*) from pg_user where usename = '".CONST_Database_Web_User."'"));
153 echo "\nERROR: Web user '".CONST_Database_Web_User."' does not exist. Create it with:\n";
154 echo "\n createuser ".CONST_Database_Web_User."\n\n";
158 if (!checkModulePresence()) {
159 fail('error loading nominatim.so module');
162 if (!file_exists(CONST_ExtraDataPath.'/country_osm_grid.sql.gz')) {
163 echo 'Error: you need to download the country_osm_grid first:';
164 echo "\n wget -O ".CONST_ExtraDataPath."/country_osm_grid.sql.gz https://www.nominatim.org/data/country_grid.sql.gz\n";
168 pgsqlRunScriptFile(CONST_BasePath.'/data/country_name.sql');
169 pgsqlRunScriptFile(CONST_BasePath.'/data/country_naturalearthdata.sql');
170 pgsqlRunScriptFile(CONST_BasePath.'/data/country_osm_grid.sql.gz');
171 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_table.sql');
172 if (file_exists(CONST_BasePath.'/data/gb_postcode_data.sql.gz')) {
173 pgsqlRunScriptFile(CONST_BasePath.'/data/gb_postcode_data.sql.gz');
175 warn('external UK postcode table not found.');
177 if (CONST_Use_Extra_US_Postcodes) {
178 pgsqlRunScriptFile(CONST_BasePath.'/data/us_postcode.sql');
181 if ($aCMDResult['no-partitions']) {
182 pgsqlRunScript('update country_name set partition = 0');
185 // the following will be needed by create_functions later but
186 // is only defined in the subsequently called create_tables.
187 // Create dummies here that will be overwritten by the proper
188 // versions in create-tables.
189 pgsqlRunScript('CREATE TABLE IF NOT EXISTS place_boundingbox ()');
190 pgsqlRunScript('CREATE TYPE wikipedia_article_match AS ()', false);
193 if ($aCMDResult['import-data'] || $aCMDResult['all']) {
195 $bDidSomething = true;
197 $osm2pgsql = CONST_Osm2pgsql_Binary;
198 if (!file_exists($osm2pgsql)) {
199 echo "Check CONST_Osm2pgsql_Binary in your local settings file.\n";
200 echo "Normally you should not need to set this manually.\n";
201 fail("osm2pgsql not found in '$osm2pgsql'");
204 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
205 $osm2pgsql .= ' --flat-nodes '.CONST_Osm2pgsql_Flatnode_File;
207 if (CONST_Tablespace_Osm2pgsql_Data)
208 $osm2pgsql .= ' --tablespace-slim-data '.CONST_Tablespace_Osm2pgsql_Data;
209 if (CONST_Tablespace_Osm2pgsql_Index)
210 $osm2pgsql .= ' --tablespace-slim-index '.CONST_Tablespace_Osm2pgsql_Index;
211 if (CONST_Tablespace_Place_Data)
212 $osm2pgsql .= ' --tablespace-main-data '.CONST_Tablespace_Place_Data;
213 if (CONST_Tablespace_Place_Index)
214 $osm2pgsql .= ' --tablespace-main-index '.CONST_Tablespace_Place_Index;
215 $osm2pgsql .= ' -lsc -O gazetteer --hstore --number-processes 1';
216 $osm2pgsql .= ' -C '.$iCacheMemory;
217 $osm2pgsql .= ' -P '.$aDSNInfo['port'];
218 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
219 $osm2pgsql .= ' -U ' . $aDSNInfo['username'];
221 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
222 $osm2pgsql .= ' -H ' . $aDSNInfo['hostspec'];
226 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
227 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
230 $osm2pgsql .= ' -d '.$aDSNInfo['database'].' '.$aCMDResult['osm-file'];
231 runWithEnv($osm2pgsql, $aProcEnv);
234 if (!$aCMDResult['ignore-errors'] && !chksql($oDB->getRow('select * from place limit 1'))) {
239 if ($aCMDResult['create-functions'] || $aCMDResult['all']) {
240 info('Create Functions');
241 $bDidSomething = true;
243 if (!checkModulePresence()) {
244 fail('error loading nominatim.so module');
247 create_sql_functions($aCMDResult);
250 if ($aCMDResult['create-tables'] || $aCMDResult['all']) {
251 info('Create Tables');
252 $bDidSomething = true;
254 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tables.sql');
255 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
256 $sTemplate = replace_tablespace(
258 CONST_Tablespace_Address_Data,
261 $sTemplate = replace_tablespace(
262 '{ts:address-index}',
263 CONST_Tablespace_Address_Index,
266 $sTemplate = replace_tablespace(
268 CONST_Tablespace_Search_Data,
271 $sTemplate = replace_tablespace(
273 CONST_Tablespace_Search_Index,
276 $sTemplate = replace_tablespace(
278 CONST_Tablespace_Aux_Data,
281 $sTemplate = replace_tablespace(
283 CONST_Tablespace_Aux_Index,
286 pgsqlRunScript($sTemplate, false);
288 // re-run the functions
289 info('Recreate Functions');
290 create_sql_functions($aCMDResult);
293 if ($aCMDResult['create-partition-tables'] || $aCMDResult['all']) {
294 info('Create Partition Tables');
295 $bDidSomething = true;
297 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-tables.src.sql');
298 $sTemplate = replace_tablespace(
300 CONST_Tablespace_Address_Data,
303 $sTemplate = replace_tablespace(
304 '{ts:address-index}',
305 CONST_Tablespace_Address_Index,
308 $sTemplate = replace_tablespace(
310 CONST_Tablespace_Search_Data,
313 $sTemplate = replace_tablespace(
315 CONST_Tablespace_Search_Index,
318 $sTemplate = replace_tablespace(
320 CONST_Tablespace_Aux_Data,
323 $sTemplate = replace_tablespace(
325 CONST_Tablespace_Aux_Index,
329 pgsqlRunPartitionScript($sTemplate);
333 if ($aCMDResult['create-partition-functions'] || $aCMDResult['all']) {
334 info('Create Partition Functions');
335 $bDidSomething = true;
337 $sTemplate = file_get_contents(CONST_BasePath.'/sql/partition-functions.src.sql');
339 pgsqlRunPartitionScript($sTemplate);
342 if ($aCMDResult['import-wikipedia-articles'] || $aCMDResult['all']) {
343 $bDidSomething = true;
344 $sWikiArticlesFile = CONST_Wikipedia_Data_Path.'/wikipedia_article.sql.bin';
345 $sWikiRedirectsFile = CONST_Wikipedia_Data_Path.'/wikipedia_redirect.sql.bin';
346 if (file_exists($sWikiArticlesFile)) {
347 info('Importing wikipedia articles');
348 pgsqlRunDropAndRestore($sWikiArticlesFile);
350 warn('wikipedia article dump file not found - places will have default importance');
352 if (file_exists($sWikiRedirectsFile)) {
353 info('Importing wikipedia redirects');
354 pgsqlRunDropAndRestore($sWikiRedirectsFile);
356 warn('wikipedia redirect dump file not found - some place importance values may be missing');
361 if ($aCMDResult['load-data'] || $aCMDResult['all']) {
362 info('Drop old Data');
363 $bDidSomething = true;
366 if (!pg_query($oDB->connection, 'TRUNCATE word')) fail(pg_last_error($oDB->connection));
368 if (!pg_query($oDB->connection, 'TRUNCATE placex')) fail(pg_last_error($oDB->connection));
370 if (!pg_query($oDB->connection, 'TRUNCATE location_property_osmline')) fail(pg_last_error($oDB->connection));
372 if (!pg_query($oDB->connection, 'TRUNCATE place_addressline')) fail(pg_last_error($oDB->connection));
374 if (!pg_query($oDB->connection, 'TRUNCATE place_boundingbox')) fail(pg_last_error($oDB->connection));
376 if (!pg_query($oDB->connection, 'TRUNCATE location_area')) fail(pg_last_error($oDB->connection));
378 if (!pg_query($oDB->connection, 'TRUNCATE search_name')) fail(pg_last_error($oDB->connection));
380 if (!pg_query($oDB->connection, 'TRUNCATE search_name_blank')) fail(pg_last_error($oDB->connection));
382 if (!pg_query($oDB->connection, 'DROP SEQUENCE seq_place')) fail(pg_last_error($oDB->connection));
384 if (!pg_query($oDB->connection, 'CREATE SEQUENCE seq_place start 100000')) fail(pg_last_error($oDB->connection));
387 $sSQL = 'select distinct partition from country_name';
388 $aPartitions = chksql($oDB->getCol($sSQL));
389 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
390 foreach ($aPartitions as $sPartition) {
391 if (!pg_query($oDB->connection, 'TRUNCATE location_road_'.$sPartition)) fail(pg_last_error($oDB->connection));
395 // used by getorcreate_word_id to ignore frequent partial words
396 $sSQL = 'CREATE OR REPLACE FUNCTION get_maxwordfreq() RETURNS integer AS ';
397 $sSQL .= '$$ SELECT '.CONST_Max_Word_Frequency.' as maxwordfreq; $$ LANGUAGE SQL IMMUTABLE';
398 if (!pg_query($oDB->connection, $sSQL)) {
399 fail(pg_last_error($oDB->connection));
403 // pre-create the word list
404 if (!$aCMDResult['disable-token-precalc']) {
405 info('Loading word list');
406 pgsqlRunScriptFile(CONST_BasePath.'/data/words.sql');
410 $sColumns = 'osm_type, osm_id, class, type, name, admin_level, address, extratags, geometry';
412 $aDBInstances = array();
413 $iLoadThreads = max(1, $iInstances - 1);
414 for ($i = 0; $i < $iLoadThreads; $i++) {
415 $aDBInstances[$i] =& getDB(true);
416 $sSQL = "INSERT INTO placex ($sColumns) SELECT $sColumns FROM place WHERE osm_id % $iLoadThreads = $i";
417 $sSQL .= " and not (class='place' and type='houses' and osm_type='W'";
418 $sSQL .= " and ST_GeometryType(geometry) = 'ST_LineString')";
419 $sSQL .= ' and ST_IsValid(geometry)';
420 if ($aCMDResult['verbose']) echo "$sSQL\n";
421 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) {
422 fail(pg_last_error($aDBInstances[$i]->connection));
425 // last thread for interpolation lines
426 $aDBInstances[$iLoadThreads] =& getDB(true);
427 $sSQL = 'insert into location_property_osmline';
428 $sSQL .= ' (osm_id, address, linegeo)';
429 $sSQL .= ' SELECT osm_id, address, geometry from place where ';
430 $sSQL .= "class='place' and type='houses' and osm_type='W' and ST_GeometryType(geometry) = 'ST_LineString'";
431 if ($aCMDResult['verbose']) echo "$sSQL\n";
432 if (!pg_send_query($aDBInstances[$iLoadThreads]->connection, $sSQL)) {
433 fail(pg_last_error($aDBInstances[$iLoadThreads]->connection));
437 for ($i = 0; $i <= $iLoadThreads; $i++) {
438 while (($hPGresult = pg_get_result($aDBInstances[$i]->connection)) !== false) {
439 $resultStatus = pg_result_status($hPGresult);
440 // PGSQL_EMPTY_QUERY, PGSQL_COMMAND_OK, PGSQL_TUPLES_OK,
441 // PGSQL_COPY_OUT, PGSQL_COPY_IN, PGSQL_BAD_RESPONSE,
442 // PGSQL_NONFATAL_ERROR and PGSQL_FATAL_ERROR
443 echo 'Query result ' . $i . ' is: ' . $resultStatus . "\n";
444 if ($resultStatus != PGSQL_COMMAND_OK && $resultStatus != PGSQL_TUPLES_OK) {
445 $resultError = pg_result_error($hPGresult);
446 echo '-- error text ' . $i . ': ' . $resultError . "\n";
452 fail('SQL errors loading placex and/or location_property_osmline tables');
455 info('Reanalysing database');
456 pgsqlRunScript('ANALYSE');
458 $sDatabaseDate = getDatabaseDate($oDB);
459 pg_query($oDB->connection, 'TRUNCATE import_status');
460 if ($sDatabaseDate === false) {
461 warn('could not determine database date.');
463 $sSQL = "INSERT INTO import_status (lastimportdate) VALUES('".$sDatabaseDate."')";
464 pg_query($oDB->connection, $sSQL);
465 echo "Latest data imported from $sDatabaseDate.\n";
469 if ($aCMDResult['import-tiger-data']) {
470 info('Import Tiger data');
471 $bDidSomething = true;
473 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_start.sql');
474 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
475 $sTemplate = replace_tablespace(
477 CONST_Tablespace_Aux_Data,
480 $sTemplate = replace_tablespace(
482 CONST_Tablespace_Aux_Index,
485 pgsqlRunScript($sTemplate, false);
487 $aDBInstances = array();
488 for ($i = 0; $i < $iInstances; $i++) {
489 $aDBInstances[$i] =& getDB(true);
492 foreach (glob(CONST_Tiger_Data_Path.'/*.sql') as $sFile) {
494 $hFile = fopen($sFile, 'r');
495 $sSQL = fgets($hFile, 100000);
499 for ($i = 0; $i < $iInstances; $i++) {
500 if (!pg_connection_busy($aDBInstances[$i]->connection)) {
501 while (pg_get_result($aDBInstances[$i]->connection));
502 $sSQL = fgets($hFile, 100000);
504 if (!pg_send_query($aDBInstances[$i]->connection, $sSQL)) fail(pg_last_error($oDB->connection));
506 if ($iLines == 1000) {
520 for ($i = 0; $i < $iInstances; $i++) {
521 if (pg_connection_busy($aDBInstances[$i]->connection)) $bAnyBusy = true;
528 info('Creating indexes on Tiger data');
529 $sTemplate = file_get_contents(CONST_BasePath.'/sql/tiger_import_finish.sql');
530 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
531 $sTemplate = replace_tablespace(
533 CONST_Tablespace_Aux_Data,
536 $sTemplate = replace_tablespace(
538 CONST_Tablespace_Aux_Index,
541 pgsqlRunScript($sTemplate, false);
544 if ($aCMDResult['calculate-postcodes'] || $aCMDResult['all']) {
545 info('Calculate Postcodes');
546 $bDidSomething = true;
548 if (!pg_query($oDB->connection, 'TRUNCATE location_postcode')) {
549 fail(pg_last_error($oDB->connection));
552 $sSQL = 'INSERT INTO location_postcode';
553 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
554 $sSQL .= "SELECT nextval('seq_place'), 1, country_code,";
555 $sSQL .= " upper(trim (both ' ' from address->'postcode')) as pc,";
556 $sSQL .= ' ST_Centroid(ST_Collect(ST_Centroid(geometry)))';
557 $sSQL .= ' FROM placex';
558 $sSQL .= " WHERE address ? 'postcode' AND address->'postcode' NOT SIMILAR TO '%(,|;)%'";
559 $sSQL .= ' AND geometry IS NOT null';
560 $sSQL .= ' GROUP BY country_code, pc';
562 if (!pg_query($oDB->connection, $sSQL)) {
563 fail(pg_last_error($oDB->connection));
566 if (CONST_Use_Extra_US_Postcodes) {
567 // only add postcodes that are not yet available in OSM
568 $sSQL = 'INSERT INTO location_postcode';
569 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
570 $sSQL .= "SELECT nextval('seq_place'), 1, 'us', postcode,";
571 $sSQL .= ' ST_SetSRID(ST_Point(x,y),4326)';
572 $sSQL .= ' FROM us_postcode WHERE postcode NOT IN';
573 $sSQL .= ' (SELECT postcode FROM location_postcode';
574 $sSQL .= " WHERE country_code = 'us')";
575 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
578 // add missing postcodes for GB (if available)
579 $sSQL = 'INSERT INTO location_postcode';
580 $sSQL .= ' (place_id, indexed_status, country_code, postcode, geometry) ';
581 $sSQL .= "SELECT nextval('seq_place'), 1, 'gb', postcode, geometry";
582 $sSQL .= ' FROM gb_postcode WHERE postcode NOT IN';
583 $sSQL .= ' (SELECT postcode FROM location_postcode';
584 $sSQL .= " WHERE country_code = 'gb')";
585 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
587 if (!$aCMDResult['all']) {
588 $sSQL = "DELETE FROM word WHERE class='place' and type='postcode'";
589 $sSQL .= 'and word NOT IN (SELECT postcode FROM location_postcode)';
590 if (!pg_query($oDB->connection, $sSQL)) {
591 fail(pg_last_error($oDB->connection));
594 $sSQL = 'SELECT count(getorcreate_postcode_id(v)) FROM ';
595 $sSQL .= '(SELECT distinct(postcode) as v FROM location_postcode) p';
597 if (!pg_query($oDB->connection, $sSQL)) {
598 fail(pg_last_error($oDB->connection));
602 if ($aCMDResult['osmosis-init']) {
603 $bDidSomething = true;
604 echo "Command 'osmosis-init' no longer available, please use utils/update.php --init-updates.\n";
607 if ($aCMDResult['index'] || $aCMDResult['all']) {
608 $bDidSomething = true;
610 $sBaseCmd = CONST_InstallPath.'/nominatim/nominatim -i -d '.$aDSNInfo['database'].' -P '.$aDSNInfo['port'].' -t '.$iInstances.$sOutputFile;
611 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
612 $sBaseCmd .= ' -H ' . $aDSNInfo['hostspec'];
614 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
615 $sBaseCmd .= ' -U ' . $aDSNInfo['username'];
618 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
619 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
622 info('Index ranks 0 - 4');
623 $iStatus = runWithEnv($sBaseCmd.' -R 4', $aProcEnv);
625 fail('error status ' . $iStatus . ' running nominatim!');
627 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
628 info('Index ranks 5 - 25');
629 $iStatus = runWithEnv($sBaseCmd.' -r 5 -R 25', $aProcEnv);
631 fail('error status ' . $iStatus . ' running nominatim!');
633 if (!$aCMDResult['index-noanalyse']) pgsqlRunScript('ANALYSE');
634 info('Index ranks 26 - 30');
635 $iStatus = runWithEnv($sBaseCmd.' -r 26', $aProcEnv);
637 fail('error status ' . $iStatus . ' running nominatim!');
640 info('Index postcodes');
642 $sSQL = 'UPDATE location_postcode SET indexed_status = 0';
643 if (!pg_query($oDB->connection, $sSQL)) fail(pg_last_error($oDB->connection));
646 if ($aCMDResult['create-search-indices'] || $aCMDResult['all']) {
647 info('Create Search indices');
648 $bDidSomething = true;
650 $sTemplate = file_get_contents(CONST_BasePath.'/sql/indices.src.sql');
651 $sTemplate = str_replace('{www-user}', CONST_Database_Web_User, $sTemplate);
652 $sTemplate = replace_tablespace(
653 '{ts:address-index}',
654 CONST_Tablespace_Address_Index,
657 $sTemplate = replace_tablespace(
659 CONST_Tablespace_Search_Index,
662 $sTemplate = replace_tablespace(
664 CONST_Tablespace_Aux_Index,
668 pgsqlRunScript($sTemplate);
671 if ($aCMDResult['create-country-names'] || $aCMDResult['all']) {
672 info('Create search index for default country names');
673 $bDidSomething = true;
675 pgsqlRunScript("select getorcreate_country(make_standard_name('uk'), 'gb')");
676 pgsqlRunScript("select getorcreate_country(make_standard_name('united states'), 'us')");
677 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');
678 pgsqlRunScript("select count(*) from (select getorcreate_country(make_standard_name(name->'name'), country_code) from country_name where name ? 'name') as x");
680 $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 ';
681 if (CONST_Languages) {
684 foreach (explode(',', CONST_Languages) as $sLang) {
685 $sSQL .= $sDelim."'name:$sLang'";
690 // all include all simple name tags
691 $sSQL .= "like 'name:%'";
694 pgsqlRunScript($sSQL);
697 if ($aCMDResult['drop']) {
698 info('Drop tables only required for updates');
699 // The implementation is potentially a bit dangerous because it uses
700 // a positive selection of tables to keep, and deletes everything else.
701 // Including any tables that the unsuspecting user might have manually
702 // created. USE AT YOUR OWN PERIL.
703 $bDidSomething = true;
705 // tables we want to keep. everything else goes.
706 $aKeepTables = array(
712 'location_property*',
725 $aDropTables = array();
726 $aHaveTables = chksql($oDB->getCol("SELECT tablename FROM pg_tables WHERE schemaname='public'"));
728 foreach ($aHaveTables as $sTable) {
730 foreach ($aKeepTables as $sKeep) {
731 if (fnmatch($sKeep, $sTable)) {
736 if (!$bFound) array_push($aDropTables, $sTable);
739 foreach ($aDropTables as $sDrop) {
740 if ($aCMDResult['verbose']) echo "dropping table $sDrop\n";
741 @pg_query($oDB->connection, "DROP TABLE $sDrop CASCADE");
742 // ignore warnings/errors as they might be caused by a table having
743 // been deleted already by CASCADE
746 if (!is_null(CONST_Osm2pgsql_Flatnode_File) && CONST_Osm2pgsql_Flatnode_File) {
747 if ($aCMDResult['verbose']) echo 'deleting '.CONST_Osm2pgsql_Flatnode_File."\n";
748 unlink(CONST_Osm2pgsql_Flatnode_File);
752 if (!$bDidSomething) {
753 showUsage($aCMDOptions, true);
755 echo "Summary of warnings:\n\n";
758 info('Setup finished.');
762 function pgsqlRunScriptFile($sFilename)
765 if (!file_exists($sFilename)) fail('unable to find '.$sFilename);
767 // Convert database DSN to psql parameters
768 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
769 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
770 $sCMD = 'psql -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'];
771 if (!$aCMDResult['verbose']) {
774 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
775 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
777 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
778 $sCMD .= ' -U ' . $aDSNInfo['username'];
781 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
782 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
786 if (preg_match('/\\.gz$/', $sFilename)) {
787 $aDescriptors = array(
788 0 => array('pipe', 'r'),
789 1 => array('pipe', 'w'),
790 2 => array('file', '/dev/null', 'a')
792 $hGzipProcess = proc_open('zcat '.$sFilename, $aDescriptors, $ahGzipPipes);
793 if (!is_resource($hGzipProcess)) fail('unable to start zcat');
794 $aReadPipe = $ahGzipPipes[1];
795 fclose($ahGzipPipes[0]);
797 $sCMD .= ' -f '.$sFilename;
798 $aReadPipe = array('pipe', 'r');
801 $aDescriptors = array(
803 1 => array('pipe', 'w'),
804 2 => array('file', '/dev/null', 'a')
807 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes, null, $aProcEnv);
808 if (!is_resource($hProcess)) fail('unable to start pgsql');
810 // TODO: error checking
811 while (!feof($ahPipes[1])) {
812 echo fread($ahPipes[1], 4096);
816 $iReturn = proc_close($hProcess);
818 fail("pgsql returned with error code ($iReturn)");
821 fclose($ahGzipPipes[1]);
822 proc_close($hGzipProcess);
826 function pgsqlRunScript($sScript, $bfatal = true)
832 $aCMDResult['verbose'],
833 $aCMDResult['ignore-errors']
837 function pgsqlRunPartitionScript($sTemplate)
842 $sSQL = 'select distinct partition from country_name';
843 $aPartitions = chksql($oDB->getCol($sSQL));
844 if (!$aCMDResult['no-partitions']) $aPartitions[] = 0;
846 preg_match_all('#^-- start(.*?)^-- end#ms', $sTemplate, $aMatches, PREG_SET_ORDER);
847 foreach ($aMatches as $aMatch) {
849 foreach ($aPartitions as $sPartitionName) {
850 $sResult .= str_replace('-partition-', $sPartitionName, $aMatch[1]);
852 $sTemplate = str_replace($aMatch[0], $sResult, $sTemplate);
855 pgsqlRunScript($sTemplate);
858 function pgsqlRunRestoreData($sDumpFile)
860 // Convert database DSN to psql parameters
861 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
862 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
863 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc -a '.$sDumpFile;
865 $aDescriptors = array(
866 0 => array('pipe', 'r'),
867 1 => array('pipe', 'w'),
868 2 => array('file', '/dev/null', 'a')
871 $hProcess = proc_open($sCMD, $aDescriptors, $ahPipes);
872 if (!is_resource($hProcess)) fail('unable to start pg_restore');
876 // TODO: error checking
877 while (!feof($ahPipes[1])) {
878 echo fread($ahPipes[1], 4096);
882 $iReturn = proc_close($hProcess);
885 function pgsqlRunDropAndRestore($sDumpFile)
887 // Convert database DSN to psql parameters
888 $aDSNInfo = DB::parseDSN(CONST_Database_DSN);
889 if (!isset($aDSNInfo['port']) || !$aDSNInfo['port']) $aDSNInfo['port'] = 5432;
890 $sCMD = 'pg_restore -p '.$aDSNInfo['port'].' -d '.$aDSNInfo['database'].' -Fc --clean '.$sDumpFile;
891 if (isset($aDSNInfo['hostspec']) && $aDSNInfo['hostspec']) {
892 $sCMD .= ' -h ' . $aDSNInfo['hostspec'];
894 if (isset($aDSNInfo['username']) && $aDSNInfo['username']) {
895 $sCMD .= ' -U ' . $aDSNInfo['username'];
898 if (isset($aDSNInfo['password']) && $aDSNInfo['password']) {
899 $aProcEnv = array_merge(array('PGPASSWORD' => $aDSNInfo['password']), $_ENV);
902 $iReturn = runWithEnv($sCMD, $aProcEnv);
905 function passthruCheckReturn($sCmd)
908 passthru($sCmd, $iResult);
911 function replace_tablespace($sTemplate, $sTablespace, $sSql)
914 $sSql = str_replace($sTemplate, 'TABLESPACE "'.$sTablespace.'"', $sSql);
916 $sSql = str_replace($sTemplate, '', $sSql);
922 function create_sql_functions($aCMDResult)
925 $sTemplate = file_get_contents(CONST_BasePath.'/sql/functions.sql');
926 $sTemplate = str_replace('{modulepath}', $sModulePath, $sTemplate);
927 if ($aCMDResult['enable-diff-updates']) {
928 $sTemplate = str_replace('RETURN NEW; -- %DIFFUPDATES%', '--', $sTemplate);
930 if ($aCMDResult['enable-debug-statements']) {
931 $sTemplate = str_replace('--DEBUG:', '', $sTemplate);
933 if (CONST_Limit_Reindexing) {
934 $sTemplate = str_replace('--LIMIT INDEXING:', '', $sTemplate);
936 if (!CONST_Use_US_Tiger_Data) {
937 $sTemplate = str_replace('-- %NOTIGERDATA% ', '', $sTemplate);
939 if (!CONST_Use_Aux_Location_data) {
940 $sTemplate = str_replace('-- %NOAUXDATA% ', '', $sTemplate);
942 pgsqlRunScript($sTemplate);
945 function checkModulePresence()
947 // Try accessing the C module, so we know early if something is wrong
948 // and can simply error out.
950 $sSQL = "CREATE FUNCTION nominatim_test_import_func(text) RETURNS text AS '";
951 $sSQL .= $sModulePath."/nominatim.so', 'transliteration' LANGUAGE c IMMUTABLE STRICT";
952 $sSQL .= ';DROP FUNCTION nominatim_test_import_func(text);';
955 $oResult = $oDB->query($sSQL);
959 if (PEAR::isError($oResult)) {
960 echo "\nERROR: Failed to load nominatim module. Reason:\n";
961 echo $oResult->userinfo."\n\n";