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', 'sqllib_dir',
72 'data_dir', 'config_dir', 'phpcgi_path'):
73 setattr(args, arg, Path(kwargs[arg]))
74 args.project_dir = Path(args.project_dir).resolve()
76 logging.basicConfig(stream=sys.stderr,
77 format='%(asctime)s: %(message)s',
78 datefmt='%Y-%m-%d %H:%M:%S',
79 level=max(4 - args.verbose, 1) * 10)
81 args.config = Configuration(args.project_dir, args.config_dir)
83 log = logging.getLogger()
84 log.warning('Using project directory: %s', str(args.project_dir))
87 return args.command.run(args)
88 except UsageError as exception:
89 if log.isEnabledFor(logging.DEBUG):
90 raise # use Python's exception printing
91 log.fatal('FATAL: %s', exception)
93 # If we get here, then execution has failed in some way.
97 ##### Subcommand classes
99 # Each class needs to implement two functions: add_args() adds the CLI parameters
100 # for the subfunction, run() executes the subcommand.
102 # The class documentation doubles as the help text for the command. The
103 # first line is also used in the summary when calling the program without
106 # No need to document the functions each time.
107 # pylint: disable=C0111
108 # Using non-top-level imports to make pyosmium optional for replication only.
109 # pylint: disable=E0012,C0415
114 Create a new Nominatim database from an OSM file.
118 def add_args(parser):
119 group_name = parser.add_argument_group('Required arguments')
120 group = group_name.add_mutually_exclusive_group(required=True)
121 group.add_argument('--osm-file',
122 help='OSM file to be imported.')
123 group.add_argument('--continue', dest='continue_at',
124 choices=['load-data', 'indexing', 'db-postprocess'],
125 help='Continue an import that was interrupted')
126 group = parser.add_argument_group('Optional arguments')
127 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
128 help='Size of cache to be used by osm2pgsql (in MB)')
129 group.add_argument('--reverse-only', action='store_true',
130 help='Do not create tables and indexes for searching')
131 group.add_argument('--enable-debug-statements', action='store_true',
132 help='Include debug warning statements in SQL code')
133 group.add_argument('--no-partitions', action='store_true',
134 help="""Do not partition search indices
135 (speeds up import of single country extracts)""")
136 group.add_argument('--no-updates', action='store_true',
137 help="""Do not keep tables that are only needed for
138 updating the database later""")
139 group = parser.add_argument_group('Expert options')
140 group.add_argument('--ignore-errors', action='store_true',
141 help='Continue import even when errors in SQL are present')
142 group.add_argument('--index-noanalyse', action='store_true',
143 help='Do not perform analyse operations during index')
148 params = ['setup.php']
150 params.extend(('--all', '--osm-file', args.osm_file))
152 if args.continue_at == 'load-data':
153 params.append('--load-data')
154 if args.continue_at in ('load-data', 'indexing'):
155 params.append('--index')
156 params.extend(('--create-search-indices', '--create-country-names',
158 if args.osm2pgsql_cache:
159 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
160 if args.reverse_only:
161 params.append('--reverse-only')
162 if args.enable_debug_statements:
163 params.append('--enable-debug-statements')
164 if args.no_partitions:
165 params.append('--no-partitions')
167 params.append('--drop')
168 if args.ignore_errors:
169 params.append('--ignore-errors')
170 if args.index_noanalyse:
171 params.append('--index-noanalyse')
173 return run_legacy_script(*params, nominatim_env=args)
178 Make database read-only.
180 About half of data in the Nominatim database is kept only to be able to
181 keep the data up-to-date with new changes made in OpenStreetMap. This
182 command drops all this data and only keeps the part needed for geocoding
185 This command has the same effect as the `--no-updates` option for imports.
189 def add_args(parser):
194 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
197 class SetupSpecialPhrases:
199 Maintain special phrases.
203 def add_args(parser):
204 group = parser.add_argument_group('Input arguments')
205 group.add_argument('--from-wiki', action='store_true',
206 help='Pull special phrases from the OSM wiki.')
207 group = parser.add_argument_group('Output arguments')
208 group.add_argument('-o', '--output', default='-',
209 help="""File to write the preprocessed phrases to.
210 If omitted, it will be written to stdout.""")
214 if args.output != '-':
215 raise NotImplementedError('Only output to stdout is currently implemented.')
216 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
221 Add additional data from a file or an online source.
223 Data is only imported, not indexed. You need to call `nominatim-update index`
224 to complete the process.
228 def add_args(parser):
229 group_name = parser.add_argument_group('Source')
230 group = group_name.add_mutually_exclusive_group(required=True)
231 group.add_argument('--file', metavar='FILE',
232 help='Import data from an OSM file')
233 group.add_argument('--diff', metavar='FILE',
234 help='Import data from an OSM diff file')
235 group.add_argument('--node', metavar='ID', type=int,
236 help='Import a single node from the API')
237 group.add_argument('--way', metavar='ID', type=int,
238 help='Import a single way from the API')
239 group.add_argument('--relation', metavar='ID', type=int,
240 help='Import a single relation from the API')
241 group.add_argument('--tiger-data', metavar='DIR',
242 help='Add housenumbers from the US TIGER census database.')
243 group = parser.add_argument_group('Extra arguments')
244 group.add_argument('--use-main-api', action='store_true',
245 help='Use OSM API instead of Overpass to download objects')
250 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
251 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
253 params = ['update.php']
255 params.extend(('--import-file', args.file))
257 params.extend(('--import-diff', args.diff))
259 params.extend(('--import-node', args.node))
261 params.extend(('--import-way', args.way))
263 params.extend(('--import-relation', args.relation))
264 if args.use_main_api:
265 params.append('--use-main-api')
266 return run_legacy_script(*params, nominatim_env=args)
271 Export addresses as CSV file from the database.
275 def add_args(parser):
276 group = parser.add_argument_group('Output arguments')
277 group.add_argument('--output-type', default='street',
278 choices=('continent', 'country', 'state', 'county',
279 'city', 'suburb', 'street', 'path'),
280 help='Type of places to output (default: street)')
281 group.add_argument('--output-format',
282 default='street;suburb;city;county;state;country',
283 help="""Semicolon-separated list of address types
284 (see --output-type). Multiple ranks can be
285 merged into one column by simply using a
286 comma-separated list.""")
287 group.add_argument('--output-all-postcodes', action='store_true',
288 help="""List all postcodes for address instead of
289 just the most likely one""")
290 group.add_argument('--language',
291 help="""Preferred language for output
292 (use local name, if omitted)""")
293 group = parser.add_argument_group('Filter arguments')
294 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
295 help='Export only objects within country')
296 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
297 help='Export only children of this OSM node')
298 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
299 help='Export only children of this OSM way')
300 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
301 help='Export only children of this OSM relation')
306 params = ['export.php',
307 '--output-type', args.output_type,
308 '--output-format', args.output_format]
309 if args.output_all_postcodes:
310 params.append('--output-all-postcodes')
312 params.extend(('--language', args.language))
313 if args.restrict_to_country:
314 params.extend(('--restrict-to-country', args.restrict_to_country))
315 if args.restrict_to_osm_node:
316 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
317 if args.restrict_to_osm_way:
318 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
319 if args.restrict_to_osm_relation:
320 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
322 return run_legacy_script(*params, nominatim_env=args)
327 Start a simple web server for serving the API.
329 This command starts the built-in PHP webserver to serve the website
330 from the current project directory. This webserver is only suitable
331 for testing and develop. Do not use it in production setups!
333 By the default, the webserver can be accessed at: http://127.0.0.1:8088
337 def add_args(parser):
338 group = parser.add_argument_group('Server arguments')
339 group.add_argument('--server', default='127.0.0.1:8088',
340 help='The address the server will listen to.')
344 run_php_server(args.server, args.project_dir / 'website')
347 def nominatim(**kwargs):
349 Command-line tools for importing, updating, administrating and
350 querying the Nominatim database.
352 parser = CommandlineParser('nominatim', nominatim.__doc__)
354 parser.add_subcommand('import', SetupAll)
355 parser.add_subcommand('freeze', SetupFreeze)
356 parser.add_subcommand('replication', clicmd.UpdateReplication)
358 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
360 parser.add_subcommand('add-data', UpdateAddData)
361 parser.add_subcommand('index', clicmd.UpdateIndex)
362 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
364 parser.add_subcommand('admin', clicmd.AdminFuncs)
366 parser.add_subcommand('export', QueryExport)
367 parser.add_subcommand('serve', AdminServe)
369 if kwargs.get('phpcgi_path'):
370 parser.add_subcommand('search', clicmd.APISearch)
371 parser.add_subcommand('reverse', clicmd.APIReverse)
372 parser.add_subcommand('lookup', clicmd.APILookup)
373 parser.add_subcommand('details', clicmd.APIDetails)
374 parser.add_subcommand('status', clicmd.APIStatus)
376 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
378 return parser.run(**kwargs)