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
15 from .clicmd.args import NominatimArgs
17 LOG = logging.getLogger()
20 class CommandlineParser:
21 """ Wraps some of the common functions for parsing the command line
22 and setting up subcommands.
24 def __init__(self, prog, description):
25 self.parser = argparse.ArgumentParser(
27 description=description,
28 formatter_class=argparse.RawDescriptionHelpFormatter)
30 self.subs = self.parser.add_subparsers(title='available commands',
33 # Arguments added to every sub-command
34 self.default_args = argparse.ArgumentParser(add_help=False)
35 group = self.default_args.add_argument_group('Default arguments')
36 group.add_argument('-h', '--help', action='help',
37 help='Show this help message and exit')
38 group.add_argument('-q', '--quiet', action='store_const', const=0,
39 dest='verbose', default=1,
40 help='Print only error messages')
41 group.add_argument('-v', '--verbose', action='count', default=1,
42 help='Increase verboseness of output')
43 group.add_argument('--project-dir', metavar='DIR', default='.',
44 help='Base directory of the Nominatim installation (default:.)')
45 group.add_argument('-j', '--threads', metavar='NUM', type=int,
46 help='Number of parallel threads to use')
49 def add_subcommand(self, name, cmd):
50 """ Add a subcommand to the parser. The subcommand must be a class
51 with a function add_args() that adds the parameters for the
52 subcommand and a run() function that executes the command.
54 parser = self.subs.add_parser(name, parents=[self.default_args],
55 help=cmd.__doc__.split('\n', 1)[0],
56 description=cmd.__doc__,
57 formatter_class=argparse.RawDescriptionHelpFormatter,
59 parser.set_defaults(command=cmd)
62 def run(self, **kwargs):
63 """ Parse the command line arguments of the program and execute the
64 appropriate subcommand.
66 args = NominatimArgs()
67 self.parser.parse_args(args=kwargs.get('cli_args'), namespace=args)
69 if args.subcommand is None:
70 self.parser.print_help()
73 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'sqllib_dir',
74 'data_dir', 'config_dir', 'phpcgi_path'):
75 setattr(args, arg, Path(kwargs[arg]))
76 args.project_dir = Path(args.project_dir).resolve()
78 logging.basicConfig(stream=sys.stderr,
79 format='%(asctime)s: %(message)s',
80 datefmt='%Y-%m-%d %H:%M:%S',
81 level=max(4 - args.verbose, 1) * 10)
83 args.config = Configuration(args.project_dir, args.config_dir)
85 log = logging.getLogger()
86 log.warning('Using project directory: %s', str(args.project_dir))
89 return args.command.run(args)
90 except UsageError as exception:
91 if log.isEnabledFor(logging.DEBUG):
92 raise # use Python's exception printing
93 log.fatal('FATAL: %s', exception)
95 # If we get here, then execution has failed in some way.
99 ##### Subcommand classes
101 # Each class needs to implement two functions: add_args() adds the CLI parameters
102 # for the subfunction, run() executes the subcommand.
104 # The class documentation doubles as the help text for the command. The
105 # first line is also used in the summary when calling the program without
108 # No need to document the functions each time.
109 # pylint: disable=C0111
110 # Using non-top-level imports to make pyosmium optional for replication only.
111 # pylint: disable=E0012,C0415
116 Create a new Nominatim database from an OSM file.
120 def add_args(parser):
121 group_name = parser.add_argument_group('Required arguments')
122 group = group_name.add_mutually_exclusive_group(required=True)
123 group.add_argument('--osm-file',
124 help='OSM file to be imported.')
125 group.add_argument('--continue', dest='continue_at',
126 choices=['load-data', 'indexing', 'db-postprocess'],
127 help='Continue an import that was interrupted')
128 group = parser.add_argument_group('Optional arguments')
129 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
130 help='Size of cache to be used by osm2pgsql (in MB)')
131 group.add_argument('--reverse-only', action='store_true',
132 help='Do not create tables and indexes for searching')
133 group.add_argument('--enable-debug-statements', action='store_true',
134 help='Include debug warning statements in SQL code')
135 group.add_argument('--no-partitions', action='store_true',
136 help="""Do not partition search indices
137 (speeds up import of single country extracts)""")
138 group.add_argument('--no-updates', action='store_true',
139 help="""Do not keep tables that are only needed for
140 updating the database later""")
141 group = parser.add_argument_group('Expert options')
142 group.add_argument('--ignore-errors', action='store_true',
143 help='Continue import even when errors in SQL are present')
144 group.add_argument('--index-noanalyse', action='store_true',
145 help='Do not perform analyse operations during index')
150 params = ['setup.php']
152 params.extend(('--all', '--osm-file', args.osm_file))
154 if args.continue_at == 'load-data':
155 params.append('--load-data')
156 if args.continue_at in ('load-data', 'indexing'):
157 params.append('--index')
158 params.extend(('--create-search-indices', '--create-country-names',
160 if args.osm2pgsql_cache:
161 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
162 if args.reverse_only:
163 params.append('--reverse-only')
164 if args.enable_debug_statements:
165 params.append('--enable-debug-statements')
166 if args.no_partitions:
167 params.append('--no-partitions')
169 params.append('--drop')
170 if args.ignore_errors:
171 params.append('--ignore-errors')
172 if args.index_noanalyse:
173 params.append('--index-noanalyse')
175 return run_legacy_script(*params, nominatim_env=args)
178 class SetupSpecialPhrases:
180 Maintain special phrases.
184 def add_args(parser):
185 group = parser.add_argument_group('Input arguments')
186 group.add_argument('--from-wiki', action='store_true',
187 help='Pull special phrases from the OSM wiki.')
188 group = parser.add_argument_group('Output arguments')
189 group.add_argument('-o', '--output', default='-',
190 help="""File to write the preprocessed phrases to.
191 If omitted, it will be written to stdout.""")
195 if args.output != '-':
196 raise NotImplementedError('Only output to stdout is currently implemented.')
197 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
202 Add additional data from a file or an online source.
204 Data is only imported, not indexed. You need to call `nominatim-update index`
205 to complete the process.
209 def add_args(parser):
210 group_name = parser.add_argument_group('Source')
211 group = group_name.add_mutually_exclusive_group(required=True)
212 group.add_argument('--file', metavar='FILE',
213 help='Import data from an OSM file')
214 group.add_argument('--diff', metavar='FILE',
215 help='Import data from an OSM diff file')
216 group.add_argument('--node', metavar='ID', type=int,
217 help='Import a single node from the API')
218 group.add_argument('--way', metavar='ID', type=int,
219 help='Import a single way from the API')
220 group.add_argument('--relation', metavar='ID', type=int,
221 help='Import a single relation from the API')
222 group.add_argument('--tiger-data', metavar='DIR',
223 help='Add housenumbers from the US TIGER census database.')
224 group = parser.add_argument_group('Extra arguments')
225 group.add_argument('--use-main-api', action='store_true',
226 help='Use OSM API instead of Overpass to download objects')
231 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
232 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
234 params = ['update.php']
236 params.extend(('--import-file', args.file))
238 params.extend(('--import-diff', args.diff))
240 params.extend(('--import-node', args.node))
242 params.extend(('--import-way', args.way))
244 params.extend(('--import-relation', args.relation))
245 if args.use_main_api:
246 params.append('--use-main-api')
247 return run_legacy_script(*params, nominatim_env=args)
252 Export addresses as CSV file from the database.
256 def add_args(parser):
257 group = parser.add_argument_group('Output arguments')
258 group.add_argument('--output-type', default='street',
259 choices=('continent', 'country', 'state', 'county',
260 'city', 'suburb', 'street', 'path'),
261 help='Type of places to output (default: street)')
262 group.add_argument('--output-format',
263 default='street;suburb;city;county;state;country',
264 help="""Semicolon-separated list of address types
265 (see --output-type). Multiple ranks can be
266 merged into one column by simply using a
267 comma-separated list.""")
268 group.add_argument('--output-all-postcodes', action='store_true',
269 help="""List all postcodes for address instead of
270 just the most likely one""")
271 group.add_argument('--language',
272 help="""Preferred language for output
273 (use local name, if omitted)""")
274 group = parser.add_argument_group('Filter arguments')
275 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
276 help='Export only objects within country')
277 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
278 help='Export only children of this OSM node')
279 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
280 help='Export only children of this OSM way')
281 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
282 help='Export only children of this OSM relation')
287 params = ['export.php',
288 '--output-type', args.output_type,
289 '--output-format', args.output_format]
290 if args.output_all_postcodes:
291 params.append('--output-all-postcodes')
293 params.extend(('--language', args.language))
294 if args.restrict_to_country:
295 params.extend(('--restrict-to-country', args.restrict_to_country))
296 if args.restrict_to_osm_node:
297 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
298 if args.restrict_to_osm_way:
299 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
300 if args.restrict_to_osm_relation:
301 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
303 return run_legacy_script(*params, nominatim_env=args)
308 Start a simple web server for serving the API.
310 This command starts the built-in PHP webserver to serve the website
311 from the current project directory. This webserver is only suitable
312 for testing and develop. Do not use it in production setups!
314 By the default, the webserver can be accessed at: http://127.0.0.1:8088
318 def add_args(parser):
319 group = parser.add_argument_group('Server arguments')
320 group.add_argument('--server', default='127.0.0.1:8088',
321 help='The address the server will listen to.')
325 run_php_server(args.server, args.project_dir / 'website')
328 def nominatim(**kwargs):
330 Command-line tools for importing, updating, administrating and
331 querying the Nominatim database.
333 parser = CommandlineParser('nominatim', nominatim.__doc__)
335 parser.add_subcommand('import', SetupAll)
336 parser.add_subcommand('freeze', clicmd.SetupFreeze)
337 parser.add_subcommand('replication', clicmd.UpdateReplication)
339 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
341 parser.add_subcommand('add-data', UpdateAddData)
342 parser.add_subcommand('index', clicmd.UpdateIndex)
343 parser.add_subcommand('refresh', clicmd.UpdateRefresh)
345 parser.add_subcommand('admin', clicmd.AdminFuncs)
347 parser.add_subcommand('export', QueryExport)
348 parser.add_subcommand('serve', AdminServe)
350 if kwargs.get('phpcgi_path'):
351 parser.add_subcommand('search', clicmd.APISearch)
352 parser.add_subcommand('reverse', clicmd.APIReverse)
353 parser.add_subcommand('lookup', clicmd.APILookup)
354 parser.add_subcommand('details', clicmd.APIDetails)
355 parser.add_subcommand('status', clicmd.APIStatus)
357 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
359 parser.add_subcommand('transition', clicmd.AdminTransition)
361 return parser.run(**kwargs)