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
126 # Using non-top-level imports to make pyosmium optional for replication only.
127 # pylint: disable=C0415
132 Create a new Nominatim database from an OSM file.
136 def add_args(parser):
137 group_name = parser.add_argument_group('Required arguments')
138 group = group_name.add_mutually_exclusive_group(required=True)
139 group.add_argument('--osm-file',
140 help='OSM file to be imported.')
141 group.add_argument('--continue', dest='continue_at',
142 choices=['load-data', 'indexing', 'db-postprocess'],
143 help='Continue an import that was interrupted')
144 group = parser.add_argument_group('Optional arguments')
145 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
146 help='Size of cache to be used by osm2pgsql (in MB)')
147 group.add_argument('--reverse-only', action='store_true',
148 help='Do not create tables and indexes for searching')
149 group.add_argument('--enable-debug-statements', action='store_true',
150 help='Include debug warning statements in SQL code')
151 group.add_argument('--no-partitions', action='store_true',
152 help="""Do not partition search indices
153 (speeds up import of single country extracts)""")
154 group.add_argument('--no-updates', action='store_true',
155 help="""Do not keep tables that are only needed for
156 updating the database later""")
157 group = parser.add_argument_group('Expert options')
158 group.add_argument('--ignore-errors', action='store_true',
159 help='Continue import even when errors in SQL are present')
160 group.add_argument('--index-noanalyse', action='store_true',
161 help='Do not perform analyse operations during index')
166 params = ['setup.php']
168 params.extend(('--all', '--osm-file', args.osm_file))
170 if args.continue_at == 'load-data':
171 params.append('--load-data')
172 if args.continue_at in ('load-data', 'indexing'):
173 params.append('--index')
174 params.extend(('--create-search-indices', '--create-country-names',
176 if args.osm2pgsql_cache:
177 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
178 if args.reverse_only:
179 params.append('--reverse-only')
180 if args.enable_debug_statements:
181 params.append('--enable-debug-statements')
182 if args.no_partitions:
183 params.append('--no-partitions')
185 params.append('--drop')
186 if args.ignore_errors:
187 params.append('--ignore-errors')
188 if args.index_noanalyse:
189 params.append('--index-noanalyse')
191 return run_legacy_script(*params, nominatim_env=args)
196 Make database read-only.
198 About half of data in the Nominatim database is kept only to be able to
199 keep the data up-to-date with new changes made in OpenStreetMap. This
200 command drops all this data and only keeps the part needed for geocoding
203 This command has the same effect as the `--no-updates` option for imports.
207 def add_args(parser):
212 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
215 class SetupSpecialPhrases:
217 Maintain special phrases.
221 def add_args(parser):
222 group = parser.add_argument_group('Input arguments')
223 group.add_argument('--from-wiki', action='store_true',
224 help='Pull special phrases from the OSM wiki.')
225 group = parser.add_argument_group('Output arguments')
226 group.add_argument('-o', '--output', default='-',
227 help="""File to write the preprocessed phrases to.
228 If omitted, it will be written to stdout.""")
232 if args.output != '-':
233 raise NotImplementedError('Only output to stdout is currently implemented.')
234 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
237 class UpdateReplication:
239 Update the database using an online replication service.
243 def add_args(parser):
244 group = parser.add_argument_group('Arguments for initialisation')
245 group.add_argument('--init', action='store_true',
246 help='Initialise the update process')
247 group.add_argument('--no-update-functions', dest='update_functions',
248 action='store_false',
249 help="""Do not update the trigger function to
250 support differential updates.""")
251 group = parser.add_argument_group('Arguments for updates')
252 group.add_argument('--check-for-updates', action='store_true',
253 help='Check if new updates are available and exit')
254 group.add_argument('--once', action='store_true',
255 help="""Download and apply updates only once. When
256 not set, updates are continuously applied""")
257 group.add_argument('--no-index', action='store_false', dest='do_index',
258 help="""Do not index the new data. Only applicable
259 together with --once""")
260 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
261 help='Size of cache to be used by osm2pgsql (in MB)')
264 def _init_replication(args):
265 from .tools import replication, refresh
267 LOG.warning("Initialising replication updates")
268 conn = connect(args.config.get_libpq_dsn())
269 replication.init_replication(conn, base_url=args.config.REPLICATION_URL)
270 if args.update_functions:
271 LOG.warning("Create functions")
272 refresh.create_functions(conn, args.config, args.data_dir,
279 def _check_for_updates(args):
280 from .tools import replication
282 conn = connect(args.config.get_libpq_dsn())
283 ret = replication.check_for_updates(conn, base_url=args.config.REPLICATION_URL)
290 from .tools import replication
291 from .indexer.indexer import Indexer
293 params = _osm2pgsql_options_from_args(args, 2000, 1)
294 params.update(base_url=args.config.REPLICATION_URL,
295 update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'),
296 import_file=args.project_dir / 'osmosischange.osc',
297 max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'),
298 indexed_only=not args.once)
300 # Sanity check to not overwhelm the Geofabrik servers.
301 if 'download.geofabrik.de'in params['base_url']\
302 and params['update_interval'] < 86400:
303 LOG.fatal("Update interval too low for download.geofabrik.de.\n"
304 "Please check install documentation "
305 "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
306 "setting-up-the-update-process).")
307 raise UsageError("Invalid replication update interval setting.")
310 if not args.do_index:
311 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
312 raise UsageError("Bad argument '--no-index'.")
313 recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
316 conn = connect(args.config.get_libpq_dsn())
317 start = dt.datetime.now(dt.timezone.utc)
318 state = replication.update(conn, params)
319 status.log_status(conn, start, 'import')
322 if state is not replication.UpdateState.NO_CHANGES and args.do_index:
323 start = dt.datetime.now(dt.timezone.utc)
324 indexer = Indexer(args.config.get_libpq_dsn(),
326 indexer.index_boundaries(0, 30)
327 indexer.index_by_rank(0, 30)
329 conn = connect(args.config.get_libpq_dsn())
330 status.set_indexed(conn, True)
331 status.log_status(conn, start, 'index')
337 if state is replication.UpdateState.NO_CHANGES:
338 LOG.warning("No new changes. Sleeping for %d sec.", recheck_interval)
339 time.sleep(recheck_interval)
346 import osmium # pylint: disable=W0611
347 except ModuleNotFoundError:
348 LOG.fatal("pyosmium not installed. Replication functions not available.\n"
349 "To install pyosmium via pip: pip3 install osmium")
353 return UpdateReplication._init_replication(args)
355 if args.check_for_updates:
356 return UpdateReplication._check_for_updates(args)
358 return UpdateReplication._update(args)
362 Add additional data from a file or an online source.
364 Data is only imported, not indexed. You need to call `nominatim-update index`
365 to complete the process.
369 def add_args(parser):
370 group_name = parser.add_argument_group('Source')
371 group = group_name.add_mutually_exclusive_group(required=True)
372 group.add_argument('--file', metavar='FILE',
373 help='Import data from an OSM file')
374 group.add_argument('--diff', metavar='FILE',
375 help='Import data from an OSM diff file')
376 group.add_argument('--node', metavar='ID', type=int,
377 help='Import a single node from the API')
378 group.add_argument('--way', metavar='ID', type=int,
379 help='Import a single way from the API')
380 group.add_argument('--relation', metavar='ID', type=int,
381 help='Import a single relation from the API')
382 group.add_argument('--tiger-data', metavar='DIR',
383 help='Add housenumbers from the US TIGER census database.')
384 group = parser.add_argument_group('Extra arguments')
385 group.add_argument('--use-main-api', action='store_true',
386 help='Use OSM API instead of Overpass to download objects')
391 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
392 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
394 params = ['update.php']
396 params.extend(('--import-file', args.file))
398 params.extend(('--import-diff', args.diff))
400 params.extend(('--import-node', args.node))
402 params.extend(('--import-way', args.way))
404 params.extend(('--import-relation', args.relation))
405 if args.use_main_api:
406 params.append('--use-main-api')
407 return run_legacy_script(*params, nominatim_env=args)
412 Reindex all new and modified data.
416 def add_args(parser):
417 group = parser.add_argument_group('Filter arguments')
418 group.add_argument('--boundaries-only', action='store_true',
419 help="""Index only administrative boundaries.""")
420 group.add_argument('--no-boundaries', action='store_true',
421 help="""Index everything except administrative boundaries.""")
422 group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
423 help='Minimum/starting rank')
424 group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
425 help='Maximum/finishing rank')
429 from .indexer.indexer import Indexer
431 indexer = Indexer(args.config.get_libpq_dsn(),
432 args.threads or _num_system_cpus() or 1)
434 if not args.no_boundaries:
435 indexer.index_boundaries(args.minrank, args.maxrank)
436 if not args.boundaries_only:
437 indexer.index_by_rank(args.minrank, args.maxrank)
439 if not args.no_boundaries and not args.boundaries_only \
440 and args.minrank == 0 and args.maxrank == 30:
441 conn = connect(args.config.get_libpq_dsn())
442 status.set_indexed(conn, True)
450 Recompute auxiliary data used by the indexing process.
452 These functions must not be run in parallel with other update commands.
456 def add_args(parser):
457 group = parser.add_argument_group('Data arguments')
458 group.add_argument('--postcodes', action='store_true',
459 help='Update postcode centroid table')
460 group.add_argument('--word-counts', action='store_true',
461 help='Compute frequency of full-word search terms')
462 group.add_argument('--address-levels', action='store_true',
463 help='Reimport address level configuration')
464 group.add_argument('--functions', action='store_true',
465 help='Update the PL/pgSQL functions in the database')
466 group.add_argument('--wiki-data', action='store_true',
467 help='Update Wikipedia/data importance numbers.')
468 group.add_argument('--importance', action='store_true',
469 help='Recompute place importances (expensive!)')
470 group.add_argument('--website', action='store_true',
471 help='Refresh the directory that serves the scripts for the web API')
472 group = parser.add_argument_group('Arguments for function refresh')
473 group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
474 help='Do not enable code for propagating updates')
475 group.add_argument('--enable-debug-statements', action='store_true',
476 help='Enable debug warning statements in functions')
480 from .tools import refresh
483 LOG.warning("Update postcodes centroid")
484 conn = connect(args.config.get_libpq_dsn())
485 refresh.update_postcodes(conn, args.data_dir)
489 LOG.warning('Recompute frequency of full-word search terms')
490 conn = connect(args.config.get_libpq_dsn())
491 refresh.recompute_word_counts(conn, args.data_dir)
494 if args.address_levels:
495 cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
496 LOG.warning('Updating address levels from %s', cfg)
497 conn = connect(args.config.get_libpq_dsn())
498 refresh.load_address_levels_from_file(conn, cfg)
502 LOG.warning('Create functions')
503 conn = connect(args.config.get_libpq_dsn())
504 refresh.create_functions(conn, args.config, args.data_dir,
505 args.diffs, args.enable_debug_statements)
509 run_legacy_script('setup.php', '--import-wikipedia-articles',
510 nominatim_env=args, throw_on_fail=True)
511 # Attention: importance MUST come after wiki data import.
513 run_legacy_script('update.php', '--recompute-importance',
514 nominatim_env=args, throw_on_fail=True)
516 run_legacy_script('setup.php', '--setup-website',
517 nominatim_env=args, throw_on_fail=True)
522 class AdminCheckDatabase:
524 Check that the database is complete and operational.
528 def add_args(parser):
533 return run_legacy_script('check_import_finished.php', nominatim_env=args)
538 Warm database caches for search and reverse queries.
542 def add_args(parser):
543 group = parser.add_argument_group('Target arguments')
544 group.add_argument('--search-only', action='store_const', dest='target',
546 help="Only pre-warm tables for search queries")
547 group.add_argument('--reverse-only', action='store_const', dest='target',
549 help="Only pre-warm tables for reverse queries")
553 params = ['warm.php']
554 if args.target == 'reverse':
555 params.append('--reverse-only')
556 if args.target == 'search':
557 params.append('--search-only')
558 return run_legacy_script(*params, nominatim_env=args)
563 Export addresses as CSV file from the database.
567 def add_args(parser):
568 group = parser.add_argument_group('Output arguments')
569 group.add_argument('--output-type', default='street',
570 choices=('continent', 'country', 'state', 'county',
571 'city', 'suburb', 'street', 'path'),
572 help='Type of places to output (default: street)')
573 group.add_argument('--output-format',
574 default='street;suburb;city;county;state;country',
575 help="""Semicolon-separated list of address types
576 (see --output-type). Multiple ranks can be
577 merged into one column by simply using a
578 comma-separated list.""")
579 group.add_argument('--output-all-postcodes', action='store_true',
580 help="""List all postcodes for address instead of
581 just the most likely one""")
582 group.add_argument('--language',
583 help="""Preferred language for output
584 (use local name, if omitted)""")
585 group = parser.add_argument_group('Filter arguments')
586 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
587 help='Export only objects within country')
588 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
589 help='Export only children of this OSM node')
590 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
591 help='Export only children of this OSM way')
592 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
593 help='Export only children of this OSM relation')
598 params = ['export.php',
599 '--output-type', args.output_type,
600 '--output-format', args.output_format]
601 if args.output_all_postcodes:
602 params.append('--output-all-postcodes')
604 params.extend(('--language', args.language))
605 if args.restrict_to_country:
606 params.extend(('--restrict-to-country', args.restrict_to_country))
607 if args.restrict_to_osm_node:
608 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
609 if args.restrict_to_osm_way:
610 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
611 if args.restrict_to_osm_relation:
612 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
614 return run_legacy_script(*params, nominatim_env=args)
617 ('street', 'housenumber and street'),
618 ('city', 'city, town or village'),
619 ('county', 'county'),
621 ('country', 'country'),
622 ('postalcode', 'postcode')
626 ('addressdetails', 'Include a breakdown of the address into elements.'),
627 ('extratags', """Include additional information if available
628 (e.g. wikipedia link, opening hours)."""),
629 ('namedetails', 'Include a list of alternative names.')
633 ('addressdetails', 'Include a breakdown of the address into elements.'),
634 ('keywords', 'Include a list of name keywords and address keywords.'),
635 ('linkedplaces', 'Include a details of places that are linked with this one.'),
636 ('hierarchy', 'Include details of places lower in the address hierarchy.'),
637 ('group_hierarchy', 'Group the places by type.'),
638 ('polygon_geojson', 'Include geometry of result.')
641 def _add_api_output_arguments(parser):
642 group = parser.add_argument_group('Output arguments')
643 group.add_argument('--format', default='jsonv2',
644 choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
645 help='Format of result')
646 for name, desc in EXTRADATA_PARAMS:
647 group.add_argument('--' + name, action='store_true', help=desc)
649 group.add_argument('--lang', '--accept-language', metavar='LANGS',
650 help='Preferred language order for presenting search results')
651 group.add_argument('--polygon-output',
652 choices=['geojson', 'kml', 'svg', 'text'],
653 help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
654 group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
655 help="""Simplify output geometry.
656 Parameter is difference tolerance in degrees.""")
661 Execute API search query.
665 def add_args(parser):
666 group = parser.add_argument_group('Query arguments')
667 group.add_argument('--query',
668 help='Free-form query string')
669 for name, desc in STRUCTURED_QUERY:
670 group.add_argument('--' + name, help='Structured query: ' + desc)
672 _add_api_output_arguments(parser)
674 group = parser.add_argument_group('Result limitation')
675 group.add_argument('--countrycodes', metavar='CC,..',
676 help='Limit search results to one or more countries.')
677 group.add_argument('--exclude_place_ids', metavar='ID,..',
678 help='List of search object to be excluded')
679 group.add_argument('--limit', type=int,
680 help='Limit the number of returned results')
681 group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
682 help='Preferred area to find search results')
683 group.add_argument('--bounded', action='store_true',
684 help='Strictly restrict results to viewbox area')
686 group = parser.add_argument_group('Other arguments')
687 group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
688 help='Do not remove duplicates from the result list')
694 params = dict(q=args.query)
696 params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
698 for param, _ in EXTRADATA_PARAMS:
699 if getattr(args, param):
701 for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
702 if getattr(args, param):
703 params[param] = getattr(args, param)
705 params['accept-language'] = args.lang
706 if args.polygon_output:
707 params['polygon_' + args.polygon_output] = '1'
708 if args.polygon_threshold:
709 params['polygon_threshold'] = args.polygon_threshold
711 params['bounded'] = '1'
713 params['dedupe'] = '0'
715 return run_api_script('search', args.project_dir,
716 phpcgi_bin=args.phpcgi_path, params=params)
720 Execute API reverse query.
724 def add_args(parser):
725 group = parser.add_argument_group('Query arguments')
726 group.add_argument('--lat', type=float, required=True,
727 help='Latitude of coordinate to look up (in WGS84)')
728 group.add_argument('--lon', type=float, required=True,
729 help='Longitude of coordinate to look up (in WGS84)')
730 group.add_argument('--zoom', type=int,
731 help='Level of detail required for the address')
733 _add_api_output_arguments(parser)
738 params = dict(lat=args.lat, lon=args.lon)
739 if args.zoom is not None:
740 params['zoom'] = args.zoom
742 for param, _ in EXTRADATA_PARAMS:
743 if getattr(args, param):
746 params['format'] = args.format
748 params['accept-language'] = args.lang
749 if args.polygon_output:
750 params['polygon_' + args.polygon_output] = '1'
751 if args.polygon_threshold:
752 params['polygon_threshold'] = args.polygon_threshold
754 return run_api_script('reverse', args.project_dir,
755 phpcgi_bin=args.phpcgi_path, params=params)
760 Execute API reverse query.
764 def add_args(parser):
765 group = parser.add_argument_group('Query arguments')
766 group.add_argument('--id', metavar='OSMID',
767 action='append', required=True, dest='ids',
768 help='OSM id to lookup in format <NRW><id> (may be repeated)')
770 _add_api_output_arguments(parser)
775 params = dict(osm_ids=','.join(args.ids))
777 for param, _ in EXTRADATA_PARAMS:
778 if getattr(args, param):
781 params['format'] = args.format
783 params['accept-language'] = args.lang
784 if args.polygon_output:
785 params['polygon_' + args.polygon_output] = '1'
786 if args.polygon_threshold:
787 params['polygon_threshold'] = args.polygon_threshold
789 return run_api_script('lookup', args.project_dir,
790 phpcgi_bin=args.phpcgi_path, params=params)
795 Execute API lookup query.
799 def add_args(parser):
800 group = parser.add_argument_group('Query arguments')
801 objs = group.add_mutually_exclusive_group(required=True)
802 objs.add_argument('--node', '-n', type=int,
803 help="Look up the OSM node with the given ID.")
804 objs.add_argument('--way', '-w', type=int,
805 help="Look up the OSM way with the given ID.")
806 objs.add_argument('--relation', '-r', type=int,
807 help="Look up the OSM relation with the given ID.")
808 objs.add_argument('--place_id', '-p', type=int,
809 help='Database internal identifier of the OSM object to look up.')
810 group.add_argument('--class', dest='object_class',
811 help="""Class type to disambiguated multiple entries
812 of the same object.""")
814 group = parser.add_argument_group('Output arguments')
815 for name, desc in DETAILS_SWITCHES:
816 group.add_argument('--' + name, action='store_true', help=desc)
817 group.add_argument('--lang', '--accept-language', metavar='LANGS',
818 help='Preferred language order for presenting search results')
823 params = dict(osmtype='N', osmid=args.node)
825 params = dict(osmtype='W', osmid=args.node)
827 params = dict(osmtype='R', osmid=args.node)
829 params = dict(place_id=args.place_id)
830 if args.object_class:
831 params['class'] = args.object_class
832 for name, _ in DETAILS_SWITCHES:
833 params[name] = '1' if getattr(args, name) else '0'
835 return run_api_script('details', args.project_dir,
836 phpcgi_bin=args.phpcgi_path, params=params)
841 Execute API status query.
845 def add_args(parser):
846 group = parser.add_argument_group('API parameters')
847 group.add_argument('--format', default='text', choices=['text', 'json'],
848 help='Format of result')
852 return run_api_script('status', args.project_dir,
853 phpcgi_bin=args.phpcgi_path,
854 params=dict(format=args.format))
857 def nominatim(**kwargs):
859 Command-line tools for importing, updating, administrating and
860 querying the Nominatim database.
862 parser = CommandlineParser('nominatim', nominatim.__doc__)
864 parser.add_subcommand('import', SetupAll)
865 parser.add_subcommand('freeze', SetupFreeze)
866 parser.add_subcommand('replication', UpdateReplication)
868 parser.add_subcommand('check-database', AdminCheckDatabase)
869 parser.add_subcommand('warm', AdminWarm)
871 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
873 parser.add_subcommand('add-data', UpdateAddData)
874 parser.add_subcommand('index', UpdateIndex)
875 parser.add_subcommand('refresh', UpdateRefresh)
877 parser.add_subcommand('export', QueryExport)
879 if kwargs.get('phpcgi_path'):
880 parser.add_subcommand('search', APISearch)
881 parser.add_subcommand('reverse', APIReverse)
882 parser.add_subcommand('lookup', APILookup)
883 parser.add_subcommand('details', APIDetails)
884 parser.add_subcommand('status', APIStatus)
886 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
888 return parser.run(**kwargs)