diff --git a/roentgen/constructor.py b/roentgen/constructor.py index a17c65d..1094679 100644 --- a/roentgen/constructor.py +++ b/roentgen/constructor.py @@ -8,6 +8,7 @@ from typing import Any, Dict, List, Optional, Set from roentgen import process, ui from roentgen.flinger import Geo, GeoFlinger from roentgen.osm_reader import OSMMember, OSMRelation, OSMWay +from roentgen.scheme import Scheme class Node: @@ -16,7 +17,7 @@ class Node: """ def __init__( self, shapes, tags: Dict[str, str], x: float, y: float, color: str, - path: Optional[str], processed, priority=0): + path: Optional[str], processed, priority: int = 0): self.shapes = shapes self.tags = tags self.x = x @@ -165,13 +166,13 @@ class Constructor: """ Röntgen node and way constructor. """ - def __init__(self, check_level, mode, seed, map_, flinger, scheme): + def __init__(self, check_level, mode, seed, map_, flinger, scheme: Scheme): self.check_level = check_level self.mode = mode self.seed = seed self.map_ = map_ self.flinger = flinger - self.scheme = scheme + self.scheme: Scheme = scheme self.nodes: List[Node] = [] self.ways: List[Way] = [] @@ -180,7 +181,7 @@ class Constructor: """ Get color from the scheme. """ - return self.scheme["colors"][name] + return self.scheme.get_color(name) def construct_ways(self): """ @@ -323,8 +324,7 @@ class Constructor: elif tags["landuse"] == "garages": style = f"fill:#{self.color('parking_color')};stroke:none;" layer += 21 - shapes, fill, processed = \ - process.get_icon(tags, self.scheme, "444444") + shapes, fill, processed = self.scheme.get_icon(tags) if way: self.nodes.append(Node( shapes, tags, c[0], c[1], fill, path, processed)) @@ -346,8 +346,7 @@ class Constructor: f"fill:#{self.color('building_color')};" \ f"stroke:#{self.color('building_border_color')};" \ f"opacity:1.0;" - shapes, fill, processed = \ - process.get_icon(tags, self.scheme, "444444") + shapes, fill, processed = self.scheme.get_icon(tags) if "height" in tags: try: layer += float(tags["height"]) @@ -368,8 +367,7 @@ class Constructor: style = \ f"fill:#{self.color('parking_color')};" \ f"stroke:none;opacity:0.5;" - shapes, fill, processed = \ - process.get_icon(tags, self.scheme, "444444") + shapes, fill, processed = self.scheme.get_icon(tags) if way: self.nodes.append(Node( shapes, tags, c[0], c[1], fill, path, processed, 1)) @@ -613,14 +611,12 @@ class Constructor: start_time = datetime.now() - node_number = 0 - # processed_tags = 0 - # skipped_tags = 0 + node_number: int = 0 s = sorted( self.map_.node_map.keys(), key=lambda x: -self.map_.node_map[x].lat) - for node_id in s: + for node_id in s: # type: int node_number += 1 ui.write_line(node_number, len(self.map_.node_map)) node = self.map_.node_map[node_id] @@ -632,7 +628,7 @@ class Constructor: if not self.check_level(tags): continue - shapes, fill, processed = process.get_icon(tags, self.scheme) + shapes, fill, processed = self.scheme.get_icon(tags) if self.mode in ["time", "user-coloring"]: if not tags: @@ -643,27 +639,6 @@ class Constructor: if self.mode == "time": fill = get_time_color(node.timestamp) - # for k in tags: - # if k in processed or self.no_draw(k): - # processed_tags += 1 - # else: - # skipped_tags += 1 - - # for k in []: # tags: - # if to_write(k): - # draw_text(k + ": " + tags[k], x, y + 18 + text_y, - # "444444") - # text_y += 10 - - # if show_missing_tags: - # for k in tags: - # v = tags[k] - # if not no_draw(k) and not k in processed: - # if ("node " + k + ": " + v) in missing_tags: - # missing_tags["node " + k + ": " + v] += 1 - # else: - # missing_tags["node " + k + ": " + v] = 1 - if shapes == [] and tags != {}: shapes = [["no"]] @@ -671,8 +646,5 @@ class Constructor: shapes, tags, x, y, fill, None, processed)) ui.write_line(-1, len(self.map_.node_map)) + print("Nodes painted in " + str(datetime.now() - start_time) + ".") - # print("Tags processed: " + str(processed_tags) + ", tags skipped: " + - # str(skipped_tags) + " (" + - # str(processed_tags / float( - # processed_tags + skipped_tags) * 100) + " %).") diff --git a/roentgen/mapper.py b/roentgen/mapper.py index 9309219..21a2758 100644 --- a/roentgen/mapper.py +++ b/roentgen/mapper.py @@ -22,6 +22,7 @@ from roentgen.flinger import GeoFlinger, Geo from roentgen.grid import draw_grid from roentgen.osm_getter import get_osm from roentgen.osm_reader import Map, OSMReader +from roentgen.scheme import Scheme ICONS_FILE_NAME: str = "icons/icons.svg" TAGS_FILE_NAME: str = "data/tags.yml" @@ -33,7 +34,7 @@ class Painter: def __init__( self, show_missing_tags, overlap, draw_nodes, mode, draw_captions, - map_, flinger, svg: svgwrite.Drawing, icons, scheme): + map_, flinger, svg: svgwrite.Drawing, icons, scheme: Scheme): self.show_missing_tags = show_missing_tags self.overlap = overlap @@ -45,27 +46,7 @@ class Painter: self.flinger = flinger self.svg: svgwrite.Drawing = svg self.icons = icons - self.scheme = scheme - - def no_draw(self, key): - if key in self.scheme["tags_to_write"] or \ - key in self.scheme["tags_to_skip"]: - return True - for prefix in \ - self.scheme["prefix_to_write"] + self.scheme["prefix_to_skip"]: - if key[:len(prefix) + 1] == prefix + ":": - return True - return False - - def to_write(self, key): - if key in self.scheme["tags_to_skip"]: - return False - if key in self.scheme["tags_to_write"]: - return True - for prefix in self.scheme["prefix_to_write"]: - if key[:len(prefix) + 1] == prefix + ":": - return True - return False + self.scheme: Scheme = scheme def draw_shapes(self, shapes, points, x, y, fill, tags, processed): @@ -379,7 +360,7 @@ class Painter: y = int(float(y)) opacity = 0.5 stroke_width = 2.2 - outline_fill = self.scheme["colors"]["outline_color"] + outline_fill = self.scheme.get_color("outline_color") if mode not in ["user-coloring", "time"]: r = int(fill[0:2], 16) g = int(fill[2:4], 16) @@ -475,11 +456,7 @@ def main(): missing_tags = {} points = [] - scheme = yaml.load(open(TAGS_FILE_NAME), Loader=yaml.FullLoader) - scheme["cache"] = {} - w3c_colors = yaml.load(open(COLORS_FILE_NAME), Loader=yaml.FullLoader) - for color_name in w3c_colors: - scheme["colors"][color_name] = w3c_colors[color_name] + scheme = Scheme(TAGS_FILE_NAME, COLORS_FILE_NAME) flinger = GeoFlinger(min1, max1, [0, 0], [w, h]) diff --git a/roentgen/process.py b/roentgen/process.py deleted file mode 100644 index 04489b6..0000000 --- a/roentgen/process.py +++ /dev/null @@ -1,86 +0,0 @@ -import copy -import re - -from typing import Any, Dict - - -STANDARD_COLOR: str = "444444" - - -def get_color(color: str, scheme: Dict[str, Any]): - if color in scheme["colors"]: # type: str - return scheme["colors"][color] - else: - m = re.match("^#?(?P[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f])" + - "(?P[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f])?$", color) - if m: - if "color2" in m.groups(): - return m.group("color1") + m.group("color2") - else: - return "".join(map(lambda x: x + x, m.group("color1"))) - return STANDARD_COLOR - - -def get_icon( - tags: Dict[str, Any], scheme: Dict[str, Any], - fill: str = STANDARD_COLOR): - - tags_hash = ",".join(tags.keys()) + ":" + \ - ",".join(map(lambda x: str(x), tags.values())) - if tags_hash in scheme["cache"]: - return scheme["cache"][tags_hash] - main_icon = None - extra_icons = [] - processed = set() - for matcher in scheme["tags"]: - matched = True - for key in matcher["tags"]: - if key not in tags: - matched = False - break - if matcher["tags"][key] != "*" and \ - matcher["tags"][key] != tags[key]: - matched = False - break - if "no_tags" in matcher: - for no_tag in matcher["no_tags"]: - if no_tag in tags.keys(): - matched = False - break - if matched: - if "draw" in matcher and not matcher["draw"]: - processed |= set(matcher["tags"].keys()) - if "icon" in matcher: - main_icon = copy.deepcopy(matcher["icon"]) - processed |= set(matcher["tags"].keys()) - if "over_icon" in matcher: - if main_icon: # TODO: check main icon in under icons - main_icon += matcher["over_icon"] - for key in matcher["tags"].keys(): - processed.add(key) - if "add_icon" in matcher: - extra_icons += matcher["add_icon"] - for key in matcher["tags"].keys(): - processed.add(key) - if "color" in matcher: - fill = scheme["colors"][matcher["color"]] - for key in matcher["tags"].keys(): - processed.add(key) - - for color_name in ["color", "colour", "building:colour"]: - if color_name in tags: - fill = get_color(tags[color_name], scheme) - if fill != STANDARD_COLOR: - processed.add(color_name) - else: - print(f"No color {tags[color_name]}.") - - if main_icon: - returned = [main_icon] + extra_icons, fill, processed - else: - returned = extra_icons, fill, processed - - scheme["cache"][tags_hash] = returned - - return returned - diff --git a/roentgen/scheme.py b/roentgen/scheme.py new file mode 100644 index 0000000..9b212d6 --- /dev/null +++ b/roentgen/scheme.py @@ -0,0 +1,142 @@ +import copy +import yaml + +from typing import Any, Dict, List + +DEFAULT_COLOR: str = "444444" + + +class Scheme: + """ + Map style. + + Specifies map colors and rules to draw icons for OpenStreetMap tags. + """ + def __init__(self, file_name: str, color_file_name: str): + """ + :param file_name: scheme file name with tags, colors, and tag key + specification + :param color_file_name: additional color scheme + """ + content: Dict[str, Any] = \ + yaml.load(open(file_name).read(), Loader=yaml.FullLoader) + + self.tags: List[Dict[str, Any]] = content["tags"] + + self.colors: Dict[str, str] = content["colors"] + w3c_colors: Dict[str, str] = \ + yaml.load(open(color_file_name), Loader=yaml.FullLoader) + + self.colors.update(w3c_colors) + + self.tags_to_write: List[str] = content["tags_to_write"] + self.prefix_to_write: List[str] = content["prefix_to_write"] + self.tags_to_skip: List[str] = content["tags_to_skip"] + self.prefix_to_skip: List[str] = content["prefix_to_skip"] + + self.cache = {} + + def get_color(self, color: str) -> str: + """ + Return color if the color is in scheme, otherwise return default color. + + :return: 6-digit color specification without "#" + """ + if color in self.colors: + return self.colors[color] + if color.startswith("#"): + return color[1:] + + print(f"No color {color}.") + + return DEFAULT_COLOR + + def is_no_drawable(self, key: str) -> bool: + """ + Return true if key is specified as no drawable (should not be + represented on the map as icon set or as text) by the scheme. + + :param key: OpenStreetMap tag key + """ + if key in self.tags_to_write or key in self.tags_to_skip: + return True + for prefix in self.prefix_to_write + self.prefix_to_skip: # type: str + if key[:len(prefix) + 1] == prefix + ":": + return True + return False + + def is_writable(self, key: str) -> bool: + """ + Return true if key is specified as writable (should be represented on + the map as text) by the scheme. + + :param key: OpenStreetMap tag key + """ + if key in self.tags_to_skip: + return False + if key in self.tags_to_write: + return True + for prefix in self.prefix_to_write: + if key[:len(prefix) + 1] == prefix + ":": + return True + return False + + def get_icon(self, tags: Dict[str, Any]): + + tags_hash = ",".join(tags.keys()) + ":" + \ + ",".join(map(lambda x: str(x), tags.values())) + if tags_hash in self.cache: + return self.cache[tags_hash] + main_icon = None + extra_icons = [] + processed = set() + fill = DEFAULT_COLOR + for matcher in self.tags: + matched = True + for key in matcher["tags"]: + if key not in tags: + matched = False + break + if matcher["tags"][key] != "*" and \ + matcher["tags"][key] != tags[key]: + matched = False + break + if "no_tags" in matcher: + for no_tag in matcher["no_tags"]: + if no_tag in tags.keys(): + matched = False + break + if matched: + if "draw" in matcher and not matcher["draw"]: + processed |= set(matcher["tags"].keys()) + if "icon" in matcher: + main_icon = copy.deepcopy(matcher["icon"]) + processed |= set(matcher["tags"].keys()) + if "over_icon" in matcher: + if main_icon: # TODO: check main icon in under icons + main_icon += matcher["over_icon"] + for key in matcher["tags"].keys(): + processed.add(key) + if "add_icon" in matcher: + extra_icons += matcher["add_icon"] + for key in matcher["tags"].keys(): + processed.add(key) + if "color" in matcher: + fill = self.colors[matcher["color"]] + for key in matcher["tags"].keys(): + processed.add(key) + + for color_name in ["color", "colour", "building:colour"]: + if color_name in tags: + fill = self.get_color(tags[color_name]) + processed.add(color_name) + + if main_icon: + returned = [main_icon] + extra_icons, fill, processed + else: + returned = extra_icons, fill, processed + + self.cache[tags_hash] = returned + + return returned +