2 Command-line interface to the Nominatim functions for import, update,
3 database administration and querying.
12 from pathlib import Path
14 from .config import Configuration
15 from .tools.exec_utils import run_legacy_script, run_api_script, run_php_server
16 from .db.connection import connect
17 from .db import status
18 from .errors import UsageError
20 LOG = logging.getLogger()
22 def _num_system_cpus():
24 cpus = len(os.sched_getaffinity(0))
25 except NotImplementedError:
28 return cpus or os.cpu_count()
31 class CommandlineParser:
32 """ Wraps some of the common functions for parsing the command line
33 and setting up subcommands.
35 def __init__(self, prog, description):
36 self.parser = argparse.ArgumentParser(
38 description=description,
39 formatter_class=argparse.RawDescriptionHelpFormatter)
41 self.subs = self.parser.add_subparsers(title='available commands',
44 # Arguments added to every sub-command
45 self.default_args = argparse.ArgumentParser(add_help=False)
46 group = self.default_args.add_argument_group('Default arguments')
47 group.add_argument('-h', '--help', action='help',
48 help='Show this help message and exit')
49 group.add_argument('-q', '--quiet', action='store_const', const=0,
50 dest='verbose', default=1,
51 help='Print only error messages')
52 group.add_argument('-v', '--verbose', action='count', default=1,
53 help='Increase verboseness of output')
54 group.add_argument('--project-dir', metavar='DIR', default='.',
55 help='Base directory of the Nominatim installation (default:.)')
56 group.add_argument('-j', '--threads', metavar='NUM', type=int,
57 help='Number of parallel threads to use')
60 def add_subcommand(self, name, cmd):
61 """ Add a subcommand to the parser. The subcommand must be a class
62 with a function add_args() that adds the parameters for the
63 subcommand and a run() function that executes the command.
65 parser = self.subs.add_parser(name, parents=[self.default_args],
66 help=cmd.__doc__.split('\n', 1)[0],
67 description=cmd.__doc__,
68 formatter_class=argparse.RawDescriptionHelpFormatter,
70 parser.set_defaults(command=cmd)
73 def run(self, **kwargs):
74 """ Parse the command line arguments of the program and execute the
75 appropriate subcommand.
77 args = self.parser.parse_args(args=kwargs.get('cli_args'))
79 if args.subcommand is None:
80 self.parser.print_help()
83 for arg in ('module_dir', 'osm2pgsql_path', 'phplib_dir', 'data_dir', 'phpcgi_path'):
84 setattr(args, arg, Path(kwargs[arg]))
85 args.project_dir = Path(args.project_dir).resolve()
87 logging.basicConfig(stream=sys.stderr,
88 format='%(asctime)s: %(message)s',
89 datefmt='%Y-%m-%d %H:%M:%S',
90 level=max(4 - args.verbose, 1) * 10)
92 args.config = Configuration(args.project_dir, args.data_dir / 'settings')
94 log = logging.getLogger()
95 log.warning('Using project directory: %s', str(args.project_dir))
98 return args.command.run(args)
99 except UsageError as exception:
100 if log.isEnabledFor(logging.DEBUG):
101 raise # use Python's exception printing
102 log.fatal('FATAL: %s', exception)
104 # If we get here, then execution has failed in some way.
108 def _osm2pgsql_options_from_args(args, default_cache, default_threads):
109 """ Set up the stanadrd osm2pgsql from the command line arguments.
111 return dict(osm2pgsql=args.osm2pgsql_path,
112 osm2pgsql_cache=args.osm2pgsql_cache or default_cache,
113 osm2pgsql_style=args.config.get_import_style_file(),
114 threads=args.threads or default_threads,
115 dsn=args.config.get_libpq_dsn(),
116 flatnode_file=args.config.FLATNODE_FILE)
118 ##### Subcommand classes
120 # Each class needs to implement two functions: add_args() adds the CLI parameters
121 # for the subfunction, run() executes the subcommand.
123 # The class documentation doubles as the help text for the command. The
124 # first line is also used in the summary when calling the program without
127 # No need to document the functions each time.
128 # pylint: disable=C0111
129 # Using non-top-level imports to make pyosmium optional for replication only.
130 # pylint: disable=E0012,C0415
135 Create a new Nominatim database from an OSM file.
139 def add_args(parser):
140 group_name = parser.add_argument_group('Required arguments')
141 group = group_name.add_mutually_exclusive_group(required=True)
142 group.add_argument('--osm-file',
143 help='OSM file to be imported.')
144 group.add_argument('--continue', dest='continue_at',
145 choices=['load-data', 'indexing', 'db-postprocess'],
146 help='Continue an import that was interrupted')
147 group = parser.add_argument_group('Optional arguments')
148 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
149 help='Size of cache to be used by osm2pgsql (in MB)')
150 group.add_argument('--reverse-only', action='store_true',
151 help='Do not create tables and indexes for searching')
152 group.add_argument('--enable-debug-statements', action='store_true',
153 help='Include debug warning statements in SQL code')
154 group.add_argument('--no-partitions', action='store_true',
155 help="""Do not partition search indices
156 (speeds up import of single country extracts)""")
157 group.add_argument('--no-updates', action='store_true',
158 help="""Do not keep tables that are only needed for
159 updating the database later""")
160 group = parser.add_argument_group('Expert options')
161 group.add_argument('--ignore-errors', action='store_true',
162 help='Continue import even when errors in SQL are present')
163 group.add_argument('--index-noanalyse', action='store_true',
164 help='Do not perform analyse operations during index')
169 params = ['setup.php']
171 params.extend(('--all', '--osm-file', args.osm_file))
173 if args.continue_at == 'load-data':
174 params.append('--load-data')
175 if args.continue_at in ('load-data', 'indexing'):
176 params.append('--index')
177 params.extend(('--create-search-indices', '--create-country-names',
179 if args.osm2pgsql_cache:
180 params.extend(('--osm2pgsql-cache', args.osm2pgsql_cache))
181 if args.reverse_only:
182 params.append('--reverse-only')
183 if args.enable_debug_statements:
184 params.append('--enable-debug-statements')
185 if args.no_partitions:
186 params.append('--no-partitions')
188 params.append('--drop')
189 if args.ignore_errors:
190 params.append('--ignore-errors')
191 if args.index_noanalyse:
192 params.append('--index-noanalyse')
194 return run_legacy_script(*params, nominatim_env=args)
199 Make database read-only.
201 About half of data in the Nominatim database is kept only to be able to
202 keep the data up-to-date with new changes made in OpenStreetMap. This
203 command drops all this data and only keeps the part needed for geocoding
206 This command has the same effect as the `--no-updates` option for imports.
210 def add_args(parser):
215 return run_legacy_script('setup.php', '--drop', nominatim_env=args)
218 class SetupSpecialPhrases:
220 Maintain special phrases.
224 def add_args(parser):
225 group = parser.add_argument_group('Input arguments')
226 group.add_argument('--from-wiki', action='store_true',
227 help='Pull special phrases from the OSM wiki.')
228 group = parser.add_argument_group('Output arguments')
229 group.add_argument('-o', '--output', default='-',
230 help="""File to write the preprocessed phrases to.
231 If omitted, it will be written to stdout.""")
235 if args.output != '-':
236 raise NotImplementedError('Only output to stdout is currently implemented.')
237 return run_legacy_script('specialphrases.php', '--wiki-import', nominatim_env=args)
240 class UpdateReplication:
242 Update the database using an online replication service.
246 def add_args(parser):
247 group = parser.add_argument_group('Arguments for initialisation')
248 group.add_argument('--init', action='store_true',
249 help='Initialise the update process')
250 group.add_argument('--no-update-functions', dest='update_functions',
251 action='store_false',
252 help="""Do not update the trigger function to
253 support differential updates.""")
254 group = parser.add_argument_group('Arguments for updates')
255 group.add_argument('--check-for-updates', action='store_true',
256 help='Check if new updates are available and exit')
257 group.add_argument('--once', action='store_true',
258 help="""Download and apply updates only once. When
259 not set, updates are continuously applied""")
260 group.add_argument('--no-index', action='store_false', dest='do_index',
261 help="""Do not index the new data. Only applicable
262 together with --once""")
263 group.add_argument('--osm2pgsql-cache', metavar='SIZE', type=int,
264 help='Size of cache to be used by osm2pgsql (in MB)')
265 group = parser.add_argument_group('Download parameters')
266 group.add_argument('--socket-timeout', dest='socket_timeout', type=int, default=60,
267 help='Set timeout for file downloads.')
270 def _init_replication(args):
271 from .tools import replication, refresh
273 socket.setdefaulttimeout(args.socket_timeout)
275 LOG.warning("Initialising replication updates")
276 conn = connect(args.config.get_libpq_dsn())
277 replication.init_replication(conn, base_url=args.config.REPLICATION_URL)
278 if args.update_functions:
279 LOG.warning("Create functions")
280 refresh.create_functions(conn, args.config, args.data_dir,
287 def _check_for_updates(args):
288 from .tools import replication
290 conn = connect(args.config.get_libpq_dsn())
291 ret = replication.check_for_updates(conn, base_url=args.config.REPLICATION_URL)
296 def _report_update(batchdate, start_import, start_index):
297 def round_time(delta):
298 return dt.timedelta(seconds=int(delta.total_seconds()))
300 end = dt.datetime.now(dt.timezone.utc)
301 LOG.warning("Update completed. Import: %s. %sTotal: %s. Remaining backlog: %s.",
302 round_time((start_index or end) - start_import),
303 "Indexing: {} ".format(round_time(end - start_index))
304 if start_index else '',
305 round_time(end - start_import),
306 round_time(end - batchdate))
310 from .tools import replication
311 from .indexer.indexer import Indexer
313 params = _osm2pgsql_options_from_args(args, 2000, 1)
314 params.update(base_url=args.config.REPLICATION_URL,
315 update_interval=args.config.get_int('REPLICATION_UPDATE_INTERVAL'),
316 import_file=args.project_dir / 'osmosischange.osc',
317 max_diff_size=args.config.get_int('REPLICATION_MAX_DIFF'),
318 indexed_only=not args.once)
320 # Sanity check to not overwhelm the Geofabrik servers.
321 if 'download.geofabrik.de'in params['base_url']\
322 and params['update_interval'] < 86400:
323 LOG.fatal("Update interval too low for download.geofabrik.de.\n"
324 "Please check install documentation "
325 "(https://nominatim.org/release-docs/latest/admin/Import-and-Update#"
326 "setting-up-the-update-process).")
327 raise UsageError("Invalid replication update interval setting.")
330 if not args.do_index:
331 LOG.fatal("Indexing cannot be disabled when running updates continuously.")
332 raise UsageError("Bad argument '--no-index'.")
333 recheck_interval = args.config.get_int('REPLICATION_RECHECK_INTERVAL')
336 conn = connect(args.config.get_libpq_dsn())
337 start = dt.datetime.now(dt.timezone.utc)
338 state = replication.update(conn, params)
339 status.log_status(conn, start, 'import')
340 batchdate, _, _ = status.get_status(conn)
343 if state is not replication.UpdateState.NO_CHANGES and args.do_index:
344 index_start = dt.datetime.now(dt.timezone.utc)
345 indexer = Indexer(args.config.get_libpq_dsn(),
347 indexer.index_boundaries(0, 30)
348 indexer.index_by_rank(0, 30)
350 conn = connect(args.config.get_libpq_dsn())
351 status.set_indexed(conn, True)
352 status.log_status(conn, index_start, 'index')
357 if LOG.isEnabledFor(logging.WARNING):
358 UpdateReplication._report_update(batchdate, start, index_start)
363 if state is replication.UpdateState.NO_CHANGES:
364 LOG.warning("No new changes. Sleeping for %d sec.", recheck_interval)
365 time.sleep(recheck_interval)
372 import osmium # pylint: disable=W0611
373 except ModuleNotFoundError:
374 LOG.fatal("pyosmium not installed. Replication functions not available.\n"
375 "To install pyosmium via pip: pip3 install osmium")
379 return UpdateReplication._init_replication(args)
381 if args.check_for_updates:
382 return UpdateReplication._check_for_updates(args)
384 return UpdateReplication._update(args)
388 Add additional data from a file or an online source.
390 Data is only imported, not indexed. You need to call `nominatim-update index`
391 to complete the process.
395 def add_args(parser):
396 group_name = parser.add_argument_group('Source')
397 group = group_name.add_mutually_exclusive_group(required=True)
398 group.add_argument('--file', metavar='FILE',
399 help='Import data from an OSM file')
400 group.add_argument('--diff', metavar='FILE',
401 help='Import data from an OSM diff file')
402 group.add_argument('--node', metavar='ID', type=int,
403 help='Import a single node from the API')
404 group.add_argument('--way', metavar='ID', type=int,
405 help='Import a single way from the API')
406 group.add_argument('--relation', metavar='ID', type=int,
407 help='Import a single relation from the API')
408 group.add_argument('--tiger-data', metavar='DIR',
409 help='Add housenumbers from the US TIGER census database.')
410 group = parser.add_argument_group('Extra arguments')
411 group.add_argument('--use-main-api', action='store_true',
412 help='Use OSM API instead of Overpass to download objects')
417 os.environ['NOMINATIM_TIGER_DATA_PATH'] = args.tiger_data
418 return run_legacy_script('setup.php', '--import-tiger-data', nominatim_env=args)
420 params = ['update.php']
422 params.extend(('--import-file', args.file))
424 params.extend(('--import-diff', args.diff))
426 params.extend(('--import-node', args.node))
428 params.extend(('--import-way', args.way))
430 params.extend(('--import-relation', args.relation))
431 if args.use_main_api:
432 params.append('--use-main-api')
433 return run_legacy_script(*params, nominatim_env=args)
438 Reindex all new and modified data.
442 def add_args(parser):
443 group = parser.add_argument_group('Filter arguments')
444 group.add_argument('--boundaries-only', action='store_true',
445 help="""Index only administrative boundaries.""")
446 group.add_argument('--no-boundaries', action='store_true',
447 help="""Index everything except administrative boundaries.""")
448 group.add_argument('--minrank', '-r', type=int, metavar='RANK', default=0,
449 help='Minimum/starting rank')
450 group.add_argument('--maxrank', '-R', type=int, metavar='RANK', default=30,
451 help='Maximum/finishing rank')
455 from .indexer.indexer import Indexer
457 indexer = Indexer(args.config.get_libpq_dsn(),
458 args.threads or _num_system_cpus() or 1)
460 if not args.no_boundaries:
461 indexer.index_boundaries(args.minrank, args.maxrank)
462 if not args.boundaries_only:
463 indexer.index_by_rank(args.minrank, args.maxrank)
465 if not args.no_boundaries and not args.boundaries_only \
466 and args.minrank == 0 and args.maxrank == 30:
467 conn = connect(args.config.get_libpq_dsn())
468 status.set_indexed(conn, True)
476 Recompute auxiliary data used by the indexing process.
478 These functions must not be run in parallel with other update commands.
482 def add_args(parser):
483 group = parser.add_argument_group('Data arguments')
484 group.add_argument('--postcodes', action='store_true',
485 help='Update postcode centroid table')
486 group.add_argument('--word-counts', action='store_true',
487 help='Compute frequency of full-word search terms')
488 group.add_argument('--address-levels', action='store_true',
489 help='Reimport address level configuration')
490 group.add_argument('--functions', action='store_true',
491 help='Update the PL/pgSQL functions in the database')
492 group.add_argument('--wiki-data', action='store_true',
493 help='Update Wikipedia/data importance numbers.')
494 group.add_argument('--importance', action='store_true',
495 help='Recompute place importances (expensive!)')
496 group.add_argument('--website', action='store_true',
497 help='Refresh the directory that serves the scripts for the web API')
498 group = parser.add_argument_group('Arguments for function refresh')
499 group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
500 help='Do not enable code for propagating updates')
501 group.add_argument('--enable-debug-statements', action='store_true',
502 help='Enable debug warning statements in functions')
506 from .tools import refresh
509 LOG.warning("Update postcodes centroid")
510 conn = connect(args.config.get_libpq_dsn())
511 refresh.update_postcodes(conn, args.data_dir)
515 LOG.warning('Recompute frequency of full-word search terms')
516 conn = connect(args.config.get_libpq_dsn())
517 refresh.recompute_word_counts(conn, args.data_dir)
520 if args.address_levels:
521 cfg = Path(args.config.ADDRESS_LEVEL_CONFIG)
522 LOG.warning('Updating address levels from %s', cfg)
523 conn = connect(args.config.get_libpq_dsn())
524 refresh.load_address_levels_from_file(conn, cfg)
528 LOG.warning('Create functions')
529 conn = connect(args.config.get_libpq_dsn())
530 refresh.create_functions(conn, args.config, args.data_dir,
531 args.diffs, args.enable_debug_statements)
535 run_legacy_script('setup.php', '--import-wikipedia-articles',
536 nominatim_env=args, throw_on_fail=True)
537 # Attention: importance MUST come after wiki data import.
539 run_legacy_script('update.php', '--recompute-importance',
540 nominatim_env=args, throw_on_fail=True)
542 run_legacy_script('setup.php', '--setup-website',
543 nominatim_env=args, throw_on_fail=True)
548 class AdminCheckDatabase:
550 Check that the database is complete and operational.
554 def add_args(parser):
559 return run_legacy_script('check_import_finished.php', nominatim_env=args)
564 Warm database caches for search and reverse queries.
568 def add_args(parser):
569 group = parser.add_argument_group('Target arguments')
570 group.add_argument('--search-only', action='store_const', dest='target',
572 help="Only pre-warm tables for search queries")
573 group.add_argument('--reverse-only', action='store_const', dest='target',
575 help="Only pre-warm tables for reverse queries")
579 params = ['warm.php']
580 if args.target == 'reverse':
581 params.append('--reverse-only')
582 if args.target == 'search':
583 params.append('--search-only')
584 return run_legacy_script(*params, nominatim_env=args)
589 Export addresses as CSV file from the database.
593 def add_args(parser):
594 group = parser.add_argument_group('Output arguments')
595 group.add_argument('--output-type', default='street',
596 choices=('continent', 'country', 'state', 'county',
597 'city', 'suburb', 'street', 'path'),
598 help='Type of places to output (default: street)')
599 group.add_argument('--output-format',
600 default='street;suburb;city;county;state;country',
601 help="""Semicolon-separated list of address types
602 (see --output-type). Multiple ranks can be
603 merged into one column by simply using a
604 comma-separated list.""")
605 group.add_argument('--output-all-postcodes', action='store_true',
606 help="""List all postcodes for address instead of
607 just the most likely one""")
608 group.add_argument('--language',
609 help="""Preferred language for output
610 (use local name, if omitted)""")
611 group = parser.add_argument_group('Filter arguments')
612 group.add_argument('--restrict-to-country', metavar='COUNTRY_CODE',
613 help='Export only objects within country')
614 group.add_argument('--restrict-to-osm-node', metavar='ID', type=int,
615 help='Export only children of this OSM node')
616 group.add_argument('--restrict-to-osm-way', metavar='ID', type=int,
617 help='Export only children of this OSM way')
618 group.add_argument('--restrict-to-osm-relation', metavar='ID', type=int,
619 help='Export only children of this OSM relation')
624 params = ['export.php',
625 '--output-type', args.output_type,
626 '--output-format', args.output_format]
627 if args.output_all_postcodes:
628 params.append('--output-all-postcodes')
630 params.extend(('--language', args.language))
631 if args.restrict_to_country:
632 params.extend(('--restrict-to-country', args.restrict_to_country))
633 if args.restrict_to_osm_node:
634 params.extend(('--restrict-to-osm-node', args.restrict_to_osm_node))
635 if args.restrict_to_osm_way:
636 params.extend(('--restrict-to-osm-way', args.restrict_to_osm_way))
637 if args.restrict_to_osm_relation:
638 params.extend(('--restrict-to-osm-relation', args.restrict_to_osm_relation))
640 return run_legacy_script(*params, nominatim_env=args)
645 Start a simple web server for serving the API.
647 This command starts the built-in PHP webserver to serve the website
648 from the current project directory. This webserver is only suitable
649 for testing and develop. Do not use it in production setups!
651 By the default, the webserver can be accessed at: http://127.0.0.1:8088
655 def add_args(parser):
656 group = parser.add_argument_group('Server arguments')
657 group.add_argument('--server', default='127.0.0.1:8088',
658 help='The address the server will listen to.')
662 run_php_server(args.server, args.project_dir / 'website')
665 ('street', 'housenumber and street'),
666 ('city', 'city, town or village'),
667 ('county', 'county'),
669 ('country', 'country'),
670 ('postalcode', 'postcode')
674 ('addressdetails', 'Include a breakdown of the address into elements.'),
675 ('extratags', """Include additional information if available
676 (e.g. wikipedia link, opening hours)."""),
677 ('namedetails', 'Include a list of alternative names.')
681 ('addressdetails', 'Include a breakdown of the address into elements.'),
682 ('keywords', 'Include a list of name keywords and address keywords.'),
683 ('linkedplaces', 'Include a details of places that are linked with this one.'),
684 ('hierarchy', 'Include details of places lower in the address hierarchy.'),
685 ('group_hierarchy', 'Group the places by type.'),
686 ('polygon_geojson', 'Include geometry of result.')
689 def _add_api_output_arguments(parser):
690 group = parser.add_argument_group('Output arguments')
691 group.add_argument('--format', default='jsonv2',
692 choices=['xml', 'json', 'jsonv2', 'geojson', 'geocodejson'],
693 help='Format of result')
694 for name, desc in EXTRADATA_PARAMS:
695 group.add_argument('--' + name, action='store_true', help=desc)
697 group.add_argument('--lang', '--accept-language', metavar='LANGS',
698 help='Preferred language order for presenting search results')
699 group.add_argument('--polygon-output',
700 choices=['geojson', 'kml', 'svg', 'text'],
701 help='Output geometry of results as a GeoJSON, KML, SVG or WKT.')
702 group.add_argument('--polygon-threshold', type=float, metavar='TOLERANCE',
703 help="""Simplify output geometry.
704 Parameter is difference tolerance in degrees.""")
709 Execute API search query.
713 def add_args(parser):
714 group = parser.add_argument_group('Query arguments')
715 group.add_argument('--query',
716 help='Free-form query string')
717 for name, desc in STRUCTURED_QUERY:
718 group.add_argument('--' + name, help='Structured query: ' + desc)
720 _add_api_output_arguments(parser)
722 group = parser.add_argument_group('Result limitation')
723 group.add_argument('--countrycodes', metavar='CC,..',
724 help='Limit search results to one or more countries.')
725 group.add_argument('--exclude_place_ids', metavar='ID,..',
726 help='List of search object to be excluded')
727 group.add_argument('--limit', type=int,
728 help='Limit the number of returned results')
729 group.add_argument('--viewbox', metavar='X1,Y1,X2,Y2',
730 help='Preferred area to find search results')
731 group.add_argument('--bounded', action='store_true',
732 help='Strictly restrict results to viewbox area')
734 group = parser.add_argument_group('Other arguments')
735 group.add_argument('--no-dedupe', action='store_false', dest='dedupe',
736 help='Do not remove duplicates from the result list')
742 params = dict(q=args.query)
744 params = {k : getattr(args, k) for k, _ in STRUCTURED_QUERY if getattr(args, k)}
746 for param, _ in EXTRADATA_PARAMS:
747 if getattr(args, param):
749 for param in ('format', 'countrycodes', 'exclude_place_ids', 'limit', 'viewbox'):
750 if getattr(args, param):
751 params[param] = getattr(args, param)
753 params['accept-language'] = args.lang
754 if args.polygon_output:
755 params['polygon_' + args.polygon_output] = '1'
756 if args.polygon_threshold:
757 params['polygon_threshold'] = args.polygon_threshold
759 params['bounded'] = '1'
761 params['dedupe'] = '0'
763 return run_api_script('search', args.project_dir,
764 phpcgi_bin=args.phpcgi_path, params=params)
768 Execute API reverse query.
772 def add_args(parser):
773 group = parser.add_argument_group('Query arguments')
774 group.add_argument('--lat', type=float, required=True,
775 help='Latitude of coordinate to look up (in WGS84)')
776 group.add_argument('--lon', type=float, required=True,
777 help='Longitude of coordinate to look up (in WGS84)')
778 group.add_argument('--zoom', type=int,
779 help='Level of detail required for the address')
781 _add_api_output_arguments(parser)
786 params = dict(lat=args.lat, lon=args.lon)
787 if args.zoom is not None:
788 params['zoom'] = args.zoom
790 for param, _ in EXTRADATA_PARAMS:
791 if getattr(args, param):
794 params['format'] = args.format
796 params['accept-language'] = args.lang
797 if args.polygon_output:
798 params['polygon_' + args.polygon_output] = '1'
799 if args.polygon_threshold:
800 params['polygon_threshold'] = args.polygon_threshold
802 return run_api_script('reverse', args.project_dir,
803 phpcgi_bin=args.phpcgi_path, params=params)
808 Execute API reverse query.
812 def add_args(parser):
813 group = parser.add_argument_group('Query arguments')
814 group.add_argument('--id', metavar='OSMID',
815 action='append', required=True, dest='ids',
816 help='OSM id to lookup in format <NRW><id> (may be repeated)')
818 _add_api_output_arguments(parser)
823 params = dict(osm_ids=','.join(args.ids))
825 for param, _ in EXTRADATA_PARAMS:
826 if getattr(args, param):
829 params['format'] = args.format
831 params['accept-language'] = args.lang
832 if args.polygon_output:
833 params['polygon_' + args.polygon_output] = '1'
834 if args.polygon_threshold:
835 params['polygon_threshold'] = args.polygon_threshold
837 return run_api_script('lookup', args.project_dir,
838 phpcgi_bin=args.phpcgi_path, params=params)
843 Execute API lookup query.
847 def add_args(parser):
848 group = parser.add_argument_group('Query arguments')
849 objs = group.add_mutually_exclusive_group(required=True)
850 objs.add_argument('--node', '-n', type=int,
851 help="Look up the OSM node with the given ID.")
852 objs.add_argument('--way', '-w', type=int,
853 help="Look up the OSM way with the given ID.")
854 objs.add_argument('--relation', '-r', type=int,
855 help="Look up the OSM relation with the given ID.")
856 objs.add_argument('--place_id', '-p', type=int,
857 help='Database internal identifier of the OSM object to look up.')
858 group.add_argument('--class', dest='object_class',
859 help="""Class type to disambiguated multiple entries
860 of the same object.""")
862 group = parser.add_argument_group('Output arguments')
863 for name, desc in DETAILS_SWITCHES:
864 group.add_argument('--' + name, action='store_true', help=desc)
865 group.add_argument('--lang', '--accept-language', metavar='LANGS',
866 help='Preferred language order for presenting search results')
871 params = dict(osmtype='N', osmid=args.node)
873 params = dict(osmtype='W', osmid=args.node)
875 params = dict(osmtype='R', osmid=args.node)
877 params = dict(place_id=args.place_id)
878 if args.object_class:
879 params['class'] = args.object_class
880 for name, _ in DETAILS_SWITCHES:
881 params[name] = '1' if getattr(args, name) else '0'
883 return run_api_script('details', args.project_dir,
884 phpcgi_bin=args.phpcgi_path, params=params)
889 Execute API status query.
893 def add_args(parser):
894 group = parser.add_argument_group('API parameters')
895 group.add_argument('--format', default='text', choices=['text', 'json'],
896 help='Format of result')
900 return run_api_script('status', args.project_dir,
901 phpcgi_bin=args.phpcgi_path,
902 params=dict(format=args.format))
905 def nominatim(**kwargs):
907 Command-line tools for importing, updating, administrating and
908 querying the Nominatim database.
910 parser = CommandlineParser('nominatim', nominatim.__doc__)
912 parser.add_subcommand('import', SetupAll)
913 parser.add_subcommand('freeze', SetupFreeze)
914 parser.add_subcommand('replication', UpdateReplication)
916 parser.add_subcommand('check-database', AdminCheckDatabase)
917 parser.add_subcommand('warm', AdminWarm)
919 parser.add_subcommand('special-phrases', SetupSpecialPhrases)
921 parser.add_subcommand('add-data', UpdateAddData)
922 parser.add_subcommand('index', UpdateIndex)
923 parser.add_subcommand('refresh', UpdateRefresh)
925 parser.add_subcommand('export', QueryExport)
926 parser.add_subcommand('serve', AdminServe)
928 if kwargs.get('phpcgi_path'):
929 parser.add_subcommand('search', APISearch)
930 parser.add_subcommand('reverse', APIReverse)
931 parser.add_subcommand('lookup', APILookup)
932 parser.add_subcommand('details', APIDetails)
933 parser.add_subcommand('status', APIStatus)
935 parser.parser.epilog = 'php-cgi not found. Query commands not available.'
937 return parser.run(**kwargs)