#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2022 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
"""
Collection of aliases for various world coordinates.
"""
ALIASES = {
-# Country aliases
-'AD': (1.58972, 42.54241),
-'AE': (54.61589, 24.82431),
-'AF': (65.90264, 34.84708),
-'AG': (-61.72430, 17.069),
-'AI': (-63.10571, 18.25461),
-'AL': (19.84941, 40.21232),
-'AM': (44.64229, 40.37821),
-'AO': (16.21924, -12.77014),
-'AQ': (44.99999, -75.65695),
-'AR': (-61.10759, -34.37615),
-'AS': (-170.68470, -14.29307),
-'AT': (14.25747, 47.36542),
-'AU': (138.23155, -23.72068),
-'AW': (-69.98255, 12.555),
-'AX': (19.91839, 59.81682),
-'AZ': (48.38555, 40.61639),
-'BA': (17.18514, 44.25582),
-'BB': (-59.53342, 13.19),
-'BD': (89.75989, 24.34205),
-'BE': (4.90078, 50.34682),
-'BF': (-0.56743, 11.90471),
-'BG': (24.80616, 43.09859),
-'BH': (50.52032, 25.94685),
-'BI': (29.54561, -2.99057),
-'BJ': (2.70062, 10.02792),
-'BL': (-62.79349, 17.907),
-'BM': (-64.77406, 32.30199),
-'BN': (114.52196, 4.28638),
-'BO': (-62.02473, -17.77723),
-'BQ': (-63.14322, 17.566),
-'BR': (-45.77065, -9.58685),
-'BS': (-77.60916, 23.8745),
-'BT': (90.01350, 27.28137),
-'BV': (3.35744, -54.4215),
-'BW': (23.51505, -23.48391),
-'BY': (26.77259, 53.15885),
-'BZ': (-88.63489, 16.33951),
-'CA': (-107.74817, 67.12612),
-'CC': (96.84420, -12.01734),
-'CD': (24.09544, -1.67713),
-'CF': (22.58701, 5.98438),
-'CG': (15.78875, 0.40388),
-'CH': (7.65705, 46.57446),
-'CI': (-6.31190, 6.62783),
-'CK': (-159.77835, -21.23349),
-'CL': (-70.41790, -53.77189),
-'CM': (13.26022, 5.94519),
-'CN': (96.44285, 38.04260),
-'CO': (-72.52951, 2.45174),
-'CR': (-83.83314, 9.93514),
-'CU': (-80.81673, 21.88852),
-'CV': (-24.50810, 14.929),
-'CW': (-68.96409, 12.1845),
-'CX': (105.62411, -10.48417),
-'CY': (32.95922, 35.37010),
-'CZ': (16.32098, 49.50692),
-'DE': (9.30716, 50.21289),
-'DJ': (42.96904, 11.41542),
-'DK': (9.18490, 55.98916),
-'DM': (-61.00358, 15.65470),
-'DO': (-69.62855, 18.58841),
-'DZ': (4.24749, 25.79721),
-'EC': (-77.45831, -0.98284),
-'EE': (23.94288, 58.43952),
-'EG': (28.95293, 28.17718),
-'EH': (-13.69031, 25.01241),
-'ER': (39.01223, 14.96033),
-'ES': (-2.59110, 38.79354),
-'ET': (38.61697, 7.71399),
-'FI': (26.89798, 63.56194),
-'FJ': (177.91853, -17.74237),
-'FK': (-58.99044, -51.34509),
-'FM': (151.95358, 8.5045),
-'FO': (-6.60483, 62.10000),
-'FR': (0.28410, 47.51045),
-'GA': (10.81070, -0.07429),
-'GB': (-0.92823, 52.01618),
-'GD': (-61.64524, 12.191),
-'GE': (44.16664, 42.00385),
-'GF': (-53.46524, 3.56188),
-'GG': (-2.50580, 49.58543),
-'GH': (-0.46348, 7.16051),
-'GI': (-5.32053, 36.11066),
-'GL': (-33.85511, 74.66355),
-'GM': (-16.40960, 13.25),
-'GN': (-13.83940, 10.96291),
-'GP': (-61.68712, 16.23049),
-'GQ': (10.23973, 1.43119),
-'GR': (23.17850, 39.06206),
-'GS': (-36.49430, -54.43067),
-'GT': (-90.74368, 15.20428),
-'GU': (144.73362, 13.44413),
-'GW': (-14.83525, 11.92486),
-'GY': (-58.45167, 5.73698),
-'HK': (114.18577, 22.34923),
-'HM': (73.68230, -53.22105),
-'HN': (-86.95414, 15.23820),
-'HR': (17.49966, 45.52689),
-'HT': (-73.51925, 18.32492),
-'HU': (20.35362, 47.51721),
-'ID': (123.34505, -0.83791),
-'IE': (-9.00520, 52.87725),
-'IL': (35.46314, 32.86165),
-'IM': (-4.86740, 54.023),
-'IN': (88.67620, 27.86155),
-'IO': (71.42743, -6.14349),
-'IQ': (42.58109, 34.26103),
-'IR': (56.09355, 30.46751),
-'IS': (-17.51785, 64.71687),
-'IT': (10.42639, 44.87904),
-'JE': (-2.19261, 49.12458),
-'JM': (-76.84020, 18.3935),
-'JO': (36.55552, 30.75741),
-'JP': (138.72531, 35.92099),
-'KE': (36.90602, 1.08512),
-'KG': (76.15571, 41.66497),
-'KH': (104.31901, 12.95555),
-'KI': (173.63353, 0.139),
-'KM': (44.31474, -12.241),
-'KN': (-62.69379, 17.2555),
-'KP': (126.65575, 39.64575),
-'KR': (127.27740, 36.41388),
-'KW': (47.30684, 29.69180),
-'KY': (-81.07455, 19.29949),
-'KZ': (72.00811, 49.88855),
-'LA': (102.44391, 19.81609),
-'LB': (35.48464, 33.41766),
-'LC': (-60.97894, 13.891),
-'LI': (9.54693, 47.15934),
-'LK': (80.38520, 8.41649),
-'LR': (-11.16960, 4.04122),
-'LS': (28.66984, -29.94538),
-'LT': (24.51735, 55.49293),
-'LU': (6.08649, 49.81533),
-'LV': (23.51033, 56.67144),
-'LY': (15.36841, 28.12177),
-'MA': (-4.03061, 33.21696),
-'MC': (7.47743, 43.62917),
-'MD': (29.61725, 46.66517),
-'ME': (19.72291, 43.02441),
-'MF': (-63.06666, 18.08102),
-'MG': (45.86378, -20.50245),
-'MH': (171.94982, 5.983),
-'MK': (21.42108, 41.08980),
-'ML': (-1.93310, 16.46993),
-'MM': (95.54624, 21.09620),
-'MN': (99.81138, 48.18615),
-'MO': (113.56441, 22.16209),
-'MP': (145.21345, 14.14902),
-'MQ': (-60.81128, 14.43706),
-'MR': (-9.42324, 22.59251),
-'MS': (-62.19455, 16.745),
-'MT': (14.38363, 35.94467),
-'MU': (57.55121, -20.41),
-'MV': (73.39292, 4.19375),
-'MW': (33.95722, -12.28218),
-'MX': (-105.89221, 25.86826),
-'MY': (112.71154, 2.10098),
-'MZ': (37.58689, -13.72682),
-'NA': (16.68569, -21.46572),
-'NC': (164.95322, -20.38889),
-'NE': (10.06041, 19.08273),
-'NF': (167.95718, -29.0645),
-'NG': (10.17781, 10.17804),
-'NI': (-85.87974, 13.21715),
-'NL': (-68.57062, 12.041),
-'NO': (23.11556, 70.09934),
-'NP': (83.36259, 28.13107),
-'NR': (166.93479, -0.5275),
-'NU': (-169.84873, -19.05305),
-'NZ': (167.97209, -45.13056),
-'OM': (56.86055, 20.47413),
-'PA': (-79.40160, 8.80656),
-'PE': (-78.66540, -7.54711),
-'PF': (-145.05719, -16.70862),
-'PG': (146.64600, -7.37427),
-'PH': (121.48359, 15.09965),
-'PK': (72.11347, 31.14629),
-'PL': (17.88136, 52.77182),
-'PM': (-56.19515, 46.78324),
-'PN': (-130.10642, -25.06955),
-'PR': (-65.88755, 18.37169),
-'PS': (35.39801, 32.24773),
-'PT': (-8.45743, 40.11154),
-'PW': (134.49645, 7.3245),
-'PY': (-59.51787, -22.41281),
-'QA': (51.49903, 24.99816),
-'RE': (55.77345, -21.36388),
-'RO': (26.37632, 45.36120),
-'RS': (20.40371, 44.56413),
-'RU': (116.44060, 59.06780),
-'RW': (29.57882, -1.62404),
-'SA': (47.73169, 22.43790),
-'SB': (164.63894, -10.23606),
-'SC': (46.36566, -9.454),
-'SD': (28.14720, 14.56423),
-'SE': (15.68667, 60.35568),
-'SG': (103.84187, 1.304),
-'SH': (-12.28155, -37.11546),
-'SI': (14.04738, 46.39085),
-'SJ': (15.27552, 79.23365),
-'SK': (20.41603, 48.86970),
-'SL': (-11.47773, 8.78156),
-'SM': (12.46062, 43.94279),
-'SN': (-15.37111, 14.99477),
-'SO': (46.93383, 9.34094),
-'SR': (-55.42864, 4.56985),
-'SS': (28.13573, 8.50933),
-'ST': (6.61025, 0.2215),
-'SV': (-89.36665, 13.43072),
-'SX': (-63.15393, 17.9345),
-'SY': (38.15513, 35.34221),
-'SZ': (31.78263, -26.14244),
-'TC': (-71.32554, 21.35),
-'TD': (17.42092, 13.46223),
-'TF': (137.5, -67.5),
-'TG': (1.06983, 7.87677),
-'TH': (102.00877, 16.42310),
-'TJ': (71.91349, 39.01527),
-'TK': (-171.82603, -9.20990),
-'TL': (126.22520, -8.72636),
-'TM': (57.71603, 39.92534),
-'TN': (9.04958, 34.84199),
-'TO': (-176.99320, -23.11104),
-'TR': (32.82002, 39.86350),
-'TT': (-60.70793, 11.1385),
-'TV': (178.77499, -9.41685),
-'TW': (120.30074, 23.17002),
-'TZ': (33.53892, -5.01840),
-'UA': (33.44335, 49.30619),
-'UG': (32.96523, 2.08584),
-'UM': (-169.50993, 16.74605),
-'US': (-116.39535, 40.71379),
-'UY': (-56.46505, -33.62658),
-'UZ': (61.35529, 42.96107),
-'VA': (12.33197, 42.04931),
-'VC': (-61.09905, 13.316),
-'VE': (-64.88323, 7.69849),
-'VG': (-64.62479, 18.419),
-'VI': (-64.88950, 18.32263),
-'VN': (104.20179, 10.27644),
-'VU': (167.31919, -15.88687),
-'WF': (-176.20781, -13.28535),
-'WS': (-172.10966, -13.85093),
-'YE': (45.94562, 16.16338),
-'YT': (44.93774, -12.60882),
-'ZA': (23.19488, -30.43276),
-'ZM': (26.38618, -14.39966),
-'ZW': (30.12419, -19.86907)
-}
+ # Country aliases
+ 'AD': (1.58972, 42.54241),
+ 'AE': (54.61589, 24.82431),
+ 'AF': (65.90264, 34.84708),
+ 'AG': (-61.72430, 17.069),
+ 'AI': (-63.10571, 18.25461),
+ 'AL': (19.84941, 40.21232),
+ 'AM': (44.64229, 40.37821),
+ 'AO': (16.21924, -12.77014),
+ 'AQ': (44.99999, -75.65695),
+ 'AR': (-61.10759, -34.37615),
+ 'AS': (-170.68470, -14.29307),
+ 'AT': (14.25747, 47.36542),
+ 'AU': (138.23155, -23.72068),
+ 'AW': (-69.98255, 12.555),
+ 'AX': (19.91839, 59.81682),
+ 'AZ': (48.38555, 40.61639),
+ 'BA': (17.18514, 44.25582),
+ 'BB': (-59.53342, 13.19),
+ 'BD': (89.75989, 24.34205),
+ 'BE': (4.90078, 50.34682),
+ 'BF': (-0.56743, 11.90471),
+ 'BG': (24.80616, 43.09859),
+ 'BH': (50.52032, 25.94685),
+ 'BI': (29.54561, -2.99057),
+ 'BJ': (2.70062, 10.02792),
+ 'BL': (-62.79349, 17.907),
+ 'BM': (-64.77406, 32.30199),
+ 'BN': (114.52196, 4.28638),
+ 'BO': (-62.02473, -17.77723),
+ 'BQ': (-63.14322, 17.566),
+ 'BR': (-45.77065, -9.58685),
+ 'BS': (-77.60916, 23.8745),
+ 'BT': (90.01350, 27.28137),
+ 'BV': (3.35744, -54.4215),
+ 'BW': (23.51505, -23.48391),
+ 'BY': (26.77259, 53.15885),
+ 'BZ': (-88.63489, 16.33951),
+ 'CA': (-107.74817, 67.12612),
+ 'CC': (96.84420, -12.01734),
+ 'CD': (24.09544, -1.67713),
+ 'CF': (22.58701, 5.98438),
+ 'CG': (15.78875, 0.40388),
+ 'CH': (7.65705, 46.57446),
+ 'CI': (-6.31190, 6.62783),
+ 'CK': (-159.77835, -21.23349),
+ 'CL': (-70.41790, -53.77189),
+ 'CM': (13.26022, 5.94519),
+ 'CN': (96.44285, 38.04260),
+ 'CO': (-72.52951, 2.45174),
+ 'CR': (-83.83314, 9.93514),
+ 'CU': (-80.81673, 21.88852),
+ 'CV': (-24.50810, 14.929),
+ 'CW': (-68.96409, 12.1845),
+ 'CX': (105.62411, -10.48417),
+ 'CY': (32.95922, 35.37010),
+ 'CZ': (16.32098, 49.50692),
+ 'DE': (9.30716, 50.21289),
+ 'DJ': (42.96904, 11.41542),
+ 'DK': (9.18490, 55.98916),
+ 'DM': (-61.00358, 15.65470),
+ 'DO': (-69.62855, 18.58841),
+ 'DZ': (4.24749, 25.79721),
+ 'EC': (-77.45831, -0.98284),
+ 'EE': (23.94288, 58.43952),
+ 'EG': (28.95293, 28.17718),
+ 'EH': (-13.69031, 25.01241),
+ 'ER': (39.01223, 14.96033),
+ 'ES': (-2.59110, 38.79354),
+ 'ET': (38.61697, 7.71399),
+ 'FI': (26.89798, 63.56194),
+ 'FJ': (177.91853, -17.74237),
+ 'FK': (-58.99044, -51.34509),
+ 'FM': (151.95358, 8.5045),
+ 'FO': (-6.60483, 62.10000),
+ 'FR': (0.28410, 47.51045),
+ 'GA': (10.81070, -0.07429),
+ 'GB': (-0.92823, 52.01618),
+ 'GD': (-61.64524, 12.191),
+ 'GE': (44.16664, 42.00385),
+ 'GF': (-53.46524, 3.56188),
+ 'GG': (-2.50580, 49.58543),
+ 'GH': (-0.46348, 7.16051),
+ 'GI': (-5.32053, 36.11066),
+ 'GL': (-33.85511, 74.66355),
+ 'GM': (-16.40960, 13.25),
+ 'GN': (-13.83940, 10.96291),
+ 'GP': (-61.68712, 16.23049),
+ 'GQ': (10.23973, 1.43119),
+ 'GR': (23.17850, 39.06206),
+ 'GS': (-36.49430, -54.43067),
+ 'GT': (-90.74368, 15.20428),
+ 'GU': (144.73362, 13.44413),
+ 'GW': (-14.83525, 11.92486),
+ 'GY': (-58.45167, 5.73698),
+ 'HK': (114.18577, 22.34923),
+ 'HM': (73.68230, -53.22105),
+ 'HN': (-86.95414, 15.23820),
+ 'HR': (17.49966, 45.52689),
+ 'HT': (-73.51925, 18.32492),
+ 'HU': (20.35362, 47.51721),
+ 'ID': (123.34505, -0.83791),
+ 'IE': (-9.00520, 52.87725),
+ 'IL': (35.46314, 32.86165),
+ 'IM': (-4.86740, 54.023),
+ 'IN': (88.67620, 27.86155),
+ 'IO': (71.42743, -6.14349),
+ 'IQ': (42.58109, 34.26103),
+ 'IR': (56.09355, 30.46751),
+ 'IS': (-17.51785, 64.71687),
+ 'IT': (10.42639, 44.87904),
+ 'JE': (-2.19261, 49.12458),
+ 'JM': (-76.84020, 18.3935),
+ 'JO': (36.55552, 30.75741),
+ 'JP': (138.72531, 35.92099),
+ 'KE': (36.90602, 1.08512),
+ 'KG': (76.15571, 41.66497),
+ 'KH': (104.31901, 12.95555),
+ 'KI': (173.63353, 0.139),
+ 'KM': (44.31474, -12.241),
+ 'KN': (-62.69379, 17.2555),
+ 'KP': (126.65575, 39.64575),
+ 'KR': (127.27740, 36.41388),
+ 'KW': (47.30684, 29.69180),
+ 'KY': (-81.07455, 19.29949),
+ 'KZ': (72.00811, 49.88855),
+ 'LA': (102.44391, 19.81609),
+ 'LB': (35.48464, 33.41766),
+ 'LC': (-60.97894, 13.891),
+ 'LI': (9.54693, 47.15934),
+ 'LK': (80.38520, 8.41649),
+ 'LR': (-11.16960, 4.04122),
+ 'LS': (28.66984, -29.94538),
+ 'LT': (24.51735, 55.49293),
+ 'LU': (6.08649, 49.81533),
+ 'LV': (23.51033, 56.67144),
+ 'LY': (15.36841, 28.12177),
+ 'MA': (-4.03061, 33.21696),
+ 'MC': (7.47743, 43.62917),
+ 'MD': (29.61725, 46.66517),
+ 'ME': (19.72291, 43.02441),
+ 'MF': (-63.06666, 18.08102),
+ 'MG': (45.86378, -20.50245),
+ 'MH': (171.94982, 5.983),
+ 'MK': (21.42108, 41.08980),
+ 'ML': (-1.93310, 16.46993),
+ 'MM': (95.54624, 21.09620),
+ 'MN': (99.81138, 48.18615),
+ 'MO': (113.56441, 22.16209),
+ 'MP': (145.21345, 14.14902),
+ 'MQ': (-60.81128, 14.43706),
+ 'MR': (-9.42324, 22.59251),
+ 'MS': (-62.19455, 16.745),
+ 'MT': (14.38363, 35.94467),
+ 'MU': (57.55121, -20.41),
+ 'MV': (73.39292, 4.19375),
+ 'MW': (33.95722, -12.28218),
+ 'MX': (-105.89221, 25.86826),
+ 'MY': (112.71154, 2.10098),
+ 'MZ': (37.58689, -13.72682),
+ 'NA': (16.68569, -21.46572),
+ 'NC': (164.95322, -20.38889),
+ 'NE': (10.06041, 19.08273),
+ 'NF': (167.95718, -29.0645),
+ 'NG': (10.17781, 10.17804),
+ 'NI': (-85.87974, 13.21715),
+ 'NL': (-68.57062, 12.041),
+ 'NO': (23.11556, 70.09934),
+ 'NP': (83.36259, 28.13107),
+ 'NR': (166.93479, -0.5275),
+ 'NU': (-169.84873, -19.05305),
+ 'NZ': (167.97209, -45.13056),
+ 'OM': (56.86055, 20.47413),
+ 'PA': (-79.40160, 8.80656),
+ 'PE': (-78.66540, -7.54711),
+ 'PF': (-145.05719, -16.70862),
+ 'PG': (146.64600, -7.37427),
+ 'PH': (121.48359, 15.09965),
+ 'PK': (72.11347, 31.14629),
+ 'PL': (17.88136, 52.77182),
+ 'PM': (-56.19515, 46.78324),
+ 'PN': (-130.10642, -25.06955),
+ 'PR': (-65.88755, 18.37169),
+ 'PS': (35.39801, 32.24773),
+ 'PT': (-8.45743, 40.11154),
+ 'PW': (134.49645, 7.3245),
+ 'PY': (-59.51787, -22.41281),
+ 'QA': (51.49903, 24.99816),
+ 'RE': (55.77345, -21.36388),
+ 'RO': (26.37632, 45.36120),
+ 'RS': (20.40371, 44.56413),
+ 'RU': (116.44060, 59.06780),
+ 'RW': (29.57882, -1.62404),
+ 'SA': (47.73169, 22.43790),
+ 'SB': (164.63894, -10.23606),
+ 'SC': (46.36566, -9.454),
+ 'SD': (28.14720, 14.56423),
+ 'SE': (15.68667, 60.35568),
+ 'SG': (103.84187, 1.304),
+ 'SH': (-12.28155, -37.11546),
+ 'SI': (14.04738, 46.39085),
+ 'SJ': (15.27552, 79.23365),
+ 'SK': (20.41603, 48.86970),
+ 'SL': (-11.47773, 8.78156),
+ 'SM': (12.46062, 43.94279),
+ 'SN': (-15.37111, 14.99477),
+ 'SO': (46.93383, 9.34094),
+ 'SR': (-55.42864, 4.56985),
+ 'SS': (28.13573, 8.50933),
+ 'ST': (6.61025, 0.2215),
+ 'SV': (-89.36665, 13.43072),
+ 'SX': (-63.15393, 17.9345),
+ 'SY': (38.15513, 35.34221),
+ 'SZ': (31.78263, -26.14244),
+ 'TC': (-71.32554, 21.35),
+ 'TD': (17.42092, 13.46223),
+ 'TF': (137.5, -67.5),
+ 'TG': (1.06983, 7.87677),
+ 'TH': (102.00877, 16.42310),
+ 'TJ': (71.91349, 39.01527),
+ 'TK': (-171.82603, -9.20990),
+ 'TL': (126.22520, -8.72636),
+ 'TM': (57.71603, 39.92534),
+ 'TN': (9.04958, 34.84199),
+ 'TO': (-176.99320, -23.11104),
+ 'TR': (32.82002, 39.86350),
+ 'TT': (-60.70793, 11.1385),
+ 'TV': (178.77499, -9.41685),
+ 'TW': (120.30074, 23.17002),
+ 'TZ': (33.53892, -5.01840),
+ 'UA': (33.44335, 49.30619),
+ 'UG': (32.96523, 2.08584),
+ 'UM': (-169.50993, 16.74605),
+ 'US': (-116.39535, 40.71379),
+ 'UY': (-56.46505, -33.62658),
+ 'UZ': (61.35529, 42.96107),
+ 'VA': (12.33197, 42.04931),
+ 'VC': (-61.09905, 13.316),
+ 'VE': (-64.88323, 7.69849),
+ 'VG': (-64.62479, 18.419),
+ 'VI': (-64.88950, 18.32263),
+ 'VN': (104.20179, 10.27644),
+ 'VU': (167.31919, -15.88687),
+ 'WF': (-176.20781, -13.28535),
+ 'WS': (-172.10966, -13.85093),
+ 'YE': (45.94562, 16.16338),
+ 'YT': (44.93774, -12.60882),
+ 'ZA': (23.19488, -30.43276),
+ 'ZM': (26.38618, -14.39966),
+ 'ZW': (30.12419, -19.86907)
+ }
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
""" Steps that run queries against the API.
"""
from pathlib import Path
-import json
-import os
import re
import logging
import asyncio
import xml.etree.ElementTree as ET
-from urllib.parse import urlencode
-from utils import run_script
from http_responses import GenericResponse, SearchResponse, ReverseResponse, StatusResponse
from check_functions import Bbox, check_for_attributes
from table_compare import NominatimID
getattr(context, 'http_headers', {})))
-@given(u'the HTTP header')
+@given('the HTTP header')
def add_http_header(context):
if not hasattr(context, 'http_headers'):
context.http_headers = {}
context.http_headers[h] = context.table[0][h]
-@when(u'sending (?P<fmt>\S+ )?search query "(?P<query>.*)"(?P<addr> with address)?')
+@when(r'sending (?P<fmt>\S+ )?search query "(?P<query>.*)"(?P<addr> with address)?')
def website_search_request(context, fmt, query, addr):
params = {}
if query:
context.response = SearchResponse(outp, fmt or 'json', status)
-@when('sending v1/reverse at (?P<lat>[\d.-]*),(?P<lon>[\d.-]*)(?: with format (?P<fmt>.+))?')
+@when(r'sending v1/reverse at (?P<lat>[\d.-]*),(?P<lon>[\d.-]*)(?: with format (?P<fmt>.+))?')
def api_endpoint_v1_reverse(context, lat, lon, fmt):
params = {}
if lat is not None:
context.response = ReverseResponse(outp, fmt or 'xml', status)
-@when('sending v1/reverse N(?P<nodeid>\d+)(?: with format (?P<fmt>.+))?')
+@when(r'sending v1/reverse N(?P<nodeid>\d+)(?: with format (?P<fmt>.+))?')
def api_endpoint_v1_reverse_from_node(context, nodeid, fmt):
params = {}
params['lon'], params['lat'] = (f'{c:f}' for c in context.osm.grid_node(int(nodeid)))
context.response = ReverseResponse(outp, fmt or 'xml', status)
-@when(u'sending (?P<fmt>\S+ )?details query for (?P<query>.*)')
+@when(r'sending (?P<fmt>\S+ )?details query for (?P<query>.*)')
def website_details_request(context, fmt, query):
params = {}
if query[0] in 'NWR':
context.response = GenericResponse(outp, fmt or 'json', status)
-@when(u'sending (?P<fmt>\S+ )?lookup query for (?P<query>.*)')
+
+@when(r'sending (?P<fmt>\S+ )?lookup query for (?P<query>.*)')
def website_lookup_request(context, fmt, query):
- params = { 'osm_ids' : query }
+ params = {'osm_ids': query}
outp, status = send_api_query('lookup', params, fmt, context)
context.response = SearchResponse(outp, fmt or 'xml', status)
-@when(u'sending (?P<fmt>\S+ )?status query')
+
+@when(r'sending (?P<fmt>\S+ )?status query')
def website_status_request(context, fmt):
params = {}
outp, status = send_api_query('status', params, fmt, context)
context.response = StatusResponse(outp, fmt or 'text', status)
-@step(u'(?P<operator>less than|more than|exactly|at least|at most) (?P<number>\d+) results? (?:is|are) returned')
+
+@step(r'(?P<operator>less than|more than|exactly|at least|at most) '
+ r'(?P<number>\d+) results? (?:is|are) returned')
def validate_result_number(context, operator, number):
context.execute_steps("Then a HTTP 200 is returned")
numres = len(context.response.result)
assert compare(operator, numres, int(number)), \
f"Bad number of results: expected {operator} {number}, got {numres}."
-@then(u'a HTTP (?P<status>\d+) is returned')
+
+@then(r'a HTTP (?P<status>\d+) is returned')
def check_http_return_status(context, status):
assert context.response.errorcode == int(status), \
f"Return HTTP status is {context.response.errorcode}."\
f" Full response:\n{context.response.page}"
-@then(u'the page contents equals "(?P<text>.+)"')
+
+@then(r'the page contents equals "(?P<text>.+)"')
def check_page_content_equals(context, text):
assert context.response.page == text
-@then(u'the result is valid (?P<fmt>\w+)')
+
+@then(r'the result is valid (?P<fmt>\w+)')
def step_impl(context, fmt):
context.execute_steps("Then a HTTP 200 is returned")
if fmt.strip() == 'html':
assert context.response.format == fmt
-@then(u'a (?P<fmt>\w+) user error is returned')
+@then(r'a (?P<fmt>\w+) user error is returned')
def check_page_error(context, fmt):
context.execute_steps("Then a HTTP 400 is returned")
assert context.response.format == fmt
else:
assert re.search(r'({"error":)', context.response.page, re.DOTALL) is not None
-@then(u'result header contains')
+
+@then('result header contains')
def check_header_attr(context):
context.execute_steps("Then a HTTP 200 is returned")
for line in context.table:
assert line['attr'] in context.response.header, \
- f"Field '{line['attr']}' missing in header. Full header:\n{context.response.header}"
+ f"Field '{line['attr']}' missing in header. " \
+ f"Full header:\n{context.response.header}"
value = context.response.header[line['attr']]
assert re.fullmatch(line['value'], value) is not None, \
f"Attribute '{line['attr']}': expected: '{line['value']}', got '{value}'"
-@then(u'result header has (?P<neg>not )?attributes (?P<attrs>.*)')
+@then('result header has (?P<neg>not )?attributes (?P<attrs>.*)')
def check_header_no_attr(context, neg, attrs):
check_for_attributes(context.response.header, attrs,
'absent' if neg else 'present')
-@then(u'results contain(?: in field (?P<field>.*))?')
-def step_impl(context, field):
+@then(r'results contain(?: in field (?P<field>.*))?')
+def results_contain_in_field(context, field):
context.execute_steps("then at least 1 result is returned")
for line in context.table:
context.response.match_row(line, context=context, field=field)
-@then(u'result (?P<lid>\d+ )?has (?P<neg>not )?attributes (?P<attrs>.*)')
+@then(r'result (?P<lid>\d+ )?has (?P<neg>not )?attributes (?P<attrs>.*)')
def validate_attributes(context, lid, neg, attrs):
for i in make_todo_list(context, lid):
check_for_attributes(context.response.result[i], attrs,
@then(u'result addresses contain')
-def step_impl(context):
+def result_addresses_contain(context):
context.execute_steps("then at least 1 result is returned")
for line in context.table:
if name != 'ID':
context.response.assert_address_field(idx, name, value)
-@then(u'address of result (?P<lid>\d+) has(?P<neg> no)? types (?P<attrs>.*)')
-def check_address(context, lid, neg, attrs):
+
+@then(r'address of result (?P<lid>\d+) has(?P<neg> no)? types (?P<attrs>.*)')
+def check_address_has_types(context, lid, neg, attrs):
context.execute_steps(f"then more than {lid} results are returned")
addr_parts = context.response.result[int(lid)]['address']
else:
assert attr in addr_parts
-@then(u'address of result (?P<lid>\d+) (?P<complete>is|contains)')
+
+@then(r'address of result (?P<lid>\d+) (?P<complete>is|contains)')
def check_address(context, lid, complete):
context.execute_steps(f"then more than {lid} results are returned")
assert len(addr_parts) == 0, f"Additional address parts found: {addr_parts!s}"
-@then(u'result (?P<lid>\d+ )?has bounding box in (?P<coords>[\d,.-]+)')
+@then(r'result (?P<lid>\d+ )?has bounding box in (?P<coords>[\d,.-]+)')
def check_bounding_box_in_area(context, lid, coords):
expected = Bbox(coords)
f"Bbox is not contained in {expected}")
-@then(u'result (?P<lid>\d+ )?has centroid in (?P<coords>[\d,.-]+)')
+@then(r'result (?P<lid>\d+ )?has centroid in (?P<coords>[\d,.-]+)')
def check_centroid_in_area(context, lid, coords):
expected = Bbox(coords)
f"Centroid is not inside {expected}")
-@then(u'there are(?P<neg> no)? duplicates')
+@then('there are(?P<neg> no)? duplicates')
def check_for_duplicates(context, neg):
context.execute_steps("then at least 1 result is returned")
assert not has_dupe, f"Found duplicate for {dup}"
else:
assert has_dupe, "No duplicates found"
-
#
# This file is part of Nominatim. (https://nominatim.org)
#
-# Copyright (C) 2024 by the Nominatim developer community.
+# Copyright (C) 2025 by the Nominatim developer community.
# For a full list of authors see the git log.
-import logging
from itertools import chain
import psycopg
from place_inserter import PlaceColumn
from table_compare import NominatimID, DBRow
-from nominatim_db.indexer import indexer
from nominatim_db.tokenizer import factory as tokenizer_factory
+
def check_database_integrity(context):
""" Check some generic constraints on the tables.
"""
cur.execute("SELECT count(*) FROM word WHERE word_token = ''")
assert cur.fetchone()[0] == 0, "Empty word tokens found in word table"
+# GIVEN ##################################
-################################ GIVEN ##################################
-
@given("the (?P<named>named )?places")
def add_data_to_place_table(context, named):
""" Add entries into the place table. 'named places' makes sure that
PlaceColumn(context).add_row(row, named is not None).db_insert(cur)
cur.execute('ALTER TABLE place ENABLE TRIGGER place_before_insert')
+
@given("the relations")
def add_data_to_planet_relations(context):
""" Add entries into the osm2pgsql relation middle table. This is needed
else:
members = None
- tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings if h.startswith("tags+")])
+ tags = chain.from_iterable([(h[5:], r[h]) for h in r.headings
+ if h.startswith("tags+")])
- cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off, parts, members, tags)
+ cur.execute("""INSERT INTO planet_osm_rels (id, way_off, rel_off,
+ parts, members, tags)
VALUES (%s, %s, %s, %s, %s, %s)""",
(r['id'], last_node, last_way, parts, members, list(tags)))
else:
(r['id'], psycopg.types.json.Json(tags),
psycopg.types.json.Json(members)))
+
@given("the ways")
def add_data_to_planet_ways(context):
""" Add entries into the osm2pgsql way middle table. This is necessary for
json_tags = row is not None and row['value'] != '1'
for r in context.table:
if json_tags:
- tags = psycopg.types.json.Json({h[5:]: r[h] for h in r.headings if h.startswith("tags+")})
+ tags = psycopg.types.json.Json({h[5:]: r[h] for h in r.headings
+ if h.startswith("tags+")})
else:
tags = list(chain.from_iterable([(h[5:], r[h])
for h in r.headings if h.startswith("tags+")]))
- nodes = [ int(x.strip()) for x in r['nodes'].split(',') ]
+ nodes = [int(x.strip()) for x in r['nodes'].split(',')]
cur.execute("INSERT INTO planet_osm_ways (id, nodes, tags) VALUES (%s, %s, %s)",
(r['id'], nodes, tags))
-################################ WHEN ##################################
+# WHEN ##################################
+
@when("importing")
def import_and_index_data_from_place_table(context):
# itself.
context.log_capture.buffer.clear()
+
@when("updating places")
def update_place_table(context):
""" Update the place table with the given data. Also runs all triggers
"""
context.nominatim.run_nominatim('refresh', '--postcodes')
+
@when("marking for delete (?P<oids>.*)")
def delete_places(context, oids):
""" Remove entries from the place table. Multiple ids may be given
# itself.
context.log_capture.buffer.clear()
-################################ THEN ##################################
+# THEN ##################################
+
@then("(?P<table>placex|place) contains(?P<exact> exactly)?")
def check_place_contents(context, table, exact):
expected_content = set()
for row in context.table:
nid = NominatimID(row['object'])
- query = 'SELECT *, ST_AsText(geometry) as geomtxt, ST_GeometryType(geometry) as geometrytype'
+ query = """SELECT *, ST_AsText(geometry) as geomtxt,
+ ST_GeometryType(geometry) as geometrytype """
if table == 'placex':
query += ' ,ST_X(centroid) as cx, ST_Y(centroid) as cy'
query += " FROM %s WHERE {}" % (table, )
if not exclude:
assert len(tokens) >= len(items), \
- "No word entry found for {}. Entries found: {!s}".format(value, len(tokens))
+ f"No word entry found for {value}. Entries found: {len(tokens)}"
for word, token, wid in tokens:
if exclude:
assert wid not in res[name], \
- "Found term for {}/{}: {}".format(nid, name, wid)
+ "Found term for {}/{}: {}".format(nid, name, wid)
else:
assert wid in res[name], \
- "Missing term for {}/{}: {}".format(nid, name, wid)
+ "Missing term for {}/{}: {}".format(nid, name, wid)
elif name != 'object':
assert db_row.contains(name, value), db_row.assert_msg(name, value)
+
@then("search_name has no entry for (?P<oid>.*)")
def check_search_name_has_entry(context, oid):
""" Check that there is noentry in the search_name table for the given
assert cur.rowcount == 0, \
"Found {} entries for ID {}".format(cur.rowcount, oid)
+
@then("location_postcode contains exactly")
def check_location_postcode(context):
""" Check full contents for location_postcode table. Each row represents a table row
with context.db.cursor() as cur:
cur.execute("SELECT *, ST_AsText(geometry) as geomtxt FROM location_postcode")
assert cur.rowcount == len(list(context.table)), \
- "Postcode table has {} rows, expected {}.".format(cur.rowcount, len(list(context.table)))
+ "Postcode table has {cur.rowcount} rows, expected {len(list(context.table))}."
results = {}
for row in cur:
key = (row['country_code'], row['postcode'])
assert key not in results, "Postcode table has duplicate entry: {}".format(row)
- results[key] = DBRow((row['country_code'],row['postcode']), row, context)
+ results[key] = DBRow((row['country_code'], row['postcode']), row, context)
for row in context.table:
- db_row = results.get((row['country'],row['postcode']))
+ db_row = results.get((row['country'], row['postcode']))
assert db_row is not None, \
f"Missing row for country '{row['country']}' postcode '{row['postcode']}'."
db_row.assert_row(row, ('country', 'postcode'))
+
@then("there are(?P<exclude> no)? word tokens for postcodes (?P<postcodes>.*)")
def check_word_table_for_postcodes(context, exclude, postcodes):
""" Check that the tokenizer produces postcode tokens for the given
assert len(found) == 0, f"Unexpected postcodes: {found}"
else:
assert set(found) == set(plist), \
- f"Missing postcodes {set(plist) - set(found)}. Found: {found}"
+ f"Missing postcodes {set(plist) - set(found)}. Found: {found}"
+
@then("place_addressline contains")
def check_place_addressline(context):
WHERE place_id = %s AND address_place_id = %s""",
(pid, apid))
assert cur.rowcount > 0, \
- "No rows found for place %s and address %s" % (row['object'], row['address'])
+ f"No rows found for place {row['object']} and address {row['address']}."
for res in cur:
DBRow(nid, res, context).assert_row(row, ('address', 'object'))
+
@then("place_addressline doesn't contain")
def check_place_addressline_exclude(context):
""" Check that the place_addressline doesn't contain any entries for the
WHERE place_id = %s AND address_place_id = %s""",
(pid, apid))
assert cur.rowcount == 0, \
- "Row found for place %s and address %s" % (row['object'], row['address'])
+ f"Row found for place {row['object']} and address {row['address']}."
+
-@then("W(?P<oid>\d+) expands to(?P<neg> no)? interpolation")
+@then(r"W(?P<oid>\d+) expands to(?P<neg> no)? interpolation")
def check_location_property_osmline(context, oid, neg):
""" Check that the given way is present in the interpolation table.
"""
for i in todo:
row = context.table[i]
if (int(row['start']) == res['startnumber']
- and int(row['end']) == res['endnumber']):
+ and int(row['end']) == res['endnumber']):
todo.remove(i)
break
else:
assert not todo, f"Unmatched lines in table: {list(context.table[i] for i in todo)}"
+
@then("location_property_osmline contains(?P<exact> exactly)?")
-def check_place_contents(context, exact):
+def check_osmline_contents(context, exact):
""" Check contents of the interpolation table. Each row represents a table row
and all data must match. Data not present in the expected table, may
be arbitrary. The rows are identified via the 'object' column which must
assert expected_content == actual, \
f"Missing entries: {expected_content - actual}\n" \
f"Not expected in table: {actual - expected_content}"
-