|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +########################################################################### |
| 4 | +## ## |
| 5 | +## Copyrights Wolfgang Schreiter 2025 ## |
| 6 | +## ## |
| 7 | +## This program is free software: you can redistribute it and/or modify ## |
| 8 | +## it under the terms of the GNU General Public License as published by ## |
| 9 | +## the Free Software Foundation, either version 3 of the License, or ## |
| 10 | +## (at your option) any later version. ## |
| 11 | +## ## |
| 12 | +## This program is distributed in the hope that it will be useful, ## |
| 13 | +## but WITHOUT ANY WARRANTY; without even the implied warranty of ## |
| 14 | +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## |
| 15 | +## GNU General Public License for more details. ## |
| 16 | +## ## |
| 17 | +## You should have received a copy of the GNU General Public License ## |
| 18 | +## along with this program. If not, see <http://www.gnu.org/licenses/>. ## |
| 19 | +## ## |
| 20 | +########################################################################### |
| 21 | + |
| 22 | +from modules.OsmoseTranslation import T_ |
| 23 | +from plugins.Plugin import Plugin |
| 24 | + |
| 25 | + |
| 26 | +class TagFix_Maxspeed_AT(Plugin): |
| 27 | + """ |
| 28 | + Checks for Austrian highway maxspeed tagging. |
| 29 | + """ |
| 30 | + |
| 31 | + only_for = ["AT"] |
| 32 | + |
| 33 | + valid_maxspeed_types = { |
| 34 | + # Agreed |
| 35 | + 'sign': '', |
| 36 | + 'AT:motorway': '130', 'AT:trunk': '100', 'AT:rural': '100', 'AT:urban': '50', |
| 37 | + 'AT:city_limit30': '30', 'AT:city_limit40': '40', |
| 38 | + 'AT:zone15': '15', 'AT:zone20': '20', 'AT:zone30': '30', 'AT:zone40': '40', 'AT:zone50': '50', 'AT:zone70': '70', |
| 39 | + 'AT:shared_zone20': '20', 'AT:shared_zone30': '30', |
| 40 | + 'AT:bicycle_road': '30', |
| 41 | + # Alternatives |
| 42 | + 'AT:zone:20': '20', 'AT:zone:30': '30', 'AT:zone:40': '40', 'AT:zone:50': '50', |
| 43 | + 'AT:zone': '' |
| 44 | + } |
| 45 | + |
| 46 | + def init(self, logger): |
| 47 | + Plugin.init(self, logger) |
| 48 | + |
| 49 | + self.errors[303220] = self.def_class(item=3032, level=2, tags=['maxspeed'], |
| 50 | + title=T_('Speed limit type without speed limit'), |
| 51 | + detail=T_( |
| 52 | +'''A speed limit type is given in `maxspeed:type` or `source:maxspeed`, but no speed limit is set in `maxspeed`.'''), |
| 53 | + fix=T_( |
| 54 | +'''Set `maxspeed` and either `maxspeed:type` or `source:maxspeed` as appropriate. For a list of values, |
| 55 | +see [Implicit maxspeed values](https://wiki.openstreetmap.org/wiki/Key:maxspeed#Implicit_maxspeed_values).'''), |
| 56 | + trap=T_( |
| 57 | +'''Do not just add a `maxspeed` value suitable for the type. The type may be incorrect too! |
| 58 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 59 | + resource='https://wiki.openstreetmap.org/wiki/Key:maxspeed') |
| 60 | + |
| 61 | + self.errors[309110] = self.def_class(item=3091, level=1, tags=['value', 'maxspeed'], |
| 62 | + title=T_('Invalid speed limit value'), |
| 63 | + detail=T_( |
| 64 | +'''The speed limit in `maxspeed` must be either numeric or `walk`. Do not specify a unit, km/h is the default.'''), |
| 65 | + fix=T_( |
| 66 | +'''Set `maxspeed` as appropriate and set speed limit type in either `maxspeed:type` or `source:maxspeed`. For a list of values, |
| 67 | +see [Implicit maxspeed values](https://wiki.openstreetmap.org/wiki/Key:maxspeed#Implicit_maxspeed_values).'''), |
| 68 | + trap=T_( |
| 69 | +'''If a speed limit type (e.g. `AT:*`) is set in `maxspeed`, do not assume it is correct! |
| 70 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 71 | + resource='https://wiki.openstreetmap.org/wiki/Key:maxspeed') |
| 72 | + |
| 73 | + self.errors[309111] = self.def_class(item=3091, level=2, tags=['maxspeed'], |
| 74 | + title=T_('Low speed limit value'), |
| 75 | + detail=T_( |
| 76 | +'''The speed limit in `maxspeed` is very low and no type is given in `maxspeed:type` or `source:maxspeed`.'''), |
| 77 | + fix=T_( |
| 78 | +'''For pedestrian areas and living streets (except shared zones), walking speed is the default and no |
| 79 | +speed limit or type should be set. If walking speed is signposted, set `maxspeed=walk`, `maxspeed:type=sign` |
| 80 | +and `traffic_sign=AT:54[text]` or `traffic_sign=AT:..,54[text]`. If a low speed is signposted, |
| 81 | +set `maxspeed` to the speed, `maxspeed:type=sign`.'''), |
| 82 | + trap=T_( |
| 83 | +'''Do not assume any of the data present is correct! |
| 84 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 85 | + resource='https://wiki.openstreetmap.org/wiki/DE:Verkehrszeichen_in_Österreich') |
| 86 | + |
| 87 | + self.errors[309112] = self.def_class(item=3091, level=2, tags=['value', 'maxspeed'], |
| 88 | + title=T_('Invalid speed limit type'), |
| 89 | + detail=T_( |
| 90 | +'''The speed limit type in `maxspeed:type` or `source:maxspeed` is not valid.'''), |
| 91 | + fix=T_( |
| 92 | +'''Set the appropriate speed limit type. For a list of values, |
| 93 | +see [Implicit maxspeed values](https://wiki.openstreetmap.org/wiki/Key:maxspeed#Implicit_maxspeed_values).'''), |
| 94 | + trap=T_( |
| 95 | +'''In case the speed limit type is `zone`, do not just change it to `AT:zone`, but to a more specific value. |
| 96 | +Do not assume any of the data present is correct! |
| 97 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 98 | + resource='https://wiki.openstreetmap.org/wiki/DE:Key:maxspeed:type') |
| 99 | + |
| 100 | + self.errors[303221] = self.def_class(item=3032, level=2, tags=['maxspeed'], |
| 101 | + title=T_('Multiple speed limit types'), |
| 102 | + detail=T_( |
| 103 | +'''`maxspeed:type` and `source:maxspeed` are both set. This may confuse mappers and data consumers |
| 104 | +if the values are different.'''), |
| 105 | + fix=T_( |
| 106 | +'''Set either `maxspeed:type` or `source:maxspeed`. For a list of values, |
| 107 | +see [Implicit maxspeed values](https://wiki.openstreetmap.org/wiki/Key:maxspeed#Implicit_maxspeed_values).'''), |
| 108 | + trap=T_( |
| 109 | +'''Do not assume any of the data present is correct! |
| 110 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 111 | + resource='https://wiki.openstreetmap.org/wiki/DE:Key:maxspeed:type') |
| 112 | + |
| 113 | + self.errors[303222] = self.def_class(item=3032, level=1, tags=['maxspeed'], |
| 114 | + title=T_('Speed limit and type mismatch'), |
| 115 | + detail=T_( |
| 116 | +'''The speed limit in `maxspeed` is not consistent with the speed limit type in `maxspeed:type` or `source:maxspeed`.'''), |
| 117 | + fix=T_( |
| 118 | +'''Adjust `maxspeed`, `maxspeed:type` or `source:maxspeed` as appropriate. For a list of values, |
| 119 | +see [Implicit maxspeed values](https://wiki.openstreetmap.org/wiki/Key:maxspeed#Implicit_maxspeed_values).'''), |
| 120 | + trap=T_( |
| 121 | +'''Do not assume any of the data present is correct! |
| 122 | +Always check `highway`, all other tags related to speed and verify on the ground.'''), |
| 123 | + resource='https://wiki.openstreetmap.org/wiki/DE:Key:maxspeed:type') |
| 124 | + |
| 125 | + |
| 126 | + def way(self, data, tags, nds): |
| 127 | + err = [] |
| 128 | + |
| 129 | + if tags.get('highway') is None: |
| 130 | + return err |
| 131 | + |
| 132 | + # Checks apply only to these tags |
| 133 | + maxspeed = tags.get('maxspeed') |
| 134 | + maxspeed_type = tags.get('maxspeed:type') |
| 135 | + source_maxspeed = tags.get('source:maxspeed') |
| 136 | + |
| 137 | + # Error: maxspeed type without maxspeed |
| 138 | + if not maxspeed: |
| 139 | + if maxspeed_type or source_maxspeed: |
| 140 | + err.append({'class': 303220, 'text': T_('`source:maxspeed` or `maxspeed:type` = `{0}` without maxspeed', |
| 141 | + maxspeed_type if maxspeed_type else source_maxspeed)}) |
| 142 | + return err |
| 143 | + |
| 144 | + # Error: maxspeed not numeric or 'walk' |
| 145 | + if maxspeed.endswith(' km/h'): |
| 146 | + maxspeed = maxspeed[:-5] |
| 147 | + if not maxspeed.isdigit() and maxspeed != 'walk': |
| 148 | + return {'class': 309110, 'text': T_('Invalid maxspeed: `{0}`', maxspeed)} |
| 149 | + |
| 150 | + # Error: maxspeed suspiciously low, probably 'walk'; needs verification |
| 151 | + # except for speeds < 5 (covered in Number.py) and if signposted |
| 152 | + if maxspeed.isdigit(): |
| 153 | + maxspeed_num = int(maxspeed) |
| 154 | + if (maxspeed_num > 4) and (maxspeed_num < 15) and (maxspeed_type != 'sign') and (source_maxspeed != 'sign'): |
| 155 | + return {'class': 309111, 'text': T_('Low maxspeed: `{0}`', maxspeed)} |
| 156 | + |
| 157 | + valid_type = None |
| 158 | + if maxspeed_type: |
| 159 | + # Error: maxspeed:type is invalid |
| 160 | + if maxspeed_type in self.valid_maxspeed_types.keys(): |
| 161 | + valid_type = maxspeed_type |
| 162 | + else: |
| 163 | + err.append({'class': 309112, |
| 164 | + 'text': T_('Invalid maxspeed:type: `{0}`', maxspeed_type)}) |
| 165 | + if source_maxspeed: |
| 166 | + # Error: source:maxspeed equal to maxspeed:type |
| 167 | + # Disabled for now to avoid excessive warnings; perform bulk cleanup first |
| 168 | + if maxspeed_type == source_maxspeed: |
| 169 | + # err.append({'class': 303221, |
| 170 | + # 'text': T_('Duplicate speed limit type: `{0}`', maxspeed_type)}) |
| 171 | + pass |
| 172 | + # Error: source:maxspeed contains different maxspeed type |
| 173 | + elif source_maxspeed.startswith('AT:') or source_maxspeed in {'zone', 'sign', 'walk'}: |
| 174 | + err.append({'class': 303221, |
| 175 | + 'text': T_('Conflicting speed limit types: `{0}`<>`{1}`', maxspeed_type, source_maxspeed)}) |
| 176 | + elif source_maxspeed: |
| 177 | + # Error: source:maxspeed is invalid |
| 178 | + if source_maxspeed in self.valid_maxspeed_types.keys(): |
| 179 | + valid_type = source_maxspeed |
| 180 | + elif source_maxspeed.startswith('AT:') or source_maxspeed in {'zone', 'walk'}: |
| 181 | + err.append({'class': 309112, |
| 182 | + 'text': T_('Invalid source:maxspeed: `{0}`', source_maxspeed)}) |
| 183 | + |
| 184 | + # Error: maxspeed type doesn't match maxspeed |
| 185 | + # except for types covered in TagFix_Maxspeed plugin and types without specific speed |
| 186 | + if valid_type and valid_type not in {'AT:motorway', 'AT:trunk', 'AT:rural', 'AT:urban'}: |
| 187 | + if self.valid_maxspeed_types.get(valid_type) and (self.valid_maxspeed_types.get(valid_type) != maxspeed): |
| 188 | + err.append({'class': 303222, |
| 189 | + 'text': T_('maxspeed and type mismatch: `{0}`<>`{1}`', maxspeed, valid_type)}) |
| 190 | + |
| 191 | + return err |
| 192 | + |
| 193 | +########################################################################### |
| 194 | +from plugins.Plugin import TestPluginCommon |
| 195 | + |
| 196 | + |
| 197 | +class Test(TestPluginCommon): |
| 198 | + def test(self): |
| 199 | + plugin = TagFix_Maxspeed_AT(None) |
| 200 | + plugin.init(None) |
| 201 | + |
| 202 | + # No error if not a highway |
| 203 | + assert not plugin.way(None, {'maxspeed_type': 'dont know', 'source:maxspeed': 'unknown'}, None) |
| 204 | + |
| 205 | + # No error if valid |
| 206 | + assert not plugin.way(None, {'highway': 'primary'}, None) |
| 207 | + |
| 208 | + assert not plugin.way(None, {'highway': 'primary', 'maxspeed': '100 km/h'}, None) |
| 209 | + |
| 210 | + assert not plugin.way(None, {'highway': 'living_street', 'maxspeed': 'walk'}, None) |
| 211 | + |
| 212 | + assert not plugin.way(None, {'highway': 'residential', 'maxspeed': '5', 'source:maxspeed': 'sign'}, None) |
| 213 | + |
| 214 | + assert not plugin.way(None, {'highway': 'secondary', 'maxspeed': '70', 'maxspeed:type': 'sign'}, None) |
| 215 | + |
| 216 | + assert not plugin.way(None, {'highway': 'tertiary', 'maxspeed': '50', 'source:maxspeed': 'AT:urban'}, None) |
| 217 | + |
| 218 | + assert not plugin.way(None, {'highway': 'unclassified', 'maxspeed': '100', 'maxspeed:type': 'AT:rural', |
| 219 | + 'source:maxspeed': 'read it in the news'}, None) |
| 220 | + |
| 221 | + # Error when maxspeed type without maxspeed |
| 222 | + self.check_err(plugin.way(None, {'highway': 'secondary', 'maxspeed:type': 'sign'}, None)) |
| 223 | + |
| 224 | + self.check_err(plugin.way(None, {'highway': 'secondary', 'source:maxspeed': 'sign'}, None)) |
| 225 | + |
| 226 | + # Error when maxspeed not numeric or walk |
| 227 | + self.check_err(plugin.way(None, {'highway': 'tertiary', 'maxspeed': 'fast'}, None)) |
| 228 | + |
| 229 | + self.check_err(plugin.way(None, {'highway': 'tertiary', 'maxspeed': '70 mph'}, None)) |
| 230 | + |
| 231 | + # Error when maxspeed too low |
| 232 | + self.check_err(plugin.way(None, {'highway': 'residential', 'maxspeed': '5'}, None)) |
| 233 | + |
| 234 | + # Error when invalid speed limit type |
| 235 | + self.check_err(plugin.way(None, {'highway': 'residential', 'maxspeed': '50', |
| 236 | + 'source:maxspeed': 'AT:city'}, None)) |
| 237 | + |
| 238 | + self.check_err(plugin.way(None, {'highway': 'secondary', 'maxspeed': '70', 'maxspeed:type': 'yes'}, None)) |
| 239 | + |
| 240 | + # Error when speed limit type duplication |
| 241 | + self.check_err(plugin.way(None, {'highway': 'unclassified', 'maxspeed': '100', |
| 242 | + 'maxspeed:type': 'AT:zone40', 'source:maxspeed': 'AT:rural'}, None)) |
| 243 | + |
| 244 | + self.check_err(plugin.way(None, {'highway': 'unclassified', 'maxspeed': '100', |
| 245 | + 'maxspeed:type': 'AT:rural', 'source:maxspeed': 'AT:urban'}, None)) |
| 246 | + |
| 247 | + # Error when speed and type mismatch |
| 248 | + self.check_err(plugin.way(None, {'highway': 'secondary', 'maxspeed': '70', |
| 249 | + 'maxspeed:type': 'AT:city_limit30'}, None)) |
| 250 | + |
| 251 | + self.check_err(plugin.way(None, {'highway': 'tertiary', 'maxspeed': '50', |
| 252 | + 'source:maxspeed': 'AT:city_limit30'}, None)) |
0 commit comments