2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
9 from pathlib import Path
11 from .config import Configuration
12 from .tools.exec_utils import run_legacy_script, run_php_server
13 from .errors import UsageError
16 LOG = logging.getLogger()
19 class CommandlineParser:
20 """ Wraps some of the common functions for parsing the command line
21 and setting up subcommands.
23 def __init__(self, prog, description):
24 self.parser = argparse.ArgumentParser(
26 description=description,
27 formatter_class=argparse.RawDescriptionHelpFormatter)
29 self.subs = self.parser.add_subparsers(title='available commands',
32 # Arguments added to every sub-command
33 self.default_args = argparse.ArgumentParser(add_help=False)
34 group = self.default_args.add_argument_group('Default arguments')
35 group.add_argument('-h', '--help', action='help',
36 help='Show this help message and exit')
37 group.add_argument('-q', '--quiet', action='store_const', const=0,
38 dest='verbose', default=1,
39 help='Print only error messages')
40 group.add_argument('-v', '--verbose', action='count', default=1,
41 help='Increase verboseness of output')
42 group.add_argument('--project-dir', metavar='DIR', default='.',
43 help='Base directory of the Nominatim installation (default:.)')
44 group.add_argument('-j', '--threads', metavar='NUM', type=int,
45 help='Number of parallel threads to use')
48 def add_subcommand(self, name, cmd):
49 """ Add a subcommand to the parser. The subcommand must be a class
50 with a function add_args() that adds the parameters for the
51 subcommand and a run() function that executes the command.
53 parser = self.subs.add_parser(name, parents=[self.default_args],
54 help=cmd.__doc__.split('\n', 1)[0],
55 description=cmd.__doc__,
56 formatter_class=argparse.RawDescriptionHelpFormatter,
58 parser.set_defaults(command=cmd)
61 def run(self, **kwargs):
62 """ Parse the command line arguments of the program and execute the
63 appropriate subcommand.
65 args = self.parser.parse_args(args=kwargs.get('cli_args'))
67 if args.subcommand is None:
68 self.parser.print_help()
71 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
72 setattr(args, arg, Path(kwargs[arg]))
73 args.project_dir = Path(args.project_dir).resolve()
75 logging.basicConfig(stream=sys.stderr,
76 format='%(asctime)s: %(message)s',
77 datefmt='%Y-%m-%d %H:%M:%S',
78 level=max(4 - args.verbose, 1) * 10)
80 args.config = Configuration(args.project_dir, args.data_dir / 'settings')
82 log = logging.getLogger()
83 log.warning('Using project directory: %s', str(args.project_dir))
86 return args.command.run(args)
87 except UsageError as exception:
88 if log.isEnabledFor(logging.DEBUG):
89 raise # use Python's exception printing
90 log.fatal('FATAL: %s', exception)
92 # If we get here, then execution has failed in some way.
96 ##### Subcommand classes
98 # Each class needs to implement two functions: add_args() adds the CLI parameters
99 # for the subfunction, run() executes the subcommand.
101 # The class documentation doubles as the help text for the command. The
102 # first line is also used in the summary when calling the program without
105 # No need to document the functions each time.
106 # pylint: disable=C0111
107 # Using non-top-level imports to make pyosmium optional for replication only.
108 # pylint: disable=E0012,C0415
113 Create a new Nominatim database from an OSM file.
117 def add_args(parser):
118 group_name = parser.add_argument_group('Required arguments')
119 group = group_name.add_mutually_exclusive_group(required=True)
120 group.add_argument('--osm-file',
121 help='OSM file to be imported.')
122 group.add_argument('--continue', dest='continue_at',
123 choices=['load-data', 'indexing', 'db-postprocess'],
124 help='Continue an import that was interrupted')
125 group = parser.add_argument_group('Optional arguments')
126 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
127 help='Size of cache to be used by osm2pgsql (in MB)')
128 group.add_argument('--reverse-only', action='store_true',
129 help='Do not create tables and indexes for searching')
130 group.add_argument('--enable-debug-statements', action='store_true',
131 help='Include debug warning statements in SQL code')
132 group.add_argument('--no-partitions', action='store_true',
133 help="""Do not partition search indices
134 (speeds up import of single country extracts)""")
135 group.add_argument('--no-updates', action='store_true',
136 help="""Do not keep tables that are only needed for
137 updating the database later""")
138 group = parser.add_argument_group('Expert options')
139 group.add_argument('--ignore-errors', action='store_true',
140 help='Continue import even when errors in SQL are present')
141 group.add_argument('--index-noanalyse', action='store_true',
142 help='Do not perform analyse operations during index')
147 params = ['setup.php']
149 params.extend(('--all', '--osm-file', args.osm_file))
151 if args.continue_at == 'load-data':
152 params.append('--load-data')
153 if args.continue_at in ('load-data', 'indexing'):
154 params.append('--index')
155 params.extend(('--create-search-indices', '--create-country-names',
157 if args.osm2pgsql_cache:
158 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
159 if args.reverse_only:
160 params.append('--reverse-only')
161 if args.enable_debug_statements:
162 params.append('--enable-debug-statements')
163 if args.no_partitions:
164 params.append('--no-partitions')
166 params.append('--drop')
167 if args.ignore_errors:
168 params.append('--ignore-errors')
169 if args.index_noanalyse:
170 params.append('--index-noanalyse')
172 return run_legacy_script(*params, nominatim_env=args)
177 Make database read-only.
179 About half of data in the Nominatim database is kept only to be able to
180 keep the data up-to-date with new changes made in OpenStreetMap. This
181 command drops all this data and only keeps the part needed for geocoding
184 This command has the same effect as the `--no-updates` option for imports.
188 def add_args(parser):
193 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
196 class SetupSpecialPhrases:
198 Maintain special phrases.
202 def add_args(parser):
203 group = parser.add_argument_group('Input arguments')
204 group.add_argument('--from-wiki', action='store_true',
205 help='Pull special phrases from the OSM wiki.')
206 group = parser.add_argument_group('Output arguments')
207 group.add_argument('-o', '--output', default='-',
208 help="""File to write the preprocessed phrases to.
209 If omitted, it will be written to stdout.""")
213 if args.output != '-':
214 raise NotImplementedError('Only output to stdout is currently implemented.')
215 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
220 Add additional data from a file or an online source.
222 Data is only imported, not indexed. You need to call `nominatim-update index`
223 to complete the process.
227 def add_args(parser):
228 group_name = parser.add_argument_group('Source')
229 group = group_name.add_mutually_exclusive_group(required=True)
230 group.add_argument('--file', metavar='FILE',
231 help='Import data from an OSM file')
232 group.add_argument('--diff', metavar='FILE',
233 help='Import data from an OSM diff file')
234 group.add_argument('--node', metavar='ID', type=int,
235 help='Import a single node from the API')
236 group.add_argument('--way', metavar='ID', type=int,
237 help='Import a single way from the API')
238 group.add_argument('--relation', metavar='ID', type=int,
239 help='Import a single relation from the API')
240 group.add_argument('--tiger-data', metavar='DIR',
241 help='Add housenumbers from the US TIGER census database.')
242 group = parser.add_argument_group('Extra arguments')
243 group.add_argument('--use-main-api', action='store_true',
244 help='Use OSM API instead of Overpass to download objects')
249 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
250 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
252 params = ['update.php']
254 params.extend(('--import-file', args.file))
256 params.extend(('--import-diff', args.diff))
258 params.extend(('--import-node', args.node))
260 params.extend(('--import-way', args.way))
262 params.extend(('--import-relation', args.relation))
263 if args.use_main_api:
264 params.append('--use-main-api')
265 return run_legacy_script(*params, nominatim_env=args)
268 class AdminCheckDatabase:
270 Check that the database is complete and operational.
274 def add_args(parser):
279 return run_legacy_script('check_import_finished.php', nominatim_env=args)
284 Warm database caches for search and reverse queries.
288 def add_args(parser):
289 group = parser.add_argument_group('Target arguments')
290 group.add_argument('--search-only', action='store_const', dest='target',
292 help="Only pre-warm tables for search queries")
293 group.add_argument('--reverse-only', action='store_const', dest='target',
295 help="Only pre-warm tables for reverse queries")
299 params = ['warm.php']
300 if args.target == 'reverse':
301 params.append('--reverse-only')
302 if args.target == 'search':
303 params.append('--search-only')
304 return run_legacy_script(*params, nominatim_env=args)
309 Export addresses as CSV file from the database.
313 def add_args(parser):
314 group = parser.add_argument_group('Output arguments')
315 group.add_argument('--output-type', default='street',
316 choices=('continent', 'country', 'state', 'county',
317 'city', 'suburb', 'street', 'path'),
318 help='Type of places to output (default: street)')
319 group.add_argument('--output-format',
320 default='street;suburb;city;county;state;country',
321 help="""Semicolon-separated list of address types
322 (see --output-type). Multiple ranks can be
323 merged into one column by simply using a
324 comma-separated list.""")
325 group.add_argument('--output-all-postcodes', action='store_true',
326 help="""List all postcodes for address instead of
327 just the most likely one""")
328 group.add_argument('--language',
329 help="""Preferred language for output
330 (use local name, if omitted)""")
331 group = parser.add_argument_group('Filter arguments')
332 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
333 help='Export only objects within country')
334 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
335 help='Export only children of this OSM node')
336 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
337 help='Export only children of this OSM way')
338 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
339 help='Export only children of this OSM relation')
344 params = ['export.php',
345 '--output-type', args.output_type,
346 '--output-format', args.output_format]
347 if args.output_all_postcodes:
348 params.append('--output-all-postcodes')
350 params.extend(('--language', args.language))
351 if args.restrict_to_country:
352 params.extend(('--restrict-to-country', args.restrict_to_country))
353 if args.restrict_to_osm_node:
354 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
355 if args.restrict_to_osm_way:
356 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
357 if args.restrict_to_osm_relation:
358 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
360 return run_legacy_script(*params, nominatim_env=args)
365 Start a simple web server for serving the API.
367 This command starts the built-in PHP webserver to serve the website
368 from the current project directory. This webserver is only suitable
369 for testing and develop. Do not use it in production setups!
371 By the default, the webserver can be accessed at: http://127.0.0.1:8088
375 def add_args(parser):
376 group = parser.add_argument_group('Server arguments')
377 group.add_argument('--server', default='127.0.0.1:8088',
378 help='The address the server will listen to.')
382 run_php_server(args.server, args.project_dir / 'website')
385 def nominatim(**kwargs):
387 Command-line tools for importing, updating, administrating and
388 querying the Nominatim database.
390 parser = CommandlineParser('nominatim', nominatim.__doc__)
392 parser.add_subcommand('import', SetupAll)
393 parser.add_subcommand('freeze', SetupFreeze)
394 parser.add_subcommand('replication', clicmd.UpdateReplication)
396 parser.add_subcommand('check-database', AdminCheckDatabase)
397 parser.add_subcommand('warm', AdminWarm)
399 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
401 parser.add_subcommand('add-data', UpdateAddData)
402 parser.add_subcommand('index', clicmd.UpdateIndex)
403 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
405 parser.add_subcommand('export', QueryExport)
406 parser.add_subcommand('serve', AdminServe)
408 if kwargs.get('phpcgi_path'):
409 parser.add_subcommand('search', clicmd.APISearch)
410 parser.add_subcommand('reverse', clicmd.APIReverse)
411 parser.add_subcommand('lookup', clicmd.APILookup)
412 parser.add_subcommand('details', clicmd.APIDetails)
413 parser.add_subcommand('status', clicmd.APIStatus)
415 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
417 return parser.run(**kwargs)