2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
11 from pathlib import Path
13 from .config import Configuration
14 from .tools.exec_utils import run_legacy_script, run_api_script
15 from .db.connection import connect
16 from .db import status
17 from .errors import UsageError
19 LOG = logging.getLogger()
21 def _num_system_cpus():
23 cpus = len(os.sched_getaffinity(0))
24 except NotImplementedError:
27 return cpus or os.cpu_count()
30 class CommandlineParser:
31 """ Wraps some of the common functions for parsing the command line
32 and setting up subcommands.
34 def __init__(self, prog, description):
35 self.parser = argparse.ArgumentParser(
37 description=description,
38 formatter_class=argparse.RawDescriptionHelpFormatter)
40 self.subs = self.parser.add_subparsers(title='available commands',
43 # Arguments added to every sub-command
44 self.default_args = argparse.ArgumentParser(add_help=False)
45 group = self.default_args.add_argument_group('Default arguments')
46 group.add_argument('-h', '--help', action='help',
47 help='Show this help message and exit')
48 group.add_argument('-q', '--quiet', action='store_const', const=0,
49 dest='verbose', default=1,
50 help='Print only error messages')
51 group.add_argument('-v', '--verbose', action='count', default=1,
52 help='Increase verboseness of output')
53 group.add_argument('--project-dir', metavar='DIR', default='.',
54 help='Base directory of the Nominatim installation (default:.)')
55 group.add_argument('-j', '--threads', metavar='NUM', type=int,
56 help='Number of parallel threads to use')
59 def add_subcommand(self, name, cmd):
60 """ Add a subcommand to the parser. The subcommand must be a class
61 with a function add_args() that adds the parameters for the
62 subcommand and a run() function that executes the command.
64 parser = self.subs.add_parser(name, parents=[self.default_args],
65 help=cmd.__doc__.split('\n', 1)[0],
66 description=cmd.__doc__,
67 formatter_class=argparse.RawDescriptionHelpFormatter,
69 parser.set_defaults(command=cmd)
72 def run(self, **kwargs):
73 """ Parse the command line arguments of the program and execute the
74 appropriate subcommand.
76 args = self.parser.parse_args(args=kwargs.get('cli_args'))
78 if args.subcommand is None:
79 self.parser.print_help()
82 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
83 setattr(args, arg, Path(kwargs[arg]))
84 args.project_dir = Path(args.project_dir)
86 logging.basicConfig(stream=sys.stderr,
87 format='%(asctime)s: %(message)s',
88 datefmt='%Y-%m-%d %H:%M:%S',
89 level=max(4 - args.verbose, 1) * 10)
91 args.config = Configuration(args.project_dir, args.data_dir / 'settings')
94 return args.command.run(args)
95 except UsageError as e:
96 log = logging.getLogger()
97 if log.isEnabledFor(logging.DEBUG):
98 raise # use Python's exception printing
99 log.fatal('FATAL: ' + str(e))
101 # If we get here, then execution has failed in some way.
105 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
106 """ Set up the stanadrd osm2pgsql from the command line arguments.
108 return dict(osm2pgsql=args.osm2pgsql_path,
109 osm2pgsql_cache=args.osm2pgsql_cache or default_cache,
110 osm2pgsql_style=args.config.get_import_style_file(),
111 threads=args.threads or default_threads,
112 dsn=args.config.get_libpq_dsn(),
113 flatnode_file=args.config.FLATNODE_FILE)
115 ##### Subcommand classes
117 # Each class needs to implement two functions: add_args() adds the CLI parameters
118 # for the subfunction, run() executes the subcommand.
120 # The class documentation doubles as the help text for the command. The
121 # first line is also used in the summary when calling the program without
124 # No need to document the functions each time.
125 # pylint: disable=C0111
130 Create a new Nominatim database from an OSM file.
134 def add_args(parser):
135 group_name = parser.add_argument_group('Required arguments')
136 group = group_name.add_mutually_exclusive_group(required=True)
137 group.add_argument('--osm-file',
138 help='OSM file to be imported.')
139 group.add_argument('--continue', dest='continue_at',
140 choices=['load-data', 'indexing', 'db-postprocess'],
141 help='Continue an import that was interrupted')
142 group = parser.add_argument_group('Optional arguments')
143 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
144 help='Size of cache to be used by osm2pgsql (in MB)')
145 group.add_argument('--reverse-only', action='store_true',
146 help='Do not create tables and indexes for searching')
147 group.add_argument('--enable-debug-statements', action='store_true',
148 help='Include debug warning statements in SQL code')
149 group.add_argument('--no-partitions', action='store_true',
150 help="""Do not partition search indices
151 (speeds up import of single country extracts)""")
152 group.add_argument('--no-updates', action='store_true',
153 help="""Do not keep tables that are only needed for
154 updating the database later""")
155 group = parser.add_argument_group('Expert options')
156 group.add_argument('--ignore-errors', action='store_true',
157 help='Continue import even when errors in SQL are present')
158 group.add_argument('--index-noanalyse', action='store_true',
159 help='Do not perform analyse operations during index')
164 params = ['setup.php']
166 params.extend(('--all', '--osm-file', args.osm_file))
168 if args.continue_at == 'load-data':
169 params.append('--load-data')
170 if args.continue_at in ('load-data', 'indexing'):
171 params.append('--index')
172 params.extend(('--create-search-indices', '--create-country-names',
174 if args.osm2pgsql_cache:
175 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
176 if args.reverse_only:
177 params.append('--reverse-only')
178 if args.enable_debug_statements:
179 params.append('--enable-debug-statements')
180 if args.no_partitions:
181 params.append('--no-partitions')
183 params.append('--drop')
184 if args.ignore_errors:
185 params.append('--ignore-errors')
186 if args.index_noanalyse:
187 params.append('--index-noanalyse')
189 return run_legacy_script(*params, nominatim_env=args)
194 Make database read-only.
196 About half of data in the Nominatim database is kept only to be able to
197 keep the data up-to-date with new changes made in OpenStreetMap. This
198 command drops all this data and only keeps the part needed for geocoding
201 This command has the same effect as the `--no-updates` option for imports.
205 def add_args(parser):
210 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
213 class SetupSpecialPhrases:
215 Maintain special phrases.
219 def add_args(parser):
220 group = parser.add_argument_group('Input arguments')
221 group.add_argument('--from-wiki', action='store_true',
222 help='Pull special phrases from the OSM wiki.')
223 group = parser.add_argument_group('Output arguments')
224 group.add_argument('-o', '--output', default='-',
225 help="""File to write the preprocessed phrases to.
226 If omitted, it will be written to stdout.""")
230 if args.output != '-':
231 raise NotImplementedError('Only output to stdout is currently implemented.')
232 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
235 class UpdateReplication:
237 Update the database using an online replication service.
241 def add_args(parser):
242 group = parser.add_argument_group('Arguments for initialisation')
243 group.add_argument('--init', action='store_true',
244 help='Initialise the update process')
245 group.add_argument('--no-update-functions', dest='update_functions',
246 action='store_false',
247 help="""Do not update the trigger function to
248 support differential updates.""")
249 group = parser.add_argument_group('Arguments for updates')
250 group.add_argument('--check-for-updates', action='store_true',
251 help='Check if new updates are available and exit')
252 group.add_argument('--once', action='store_true',
253 help="""Download and apply updates only once. When
254 not set, updates are continuously applied""")
255 group.add_argument('--no-index', action='store_false', dest='do_index',
256 help="""Do not index the new data. Only applicable
257 together with --once""")
258 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
259 help='Size of cache to be used by osm2pgsql (in MB)')
262 def _init_replication(args):
263 from .tools import replication, refresh
265 LOG.warning("Initialising replication updates")
266 conn = connect(args.config.get_libpq_dsn())
267 replication.init_replication(conn, base_url=args.config.REPLICATION_URL)
268 if args.update_functions:
269 LOG.warning("Create functions")
270 refresh.create_functions(conn, args.config, args.data_dir,
277 def _check_for_updates(args):
278 from .tools import replication
280 conn = connect(args.config.get_libpq_dsn())
281 ret = replication.check_for_updates(conn, base_url=args.config.REPLICATION_URL)
288 from .tools import replication
289 from .indexer.indexer import Indexer
291 params = _osm2pgsql_options_from_args(args, 2000, 1)
292 params.update(base_url=args.config.REPLICATION_URL,
293 update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'),
294 import_file=args.project_dir / 'osmosischange.osc',
295 max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'),
296 indexed_only=not args.once)
298 # Sanity check to not overwhelm the Geofabrik servers.
299 if 'download.geofabrik.de'in params['base_url']\
300 and params['update_interval'] < 86400:
301 LOG.fatal("Update interval too low for download.geofabrik.de.\n"
302 "Please check install documentation "
303 "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
304 "setting-up-the-update-process).")
305 raise UsageError("Invalid replication update interval setting.")
308 if not args.do_index:
309 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
310 raise UsageError("Bad argument '--no-index'.")
311 recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
314 conn = connect(args.config.get_libpq_dsn())
315 start = dt.datetime.now(dt.timezone.utc)
316 state = replication.update(conn, params)
317 status.log_status(conn, start, 'import')
320 if state is not replication.UpdateState.NO_CHANGES and args.do_index:
321 start = dt.datetime.now(dt.timezone.utc)
322 indexer = Indexer(args.config.get_libpq_dsn(),
324 indexer.index_boundaries(0, 30)
325 indexer.index_by_rank(0, 30)
327 conn = connect(args.config.get_libpq_dsn())
328 status.set_indexed(conn, True)
329 status.log_status(conn, start, 'index')
335 if state is replication.UpdateState.NO_CHANGES:
336 LOG.warning("No new changes. Sleeping for %d sec.", recheck_interval)
337 time.sleep(recheck_interval)
344 import osmium # pylint: disable=W0611
345 except ModuleNotFoundError:
346 LOG.fatal("pyosmium not installed. Replication functions not available.\n"
347 "To install pyosmium via pip: pip3 install osmium")
351 return UpdateReplication._init_replication(args)
353 if args.check_for_updates:
354 return UpdateReplication._check_for_updates(args)
356 return UpdateReplication._update(args)
360 Add additional data from a file or an online source.
362 Data is only imported, not indexed. You need to call `nominatim-update index`
363 to complete the process.
367 def add_args(parser):
368 group_name = parser.add_argument_group('Source')
369 group = group_name.add_mutually_exclusive_group(required=True)
370 group.add_argument('--file', metavar='FILE',
371 help='Import data from an OSM file')
372 group.add_argument('--diff', metavar='FILE',
373 help='Import data from an OSM diff file')
374 group.add_argument('--node', metavar='ID', type=int,
375 help='Import a single node from the API')
376 group.add_argument('--way', metavar='ID', type=int,
377 help='Import a single way from the API')
378 group.add_argument('--relation', metavar='ID', type=int,
379 help='Import a single relation from the API')
380 group.add_argument('--tiger-data', metavar='DIR',
381 help='Add housenumbers from the US TIGER census database.')
382 group = parser.add_argument_group('Extra arguments')
383 group.add_argument('--use-main-api', action='store_true',
384 help='Use OSM API instead of Overpass to download objects')
389 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
390 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
392 params = ['update.php']
394 params.extend(('--import-file', args.file))
396 params.extend(('--import-diff', args.diff))
398 params.extend(('--import-node', args.node))
400 params.extend(('--import-way', args.way))
402 params.extend(('--import-relation', args.relation))
403 if args.use_main_api:
404 params.append('--use-main-api')
405 return run_legacy_script(*params, nominatim_env=args)
410 Reindex all new and modified data.
414 def add_args(parser):
415 group = parser.add_argument_group('Filter arguments')
416 group.add_argument('--boundaries-only', action='store_true',
417 help="""Index only administrative boundaries.""")
418 group.add_argument('--no-boundaries', action='store_true',
419 help="""Index everything except administrative boundaries.""")
420 group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
421 help='Minimum/starting rank')
422 group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
423 help='Maximum/finishing rank')
427 from .indexer.indexer import Indexer
429 indexer = Indexer(args.config.get_libpq_dsn(),
430 args.threads or _num_system_cpus() or 1)
432 if not args.no_boundaries:
433 indexer.index_boundaries(args.minrank, args.maxrank)
434 if not args.boundaries_only:
435 indexer.index_by_rank(args.minrank, args.maxrank)
437 if not args.no_boundaries and not args.boundaries_only \
438 and args.minrank == 0 and args.maxrank == 30:
439 conn = connect(args.config.get_libpq_dsn())
440 status.set_indexed(conn, True)
448 Recompute auxiliary data used by the indexing process.
450 These functions must not be run in parallel with other update commands.
454 def add_args(parser):
455 group = parser.add_argument_group('Data arguments')
456 group.add_argument('--postcodes', action='store_true',
457 help='Update postcode centroid table')
458 group.add_argument('--word-counts', action='store_true',
459 help='Compute frequency of full-word search terms')
460 group.add_argument('--address-levels', action='store_true',
461 help='Reimport address level configuration')
462 group.add_argument('--functions', action='store_true',
463 help='Update the PL/pgSQL functions in the database')
464 group.add_argument('--wiki-data', action='store_true',
465 help='Update Wikipedia/data importance numbers.')
466 group.add_argument('--importance', action='store_true',
467 help='Recompute place importances (expensive!)')
468 group.add_argument('--website', action='store_true',
469 help='Refresh the directory that serves the scripts for the web API')
470 group = parser.add_argument_group('Arguments for function refresh')
471 group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
472 help='Do not enable code for propagating updates')
473 group.add_argument('--enable-debug-statements', action='store_true',
474 help='Enable debug warning statements in functions')
478 from .tools import refresh
481 LOG.warning("Update postcodes centroid")
482 conn = connect(args.config.get_libpq_dsn())
483 refresh.update_postcodes(conn, args.data_dir)
487 LOG.warning('Recompute frequency of full-word search terms')
488 conn = connect(args.config.get_libpq_dsn())
489 refresh.recompute_word_counts(conn, args.data_dir)
492 if args.address_levels:
493 cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
494 LOG.warning('Updating address levels from %s', cfg)
495 conn = connect(args.config.get_libpq_dsn())
496 refresh.load_address_levels_from_file(conn, cfg)
500 LOG.warning('Create functions')
501 conn = connect(args.config.get_libpq_dsn())
502 refresh.create_functions(conn, args.config, args.data_dir,
503 args.diffs, args.enable_debug_statements)
507 run_legacy_script('setup.php', '--import-wikipedia-articles',
508 nominatim_env=args, throw_on_fail=True)
509 # Attention: importance MUST come after wiki data import.
511 run_legacy_script('update.php', '--recompute-importance',
512 nominatim_env=args, throw_on_fail=True)
514 run_legacy_script('setup.php', '--setup-website',
515 nominatim_env=args, throw_on_fail=True)
520 class AdminCheckDatabase:
522 Check that the database is complete and operational.
526 def add_args(parser):
531 return run_legacy_script('check_import_finished.php', nominatim_env=args)
536 Warm database caches for search and reverse queries.
540 def add_args(parser):
541 group = parser.add_argument_group('Target arguments')
542 group.add_argument('--search-only', action='store_const', dest='target',
544 help="Only pre-warm tables for search queries")
545 group.add_argument('--reverse-only', action='store_const', dest='target',
547 help="Only pre-warm tables for reverse queries")
551 params = ['warm.php']
552 if args.target == 'reverse':
553 params.append('--reverse-only')
554 if args.target == 'search':
555 params.append('--search-only')
556 return run_legacy_script(*params, nominatim_env=args)
561 Export addresses as CSV file from the database.
565 def add_args(parser):
566 group = parser.add_argument_group('Output arguments')
567 group.add_argument('--output-type', default='street',
568 choices=('continent', 'country', 'state', 'county',
569 'city', 'suburb', 'street', 'path'),
570 help='Type of places to output (default: street)')
571 group.add_argument('--output-format',
572 default='street;suburb;city;county;state;country',
573 help="""Semicolon-separated list of address types
574 (see --output-type). Multiple ranks can be
575 merged into one column by simply using a
576 comma-separated list.""")
577 group.add_argument('--output-all-postcodes', action='store_true',
578 help="""List all postcodes for address instead of
579 just the most likely one""")
580 group.add_argument('--language',
581 help="""Preferred language for output
582 (use local name, if omitted)""")
583 group = parser.add_argument_group('Filter arguments')
584 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
585 help='Export only objects within country')
586 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
587 help='Export only children of this OSM node')
588 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
589 help='Export only children of this OSM way')
590 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
591 help='Export only children of this OSM relation')
596 params = ['export.php',
597 '--output-type', args.output_type,
598 '--output-format', args.output_format]
599 if args.output_all_postcodes:
600 params.append('--output-all-postcodes')
602 params.extend(('--language', args.language))
603 if args.restrict_to_country:
604 params.extend(('--restrict-to-country', args.restrict_to_country))
605 if args.restrict_to_osm_node:
606 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
607 if args.restrict_to_osm_way:
608 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
609 if args.restrict_to_osm_relation:
610 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
612 return run_legacy_script(*params, nominatim_env=args)
615 ('street', 'housenumber and street'),
616 ('city', 'city, town or village'),
617 ('county', 'county'),
619 ('country', 'country'),
620 ('postalcode', 'postcode')
624 ('addressdetails', 'Include a breakdown of the address into elements.'),
625 ('extratags', """Include additional information if available
626 (e.g. wikipedia link, opening hours)."""),
627 ('namedetails', 'Include a list of alternative names.')
631 ('addressdetails', 'Include a breakdown of the address into elements.'),
632 ('keywords', 'Include a list of name keywords and address keywords.'),
633 ('linkedplaces', 'Include a details of places that are linked with this one.'),
634 ('hierarchy', 'Include details of places lower in the address hierarchy.'),
635 ('group_hierarchy', 'Group the places by type.'),
636 ('polygon_geojson', 'Include geometry of result.')
639 def _add_api_output_arguments(parser):
640 group = parser.add_argument_group('Output arguments')
641 group.add_argument('--format', default='jsonv2',
642 choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
643 help='Format of result')
644 for name, desc in EXTRADATA_PARAMS:
645 group.add_argument('--' + name, action='store_true', help=desc)
647 group.add_argument('--lang', '--accept-language', metavar='LANGS',
648 help='Preferred language order for presenting search results')
649 group.add_argument('--polygon-output',
650 choices=['geojson', 'kml', 'svg', 'text'],
651 help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
652 group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
653 help="""Simplify output geometry.
654 Parameter is difference tolerance in degrees.""")
659 Execute API search query.
663 def add_args(parser):
664 group = parser.add_argument_group('Query arguments')
665 group.add_argument('--query',
666 help='Free-form query string')
667 for name, desc in STRUCTURED_QUERY:
668 group.add_argument('--' + name, help='Structured query: ' + desc)
670 _add_api_output_arguments(parser)
672 group = parser.add_argument_group('Result limitation')
673 group.add_argument('--countrycodes', metavar='CC,..',
674 help='Limit search results to one or more countries.')
675 group.add_argument('--exclude_place_ids', metavar='ID,..',
676 help='List of search object to be excluded')
677 group.add_argument('--limit', type=int,
678 help='Limit the number of returned results')
679 group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
680 help='Preferred area to find search results')
681 group.add_argument('--bounded', action='store_true',
682 help='Strictly restrict results to viewbox area')
684 group = parser.add_argument_group('Other arguments')
685 group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
686 help='Do not remove duplicates from the result list')
692 params = dict(q=args.query)
694 params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
696 for param, _ in EXTRADATA_PARAMS:
697 if getattr(args, param):
699 for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
700 if getattr(args, param):
701 params[param] = getattr(args, param)
703 params['accept-language'] = args.lang
704 if args.polygon_output:
705 params['polygon_' + args.polygon_output] = '1'
706 if args.polygon_threshold:
707 params['polygon_threshold'] = args.polygon_threshold
709 params['bounded'] = '1'
711 params['dedupe'] = '0'
713 return run_api_script('search', args.project_dir,
714 phpcgi_bin=args.phpcgi_path, params=params)
718 Execute API reverse query.
722 def add_args(parser):
723 group = parser.add_argument_group('Query arguments')
724 group.add_argument('--lat', type=float, required=True,
725 help='Latitude of coordinate to look up (in WGS84)')
726 group.add_argument('--lon', type=float, required=True,
727 help='Longitude of coordinate to look up (in WGS84)')
728 group.add_argument('--zoom', type=int,
729 help='Level of detail required for the address')
731 _add_api_output_arguments(parser)
736 params = dict(lat=args.lat, lon=args.lon)
737 if args.zoom is not None:
738 params['zoom'] = args.zoom
740 for param, _ in EXTRADATA_PARAMS:
741 if getattr(args, param):
744 params['format'] = args.format
746 params['accept-language'] = args.lang
747 if args.polygon_output:
748 params['polygon_' + args.polygon_output] = '1'
749 if args.polygon_threshold:
750 params['polygon_threshold'] = args.polygon_threshold
752 return run_api_script('reverse', args.project_dir,
753 phpcgi_bin=args.phpcgi_path, params=params)
758 Execute API reverse query.
762 def add_args(parser):
763 group = parser.add_argument_group('Query arguments')
764 group.add_argument('--id', metavar='OSMID',
765 action='append', required=True, dest='ids',
766 help='OSM id to lookup in format <NRW><id> (may be repeated)')
768 _add_api_output_arguments(parser)
773 params = dict(osm_ids=','.join(args.ids))
775 for param, _ in EXTRADATA_PARAMS:
776 if getattr(args, param):
779 params['format'] = args.format
781 params['accept-language'] = args.lang
782 if args.polygon_output:
783 params['polygon_' + args.polygon_output] = '1'
784 if args.polygon_threshold:
785 params['polygon_threshold'] = args.polygon_threshold
787 return run_api_script('lookup', args.project_dir,
788 phpcgi_bin=args.phpcgi_path, params=params)
793 Execute API lookup query.
797 def add_args(parser):
798 group = parser.add_argument_group('Query arguments')
799 objs = group.add_mutually_exclusive_group(required=True)
800 objs.add_argument('--node', '-n', type=int,
801 help="Look up the OSM node with the given ID.")
802 objs.add_argument('--way', '-w', type=int,
803 help="Look up the OSM way with the given ID.")
804 objs.add_argument('--relation', '-r', type=int,
805 help="Look up the OSM relation with the given ID.")
806 objs.add_argument('--place_id', '-p', type=int,
807 help='Database internal identifier of the OSM object to look up.')
808 group.add_argument('--class', dest='object_class',
809 help="""Class type to disambiguated multiple entries
810 of the same object.""")
812 group = parser.add_argument_group('Output arguments')
813 for name, desc in DETAILS_SWITCHES:
814 group.add_argument('--' + name, action='store_true', help=desc)
815 group.add_argument('--lang', '--accept-language', metavar='LANGS',
816 help='Preferred language order for presenting search results')
821 params = dict(osmtype='N', osmid=args.node)
823 params = dict(osmtype='W', osmid=args.node)
825 params = dict(osmtype='R', osmid=args.node)
827 params = dict(place_id=args.place_id)
828 if args.object_class:
829 params['class'] = args.object_class
830 for name, _ in DETAILS_SWITCHES:
831 params[name] = '1' if getattr(args, name) else '0'
833 return run_api_script('details', args.project_dir,
834 phpcgi_bin=args.phpcgi_path, params=params)
839 Execute API status query.
843 def add_args(parser):
844 group = parser.add_argument_group('API parameters')
845 group.add_argument('--format', default='text', choices=['text', 'json'],
846 help='Format of result')
850 return run_api_script('status', args.project_dir,
851 phpcgi_bin=args.phpcgi_path,
852 params=dict(format=args.format))
855 def nominatim(**kwargs):
857 Command-line tools for importing, updating, administrating and
858 querying the Nominatim database.
860 parser = CommandlineParser('nominatim', nominatim.__doc__)
862 parser.add_subcommand('import', SetupAll)
863 parser.add_subcommand('freeze', SetupFreeze)
864 parser.add_subcommand('replication', UpdateReplication)
866 parser.add_subcommand('check-database', AdminCheckDatabase)
867 parser.add_subcommand('warm', AdminWarm)
869 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
871 parser.add_subcommand('add-data', UpdateAddData)
872 parser.add_subcommand('index', UpdateIndex)
873 parser.add_subcommand('refresh', UpdateRefresh)
875 parser.add_subcommand('export', QueryExport)
877 if kwargs.get('phpcgi_path'):
878 parser.add_subcommand('search', APISearch)
879 parser.add_subcommand('reverse', APIReverse)
880 parser.add_subcommand('lookup', APILookup)
881 parser.add_subcommand('details', APIDetails)
882 parser.add_subcommand('status', APIStatus)
884 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
886 return parser.run(**kwargs)