diff --git a/data/colors.yml b/data/colors.yml deleted file mode 100644 index c14b45c..0000000 --- a/data/colors.yml +++ /dev/null @@ -1,147 +0,0 @@ -aliceblue: 'F0F8FF' -antiquewhite: 'FAEBD7' -aqua: '00FFFF' -aquamarine: '7FFFD4' -azure: 'F0FFFF' -beige: 'F5F5DC' -bisque: 'FFE4C4' -black: '000000' -blanchedalmond: 'FFEBCD' -blue: '0000FF' -blueviolet: '8A2BE2' -brown: 'A52A2A' -burlywood: 'DEB887' -cadetblue: '5F9EA0' -chartreuse: '7FFF00' -chocolate: 'D2691E' -coral: 'FF7F50' -cornflowerblue: '6495ED' -cornsilk: 'FFF8DC' -crimson: 'DC143C' -cyan: '00FFFF' -darkblue: '00008B' -darkcyan: '008B8B' -darkgoldenrod: 'B8860B' -darkgray: 'A9A9A9' -darkgreen: '006400' -darkgrey: 'A9A9A9' -darkkhaki: 'BDB76B' -darkmagenta: '8B008B' -darkolivegreen: '556B2F' -darkorange: 'FF8C00' -darkorchid: '9932CC' -darkred: '8B0000' -darksalmon: 'E9967A' -darkseagreen: '8FBC8F' -darkslateblue: '483D8B' -darkslategray: '2F4F4F' -darkslategrey: '2F4F4F' -darkturquoise: '00CED1' -darkviolet: '9400D3' -deeppink: 'FF1493' -deepskyblue: '00BFFF' -dimgray: '696969' -dimgrey: '696969' -dodgerblue: '1E90FF' -firebrick: 'B22222' -floralwhite: 'FFFAF0' -forestgreen: '228B22' -fuchsia: 'FF00FF' -gainsboro: 'DCDCDC' -ghostwhite: 'F8F8FF' -gold: 'FFD700' -goldenrod: 'DAA520' -gray: '808080' -green: '008000' -greenyellow: 'ADFF2F' -grey: '808080' -honeydew: 'F0FFF0' -hotpink: 'FF69B4' -indianred: 'CD5C5C' -indigo: '4B0082' -ivory: 'FFFFF0' -khaki: 'F0E68C' -lavender: 'E6E6FA' -lavenderblush: 'FFF0F5' -lawngreen: '7CFC00' -lemonchiffon: 'FFFACD' -lightblue: 'ADD8E6' -lightcoral: 'F08080' -lightcyan: 'E0FFFF' -lightgoldenrodyellow: 'FAFAD2' -lightgray: 'D3D3D3' -lightgreen: '90EE90' -lightgrey: 'D3D3D3' -lightpink: 'FFB6C1' -lightsalmon: 'FFA07A' -lightseagreen: '20B2AA' -lightskyblue: '87CEFA' -lightslategray: '778899' -lightslategrey: '778899' -lightsteelblue: 'B0C4DE' -lightyellow: 'FFFFE0' -lime: '00FF00' -limegreen: '32CD32' -linen: 'FAF0E6' -magenta: 'FF00FF' -maroon: '800000' -mediumaquamarine: '66CDAA' -mediumblue: '0000CD' -mediumorchid: 'BA55D3' -mediumpurple: '9370DB' -mediumseagreen: '3CB371' -mediumslateblue: '7B68EE' -mediumspringgreen: '00FA9A' -mediumturquoise: '48D1CC' -mediumvioletred: 'C71585' -midnightblue: '191970' -mintcream: 'F5FFFA' -mistyrose: 'FFE4E1' -moccasin: 'FFE4B5' -navajowhite: 'FFDEAD' -navy: '000080' -oldlace: 'FDF5E6' -olive: '808000' -olivedrab: '6B8E23' -orange: 'FFA500' -orangered: 'FF4500' -orchid: 'DA70D6' -palegoldenrod: 'EEE8AA' -palegreen: '98FB98' -paleturquoise: 'AFEEEE' -palevioletred: 'DB7093' -papayawhip: 'FFEFD5' -peachpuff: 'FFDAB9' -peru: 'CD853F' -pink: 'FFC0CB' -plum: 'DDA0DD' -powderblue: 'B0E0E6' -purple: '800080' -red: 'FF0000' -rosybrown: 'BC8F8F' -royalblue: '4169E1' -saddlebrown: '8B4513' -salmon: 'FA8072' -sandybrown: 'F4A460' -seagreen: '2E8B57' -seashell: 'FFF5EE' -sienna: 'A0522D' -silver: 'C0C0C0' -skyblue: '87CEEB' -slateblue: '6A5ACD' -slategray: '708090' -slategrey: '708090' -snow: 'FFFAFA' -springgreen: '00FF7F' -steelblue: '4682B4' -tan: 'D2B48C' -teal: '008080' -thistle: 'D8BFD8' -tomato: 'FF6347' -turquoise: '40E0D0' -violet: 'EE82EE' -wheat: 'F5DEB3' -white: 'FFFFFF' -whitesmoke: 'F5F5F5' -yellow: 'FFFF00' -yellowgreen: '9ACD32' diff --git a/data/tags.yml b/data/tags.yml index 6b47520..5aec5e2 100644 --- a/data/tags.yml +++ b/data/tags.yml @@ -69,7 +69,7 @@ colors: "rose": "FF007F" # Wikipedia "slate_blue": "6A5ACD" # W3C slateblue -tags: +nodes: # No draw @@ -120,7 +120,6 @@ tags: - tags: {amenity: waste_basket} icon: [waste_basket] - # Emergency - tags: {emergency: defibrillator} @@ -148,6 +147,10 @@ tags: icon: [cross] - tags: {man_made: flagpole} icon: [flagpole] + - tags: {man_made: manhole} + icon: [manhole] + - tags: {manhole: drain} + icon: [manhole_drain] - tags: {man_made: pole} icon: [pole] - tags: {man_made: pole, highway: street_lamp} @@ -173,10 +176,12 @@ tags: - tags: {power: tower} icon: [power_tower] - - tags: {tourism: "*"} - icon: [historic] + # Information + - tags: {information: "*"} icon: [information] + - tags: {tourism: "*"} + icon: [historic] - tags: {tourism: information} icon: [information] - tags: {information: guidepost} @@ -186,6 +191,8 @@ tags: - tags: {information: board} icon: [information_board] + # Vending + - tags: {vending: admission_tickets} icon: [vending_tickets] - tags: {vending: candles} @@ -596,11 +603,6 @@ tags: - tags: {traffic_calming: cushion} icon: [traffic_cushion] - - tags: {man_made: manhole} - icon: [manhole] - - tags: {manhole: drain} - icon: [manhole_drain] - # Historic - tags: {historic: "*"} diff --git a/requirements.txt b/requirements.txt index 271ebcd..bda9202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ numpy>=1.18.1 -portolan +portolan~=1.0.1 pyyaml>=4.2b1 -svgwrite +svgwrite~=1.4 urllib3>=1.25.6 +colour~=0.1.5 \ No newline at end of file diff --git a/roentgen/constructor.py b/roentgen/constructor.py index f5a7e63..98a365d 100644 --- a/roentgen/constructor.py +++ b/roentgen/constructor.py @@ -5,6 +5,7 @@ Author: Sergey Vartanov (me@enzet.ru). """ import numpy as np +from colour import Color from datetime import datetime from hashlib import sha256 from typing import Any, Dict, List, Optional, Set @@ -103,6 +104,13 @@ class Way: return path +class TextStruct: + def __init__( + self, text: str, fill: Color = Color("#444444"), size: float = 10): + self.text = text + self.fill = fill + self.size = size + def line_center(nodes: List[OSMNode], flinger: Flinger) -> np.array: """ @@ -121,12 +129,12 @@ def line_center(nodes: List[OSMNode], flinger: Flinger) -> np.array: return flinger.fling(center_coordinates), center_coordinates -def get_user_color(text: str, seed: str): +def get_user_color(text: str, seed: str) -> Color: """ Generate random color based on text. """ if text == "": - return "#000000" + return Color("black") rgb = sha256((seed + text).encode("utf-8")).hexdigest()[-6:] r = int(rgb[0:2], 16) g = int(rgb[2:4], 16) @@ -137,15 +145,15 @@ def get_user_color(text: str, seed: str): g = g * (1 - cc) + c * cc b = b * (1 - cc) + c * cc h = hex(int(r))[2:] + hex(int(g))[2:] + hex(int(b))[2:] - return "#" + "0" * (6 - len(h)) + h + return Color("#" + "0" * (6 - len(h)) + h) -def get_time_color(time: Optional[datetime]): +def get_time_color(time: Optional[datetime]) -> Color: """ Generate color based on time. """ if time is None: - return "000000" + return Color("black") delta = (datetime.now() - time).total_seconds() time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:] i_time_color = hex(min(0xFF, int(delta / 500000.)))[2:] @@ -153,7 +161,7 @@ def get_time_color(time: Optional[datetime]): time_color = "0" + time_color if len(i_time_color) == 1: i_time_color = "0" + i_time_color - return "#" + time_color + "AA" + i_time_color + return Color("#" + time_color + "AA" + i_time_color) def glue(ways: List[OSMWay]) -> List[List[OSMNode]]: @@ -214,12 +222,12 @@ class Constructor: Röntgen node and way constructor. """ def __init__( - self, check_level, mode, seed, map_, flinger: Flinger, + self, check_level, mode: str, seed: str, map_, flinger: Flinger, scheme: Scheme): self.check_level = check_level - self.mode = mode - self.seed = seed + self.mode: str = mode + self.seed: str = seed self.map_ = map_ self.flinger: Flinger = flinger self.scheme: Scheme = scheme @@ -282,7 +290,7 @@ class Constructor: user_color = get_user_color(way.user, self.seed) self.ways.append( Way("way", inners, outers, - {"fill": "none", "stroke": user_color, + {"fill": "none", "stroke": user_color.hex, "stroke-width": 1})) return @@ -292,14 +300,14 @@ class Constructor: time_color = get_time_color(way.timestamp) self.ways.append( Way("way", inners, outers, - {"fill": "none", "stroke": time_color, + {"fill": "none", "stroke": time_color.hex, "stroke-width": 1})) return if not tags: return - appended = False + appended: bool = False kind: str = "way" levels = None @@ -323,9 +331,9 @@ class Constructor: break if "no_tags" in element: for config_tag_key in element["no_tags"]: # type: str - if config_tag_key in tags and \ - tags[config_tag_key] == \ - element["no_tags"][config_tag_key]: + if (config_tag_key in tags and + tags[config_tag_key] == + element["no_tags"][config_tag_key]): matched = False break if matched: @@ -360,7 +368,8 @@ class Constructor: if not appended: if DEBUG: style: Dict[str, Any] = { - "fill": "none", "stroke": "#FF0000", "stroke-width": 1} + "fill": "none", "stroke": Color("red").hex, + "stroke-width": 1} self.ways.append(Way( kind, inners, outers, style, layer, levels)) if center_point is not None and (way.is_cycle() or diff --git a/roentgen/direction.py b/roentgen/direction.py index 3e74462..002d6d5 100644 --- a/roentgen/direction.py +++ b/roentgen/direction.py @@ -58,6 +58,7 @@ class Sector: """ Sector described by two vectors. """ + def __init__(self, text: str, angle: Optional[float] = None): """ :param text: sector text representation. E.g. "70-210", "N-NW" @@ -107,11 +108,12 @@ class DirectionSet: """ Describes direction, set of directions. """ + def __init__(self, text: str): """ :param text: direction tag value """ - self.sectors = list(map(Sector, text.split(";"))) + self.sectors: Iterator[Optional[Sector]] = map(Sector, text.split(";")) def __str__(self): return ", ".join(map(str, self.sectors)) diff --git a/roentgen/grid.py b/roentgen/grid.py index d1901fc..b35cd25 100644 --- a/roentgen/grid.py +++ b/roentgen/grid.py @@ -31,7 +31,7 @@ def draw_grid(step: float = 24, columns: int = 16): to_draw = [] - for element in scheme["tags"]: + for element in scheme["nodes"]: if "icon" in element: if set(element["icon"]) not in to_draw: to_draw.append(set(element["icon"])) diff --git a/roentgen/mapper.py b/roentgen/mapper.py index 871a71b..22bf956 100644 --- a/roentgen/mapper.py +++ b/roentgen/mapper.py @@ -8,15 +8,16 @@ import os import svgwrite import sys +from colour import Color from svgwrite.container import Group from svgwrite.path import Path from svgwrite.shapes import Rect from svgwrite.text import Text -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from roentgen import ui from roentgen.address import get_address -from roentgen.constructor import Constructor, Node, Way +from roentgen.constructor import Constructor, Node, Way, TextStruct from roentgen.flinger import Flinger from roentgen.grid import draw_grid from roentgen.extract_icon import Icon, IconExtractor @@ -24,21 +25,23 @@ from roentgen.osm_getter import get_osm from roentgen.osm_reader import Map, OSMReader from roentgen.scheme import Scheme from roentgen.direction import DirectionSet, Sector -from roentgen.util import MinMax +from roentgen.util import MinMax, is_bright ICONS_FILE_NAME: str = "icons/icons.svg" TAGS_FILE_NAME: str = "data/tags.yml" -COLORS_FILE_NAME: str = "data/colors.yml" MISSING_TAGS_FILE_NAME: str = "missing_tags.yml" AUTHOR_MODE = "user-coloring" CREATION_TIME_MODE = "time" +DEFAULT_FONT = "Roboto" + class Painter: """ Map drawing. """ + def __init__( self, show_missing_tags: bool, overlap: int, draw_nodes: bool, mode: str, draw_captions: str, map_: Map, flinger: Flinger, @@ -97,17 +100,15 @@ class Painter: write_tags = self.construct_text(node.tags, node.icon_set.processed) - for text_struct in write_tags: - fill = text_struct["fill"] if "fill" in text_struct else "#444444" - size = text_struct["size"] if "size" in text_struct else 10 - text_y += size + 1 - text = text_struct["text"] + for text_struct in write_tags: # type: TextStruct + text_y += text_struct.size + 1 + text = text_struct.text text = text.replace(""", '"') text = text.replace("&", '&') text = text[:26] + ("..." if len(text) > 26 else "") self.draw_text( text, (node.point[0], node.point[1] + text_y + 8), - fill, size=size) + text_struct.fill, size=text_struct.size) if self.show_missing_tags: for tag in node.tags: # type: str @@ -116,12 +117,13 @@ class Painter: text = f"{tag}: {node.tags[tag]}" self.draw_text( text, (node.point[0], node.point[1] + text_y + 18), - "#734A08") + Color("#734A08")) text_y += 10 def draw_text( - self, text: str, point, fill, size=10, out_fill="#FFFFFF", - out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0): + self, text: str, point, fill: Color, size: float = 10, + out_fill=Color("white"), out_opacity=1.0, + out_fill_2: Optional[Color] = None, out_opacity_2=1.0): """ Drawing text. @@ -134,24 +136,25 @@ class Painter: if out_fill_2: self.svg.add(Text( text, point, font_size=size, text_anchor="middle", - font_family="Roboto", fill=out_fill_2, + font_family=DEFAULT_FONT, fill=out_fill_2.hex, stroke_linejoin="round", stroke_width=5, - stroke=out_fill_2, opacity=out_opacity_2)) + stroke=out_fill_2.hex, opacity=out_opacity_2)) if out_fill: self.svg.add(Text( text, point, font_size=size, text_anchor="middle", - font_family="Roboto", fill=out_fill, + font_family=DEFAULT_FONT, fill=out_fill.hex, stroke_linejoin="round", stroke_width=3, - stroke=out_fill, opacity=out_opacity)) + stroke=out_fill.hex, opacity=out_opacity)) self.svg.add(Text( text, point, font_size=size, text_anchor="middle", - font_family="Roboto", fill=fill)) + font_family=DEFAULT_FONT, fill=fill.hex)) def construct_text(self, tags, processed): """ Construct labels for not processed tags. """ - texts = [] + texts: List[TextStruct] = [] + name = None alt_name = None if "name" in tags: @@ -184,20 +187,20 @@ class Painter: address = get_address(tags, self.draw_captions) if name: - texts.append({"text": name, "fill": "#000000"}) + texts.append(TextStruct(name, Color("black"))) if alt_name: - texts.append({"text": "(" + alt_name + ")"}) + texts.append(TextStruct("(" + alt_name + ")")) if address: - texts.append({"text": ", ".join(address)}) + texts.append(TextStruct(", ".join(address))) if self.draw_captions == "main": return texts if "route_ref" in tags: - texts.append({"text": tags["route_ref"].replace(";", " ")}) + texts.append(TextStruct(tags["route_ref"].replace(";", " "))) tags.pop("route_ref", None) if "cladr:code" in tags: - texts.append({"text": tags["cladr:code"], "size": 7}) + texts.append(TextStruct(tags["cladr:code"], size=7)) tags.pop("cladr:code", None) if "website" in tags: link = tags["website"] @@ -210,18 +213,18 @@ class Painter: if link[-1] == "/": link = link[:-1] link = link[:25] + ("..." if len(tags["website"]) > 25 else "") - texts.append({"text": link, "fill": "#000088"}) + texts.append(TextStruct(link, Color("#000088"))) tags.pop("website", None) for k in ["phone"]: if k in tags: - texts.append({"text": tags[k], "fill": "#444444"}) + texts.append(TextStruct(tags[k], Color("#444444"))) tags.pop(k) for tag in tags: if self.scheme.is_writable(tag) and not (tag in processed): - texts.append({"text": tags[tag]}) + texts.append(TextStruct(tags[tag])) return texts - def draw_building_walls(self, stage, color, ways): + def draw_building_walls(self, stage, color: Color, ways): """ Draw area between way and way shifted by the vector. """ @@ -251,7 +254,7 @@ class Painter: d=("M", np.add(flung_1, shift_1), "L", np.add(flung_2, shift_1), np.add(flung_2, shift_2), np.add(flung_1, shift_2), "Z"), - fill=color, stroke=color, stroke_width=1)) + fill=color.hex, stroke=color.hex, stroke_width=1)) def draw(self, nodes: List[Node], ways: List[Way], points): """ @@ -289,9 +292,9 @@ class Painter: # Building walls - self.draw_building_walls(1, "#AAAAAA", ways) - self.draw_building_walls(2, "#C3C3C3", ways) - self.draw_building_walls(3, "#DDDDDD", ways) + self.draw_building_walls(1, Color("#AAAAAA"), ways) + self.draw_building_walls(2, Color("#C3C3C3"), ways) + self.draw_building_walls(3, Color("#DDDDDD"), ways) # Building roof @@ -321,7 +324,7 @@ class Painter: # Trees for node in nodes: - if not(node.get_tag("natural") == "tree" and + if not (node.get_tag("natural") == "tree" and ("diameter_crown" in node.tags or "circumference" in node.tags)): continue @@ -359,7 +362,7 @@ class Painter: direction = node.get_tag("direction") direction_radius: float = \ 25 * self.flinger.get_scale(node.coordinates) - direction_color: str = "#FF0000" + direction_color: str = Color("red") else: direction = node.get_tag("direction") direction_radius: float = \ @@ -384,12 +387,12 @@ class Painter: gradientUnits="userSpaceOnUse")) if is_revert_gradient: gradient \ - .add_stop_color(0, direction_color, opacity=0) \ - .add_stop_color(1, direction_color, opacity=0.7) + .add_stop_color(0, direction_color.hex, opacity=0) \ + .add_stop_color(1, direction_color.hex, opacity=0.7) else: gradient \ - .add_stop_color(0, direction_color, opacity=0.4) \ - .add_stop_color(1, direction_color, opacity=0) + .add_stop_color(0, direction_color.hex, opacity=0.4) \ + .add_stop_color(1, direction_color.hex, opacity=0) self.svg.add(self.svg.path( d=["M", point] + path + ["L", point, "Z"], fill=gradient.get_paint_server())) @@ -413,7 +416,8 @@ class Painter: if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]: self.draw_texts(node) - def draw_point_shape(self, shape_ids: List[str], point, fill, tags=None): + def draw_point_shape( + self, shape_ids: List[str], point, fill: Color, tags=None): """ Draw one icon. """ @@ -426,37 +430,34 @@ class Painter: self.draw_point(icon, point, fill, tags=tags) def draw_point( - self, icon: Icon, point: (float, float), fill: str, + self, icon: Icon, point: (float, float), fill: Color, tags: Dict[str, str] = None) -> None: point = np.array(list(map(lambda x: int(x), point))) title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags)) path: svgwrite.path.Path = icon.get_path(self.svg, point) - path.update({"fill": fill}) + path.update({"fill": fill.hex}) path.set_desc(title=title) self.svg.add(path) - def draw_point_outline(self, icon: Icon, point, fill, mode="default"): + def draw_point_outline( + self, icon: Icon, point, fill: Color, mode="default"): point = np.array(list(map(lambda x: int(x), point))) - opacity = 0.5 - stroke_width = 2.2 - outline_fill = self.scheme.get_color("outline_color") - if mode not in [AUTHOR_MODE, CREATION_TIME_MODE]: - r = int(fill[1:3], 16) - g = int(fill[3:5], 16) - b = int(fill[5:7], 16) - Y = 0.2126 * r + 0.7152 * g + 0.0722 * b - if Y > 200: - outline_fill = "#000000" - opacity = 0.7 + opacity: float = 0.5 + stroke_width: float = 2.2 + outline_fill: Color = self.scheme.get_color("outline_color") + + if mode not in [AUTHOR_MODE, CREATION_TIME_MODE] and is_bright(fill): + outline_fill = Color("black") + opacity = 0.7 path = icon.get_path(self.svg, point) path.update({ - "fill": outline_fill, "opacity": opacity, - "stroke": outline_fill, "stroke-width": stroke_width, + "fill": outline_fill.hex, "opacity": opacity, + "stroke": outline_fill.hex, "stroke-width": stroke_width, "stroke-linejoin": "round"}) self.svg.add(path) @@ -510,9 +511,9 @@ def main(argv): if not options: sys.exit(1) - background_color = "#EEEEEE" + background_color: Color = Color("#EEEEEE") if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]: - background_color = "#111111" + background_color: Color = Color("#111111") if options.input_file_name: input_file_name = options.input_file_name @@ -546,7 +547,7 @@ def main(argv): missing_tags = {} points = [] - scheme: Scheme = Scheme(TAGS_FILE_NAME, COLORS_FILE_NAME) + scheme: Scheme = Scheme(TAGS_FILE_NAME) min1: np.array = np.array((boundary_box[1], boundary_box[0])) max1: np.array = np.array((boundary_box[3], boundary_box[2])) @@ -626,7 +627,7 @@ def draw_index(flinger, map_, max1, min1, svg): if (0 <= i < lat_number) and (0 <= j < lon_number): matrix[i][j] += 1 if "tags" in node: - matrix[i][j] += len(node.tags) + matrix[i][j] += len(node.nodes) for way_id in map_.way_map: # type: int way = map_.way_map[way_id] if "tags" in way: @@ -635,7 +636,7 @@ def draw_index(flinger, map_, max1, min1, svg): i = int((node[0] - min1[0]) / lat_step) j = int((node[1] - min1[1]) / lon_step) if (0 <= i < lat_number) and (0 <= j < lon_number): - matrix[i][j] += len(way.tags) / float( + matrix[i][j] += len(way.nodes) / float( len(way.nodes)) for i in range(lat_number): for j in range(lon_number): diff --git a/roentgen/scheme.py b/roentgen/scheme.py index 82a1a65..040ed2d 100644 --- a/roentgen/scheme.py +++ b/roentgen/scheme.py @@ -6,11 +6,12 @@ Author: Sergey Vartanov (me@enzet.ru). import copy import yaml +from colour import Color from typing import Any, Dict, List, Optional, Set from roentgen.extract_icon import DEFAULT_SHAPE_ID -DEFAULT_COLOR: str = "#444444" +DEFAULT_COLOR: Color = Color("#444444") class IconSet: @@ -18,7 +19,7 @@ class IconSet: Node representation: icons and color. """ def __init__( - self, icons: List[List[str]], color: str, processed: Set[str], + self, icons: List[List[str]], color: Color, processed: Set[str], is_default: bool): """ :param icons: list of lists of shape identifiers @@ -27,7 +28,7 @@ class IconSet: tag keys should be displayed by text or ignored) """ self.icons: List[List[str]] = icons - self.color: str = color + self.color: Color = color self.processed: Set[str] = processed self.is_default = is_default @@ -38,23 +39,18 @@ class Scheme: Specifies map colors and rules to draw icons for OpenStreetMap tags. """ - def __init__(self, file_name: str, color_file_name: str): + def __init__(self, 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.nodes: List[Dict[str, Any]] = content["nodes"] self.ways: List[Dict[str, Any]] = content["ways"] 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"] @@ -64,18 +60,20 @@ class Scheme: # Storage for created icon sets. self.cache: Dict[str, IconSet] = {} - def get_color(self, color: str) -> str: + def get_color(self, color: str) -> Color: """ Return color if the color is in scheme, otherwise return default color. :return: 6-digit color specification with "#" """ if color in self.colors: - return "#" + self.colors[color] + return Color("#" + self.colors[color]) if color.lower() in self.colors: - return "#" + self.colors[color.lower()] - if color.startswith("#"): - return color + return Color("#" + self.colors[color.lower()]) + try: + return Color(color) + except ValueError: + pass return DEFAULT_COLOR @@ -124,12 +122,12 @@ class Scheme: main_icon: Optional[List[str]] = None extra_icons: List[List[str]] = [] - processed = set() - fill = DEFAULT_COLOR + processed: Set[str] = set() + fill: Color = DEFAULT_COLOR - for matcher in self.tags: - matched = True - for key in matcher["tags"]: + for matcher in self.nodes: # type: Dict[str, Any] + matched: bool = True + for key in matcher["tags"]: # type: str if key not in tags: matched = False break @@ -173,10 +171,12 @@ class Scheme: else: result_set: List[List[str]] = extra_icons + keys_left = list(filter( + lambda x: x not in processed and + not self.is_no_drawable(x), tags.keys())) + is_default: bool = False - if not result_set and \ - list(filter(lambda x: x not in processed and - not self.is_no_drawable(x), tags.keys())): + if not result_set and keys_left: result_set = [[DEFAULT_SHAPE_ID]] is_default = True diff --git a/roentgen/util.py b/roentgen/util.py index 073af9c..c6429b3 100644 --- a/roentgen/util.py +++ b/roentgen/util.py @@ -1,3 +1,6 @@ +from colour import Color + + class MinMax: """ Minimum and maximum. @@ -21,3 +24,12 @@ class MinMax: def center(self): return (self.min_ + self.max_) / 2 + +def is_bright(color: Color) -> bool: + """ + Is color bright enough to have black outline instead of white. + """ + return ( + 0.2126 * color.red * 256 + + 0.7152 * color.green * 256 + + 0.0722 * color.blue * 256 > 200)