def node(self, data, tags): err = [] keys = tags.keys() keys = set(keys) & self.SimpleValuedTag for k in keys: if ';' in tags[k]: err.append({"class": 3070, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) return err
def check(self, data, tags, check_list_open): err = [] keyss = tags.keys() keys = set(keyss) & check_list_open for k in keys: if not self.Values_open.match(tags[k]): if k in self.exceptions_open: if tags[k] in self.exceptions_open[k]: # no error if in exception list continue err.append({"class": 3040, "subclass": stablehash64(k), "text": T_("Bad value for %(key)s=%(val)s", {"key": k, "val": tags[k]})}) keys = set(keyss) & self.check_list_closed for k in keys: if tags[k] not in self.allow_closed[k]: err.append({"class": 3040, "subclass": stablehash64(k), "text": T_("Bad value for %(key)s=%(val)s", {"key": k, "val": tags[k]})}) return err
def node(self, data, tags): err = [] keys = tags.keys() for k in keys: part = k.split(':', 1) if ":(" in k or k.startswith("def:") or part[0] in self.exceptions: # acess:([date]) # key def: can contains sign = continue if k in self.exceptions_whole: continue if not self.KeyPart1.match(part[0]): if self.KeyPart1Less.match(part[0]): err.append({ "class": 30502, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) else: err.append({ "class": 3050, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) elif len(part) == 2 and not self.KeyPart2.match(part[1]): err.append({ "class": 30501, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) return err
def __init__(self, config, logger=None): Analyser_Osmosis.__init__(self, config, logger) self.FR = config.options and ( "country" in config.options and config.options["country"].startswith("FR") or "test" in config.options) self.classs[100] = self.def_class( item=6070, level=3, tags=['boundary', 'geom', 'fix:chair'], title=T_('Survey point out of boundary'), fix=T_( '''Check the position of node admin_centre role and boundaries.''' ), trap=T_( '''The geodesic markers should not be moved. These are reference points. Some geodesic markers are really out of boundary.''')) self.classs[2] = self.def_class( item=6060, level=1, tags=['boundary', 'geom', 'fix:chair'], title=T_('Boundary intersection'), detail=T_( '''An area is marked as belonging to several city at once.'''), fix=T_('''Check how this area should be owned.''')) self.classs_change[3] = self.def_class( item=6060, level=2, tags=['boundary', 'geom', 'fix:chair'], title=T_('Lone boundary fragment'), detail=T_( '''Unconnected boundary fragment, a way with a boundary tag not part of a boundary relation.'''), fix=T_( '''Delete the way, remove boundary tag or add to a relation.''' )) self.callback20 = lambda res: { "class": 100, "data": [self.relation_full, self.relation_full, self.positionAsText] } self.callback40 = lambda res: { "class": 2, "subclass": stablehash64(res[2]), "data": [self.relation_full, self.relation_full, self.positionAsText] } self.callback50 = lambda res: { "class": 3, "data": [self.way_full, self.positionAsText] }
def node(self, data, tags): err = [] keys = tags.keys() for k in keys: part = k.split(':', 1) if ":(" in k or k.startswith("def:") or part[0] in self.exceptions: # acess:([date]) # key def: can contains sign = continue if k in self.exceptions_whole: continue if not self.KeyPart1.match(part[0]): err.append({ "class": 3050, "subclass": stablehash64(k), "text": T_("Bad tag %(k)s=%(v)s", { "k": k, "v": tags[k] }) }) elif len(part) == 2 and not self.KeyPart2.match(part[1]): err.append({ "class": 30501, "subclass": stablehash64(k), "text": T_("Bad tag suffix %(k)s=%(v)s", { "k": k, "v": tags[k] }) }) return err
def __init__(self, config, logger = None): Analyser_Osmosis.__init__(self, config, logger) self.classs_change[1] = self.def_class(item = 1170, level = 3, tags = ['relation', 'fix:chair', 'geom'], title = T_('Double inner polygon'), detail = T_( '''The geometry of the inner of the multipolygon is duplicated. One in relation without tag and another with tags not part of the relation.'''), fix = T_( '''Remove the ring without tag. Add in the relation the one with the tags as `inner` role.''')) self.classs_change[2] = self.def_class(item = 1170, level = 2, tags = ['relation', 'fix:chair', 'multipolygon'], title = T_('Inconsistant multipolygon nature with members nature'), detail = T_( '''Multipolygon defines a nature that is different from that specified in the outers roles.''')) self.classs_change[3] = self.def_class(item = 1170, level = 2, tags = ['relation', 'fix:chair', 'multipolygon'], title = T_('Inconsistant multipolygon member nature'), detail = T_( '''Multipolygon does not define nature, several found on the outer role members.''')) self.classs_change[4] = self.def_class(item = 1170, level = 1, tags = ['relation', 'fix:chair', 'geom'], title = T_('Should be polygon, part of multipolygon or not having area tag'), detail = T_( '''The nature of the way indicates that it is a surface, the way would be a polygon or a part of a multipolygon as outer role.'''), fix = T_( '''Close the way to make a polygon or add to a multipolygon.''')) self.callback10 = lambda res: {"class":1, "data":[self.relation_full, self.way_full, self.way_full, self.positionAsText]} self.callback20 = lambda res: {"class":2, "subclass":stablehash64(res[11]), "data":[self.relation_full, self.way_full, self.positionAsText], "text": {"en": u", ".join(map(lambda k: "%s=(%s,%s)"%k, filter(lambda k: k[1], (("landuse",res[3],res[4]), ("natural",res[5],res[6]), ("waterway",res[7],res[8]), ("building",res[9],res[10])))))} } self.callback30 = lambda res: {"class":3, "subclass":1, "data":[self.relation_full, self.positionAsText], "text": {"en": u", ".join(map(lambda k: "%s=(%s)"%k, filter(lambda k: k[1], (("landuse",res[2]), ("natural",res[3]), ("waterway",res[4]), ("building",res[5])))))} } self.callback40 = lambda res: {"class":4, "subclass":stablehash64(res[9]), "data":[self.way_full, self.positionAsText], "text": {"en": u", ".join(map(lambda k: "%s=%s"%k, filter(lambda k: k[1], (("area",res[2]), ("landuse",res[3]), ("natural",res[4]), ("waterway",res[5]), ("leisure",res[6]), ("amenity",res[7]), ("building",res[8])))))} }
def check(self, data, tags, check_list_open): err = [] keyss = tags.keys() keys = set(keyss) & check_list_open for k in keys: if not self.Values_open.match(tags[k]): if k in self.exceptions_open: if tags[k] in self.exceptions_open[k]: # no error if in exception list continue err.append({"class": 3040, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) keys = set(keyss) & self.check_list_closed for k in keys: if tags[k] not in self.allow_closed[k]: err.append({"class": 3040, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) for k in keyss: if tags[k] == "unknown": err.append({"class": 40613, "subclass": stablehash64(k), "text": T_("Concerns tag: `{0}`", '='.join([k, tags[k]])) }) return err
def checkAccessKeys(self, tags, suffixes): err = [] accessTags = {} for accesskey in self.accessKeys: for suffix in suffixes: if accesskey + suffix in tags: accessTags[accesskey + suffix] = {"transportMode": accesskey, "value": tags[accesskey + suffix], "suffix": suffix} for tag in accessTags: values = accessTags[tag]["value"].split(";") isConditional = ":conditional" in tag for accessVal in values: accessValue = accessVal if isConditional: if "@" in accessVal: accessValue = accessValue.split("@")[0] else: continue # value was split on a ";" in the condition accessValue = accessValue.strip() transportMode = accessTags[tag]["transportMode"] if transportMode in self.accessValuesSpecial and accessValue in self.accessValuesSpecial[transportMode]: continue if not accessValue in self.accessValuesGeneral: if (accessValue in self.accessKeys or accessValue == "emergency") and accessValue != transportMode: propose = tag + " = ### + " + accessValue + accessTags[tag]["suffix"] + " = yes" if len(values) > 1 or isConditional: propose = propose.replace("###", "...") # i.e. access=bus;destination should become access=destination + bus=yes instead of access=no + bus=yes else: propose = propose.replace("###", "no") # assume 'no' holds for all other transport modes if isConditional: propose = propose + " @ (...)" # conditional may need to change err.append({"class": 30405, "subclass": 0 + stablehash64(tag + '|' + accessValue), "text": T_("Access value \"{0}\" for key \"{1}\" is a transport mode. Consider using \"{2}\" instead", accessValue, tag, propose)}) else: err.append({"class": 30404, "subclass": 0 + stablehash64(tag + '|' + accessValue), "text": T_("Unknown access value \"{0}\" for key \"{1}\"", accessValue, tag)}) if err != []: return err
def __init__(self, config, logger = None): Analyser_Osmosis.__init__(self, config, logger) self.classs[1] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:imagery'], title = T_('Lone power tower or pole'), fix = T_( '''This tower should probably be connected to a power line.'''), trap = T_( '''It's possible that disused power features could be disconnected from the network. In which case make use of the `disused:` [lifecycle prefix](https://wiki.openstreetmap.org/wiki/Lifecycle_prefix).''')) self.classs[2] = self.def_class(item = 7040, level = 2, tags = ['power', 'fix:imagery'], title = T_('Unfinished power major line'), detail = T_( '''The line ends in a vacuum, and should be connected to another line or a transformer (`power=transformer`), a generator (`power=generator`) or marked as transitioning into ground (`location:transition=yes`).'''), trap = T_( '''It's possible that disused power features could be disconnected from the network. In which case make use of the `disused:` [lifecycle prefix](https://wiki.openstreetmap.org/wiki/Lifecycle_prefix).''')) self.classs[6] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:imagery'], title = T_('Unfinished power minor line'), detail = T_( '''The line ends in a vacuum, and should be connected to another line or a transformer (`power=transformer`), a generator (`power=generator`) or marked as transitioning into ground (`location:transition=yes`).'''), trap = T_( '''It's possible that disused power features could be disconnected from the network. In which case make use of the `disused:` [lifecycle prefix](https://wiki.openstreetmap.org/wiki/Lifecycle_prefix).''')) self.classs[3] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:chair'], title = T_('Connection between different voltages'), detail = T_('Two power lines meet at this point, but have inconsistent voltages (`voltage=*`).')) self.classs[4] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:imagery'], title = T_('Non power node on power way'), detail = T_( '''Power lines can only form a straight line between supports and therefore shouldn't have additional nodes that aren't tagged as a `power` feature.'''), fix = T_( '''If this node is a tower or pole, use the tag `power=tower` or `power=pole`. Otherwise remove it.''')) self.classs_change[5] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:imagery'], title = T_('Missing power tower or pole'), detail = T_( '''Based on the statistical frequency of the poles on this power line, there's likely an unmapped pole nearby.''')) self.classs[7] = self.def_class(item = 7040, level = 3, tags = ['power', 'fix:chair'], title = T_('Unmatched voltage of line on substation')) self.callback40 = lambda res: {"class":4, "data":[self.node_full, self.positionAsText], "fix":[{"+": {"power": "tower"}}, {"+": {"power": "pole"}}]} self.callback50 = lambda res: {"class":5, "subclass": stablehash64(res[1]), "data":[self.way_full, self.positionAsText]}
def way(self, data, tags, nds): if not "highway" in tags: return err = [] for tag in tags: if tag[-5:] == ":both": tag_default = tag[ 0:-5] # special handling because :both_ways also exists else: tag_default = tag.replace(":both:", ":").replace(":left", "").replace( ":right", "") if tag_default == tag: continue # tag does not contain :both/:left/:right allowedAlternativeValues = [] if tag_default in tags: # Some tags allow left/right/both as values, e.g. sidewalk=both equals sidewalk:both=yes if self.simplifyValue(tags[tag]) == "no": allowedAlternativeValues = [ "yes", "left", "right", "separate", "opposite" ] else: allowedAlternativeValues = ["yes", "left", "right", "both"] else: tag_default = tag.replace(":left", ":both").replace(":right", ":both") if tag_default in tags: tt = self.simplifyValue(tags[tag]) ttd = self.simplifyValue(tags[tag_default]) if tag[0:5] == "name:" and tt in ttd: continue # 'name' probably equals "name:left" + "/" + name:right, handled by Name_Multiple if tt != ttd and not ttd in allowedAlternativeValues: err.append({ "class": 33601, "subclass": 1 + stablehash64(tag), "text": T_("Conflicting values of \"{0}\" and \"{1}\"", tag_default, tag) }) if err != []: return err
def __init__(self, config, logger = None): Analyser_Osmosis.__init__(self, config, logger) self.FR = config.options and ("country" in config.options and config.options["country"].startswith("FR") or "test" in config.options) detail = T_( '''In France, probably a building imported from cadastre without the [controls needed](https://wiki.openstreetmap.org/wiki/France/Cadastre/Import_semi-automatique_des_b%C3%A2timents#Traitements_des_b.C3.A2timents_avec_JOSM_avant_envoi_vers_serveur_OSM). Error is frequent on areas where lots of building are drawn manually, such as the HOT activations. ''') self.classs_change[1] = self.def_class(item = 0, level = 3, tags = ['building', 'geom', 'fix:chair'], title = T_('Building intersection'), detail = self.merge_doc(detail, T_( '''The intersection surface is probably due to inaccuracies in the cadastre/import tools.'''))) self.classs_change[2] = self.def_class(item = 0, level = 2, tags = ['building', 'geom', 'fix:chair'], title = T_('Large building intersection'), detail = self.merge_doc(detail, T_( '''A large overlap. Requires a visual check (Bing, cadastre or survey).'''))) self.classs_change[3] = self.def_class(item = 0, level = 3, tags = ['building', 'geom', 'fix:chair'], title = T_('Building too small'), detail = self.merge_doc(detail, T_( '''There is no intersection, but the surface is too small to be a building.'''))) self.classs_change[4] = self.def_class(item = 0, level = 3, tags = ['building', 'geom', 'fix:chair'], title = T_('Gap between buildings'), detail = self.merge_doc(detail, T_( '''Space separation is probably due to inaccuracies in the cadastre/import tools.'''))) self.classs_change[5] = self.def_class(item = 0, level = 1, tags = ['building', 'fix:chair'], title = T_('Large building intersection cluster'), detail = self.merge_doc(detail, T_( '''Group of important overlaps. Major problem like a double import.'''))) if self.FR: self.classs_change[6] = self.def_class(item = 1, level = 3, tags = ['building', 'geom', 'fix:chair'], title = T_(u"Building in parts"), detail = detail) self.callback30 = lambda res: {"class":2 if res[3]>res[4] else 1, "data":[self.way, self.way, self.positionAsText]} self.callback40 = lambda res: {"class":3, "data":[self.way, self.positionAsText]} self.callback50 = lambda res: {"class":4, "data":[self.way, self.way, self.positionAsText]} self.callback60 = lambda res: {"class":5, "subclass": stablehash64(res[0]), "data":[self.positionAsText]} if self.FR: self.callback70 = lambda res: {"class":6, "data":[self.way, self.positionAsText]}
def node(self, data, tags): err = [] keys = tags.keys() keys = set(keys) & self.SimpleValuedTag for k in keys: if ';' in tags[k]: err.append({ "class": 3070, "subclass": stablehash64(k), "text": T_("Multiple values on tag: %(key)s=%(val)s", { "key": k, "val": tags[k] }) }) return err
def way(self, data, tags, nds): if not "highway" in tags: return err = [] if (("parking:condition:right" in tags and not "parking:lane:right" in tags and not "parking:lane:both" in tags) or ("parking:condition:left" in tags and not "parking:lane:left" in tags and not "parking:lane:both" in tags) or ("parking:condition:both" in tags and not "parking:lane:both" in tags and not ("parking:lane:left" in tags and "parking:lane:right" in tags))): err.append({"class": 31616}) sides = list(map(lambda tag: tag[len("parking:lane:"):].split(":")[0], filter(lambda tag: tag.startswith("parking:lane:"), tags))) if len(sides) == 0: return err sides = [i for i in sides if i not in ("left", "right", "both")] if len(sides) > 0: err.append({"class": 31611}) if "parking:lane:both" in tags and (("parking:lane:right" in tags and tags["parking:lane:right"] == tags["parking:lane:both"]) or ("parking:lane:left" in tags and tags["parking:lane:left"] == tags["parking:lane:both"])): # Conflicting values are dealt with in Highway_Sides err.append({"class": 31614}) for side in ("parking:lane:right", "parking:lane:left", "parking:lane:both"): if side in tags: if tags[side] not in self.parkingLaneValues: # Unknown value of parking:lane:[side] if tags[side] in self.parkingConditionValues: err.append({"class": 31615, "subclass": stablehash64(side), "text": T_("`{0}` is a value for key `{1}`", tags[side], "parking:condition:[side]")}) elif tags[side] in self.parkingConditionReasonValues: err.append({"class": 31615, "subclass": stablehash64(side), "text": T_("`{0}` is a value for key `{1}`", tags[side], "parking:condition:[side]:reason")}) else: err.append({"class": 31615, "subclass": stablehash64(side)}) condition = side.replace("lane", "condition") if not condition in tags: condition = "parking:condition:both" if condition in tags: if tags[side] == "no" and tags[condition][0:2] != "no": # parking:lane:[side] = no together with parking:condition:[side] err.append({"class": 31617, "subclass": stablehash64(side)}) elif tags[side] == "separate": # parking:lane:[side] = separate together with parking:condition:[side] err.append({"class": 31618, "subclass": stablehash64(side)}) elif tags[condition] in self.parkingLaneValues and tags[condition] not in self.parkingConditionValues: # A value for parking:lane:[side] was used as condition err.append({"class": 31619, "subclass": stablehash64(side), "text": T_("`{0}` is a value for key `{1}`", tags[condition], "parking:lane:[side]")}) return err
def way(self, data, tags, nds): if not "highway" in tags: return err = [] sides = list( map(lambda tag: tag[len(self.parking_lane):].split(":")[0], filter(lambda tag: tag.startswith(self.parking_lane), tags))) n_sides = len(sides) sides = [i for i in sides if i not in ("left", "right", "both")] conditions = map( lambda tag: ":".join(tag.split(":")[0:3]).replace( ":condition:", ":lane:"), filter(lambda tag: tag.startswith(self.parking_condition), tags)) for c in conditions: if c not in tags: err.append({"class": 31616}) break if n_sides == 0: return err if len(sides) > 0: err.append({"class": 31611}) if ("parking:lane:right" in tags or "parking:lane:left" in tags) and "parking:lane:both" in tags: err.append({"class": 31614}) for side in ("parking:lane:right", "parking:lane:left", "parking:lane:both"): if side in tags and tags[side] not in ("parallel", "diagonal", "perpendicular", "marked", "no_parking", "no_stopping", "fire_lane"): err.append({"class": 31615, "subclass": stablehash64(side)}) return err
def node(self, data, tags): if u"name" not in tags: return if (u"highway" not in tags) and (u"waterway" not in tags) and (u"place" not in tags): return words = [] name = tags[u"name"] name_subst = self.apply_special_subst(name) split = self._split(name_subst) for i in range(0, len(split), 2): split[i] = self.remove_special_subst(split[i]) splitfix = list(split) if split and split[0] and split[0][0] in self.minus: words.append(split[0]) splitfix[0] = split[0].capitalize() for i in range(0, len(split), 2): word = split[i] if word in self.special: continue if word[0] in self.minus: words.append(word) splitfix[i] = split[i].capitalize() if words: return { "class": 906, "subclass": stablehash64(','.join(words)), "text": T_("Missing capital letter for: {0}", u", ".join(sorted(set(words)))), "fix": { "name": "".join(splitfix) } } return
def __init__(self, config, logger=None): Analyser_Osmosis.__init__(self, config, logger) self.FR = config.options and ( "country" in config.options and config.options["country"].startswith("FR") or "test" in config.options) self.classs[100] = { "item": "6070", "level": 3, "tag": ["boundary", "geom", "fix:chair"], "desc": T_(u"Survey point out of boundary") } self.classs[2] = { "item": "6060", "level": 1, "tag": ["boundary", "geom", "fix:chair"], "desc": T_(u"Boundary intersection") } self.classs_change[3] = { "item": "6060", "level": 2, "tag": ["boundary", "geom", "fix:chair"], "desc": T_(u"Lone boundary fragment") } self.callback20 = lambda res: { "class": 100, "data": [self.relation_full, self.relation_full, self.positionAsText] } self.callback40 = lambda res: { "class": 2, "subclass": stablehash64(res[2]), "data": [self.relation_full, self.relation_full, self.positionAsText] } self.callback50 = lambda res: { "class": 3, "data": [self.way_full, self.positionAsText] }
def node(self, data, tags): err = [] for key, value in tags.items(): m = self.non_printable.search(key) if m: err.append({"class": 50702, "subclass": 0 + stablehash64(key), "text": T_f(u"\"{0}\" unexpected non printable char ({1}, 0x{2:04x}) in key at position {3}", key, unicodedata.name(m.group(0), ''), ord(m.group(0)), m.start() + 1)}) break m = self.non_printable.search(value) if m: err.append({"class": 50702, "subclass": 1 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected non printable char ({2}, 0x{3:04x}) in value at position {4}", key, value, unicodedata.name(m.group(0), ''), ord(m.group(0)), m.start() + 1)}) break m = self.other_symbol.search(key) if m: err.append({"class": 50703, "subclass": 0 + stablehash64(key), "text": T_f(u"\"{0}\" unexpected symbol char ({1}, 0x{2:04x}) in key at position {3}", key, unicodedata.name(m.group(0), ''), ord(m.group(0)), m.start() + 1)}) break m = self.other_symbol.search(value) if m: err.append({"class": 50703, "subclass": 1 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected symbol char ({2}, 0x{3:04x}) in value at position {4}", key, value, unicodedata.name(m.group(0), ''), ord(m.group(0)), m.start() + 1)}) break err_size = len(err) # https://en.wikipedia.org/wiki/Bi-directional_text#Table_of_possible_BiDi-types for c in u"\u200E\u200F\u061C\u202A\u202D\u202B\u202E\u202C\u2066\u2067\u2068\u2069": m = key.find(c) if m > 0: err.append({"class": 50702, "subclass": 2 + stablehash64(key), "text": T_f(u"\"{0}\" unexpected non printable char ({1}, 0x{2:04x}) in key at position {3}", key, unicodedata.name(c, ''), ord(c), m + 1)}) break m = value.find(c) if m > 0: err.append({"class": 50702, "subclass": 3 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected non printable char ({2}, 0x{3:04x}) in value at position {4}", key, value, unicodedata.name(c, ''), ord(c), m + 1)}) break if err_size != len(err): break if self.default: if key in self.names: s = self.non_letter.sub(u" ", value) s = self.alone_char.sub(u"", s) s = self.roman_number.sub(u"", s) s = self.default.sub(u"", s) if len(s) > 0 and not(len(value) == 2 and len(s) == 1) and len(s) <= len(value) / 10 + 1: if len(s) == 1: c = s[0] u = self.uniq_script and confusables.unconfuse(c, self.uniq_script) if u: err.append({"class": 50701, "subclass": 0 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected char \"{2}\" ({3}, 0x{4:04x}). Means \"{5}\" ({6}, 0x{7:04x})?", key, value, s, unicodedata.name(c, ''), ord(c), u, unicodedata.name(u, ''), ord(u)), "fix": {key: value.replace(c, u)} }) else: err.append({"class": 50701, "subclass": 0 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected char \"{2}\" ({3}, 0x{4:04x})", key, value, s, unicodedata.name(c, ''), ord(c)) }) else: err.append({"class": 50701, "subclass": 0 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected \"{2}\"", key, value, s)}) break l = key.split(':') if len(l) > 1 and l[0] in self.names and l[1] in self.lang: s = self.non_letter.sub(u" ", value) s = self.alone_char.sub(u"\\1", s) s = self.roman_number.sub(u"\\1", s) s = self.lang[l[1]].sub(u"", s) if len(s) > 0: if len(s) == 1: c = s[0] u = self.uniq_scripts.get(l[1]) and confusables.unconfuse(c, self.uniq_scripts.get(l[1])) if u: err.append({"class": 50701, "subclass": 1 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected char \"{2}\" ({3}, 0x{4:04x}). Means \"{5}\" ({6}, 0x{7:04x})?", key, value, s, unicodedata.name(c, ''), ord(c), u, unicodedata.name(u, ''), ord(u)), "fix": {key: value.replace(c, u)} }) else: err.append({"class": 50701, "subclass": 1 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected char \"{2}\" ({3}, 0x{4:04x})", key, value, s, unicodedata.name(c, ''), ord(c)) }) else: err.append({"class": 50701, "subclass": 1 + stablehash64(key), "text": T_f(u"\"{0}\"=\"{1}\" unexpected \"{2}\"", key, value, s)}) break return err
def way(self, data, tags, nds): if not "highway" in tags: return lanes = False for tag in tags: if "lanes" in tag: lanes = True break if not lanes: return tags_lanes = {} for tag in tags: if "lanes" in tag and not "source" in tag and not "FIXME" in tag: tags_lanes[tag] = tags[tag] err = [] # Check turn lanes values tl = "turn:lanes" in tags_lanes tlf = "turn:lanes:forward" in tags_lanes tlb = "turn:lanes:backward" in tags_lanes tl2 = "turn:lanes:both_ways" in tags_lanes if tl or tlf or tlb or tl2: for tl in ["turn:lanes", "turn:lanes:forward", "turn:lanes:backward", "turn:lanes:both_ways"]: if tl in tags_lanes: ttt = tags_lanes[tl].split("|") unknown_turn_lanes_value = False bad_turn_lanes_value = False i = 0 for tt in ttt: settt = set(tt.split(";")) for t in settt: if t not in ["left", "slight_left", "sharp_left", "through", "right", "slight_right", "sharp_right", "reverse", "merge_to_left", "merge_to_right", "none", ""]: unknown_turn_lanes_value = True err.append({"class": 31606, "subclass": 0 + stablehash64(tl + '|' + t + '|' + str(i)), "text": T_("Unknown turn lanes value \"{0}\"", t)}) if ("merge_to_left" in settt and i == 0) or ("merge_to_right" in settt and i == len(ttt) - 1): # A lane must exist in the merging direction err.append({"class": 31600, "subclass": 1 + stablehash64(tl + '|' + tt + '|' + str(i))}) bad_turn_lanes_value = True elif (not unknown_turn_lanes_value and len(settt) > 1 and ("none" in settt or "" in settt)): # A single turn lane containing both `none` and `something` if (not ("none" in settt and "" in settt and len(settt) == 2)): err.append({"class": 316012, "subclass": 3 + stablehash64(tl + '|' + tt + '|' + str(i)), "text": T_("Combined none and indicated turn lane: \"{0}\"", tt)}) bad_turn_lanes_value = True elif (not unknown_turn_lanes_value and len(settt) > 1 and ((len(settt) > 2 and ("merge_to_right" in settt or "merge_to_left" in settt)) or ("merge_to_right" in settt and not "merge_to_left" in settt) or ("merge_to_left" in settt and not "merge_to_right" in settt))): # A combination of merge_to_* with a turn (other than another merge_to_*) err.append({"class": 316011, "subclass": 2 + stablehash64(tl + '|' + tt + '|' + str(i)), "text": T_("Merge together with another turn lane: \"{0}\"", tt)}) i += 1 if not unknown_turn_lanes_value and not bad_turn_lanes_value: # Sequence (left-to-right) should be sharp_left (1)|left (2)|slight_left (3)|through (4)|slight_right (5)|right (6)|sharp_right (7) # Reverse (depends on driving direction) and merge_to_* (can be anywhere except # outer lane in merging direction; already handled by error 31600) are ignored # None is a special case: considered as 'through' except when outer lane throughvalue = "4" t = tags_lanes[tl] \ .replace("reverse", "-") \ .replace("merge_to_right", "-").replace("merge_to_left", "-") \ .replace("none", "N") \ .replace("sharp_left", "1").replace("slight_left", "3").replace("left", "2") \ .replace("through", throughvalue) \ .replace("slight_right", "5").replace("sharp_right", "7").replace("right", "6") \ .replace(";", "").split("|") # Empty equals a 'none', otherwise sort values within a single lane (as left;right equals right;left) t = ''.join(map(lambda e: "N" if len(e) == 0 else e[0] if len(e) == 1 else ''.join(sorted(e)), t)) t = t.replace('-', '') # Ignored values # Ignore single none on the outside lanes: it could be a bus lane # Treat all other nones as throughs (multiple bus lanes or a dedicated lane in between two turns is unlikely) if t[0:1] == "N": t = t[1:] if t[-1:] == "N": t = t[0:-1] t = t.replace('N', throughvalue) if t != ''.join(sorted(t)): err.append({"class": 31607, "subclass": 1 + stablehash64(tl), "text": T_("Bad turn lanes order in \"{0}\"", tl)}) # Check access lanes values # Count for non fullwidth lanes non_fullwidth_lanes_number = {} for direction in ['', ':forward', ':backward', ':both_ways']: o = tags_lanes.get('bicycle:lanes'+direction) if o: non_fullwidth_lanes_number[direction] = ilen(filter(lambda i: i == 'designated', o.split('|'))) for access in ['hgv', 'bus', 'access', 'bicycle', 'psv', 'taxi', 'vehicle', 'motor_vehicle', 'hov', 'motorcycle', 'goods']: base = access+':lanes' for tag in tags_lanes: if tag.startswith(base): try: int(tags_lanes[tag]) err.append({"class": 31609, "subclass": 1 + stablehash64(tag), "text": {'en': '{0}={1}'.format(tag, tags_lanes[tag]) }}) except ValueError: # Ok, should not be an integer pass stars = [] for tag in tags_lanes: if ":lanes" in tag: star = tag.split(':')[0] if star not in ('source', 'proposed', 'construction', 'note'): stars.append(star) stars = list(set(stars)) for star in stars: l = star + '' in tags_lanes lf = star + ':forward' in tags_lanes lb = star + ':backward' in tags_lanes l2 = star + ':both_ways' in tags_lanes if l and (lf or lb or l2): err.append({"class": 31603, "subclass": 0 + stablehash64(star), "text": {"en": star + ":*"}}) if err != []: return err number = {'lanes': {}} for tag in tags_lanes: if tag == 'lanes' or tag.startswith('lanes:'): try: n = int(tags_lanes[tag]) parts = tag.split(':') direction = '' if len(parts) == 1: number['lanes'][''] = n elif len(parts) == 2 and parts[1] in ['forward', 'backward', 'both_ways']: number['lanes'][':'+parts[1]] = n except ValueError: err.append({"class": 31601, "subclass": 0 + stablehash64(tag), "text": T_("lanes={0} is not an integer", tags_lanes[tag])}) for star in stars: number[star] = {} for direction in ['', ':forward', ':backward', ':both_ways']: o = tags_lanes.get(star+':lanes'+direction) if o: number[star][direction] = len(o.split('|')) n_lanes = {} for direction in ['', ':forward', ':backward', ':both_ways']: tag = None for star in sorted(number.keys()): non_fullwidth_lanes_number_star = ((non_fullwidth_lanes_number.get(direction) or 0) if star != 'lanes' else 0) non_fullwidth_lanes_number_tag = ((non_fullwidth_lanes_number.get(direction) or 0) if tag != 'lanes:lanes'+direction else 0) if n_lanes.get(direction) is not None and number[star].get(direction) is not None and \ number[star][direction] - non_fullwidth_lanes_number_star != \ n_lanes[direction] - non_fullwidth_lanes_number_tag: err.append({"class": 31608, "subclass": 0 + stablehash64(direction + '|' + star), "text": { "en": "(lanes({0})={1}) - (non fullwidth={2}) != (lanes({3})={4}) - (non fullwidth={5})".format( star+":*"+direction, number[star][direction], non_fullwidth_lanes_number_star, tag, n_lanes[direction], non_fullwidth_lanes_number_tag) }}) elif n_lanes.get(direction) is None and number[star].get(direction) is not None: # Fist loop, pick the star as tag and the number of lanes to compare to the others n_lanes[direction] = number[star][direction] tag = star+":lanes"+direction if err != []: return err if tags["highway"] == 'motorway': oneway = "oneway" not in tags or tags["oneway"] not in ["no", "false"] else: oneway = "oneway" in tags and tags["oneway"] not in ["no", "false"] if oneway: for tag in tags: if tag.startswith('oneway:') and tags[tag] in ["no", "false"]: # Oneway for mainstream traffic, but not for an other one, so we are not really on a oneway oneway = False if tags.get('junction') == 'roundabout': oneway = True nl = n_lanes.get('') nlf = n_lanes.get(':forward') nlb = n_lanes.get(':backward') nl2 = n_lanes.get(':both_ways') nfw_nl = non_fullwidth_lanes_number.get('') or 0 nfw_nlf = non_fullwidth_lanes_number.get(':forward') or 0 nfw_nlb = non_fullwidth_lanes_number.get(':backward') or 0 nfw_nl2 = non_fullwidth_lanes_number.get(':both_ways') or 0 if oneway: if nl is not None and nlf is not None and nl != nlf - nfw_nlf: err.append({"class": 31604, "subclass": 0, "text": T_("on oneway, (lanes={0}) != (lanes:forward={1}) - (non fullwidth forward={2})", nl, nlf, nfw_nlf)}) elif nlb is not None or nl2 is not None: err.append({"class": 31605, "subclass": 0}) else: if nl is not None and nlf is not None and nlb is not None and nl != nlf + nlb + (nl2 or 0) - nfw_nl - nfw_nlf - nfw_nlb - nfw_nl2: err.append({"class": 31604, "subclass": 0, "text": T_("on two way, (lanes={0}) != (lanes:forward={1}) + (lanes:backward={2}) + (lanes:both_ways={3}) - (non fullwidth={4}) - (non fullwidth forward={5}) - (non fullwidth backward={6}) - (non fullwidth both_ways={7})", nl, nlf, nlb, nl2, nfw_nl, nfw_nlf, nfw_nlb, nfw_nl2)}) elif nl is not None and nlf is not None and nl <= nlf - nfw_nlf: err.append({"class": 31604, "subclass": 0, "text": T_("on two way, (lanes={0}) <= (lanes:forward={1}) - (non fullwidth forward={2})", nl, nlf, nfw_nlf)}) elif nl is not None and nlb is not None and nl <= nlb - nfw_nlb: err.append({"class": 31604, "subclass": 0, "text": T_("on two way, (lanes={0}) <= (lanes:backward={1}) - (non fullwidth backward={2})", nl, nlb, nfw_nlb)}) elif nl is not None and nl2 is not None and nl < nl2 - nfw_nl2: err.append({"class": 31604, "subclass": 0, "text": T_("on two way, (lanes={0}) < (lanes:both_ways={1}) - (non fullwidth both_ways={2})", nl, nl2, nfw_nl2)}) if err != []: return err
def analyser_osmosis_common(self): self.run(sql10, lambda res: {"class":1, "subclass":stablehash64(res[0]), "data":[self.positionAsText]} )
def analyse(self, tags, wikipediaTag="wikipedia"): err = [] if wikipediaTag in tags: m = self.wiki_regexp.match(tags[wikipediaTag]) if (tags[wikipediaTag].startswith(u"http://") or tags[wikipediaTag].startswith(u"https://")) and not m: # tag 'wikipedia' starts with 'http://' but it's not a wikipedia url return [{"class": 30310, "subclass": 0}] elif m: # tag 'wikipedia' seams to be an url return [{ "class": 30311, "subclass": 1, "text": T_("Use wikipedia={0}:*", m.group(2)), "fix": { wikipediaTag: "{0}:{1}".format(m.group(2), self.human_readable(m.group(3))) } }] if not self.lang_regexp.match(tags[wikipediaTag]): err.append({"class": 30312, "subclass": 2}) else: prefix = tags[wikipediaTag].split(':', 1)[0] tag = wikipediaTag + ':' + prefix if tag in tags: err.append({ "class": 30316, "subclass": 6, "fix": { '-': [tag] } }) if "%" in tags[wikipediaTag] or "_" in tags[wikipediaTag]: err.append({ "class": 30313, "subclass": 3, "fix": { wikipediaTag: self.human_readable(tags[wikipediaTag]) } }) interwiki = False missing_primary = [] for tag in [t for t in tags if t.startswith(wikipediaTag + ":")]: suffix = tag[len(wikipediaTag) + 1:] if ":" in suffix: suffix = suffix.split(":")[0] if self.Country and self.Country.startswith( "UA" ) and suffix == "ru": # In Ukraine wikipedia=uk:X + wikipedia:ru=Y are allowed continue if wikipediaTag in tags: if interwiki is False: try: lang, title = tags[wikipediaTag].split(':') json_str = urlread( u"https://" + lang + u".wikipedia.org/w/api.php?action=query&prop=langlinks&titles=" + title + u"&redirects=&lllimit=500&format=json", 30) interwiki = json.loads(json_str) interwiki = dict( map( lambda x: [x["lang"], x["*"]], list(interwiki["query"]["pages"].values())[0] ["langlinks"])) except: interwiki = None if interwiki and suffix in interwiki and interwiki[ suffix] == self.human_readable(tags[tag]): err.append({ "class": 30317, "subclass": stablehash64(tag), "fix": [{ '-': [tag] }, { '-': [tag], '~': { wikipediaTag: suffix + ':' + interwiki[suffix] } }] }) if suffix in tags: # wikipedia:xxxx only authorized if tag xxxx exist err.extend(self.analyse(tags, wikipediaTag + ":" + suffix)) elif self.lang_restriction_regexp.match(suffix): if not wikipediaTag in tags: m = self.wiki_regexp.match(tags[tag]) if m: value = self.human_readable(m.group(3)) elif tags[tag].startswith(suffix + ":"): value = tags[tag][len(suffix) + 1:] else: value = self.human_readable(tags[tag]) missing_primary.append({ '-': [tag], '+': { wikipediaTag: "{0}:{1}".format(suffix, value) } }) else: err.append({ "class": 30315, "subclass": stablehash64(tag), "text": T_("Invalid wikipedia suffix '{0}'", suffix) }) if missing_primary != []: if self.Language: missing_primary = sorted( missing_primary, key=lambda x: x['+'][wikipediaTag][0:2] if x['+'][wikipediaTag][0:2] != self.Language.split('_')[ 0] else '') err.append({"class": 30314, "subclass": 4, "fix": missing_primary}) return err
def analyser_osmosis_common(self): self.run(sql10.format(type="nodes")) self.run(sql20.format(type="nodes")) self.run( sql30.format(type="nodes", as_text="geom", table="nodes", geo="geom"), lambda res: { "class": 1, "subclass": stablehash64(res[1]), "data": [self.node_full, None, None, None, None, self.positionAsText], "text": { "en": "{0} -> {1}".format(res[1], res[1].replace( res[3], res[4], 1)) }, "fix": { "-": [res[1]], "+": { res[1].replace(res[3], res[4], 1): res[2] } } }) self.run(sql10.format(type="ways")) self.run(sql20.format(type="ways")) self.run( sql30.format(type="ways", as_text="way_locate(linestring)", table="ways", geo="linestring"), lambda res: { "class": 1, "subclass": stablehash64(res[1]), "data": [self.way_full, None, None, None, None, self.positionAsText], "text": { "en": "{0} -> {1}".format(res[1], res[1].replace( res[3], res[4], 1)) }, "fix": { "-": [res[1]], "+": { res[1].replace(res[3], res[4], 1): res[2] } } }) self.run(sql10.format(type="relations")) self.run(sql20.format(type="relations")) self.run( sql30.format(type="relations", as_text="relation_locate(id)", table="relations", geo="user"), lambda res: { "class": 1, "subclass": stablehash64(res[1]), "data": [ self.relation_full, None, None, None, None, self.positionAsText ], "text": { "en": "{0} -> {1}".format( res[1], res[1].replace(res[3], res[4], 1)) }, "fix": { "-": [res[1]], "+": { res[1].replace(res[3], res[4], 1): res[2] } } })
def check(self, tags): err = [] for tag in self.PHONE_TAGS: if tag not in tags: continue phone = tags[tag] if u';' in phone: continue # Ignore multiple phone numbers if self.suffix_separators is not None: phone = phone.split(self.suffix_separators, 1)[0] if self.values_separators: p = phone for sep in self.values_separators: if sep in phone: phone = phone.replace(sep, '; ') if p != phone: phone = phone.replace(' ', ' ') err.append({ "class": 30926, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) }, "fix": { tag: phone.replace(' / ', '; ').replace(' - ', '; ').replace( ',', ';') } }) continue phone_test = phone for c in '+0123456789 -./()': phone_test = phone_test.replace(c, '') if len(phone_test) > 0: err.append({ "class": 30925, "subclass": stablehash64(tag), "text": T_f( u"Not allowed char \"{0}\" in phone number tag \"{1}\"", phone_test, tag) }) continue # Before local prefix if self.InternationalPrefix: r = self.InternationalPrefix.match(phone) if r: err.append({ "class": 30924, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) }, "fix": { tag: "+" + r.group(1) } }) continue if self.InternationalAndLocalPrefix: r = self.InternationalAndLocalPrefix.match(phone) if r: err.append({ "class": 30921, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) }, "fix": { tag: "+" + self.code + " " + r.group(1) } }) continue if self.MissingInternationalPrefix: r = self.MissingInternationalPrefix.match(phone) if r: err.append({ "class": 30923, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) }, "fix": { tag: "+" + self.code + " " + r.group(1) } }) continue if self.BadShort: r = self.BadShort.match(phone) if r: err.append({ "class": 30922, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) }, "fix": { tag: r.group(1) } }) continue # Last if self.Format: r = self.Format.match(phone) if not r: err.append({ "class": 30920, "subclass": stablehash64(tag), "text": { 'en': u'='.join([tag, phone]) } }) continue return err
def way(self, data, tags, nds): # Get the relevant tags with *:conditional tags_conditional = {} for tag in tags: if tag[-12:] == ":conditional": if "source:" in tag or "note:" in tag or "fixme:" in tag: continue tags_conditional[tag] = tags[tag] if len(tags_conditional) == 0: return err = [] for tag in tags_conditional: tag_value = tags_conditional[tag] conditions = [] parentheses = 0 bad_tag = False if not "@" in tag_value: err.append({"class": 33501, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Missing `@` in \"{0}\"", tag)}) continue # Conditionals are split by semicolons, i.e. value @ condition; value @ condition # Herein, condition can also contain semicolons, e.g. no @ (Mo 06:00-24:00; Tu-Fr 00:00-24:00) # In this case, the condition is wrapped in parentheses ( ) # Additionally, there's the magic keyword 'AND' to combine conditions # Get the parts after the @ excluding parentheses and put them in the list conditions # Also validate the syntax of value @ (condition); value @ condition is obeyed tmp_str = "" condition_started = False for c in tag_value: if c == "@": if len(tmp_str.strip()) == 0: err.append({"class": 33501, "subclass": 1 + stablehash64(tag + '|' + tag_value), "text": T_("Missing value for the condition in \"{0}\"", tag)}) bad_tag = True break tmp_str = "" condition_started = True elif c == "(": parentheses += 1 if not condition_started: err.append({"class": 33501, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Missing `@` in \"{0}\"", tag)}) bad_tag = True break elif c == ")": parentheses -= 1 if parentheses == -1: err.append({"class": 33501, "subclass": 2 + stablehash64(tag + '|' + tag_value), "text": T_("Mismatch in the number of parentheses in \"{0}\"", tag)}) bad_tag = True break elif c == ";" and parentheses == 0 and condition_started: tmp_str = tmp_str.strip() if len(tmp_str) == 0: err.append({"class": 33501, "subclass": 3 + stablehash64(tag + '|' + tag_value), "text": T_("Missing condition, `@` or parentheses in \"{0}\"", tag)}) bad_tag = True break conditions.append(tmp_str) condition_started = False tmp_str = "" else: tmp_str += c if not bad_tag: if parentheses == 0: # Last condition wouldn't be added in the loop tmp_str = tmp_str.strip() if not condition_started or len(tmp_str) == 0: err.append({"class": 33501, "subclass": 3 + stablehash64(tag + '|' + tag_value), "text": T_("Missing condition, `@` or parentheses in \"{0}\"", tag)}) continue conditions.append(tmp_str) else: err.append({"class": 33501, "subclass": 2 + stablehash64(tag + '|' + tag_value), "text": T_("Mismatch in the number of parentheses in \"{0}\"", tag)}) continue # Check the position of AND is ok if not bad_tag: for condition in conditions: tmp_cond = " " + condition.replace(" ", " ") + " " tmp_ANDsplitted = tmp_cond.upper().split(" AND ") for splittedANDpart in tmp_ANDsplitted: if len(splittedANDpart.strip()) == 0: err.append({"class": 33501, "subclass": 4 + stablehash64(tag + '|' + tag_value), "text": T_("Missing condition before or after AND combinator in \"{0}\"", tag)}) bad_tag = True break if bad_tag: continue # Find outdated conditional restrictions, i.e. temporary road closures for condition in conditions: years_str = re.findall(self.ReYear, condition) if len(years_str) == 0: continue maxYear = int(max(years_str)) if maxYear < self.currentYear: err.append({"class": 33503, "subclass": 0 + stablehash64(tag + '|' + tag_value + '|' + condition), "text": T_("Condition \"{0}\" in \"{1}\" was only valid until {2}", condition, tag, maxYear)}) # No parentheses around conditions if tag_value.count("(") < len(conditions): if not (tag_value.count("(") == len(conditions) - 1 and not tag_value[-1] == ")" and re.search(self.ReSimpleCondition, conditions[-1])): # Accept no parentheses around the last one if the last condition was a simple one err.append({"class": 33502, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Add parentheses around the condition(s) in \"{0}\"", tag)}) if err != []: return err
def __init__(self, config, logger=None): Analyser_Osmosis.__init__(self, config, logger) self.classs[1] = self.def_class( item=7040, level=3, tags=['power', 'fix:imagery'], title=T_('Lone power tower or pole'), fix=T_( '''This tower should probably be connected to a power line.''') ) self.classs[2] = self.def_class( item=7040, level=2, tags=['power', 'fix:imagery'], title=T_('Unfinished power major line'), detail=T_( '''The line ends in a vacuum, and should be connected to another line or a transformer `power=substation` or a generator `power=generator`.''')) self.classs[6] = self.def_class( item=7040, level=3, tags=['power', 'fix:imagery'], title=T_('Unfinished power minor line')) self.classs[3] = self.def_class( item=7040, level=3, tags=['power', 'fix:chair'], title=T_('Connection between different voltages'), detail=T_( '''Two lines meet in one point, but inconsistent with voltages from the tag `voltage=*`.''')) self.classs_change[4] = self.def_class( item=7040, level=3, tags=['power', 'fix:imagery'], title=T_('Non power node on power way'), fix=T_( '''If this node is a tower or pole, use the tag `power=tower` or `power=pole` else remove it.''')) self.classs_change[5] = self.def_class( item=7040, level=3, tags=['power', 'fix:imagery'], title=T_('Missing power tower or pole'), detail=T_( '''According to the statistical frequency of the poles on the power line, it should miss one pole nearby.''')) self.classs[7] = self.def_class( item=7040, level=3, tags=['power', 'fix:chair'], title=T_('Unmatched voltage of line on substation')) self.callback40 = lambda res: { "class": 4, "data": [self.node_full, self.positionAsText], "fix": [{ "+": { "power": "tower" } }, { "+": { "power": "pole" } }] } self.callback50 = lambda res: { "class": 5, "subclass": stablehash64(res[1]), "data": [self.way_full, self.positionAsText] }
def way(self, data, tags, nds): # Get the relevant tags with *:conditional tags_conditional = {} for tag in tags: if tag[-12:] == ":conditional": if "source:" in tag or "note:" in tag or "fixme:" in tag: continue tags_conditional[tag] = tags[tag] if len(tags_conditional) == 0: return err = [] for tag in tags_conditional: tag_value = tags_conditional[tag] conditions = [] parentheses = 0 past_parentheses = False bad_tag = False if not "@" in tag_value: err.append({"class": 33501, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Missing `@` in \"{0}\"", tag)}) continue # Conditionals are split by semicolons, i.e. value @ condition; value @ condition # Herein, condition can also contain semicolons, e.g. no @ (Mo 06:00-24:00; Tu-Fr 00:00-24:00) # In this case, the condition is wrapped in parentheses ( ) # Additionally, there's the magic keyword 'AND' to combine conditions # Get the parts after the @ excluding parentheses and put them in the list conditions # Also validate the syntax of value @ (condition); value @ condition is obeyed tmp_str = "" condition_started = False for c in tag_value: if c == "@" and not past_parentheses: if len(tmp_str.strip()) == 0: err.append({"class": 33501, "subclass": 1 + stablehash64(tag + '|' + tag_value), "text": T_("Missing value for the condition in \"{0}\"", tag)}) bad_tag = True break tmp_str = "" condition_started = True elif c == "(": parentheses += 1 if not condition_started: err.append({"class": 33501, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Missing `@` in \"{0}\"", tag)}) bad_tag = True break if parentheses == 1 and tmp_str.lstrip() != "": err.append({"class": 33501, "subclass": 7 + stablehash64(tag + '|' + tag_value), "text": T_("Unexpected \"{0}\" before or after parentheses in \"{1}\"", tmp_str.strip(), tag)}) bad_tag = True break elif c == ")": parentheses -= 1 if parentheses == -1: err.append({"class": 33501, "subclass": 2 + stablehash64(tag + '|' + tag_value), "text": T_("Mismatch in the number of parentheses in \"{0}\"", tag)}) bad_tag = True break if parentheses == 0: past_parentheses = True elif c == ";" and parentheses == 0 and condition_started: tmp_str = tmp_str.strip() if len(tmp_str) == 0: err.append({"class": 33501, "subclass": 3 + stablehash64(tag + '|' + tag_value), "text": T_("Missing condition, `@` or parentheses in \"{0}\"", tag)}) bad_tag = True break conditions.append(tmp_str) condition_started = False past_parentheses = False tmp_str = "" else: tmp_str += c if past_parentheses and c != " ": # tolerate spaces err.append({"class": 33501, "subclass": 7 + stablehash64(tag + '|' + tag_value), "text": T_("Unexpected \"{0}\" before or after parentheses in \"{1}\"", c, tag)}) bad_tag = True break if not bad_tag: if parentheses == 0: # Last condition wouldn't be added in the loop tmp_str = tmp_str.strip() if not condition_started or len(tmp_str) == 0: err.append({"class": 33501, "subclass": 3 + stablehash64(tag + '|' + tag_value), "text": T_("Missing condition, `@` or parentheses in \"{0}\"", tag)}) continue conditions.append(tmp_str) else: err.append({"class": 33501, "subclass": 2 + stablehash64(tag + '|' + tag_value), "text": T_("Mismatch in the number of parentheses in \"{0}\"", tag)}) continue if not bad_tag: for condition in conditions: condition_ANDsplitted = list(map(str.strip, self.ReAND.split(condition))) # Check the position of AND is ok if "" in condition_ANDsplitted: err.append({"class": 33501, "subclass": 4 + stablehash64(tag + '|' + tag_value + '|' + condition), "text": T_("Missing condition before or after AND combinator in \"{0}\"", tag)}) bad_tag = True continue if len(condition_ANDsplitted) != condition.count("AND") + 1: # Likely lower/mixed case 'AND' used. Might also be a opening_hours fallback rule # For simplicity: ignore. continue for c in condition_ANDsplitted: # Validate time-based conditionals if self.isLikelyOpeningHourSyntax(c): sanitized = self.sanitize_openinghours(self.kOpeningHours452236.sub(r"\1\2", c)) if not sanitized['isValid']: if "fix" in sanitized: err.append({"class": 33504, "subclass": 6 + stablehash64(tag + '|' + tag_value + '|' + c), "text": T_("Involves \"{0}\" in \"{1}\". Consider using \"{2}\"", c, tag, sanitized['fix'])}) else: err.append({"class": 33504, "subclass": 6 + stablehash64(tag + '|' + tag_value + '|' + c), "text": T_("Involves \"{0}\" in \"{1}\"", c, tag)}) bad_tag = True break else: # Validate vehicle property comparisons if c[0] in self.comparisonOperatorChars or c[-1] in self.comparisonOperatorChars: err.append({"class": 33501, "subclass": 5 + stablehash64(tag + '|' + tag_value + '|' + c), "text": T_("Unexpected <, = or > in \"{0}\"", tag)}) bad_tag = True break if bad_tag: continue # Find outdated conditional restrictions, i.e. temporary road closures for condition in conditions: years_str = re.findall(self.ReYear, condition) if len(years_str) == 0: continue maxYear = int(max(years_str)) if maxYear < self.currentYear: err.append({"class": 33503, "subclass": 0 + stablehash64(tag + '|' + tag_value + '|' + condition), "text": T_("Condition \"{0}\" in \"{1}\" was only valid until {2}", condition, tag, maxYear)}) # No parentheses around conditions if tag_value.count("(") < len(conditions): if not (tag_value.count("(") == len(conditions) - 1 and not tag_value[-1] == ")" and re.search(self.ReSimpleCondition, conditions[-1])): # Accept no parentheses around the last one if the last condition was a simple one err.append({"class": 33502, "subclass": 0 + stablehash64(tag + '|' + tag_value), "text": T_("Add parentheses around the condition(s) in \"{0}\"", tag)}) if err != []: return err
def __init__(self, config, logger=None): Analyser_Osmosis.__init__(self, config, logger) self.FR = config.options and ( "country" in config.options and config.options["country"].startswith("FR") or "test" in config.options) self.classs_change[1] = { "item": "0", "level": 3, "tag": ["building", "geom", "fix:chair"], "desc": T_(u"Building intersection") } self.classs_change[2] = { "item": "0", "level": 2, "tag": ["building", "geom", "fix:chair"], "desc": T_(u"Large building intersection") } self.classs_change[3] = { "item": "0", "level": 3, "tag": ["building", "geom", "fix:chair"], "desc": T_(u"Building too small") } self.classs_change[4] = { "item": "0", "level": 3, "tag": ["building", "geom", "fix:chair"], "desc": T_(u"Gap between buildings") } self.classs_change[5] = { "item": "0", "level": 1, "tag": ["building", "fix:chair"], "desc": T_(u"Large building intersection cluster") } if self.FR: self.classs_change[6] = { "item": "1", "level": 3, "tag": ["building", "geom", "fix:chair"], "desc": T_(u"Building in parts") } self.callback30 = lambda res: { "class": 2 if res[3] > res[4] else 1, "data": [self.way, self.way, self.positionAsText] } self.callback40 = lambda res: { "class": 3, "data": [self.way, self.positionAsText] } self.callback50 = lambda res: { "class": 4, "data": [self.way, self.way, self.positionAsText] } self.callback60 = lambda res: { "class": 5, "subclass": stablehash64(res[0]), "data": [self.positionAsText] } if self.FR: self.callback70 = lambda res: { "class": 6, "data": [self.way, self.positionAsText] }
def analyser_osmosis_common(self): self.run( "SET search_path TO %s" % (self.config.db_schema_path or ','.join( [self.config.db_user, self.config.db_schema, 'public']), )) table = self.load.run(self, self.parser, self.mapping, self.config.db_user, self.__class__.__name__.lower()[15:], self.analyser_version()) if not table: self.logger.log(u"Empty bbox, abort") return # Extract OSM objects if self.load.srid: typeSelect = { 'N': 'geom', 'W': 'linestring', 'R': 'relation_locate(id)' } typeGeom = { 'N': 'geom', 'W': 'way_locate(linestring)', 'R': 'relation_locate(id)' } if self.mapping.osmRef == "NULL" or self.possible_merge: typeShape = { 'N': 'geom', 'W': 'ST_Envelope(linestring)', 'R': 'relation_shape(id)' } else: typeShape = {'N': 'NULL', 'W': 'NULL', 'R': 'NULL'} else: typeSelect = {'N': 'NULL', 'W': 'NULL', 'R': 'NULL'} typeGeom = {'N': 'NULL', 'W': 'NULL', 'R': 'NULL'} typeShape = {'N': 'NULL', 'W': 'NULL', 'R': 'NULL'} self.logger.log(u"Retrive OSM item") where = "((" + (") OR (".join( map(lambda x: self.where(x), self.mapping.select.tags))) + "))" self.run("CREATE TEMP TABLE osm_item AS " + ("UNION ALL".join( map( lambda type: ("""( SELECT '%(type)s'::char(1) AS type, id, trim(both from ref) AS ref, %(geom)s::geography AS geom, %(shape)s::geography AS shape, tags FROM %(from)s LEFT JOIN LATERAL regexp_split_to_table(tags->'%(ref)s', ';') a(ref) ON true WHERE""" + (""" %(geomSelect)s IS NOT NULL AND""" if self.load.srid else "") + (""" ST_SetSRID(ST_Expand(ST_GeomFromText('%(bbox)s'), %(distance)s), 4326) && %(geomSelect)s AND""" if self.load.bbox and self.load. srid else "") + """ tags != ''::hstore AND %(where)s)""") % { "type": type[0].upper(), "ref": self.mapping.osmRef, "geomSelect": typeSelect[type[0].upper()], "geom": typeGeom[type[0].upper()], "shape": typeShape[type[0].upper()], "from": type, "bbox": self.load.bbox, "distance": self.mapping.conflationDistance or 0, "where": where }, self.mapping.select.types)))) if self.mapping.osmRef != "NULL": self.run("CREATE INDEX osm_item_index_ref ON osm_item(ref)") self.run( "CREATE INDEX osm_item_index_shape ON osm_item USING GIST(shape)") joinClause = [] if self.mapping.osmRef != "NULL": joinClause.append("official.ref = osm_item.ref") elif self.load.srid: joinClause.append("ST_DWithin(official.geom, osm_item.shape, %s)" % self.mapping.conflationDistance) if self.mapping.extraJoin: joinClause.append( "official.tags->'%(tag)s' = osm_item.tags->'%(tag)s'" % {"tag": self.mapping.extraJoin}) joinClause = " AND\n".join(joinClause) + "\n" # Missing official self.run(sql10 % {"official": table, "joinClause": joinClause}) self.run(sql11) if self.missing_official: self.run( sql12, lambda res: { "class": self.missing_official["class"], "subclass": str(stablehash64("%s%s%s" % (res[0], res[1], res[3]))), "self": lambda r: [0] + r[1:], "data": [self.node_new, self.positionAsText], "text": self.mapping.generate.text( defaultdict(lambda: None, res[2]), defaultdict(lambda: None, res[3])), "fix": self.passTags(res[2]) if self.mapping.generate. missing_official_fix and res[2] != {} else None, }) if self.mapping.osmRef != "NULL": self.run(sql20 % {"official": table, "joinClause": joinClause}) self.run(sql21) if self.missing_osm: # Missing OSM self.run( sql22, lambda res: { "class": self.missing_osm["class"], "data": [self.typeMapping[res[1]], None, self.positionAsText] }) # Invalid OSM self.run( sql23 % { "official": table, "joinClause": joinClause }, lambda res: { "class": self.missing_osm["class"], "subclass": str(stablehash64(res[5])) if self.mapping.osmRef != "NULL" else None, "data": [self.typeMapping[res[1]], None, self.positionAsText] }) # Possible merge if self.possible_merge: possible_merge_joinClause = [] possible_merge_orderBy = "" if self.load.srid: possible_merge_joinClause.append( "ST_DWithin(missing_official.geom, missing_osm.shape, %s)" % self.mapping.conflationDistance) possible_merge_orderBy = ", ST_Distance(missing_official.geom, missing_osm.shape) ASC" if self.mapping.extraJoin: possible_merge_joinClause.append( "missing_official.tags->'%(tag)s' = missing_osm.tags->'%(tag)s'" % {"tag": self.mapping.extraJoin}) possible_merge_joinClause = " AND\n".join( possible_merge_joinClause) + "\n" self.run( sql30 % { "joinClause": possible_merge_joinClause, "orderBy": possible_merge_orderBy }, lambda res: { "class": self.possible_merge["class"], "subclass": str(stablehash64("%s%s" % (res[0], str(res[3])))), "data": [self.typeMapping[res[1]], None, self.positionAsText], "text": self.mapping.generate.text( defaultdict(lambda: None, res[3]), defaultdict(lambda: None, res[4])), "fix": self.mergeTags( res[5], res[3], self.mapping.osmRef, self.mapping. generate.tag_keep_multiple_values), }) self.dumpCSV( "SELECT ST_X(geom::geometry) AS lon, ST_Y(geom::geometry) AS lat, tags FROM %s" % table, "", ["lon", "lat"], lambda r, cc: list( (r['lon'], r['lat'])) + cc) self.run(sql40 % {"official": table, "joinClause": joinClause}) self.dumpCSV( sql41, ".byOSM", ["osm_id", "osm_type", "lon", "lat"], lambda r, cc: list( (r['osm_id'], r['osm_type'], r['lon'], r['lat'])) + cc) file = io.open("%s/%s.metainfo.csv" % (self.config.dst_dir, self.name), "w", encoding="utf8") file.write( u"file,origin,osm_date,official_non_merged,osm_non_merged,merged\n" ) if self.missing_official: self.giscurs.execute("SELECT COUNT(*) FROM missing_official;") official_non_merged = self.giscurs.fetchone()[0] else: official_non_merged = 0 self.giscurs.execute("SELECT COUNT(*) FROM missing_osm;") osm_non_merged = self.giscurs.fetchone()[0] self.giscurs.execute("SELECT COUNT(*) FROM match;") merged = self.giscurs.fetchone()[0] file.write(u"\"%s\",\"%s\",FIXME,%s,%s,%s\n" % (self.name, self.parser.source.fileUrl or self.url, official_non_merged, osm_non_merged, merged)) file.close() # Moved official if self.moved_official: self.run( sql50 % { "official": table, "joinClause": joinClause }, lambda res: { "class": self.moved_official["class"], "data": [self.node_full, self.positionAsText], }) # Update official if self.update_official: self.run( sql60 % { "official": table, "joinClause": joinClause }, lambda res: { "class": self.update_official["class"], "subclass": str(stablehash64("%s%s" % (res[0], str(res[5])))), "data": [self.typeMapping[res[1]], None, self.positionAsText], "text": self.mapping.generate.text( defaultdict(lambda: None, res[3]), defaultdict(lambda: None, res[5])), "fix": self.mergeTags( res[4], res[3], self.mapping.osmRef, self.mapping. generate.tag_keep_multiple_values), })
def way(self, data, tags, nds): if not "highway" in tags: return lanes = False for tag in tags: if "lanes" in tag: lanes = True break if not lanes: return tags_lanes = {} for tag in tags: if "lanes" in tag and not "source" in tag and not "FIXME" in tag: tags_lanes[tag] = tags[tag] err = [] # Check trun lanes values tl = "turn:lanes" in tags_lanes tlf = "turn:lanes:forward" in tags_lanes tlb = "turn:lanes:backward" in tags_lanes tl2 = "turn:lanes:both_ways" in tags_lanes if tl or tlf or tlb or tl2: for tl in [ "turn:lanes", "turn:lanes:forward", "turn:lanes:backward", "turn:lanes:both_ways" ]: if tl in tags_lanes: ttt = tags_lanes[tl].split("|") unknown = False i = 0 for tt in ttt: for t in set(tt.split(";")): if t not in [ "left", "slight_left", "sharp_left", "through", "right", "slight_right", "sharp_right", "reverse", "merge_to_left", "merge_to_right", "none", "" ]: unknown = True err.append({ "class": 31606, "subclass": 0 + stablehash64(tl + '|' + t + '|' + str(i)), "text": T_("Unknown turn lanes value \"{0}\"", t) }) if (t == "merge_to_left" and i == 0) or (t == "merge_to_right" and i == len(ttt) - 1): err.append({ "class": 31600, "subclass": 1 + stablehash64(tl + '|' + t + '|' + str(i)) }) i += 1 if not unknown: # merge_to_left is a on the right and vice versa t = tags_lanes[tl] \ .replace("left", "l").replace("slight_left", "l").replace("sharp_left", "l") \ .replace("through", " ") \ .replace("right", "r").replace("slight_right", "r").replace("sharp_right", "r") \ .replace("reverse", "U") \ .replace("merge_to_left", "r").replace("merge_to_right", "l") \ .replace("none", " ").replace(";", "").split("|") t = ''.join( map( lambda e: " " if len(e) == 0 or e[0] != e[-1] else e[0], map(sorted, t))) t = t.replace('U', '') # Ignore reverse last_left = self.rindex_(t, "l") first_space = self.index_(t, " ") last_space = self.rindex_(t, " ") first_right = self.index_(t, "r") # Check right is on the right and left is on the left... if not ((last_left is None or first_space is None or last_left < first_space) and (first_space is None or last_space is None or first_space <= last_space) and (last_space is None or first_right is None or last_space < first_right) and (last_left is None or first_right is None or last_left < first_right)): err.append({ "class": 31607, "subclass": 1 + stablehash64(tl) }) # Check access lanes values # Count for non fullwidth lanes non_fullwidth_lanes_number = {} for direction in ['', ':forward', ':backward', ':both_ways']: o = tags_lanes.get('bicycle:lanes' + direction) if o: non_fullwidth_lanes_number[direction] = ilen( filter(lambda i: i == 'designated', o.split('|'))) for access in [ 'hgv', 'bus', 'access', 'bicycle', 'psv', 'taxi', 'vehicle', 'motor_vehicle', 'hov', 'motorcycle', 'goods' ]: base = access + ':lanes' for tag in tags_lanes: if tag.startswith(base): try: int(tags_lanes[tag]) err.append({ "class": 31609, "subclass": 1 + stablehash64(tag), "text": { 'en': '{0}={1}'.format(tag, tags_lanes[tag]) } }) except ValueError: # Ok, should not be an integer pass stars = [] for tag in tags_lanes: if ":lanes" in tag: star = tag.split(':')[0] if star not in ('source', 'proposed', 'construction', 'note'): stars.append(star) stars = list(set(stars)) for star in stars: l = star + '' in tags_lanes lf = star + ':forward' in tags_lanes lb = star + ':backward' in tags_lanes l2 = star + ':both_ways' in tags_lanes if l and (lf or lb or l2): err.append({ "class": 31603, "subclass": 0 + stablehash64(star), "text": { "en": star + ":*" } }) if err != []: return err number = {'lanes': {}} for tag in tags_lanes: if tag == 'lanes' or tag.startswith('lanes:'): try: n = int(tags_lanes[tag]) parts = tag.split(':') direction = '' if len(parts) == 1: number['lanes'][''] = n elif len(parts) == 2 and parts[1] in [ 'forward', 'backward', 'both_ways' ]: number['lanes'][':' + parts[1]] = n except ValueError: err.append({ "class": 31601, "subclass": 0 + stablehash64(tag), "text": T_("lanes={0} is not an integer", tags_lanes[tag]) }) for star in stars: number[star] = {} for direction in ['', ':forward', ':backward', ':both_ways']: o = tags_lanes.get(star + ':lanes' + direction) if o: number[star][direction] = len(o.split('|')) n_lanes = {} for direction in ['', ':forward', ':backward', ':both_ways']: tag = None for star in sorted(number.keys()): non_fullwidth_lanes_number_star = (( non_fullwidth_lanes_number.get(direction) or 0) if star != 'lanes' else 0) non_fullwidth_lanes_number_tag = ( (non_fullwidth_lanes_number.get(direction) or 0) if tag != 'lanes:lanes' + direction else 0) if n_lanes.get(direction) is not None and number[star].get(direction) is not None and \ number[star][direction] - non_fullwidth_lanes_number_star != \ n_lanes[direction] - non_fullwidth_lanes_number_tag: err.append({ "class": 31608, "subclass": 0 + stablehash64(direction + '|' + star), "text": { "en": "(lanes({0})={1}) - (non fullwidth={2}) != (lanes({3})={4}) - (non fullwidth={5})" .format(star + ":*" + direction, number[star][direction], non_fullwidth_lanes_number_star, tag, n_lanes[direction], non_fullwidth_lanes_number_tag) } }) elif n_lanes.get(direction) is None and number[star].get( direction) is not None: # Fist loop, pick the star as tag and the number of lanes to compare to the others n_lanes[direction] = number[star][direction] tag = star + ":lanes" + direction if err != []: return err if tags["highway"] == 'motorway': oneway = "oneway" not in tags or tags["oneway"] not in [ "no", "false" ] else: oneway = "oneway" in tags and tags["oneway"] not in ["no", "false"] if oneway: for tag in tags: if tag.startswith('oneway:') and tags[tag] in ["no", "false"]: # Oneway for mainstream traffic, but not for an other one, so we are not really on a oneway oneway = False if tags.get('junction') == 'roundabout': oneway = True nl = n_lanes.get('') nlf = n_lanes.get(':forward') nlb = n_lanes.get(':backward') nl2 = n_lanes.get(':both_ways') nfw_nl = non_fullwidth_lanes_number.get('') or 0 nfw_nlf = non_fullwidth_lanes_number.get(':forward') or 0 nfw_nlb = non_fullwidth_lanes_number.get(':backward') or 0 nfw_nl2 = non_fullwidth_lanes_number.get(':both_ways') or 0 if oneway: if nl is not None and nlf is not None and nl != nlf - nfw_nlf: err.append({ "class": 31604, "subclass": 0, "text": T_( "on oneway, (lanes={0}) != (lanes:forward={1}) - (non fullwidth forward={2})", nl, nlf, nfw_nlf) }) elif nlb is not None or nl2 is not None: err.append({"class": 31605, "subclass": 0}) else: if nl is not None and nlf is not None and nlb is not None and nl != nlf + nlb + ( nl2 or 0) - nfw_nl - nfw_nlf - nfw_nlb - nfw_nl2: err.append({ "class": 31604, "subclass": 0, "text": T_( "on two way, (lanes={0}) != (lanes:forward={1}) + (lanes:backward={2}) + (lanes:both_ways={3}) - (non fullwidth={4}) - (non fullwidth forward={5}) - (non fullwidth backward={6}) - (non fullwidth both_ways={7})", nl, nlf, nlb, nl2, nfw_nl, nfw_nlf, nfw_nlb, nfw_nl2) }) elif nl is not None and nlf is not None and nl <= nlf - nfw_nlf: err.append({ "class": 31604, "subclass": 0, "text": T_( "on two way, (lanes={0}) <= (lanes:forward={1}) - (non fullwidth forward={2})", nl, nlf, nfw_nlf) }) elif nl is not None and nlb is not None and nl <= nlb - nfw_nlb: err.append({ "class": 31604, "subclass": 0, "text": T_( "on two way, (lanes={0}) <= (lanes:backward={1}) - (non fullwidth backward={2})", nl, nlb, nfw_nlb) }) elif nl is not None and nl2 is not None and nl < nl2 - nfw_nl2: err.append({ "class": 31604, "subclass": 0, "text": T_( "on two way, (lanes={0}) < (lanes:both_ways={1}) - (non fullwidth both_ways={2})", nl, nl2, nfw_nl2) }) if err != []: return err
def __init__(self, config, logger=None): Analyser_Osmosis.__init__(self, config, logger) self.FR = config.options and ( "country" in config.options and config.options["country"].startswith("FR") or "test" in config.options) fix = T_( '''Fix geometry so that buildings don't overlap, but share nodes if physically joined. If geometry is correct and there's some vertical difference then make use of the `layer` tag to indicate this.''' ) self.classs_change[1] = self.def_class( item=0, level=3, tags=['building', 'geom', 'fix:chair'], title=T_('Building intersection'), fix=fix) self.classs_change[2] = self.def_class( item=0, level=2, tags=['building', 'geom', 'fix:chair'], title=T_('Large building intersection'), fix=self.merge_doc( fix, T_('''Large intersections may also be a duplicated mapping - in which case delete the less accurate elements.''' ))) self.classs_change[3] = self.def_class( item=0, level=3, tags=['building', 'geom', 'fix:chair'], title=T_('Building too small'), detail=T_( 'The area of this feature is too small to possibly be a building.' ), fix=T_('''- Correct the gometry if inaccurately mapped. - Correct the tagging if this isn't a building. - Delete the feature if it's invalid.''')) self.classs_change[4] = self.def_class( item=0, level=3, tags=['building', 'geom', 'fix:chair'], title=T_('Gap between buildings'), detail=T_( '''It looks like these buildings should be physically joined, but they don't share nodes to indicate this.''' ), fix=T_( 'Connect the buildings by joining nodes where appropriate.')) self.classs_change[5] = self.def_class( item=0, level=1, tags=['building', 'fix:chair'], title=T_('Large building intersection cluster'), fix=self.merge_doc( fix, T_('''Large intersections may also be a duplicated mapping - in which case delete the less accurate elements.''' ))) if self.FR: self.classs_change[6] = self.def_class( item=1, level=3, tags=['building', 'geom', 'fix:chair'], title=T_("Building in parts"), fix=T_('Merge the building parts together as appropriate.')) self.callback30 = lambda res: { "class": 2 if res[3] > res[4] else 1, "data": [self.way, self.way, self.positionAsText] } self.callback40 = lambda res: { "class": 3, "data": [self.way, self.positionAsText] } self.callback50 = lambda res: { "class": 4, "data": [self.way, self.way, self.positionAsText] } self.callback60 = lambda res: { "class": 5, "subclass": stablehash64(res[0]), "data": [self.positionAsText] } if self.FR: self.callback70 = lambda res: { "class": 6, "data": [self.way, self.positionAsText] }
def node(self, data, tags): err = [] for k in tags: if k in self._update_ks: err.append({ "class": self._update_ks[k][1], "subclass": stablehash64(u"%s|%s" % (self._update_ks, k)), "text": T_f(u"tag key: {0} => {1} (rule ks)", k, self._update_ks[k][0]) }) if k in self._update_ks_vs and tags[k] in self._update_ks_vs[k]: err.append({ "class": self._update_ks_vs[k][tags[k]][1], "subclass": stablehash64(u"%s|%s" % (self._update_ks, k)), "text": T_f(u"tag value: {0}={1} => {2} (rule ks_vs)", k, tags[k], self._update_ks_vs[k][tags[k]][0]) }) if k in self._update_ks_vr: for v in self._update_ks_vr[k]: if v.match(tags[k]): err.append({ "class": self._update_ks_vr[k][v][1], "subclass": stablehash64(u"%s|%s" % (v, k)), "text": T_f(u"tag value: {0}={1} => {2} (rule ks_vr)", k, tags[k], self._update_ks_vr[k][v][0]) }) for kk in tags: for k in self._update_kr: if k.match(kk): err.append({ "class": self._update_kr[k][1], "subclass": stablehash64(u"%s|%s" % (kk, k)), "text": T_f(u"tag key: {0} => {1} (rule kr)", kk, self._update_kr[k][0]) }) for k in self._update_kr_vs: if k.match(kk): if tags[kk] in self._update_kr_vs[k]: err.append({ "class": self._update_kr_vs[k][tags[kk]][1], "subclass": stablehash64(u"%s|%s" % (kk, k)), "text": T_f(u"tag value: {0}={1} => {2} (rule kr_vs)", kk, tags[kk], self._update_kr_vs[k][tags[kk]][0]) }) for k in self._update_kr_vr: if k.match(kk): for v in self._update_kr_vr[k]: if v.match(tags[kk]): err.append({ "class": self._update_kr_vr[k][v][1], "subclass": stablehash64(u"%s|%s" % (kk, k)), "text": T_f(u"tag value: {0}={1} => {2} (rule kr_vr)", kk, tags[kk], self._update_kr_vr[k][v][0]) }) return err