]> git.openstreetmap.org Git - nominatim.git/blob - src/nominatim_db/clicmd/refresh.py
741411658e6977ee5dc4552a9f93b5b07d28d3b6
[nominatim.git] / src / nominatim_db / clicmd / refresh.py
1 # SPDX-License-Identifier: GPL-3.0-or-later
2 #
3 # This file is part of Nominatim. (https://nominatim.org)
4 #
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
7 """
8 Implementation of 'refresh' subcommand.
9 """
10 from typing import Tuple, Optional
11 import argparse
12 import logging
13 from pathlib import Path
14 import asyncio
15
16 from ..config import Configuration
17 from ..db.connection import connect, table_exists
18 from ..tokenizer.base import AbstractTokenizer
19 from .args import NominatimArgs
20
21 # Do not repeat documentation of subcommand classes.
22 # pylint: disable=C0111
23 # Using non-top-level imports to avoid eventually unused imports.
24 # pylint: disable=E0012,C0415
25
26 LOG = logging.getLogger()
27
28 def _parse_osm_object(obj: str) -> Tuple[str, int]:
29     """ Parse the given argument into a tuple of OSM type and ID.
30         Raises an ArgumentError if the format is not recognized.
31     """
32     if len(obj) < 2 or obj[0].lower() not in 'nrw' or not obj[1:].isdigit():
33         raise argparse.ArgumentTypeError("Cannot parse OSM ID. Expect format: [N|W|R]<id>.")
34
35     return (obj[0].upper(), int(obj[1:]))
36
37
38 class UpdateRefresh:
39     """\
40     Recompute auxiliary data used by the indexing process.
41
42     This sub-commands updates various static data and functions in the database.
43     It usually needs to be run after changing various aspects of the
44     configuration. The configuration documentation will mention the exact
45     command to use in such case.
46
47     Warning: the 'update' command must not be run in parallel with other update
48              commands like 'replication' or 'add-data'.
49     """
50     def __init__(self) -> None:
51         self.tokenizer: Optional[AbstractTokenizer] = None
52
53     def add_args(self, parser: argparse.ArgumentParser) -> None:
54         group = parser.add_argument_group('Data arguments')
55         group.add_argument('--postcodes', action='store_true',
56                            help='Update postcode centroid table')
57         group.add_argument('--word-tokens', action='store_true',
58                            help='Clean up search terms')
59         group.add_argument('--word-counts', action='store_true',
60                            help='Compute frequency of full-word search terms')
61         group.add_argument('--address-levels', action='store_true',
62                            help='Reimport address level configuration')
63         group.add_argument('--functions', action='store_true',
64                            help='Update the PL/pgSQL functions in the database')
65         group.add_argument('--wiki-data', action='store_true',
66                            help='Update Wikipedia/data importance numbers')
67         group.add_argument('--secondary-importance', action='store_true',
68                            help='Update secondary importance raster data')
69         group.add_argument('--importance', action='store_true',
70                            help='Recompute place importances (expensive!)')
71         group.add_argument('--website', action='store_true',
72                            help='DEPRECATED. This function has no function anymore'
73                                 ' and will be removed in a future version.')
74         group.add_argument('--data-object', action='append',
75                            type=_parse_osm_object, metavar='OBJECT',
76                            help='Mark the given OSM object as requiring an update'
77                                 ' (format: [NWR]<id>)')
78         group.add_argument('--data-area', action='append',
79                            type=_parse_osm_object, metavar='OBJECT',
80                            help='Mark the area around the given OSM object as requiring an update'
81                                 ' (format: [NWR]<id>)')
82
83         group = parser.add_argument_group('Arguments for function refresh')
84         group.add_argument('--no-diff-updates', action='store_false', dest='diffs',
85                            help='Do not enable code for propagating updates')
86         group.add_argument('--enable-debug-statements', action='store_true',
87                            help='Enable debug warning statements in functions')
88
89
90     def run(self, args: NominatimArgs) -> int: #pylint: disable=too-many-branches, too-many-statements
91         from ..tools import refresh, postcodes
92         from ..indexer.indexer import Indexer
93
94         need_function_refresh = args.functions
95
96         if args.postcodes:
97             if postcodes.can_compute(args.config.get_libpq_dsn()):
98                 LOG.warning("Update postcodes centroid")
99                 tokenizer = self._get_tokenizer(args.config)
100                 postcodes.update_postcodes(args.config.get_libpq_dsn(),
101                                            args.project_dir, tokenizer)
102                 indexer = Indexer(args.config.get_libpq_dsn(), tokenizer,
103                                   args.threads or 1)
104                 asyncio.run(indexer.index_postcodes())
105             else:
106                 LOG.error("The place table doesn't exist. "
107                           "Postcode updates on a frozen database is not possible.")
108
109         if args.word_tokens:
110             LOG.warning('Updating word tokens')
111             tokenizer = self._get_tokenizer(args.config)
112             tokenizer.update_word_tokens()
113
114         if args.word_counts:
115             LOG.warning('Recompute word statistics')
116             self._get_tokenizer(args.config).update_statistics(args.config,
117                                                                threads=args.threads or 1)
118
119         if args.address_levels:
120             LOG.warning('Updating address levels')
121             with connect(args.config.get_libpq_dsn()) as conn:
122                 refresh.load_address_levels_from_config(conn, args.config)
123
124         # Attention: must come BEFORE functions
125         if args.secondary_importance:
126             with connect(args.config.get_libpq_dsn()) as conn:
127                 # If the table did not exist before, then the importance code
128                 # needs to be enabled.
129                 if not table_exists(conn, 'secondary_importance'):
130                     args.functions = True
131
132             LOG.warning('Import secondary importance raster data from %s', args.project_dir)
133             if refresh.import_secondary_importance(args.config.get_libpq_dsn(),
134                                                 args.project_dir) > 0:
135                 LOG.fatal('FATAL: Cannot update secondary importance raster data')
136                 return 1
137             need_function_refresh = True
138
139         if args.wiki_data:
140             data_path = Path(args.config.WIKIPEDIA_DATA_PATH
141                              or args.project_dir)
142             LOG.warning('Import wikipedia article importance from %s', data_path)
143             if refresh.import_wikipedia_articles(args.config.get_libpq_dsn(),
144                                                  data_path) > 0:
145                 LOG.fatal('FATAL: Wikipedia importance file not found in %s', data_path)
146                 return 1
147             need_function_refresh = True
148
149         if need_function_refresh:
150             LOG.warning('Create functions')
151             with connect(args.config.get_libpq_dsn()) as conn:
152                 refresh.create_functions(conn, args.config,
153                                          args.diffs, args.enable_debug_statements)
154                 self._get_tokenizer(args.config).update_sql_functions(args.config)
155
156         # Attention: importance MUST come after wiki data import and after functions.
157         if args.importance:
158             LOG.warning('Update importance values for database')
159             with connect(args.config.get_libpq_dsn()) as conn:
160                 refresh.recompute_importance(conn)
161
162         if args.website:
163             LOG.error('WARNING: Website setup is no longer required. '
164                       'This function will be removed in future version of Nominatim.')
165
166         if args.data_object or args.data_area:
167             with connect(args.config.get_libpq_dsn()) as conn:
168                 for obj in args.data_object or []:
169                     refresh.invalidate_osm_object(*obj, conn, recursive=False)
170                 for obj in args.data_area or []:
171                     refresh.invalidate_osm_object(*obj, conn, recursive=True)
172                 conn.commit()
173
174         return 0
175
176
177     def _get_tokenizer(self, config: Configuration) -> AbstractTokenizer:
178         if self.tokenizer is None:
179             from ..tokenizer import factory as tokenizer_factory
180
181             self.tokenizer = tokenizer_factory.get_tokenizer_for_db(config)
182
183         return self.tokenizer