Use colour; add text structure.

This commit is contained in:
Sergey Vartanov 2020-09-19 14:43:47 +03:00
parent 54b88f05d2
commit 963fb12496
9 changed files with 139 additions and 259 deletions

View file

@ -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'

View file

@ -69,7 +69,7 @@ colors:
"rose": "FF007F" # Wikipedia "rose": "FF007F" # Wikipedia
"slate_blue": "6A5ACD" # W3C slateblue "slate_blue": "6A5ACD" # W3C slateblue
tags: nodes:
# No draw # No draw
@ -120,7 +120,6 @@ tags:
- tags: {amenity: waste_basket} - tags: {amenity: waste_basket}
icon: [waste_basket] icon: [waste_basket]
# Emergency # Emergency
- tags: {emergency: defibrillator} - tags: {emergency: defibrillator}
@ -148,6 +147,10 @@ tags:
icon: [cross] icon: [cross]
- tags: {man_made: flagpole} - tags: {man_made: flagpole}
icon: [flagpole] icon: [flagpole]
- tags: {man_made: manhole}
icon: [manhole]
- tags: {manhole: drain}
icon: [manhole_drain]
- tags: {man_made: pole} - tags: {man_made: pole}
icon: [pole] icon: [pole]
- tags: {man_made: pole, highway: street_lamp} - tags: {man_made: pole, highway: street_lamp}
@ -173,10 +176,12 @@ tags:
- tags: {power: tower} - tags: {power: tower}
icon: [power_tower] icon: [power_tower]
- tags: {tourism: "*"} # Information
icon: [historic]
- tags: {information: "*"} - tags: {information: "*"}
icon: [information] icon: [information]
- tags: {tourism: "*"}
icon: [historic]
- tags: {tourism: information} - tags: {tourism: information}
icon: [information] icon: [information]
- tags: {information: guidepost} - tags: {information: guidepost}
@ -186,6 +191,8 @@ tags:
- tags: {information: board} - tags: {information: board}
icon: [information_board] icon: [information_board]
# Vending
- tags: {vending: admission_tickets} - tags: {vending: admission_tickets}
icon: [vending_tickets] icon: [vending_tickets]
- tags: {vending: candles} - tags: {vending: candles}
@ -596,11 +603,6 @@ tags:
- tags: {traffic_calming: cushion} - tags: {traffic_calming: cushion}
icon: [traffic_cushion] icon: [traffic_cushion]
- tags: {man_made: manhole}
icon: [manhole]
- tags: {manhole: drain}
icon: [manhole_drain]
# Historic # Historic
- tags: {historic: "*"} - tags: {historic: "*"}

View file

@ -1,5 +1,6 @@
numpy>=1.18.1 numpy>=1.18.1
portolan portolan~=1.0.1
pyyaml>=4.2b1 pyyaml>=4.2b1
svgwrite svgwrite~=1.4
urllib3>=1.25.6 urllib3>=1.25.6
colour~=0.1.5

View file

@ -5,6 +5,7 @@ Author: Sergey Vartanov (me@enzet.ru).
""" """
import numpy as np import numpy as np
from colour import Color
from datetime import datetime from datetime import datetime
from hashlib import sha256 from hashlib import sha256
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
@ -103,6 +104,13 @@ class Way:
return path 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: 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 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. Generate random color based on text.
""" """
if text == "": if text == "":
return "#000000" return Color("black")
rgb = sha256((seed + text).encode("utf-8")).hexdigest()[-6:] rgb = sha256((seed + text).encode("utf-8")).hexdigest()[-6:]
r = int(rgb[0:2], 16) r = int(rgb[0:2], 16)
g = int(rgb[2:4], 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 g = g * (1 - cc) + c * cc
b = b * (1 - cc) + c * cc b = b * (1 - cc) + c * cc
h = hex(int(r))[2:] + hex(int(g))[2:] + hex(int(b))[2:] 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. Generate color based on time.
""" """
if time is None: if time is None:
return "000000" return Color("black")
delta = (datetime.now() - time).total_seconds() delta = (datetime.now() - time).total_seconds()
time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:] time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:]
i_time_color = hex(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 time_color = "0" + time_color
if len(i_time_color) == 1: if len(i_time_color) == 1:
i_time_color = "0" + i_time_color 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]]: def glue(ways: List[OSMWay]) -> List[List[OSMNode]]:
@ -214,12 +222,12 @@ class Constructor:
Röntgen node and way constructor. Röntgen node and way constructor.
""" """
def __init__( def __init__(
self, check_level, mode, seed, map_, flinger: Flinger, self, check_level, mode: str, seed: str, map_, flinger: Flinger,
scheme: Scheme): scheme: Scheme):
self.check_level = check_level self.check_level = check_level
self.mode = mode self.mode: str = mode
self.seed = seed self.seed: str = seed
self.map_ = map_ self.map_ = map_
self.flinger: Flinger = flinger self.flinger: Flinger = flinger
self.scheme: Scheme = scheme self.scheme: Scheme = scheme
@ -282,7 +290,7 @@ class Constructor:
user_color = get_user_color(way.user, self.seed) user_color = get_user_color(way.user, self.seed)
self.ways.append( self.ways.append(
Way("way", inners, outers, Way("way", inners, outers,
{"fill": "none", "stroke": user_color, {"fill": "none", "stroke": user_color.hex,
"stroke-width": 1})) "stroke-width": 1}))
return return
@ -292,14 +300,14 @@ class Constructor:
time_color = get_time_color(way.timestamp) time_color = get_time_color(way.timestamp)
self.ways.append( self.ways.append(
Way("way", inners, outers, Way("way", inners, outers,
{"fill": "none", "stroke": time_color, {"fill": "none", "stroke": time_color.hex,
"stroke-width": 1})) "stroke-width": 1}))
return return
if not tags: if not tags:
return return
appended = False appended: bool = False
kind: str = "way" kind: str = "way"
levels = None levels = None
@ -323,9 +331,9 @@ class Constructor:
break break
if "no_tags" in element: if "no_tags" in element:
for config_tag_key in element["no_tags"]: # type: str for config_tag_key in element["no_tags"]: # type: str
if config_tag_key in tags and \ if (config_tag_key in tags and
tags[config_tag_key] == \ tags[config_tag_key] ==
element["no_tags"][config_tag_key]: element["no_tags"][config_tag_key]):
matched = False matched = False
break break
if matched: if matched:
@ -360,7 +368,8 @@ class Constructor:
if not appended: if not appended:
if DEBUG: if DEBUG:
style: Dict[str, Any] = { style: Dict[str, Any] = {
"fill": "none", "stroke": "#FF0000", "stroke-width": 1} "fill": "none", "stroke": Color("red").hex,
"stroke-width": 1}
self.ways.append(Way( self.ways.append(Way(
kind, inners, outers, style, layer, levels)) kind, inners, outers, style, layer, levels))
if center_point is not None and (way.is_cycle() or if center_point is not None and (way.is_cycle() or

View file

@ -58,6 +58,7 @@ class Sector:
""" """
Sector described by two vectors. Sector described by two vectors.
""" """
def __init__(self, text: str, angle: Optional[float] = None): def __init__(self, text: str, angle: Optional[float] = None):
""" """
:param text: sector text representation. E.g. "70-210", "N-NW" :param text: sector text representation. E.g. "70-210", "N-NW"
@ -107,11 +108,12 @@ class DirectionSet:
""" """
Describes direction, set of directions. Describes direction, set of directions.
""" """
def __init__(self, text: str): def __init__(self, text: str):
""" """
:param text: direction tag value :param text: direction tag value
""" """
self.sectors = list(map(Sector, text.split(";"))) self.sectors: Iterator[Optional[Sector]] = map(Sector, text.split(";"))
def __str__(self): def __str__(self):
return ", ".join(map(str, self.sectors)) return ", ".join(map(str, self.sectors))

View file

@ -31,7 +31,7 @@ def draw_grid(step: float = 24, columns: int = 16):
to_draw = [] to_draw = []
for element in scheme["tags"]: for element in scheme["nodes"]:
if "icon" in element: if "icon" in element:
if set(element["icon"]) not in to_draw: if set(element["icon"]) not in to_draw:
to_draw.append(set(element["icon"])) to_draw.append(set(element["icon"]))

View file

@ -8,15 +8,16 @@ import os
import svgwrite import svgwrite
import sys import sys
from colour import Color
from svgwrite.container import Group from svgwrite.container import Group
from svgwrite.path import Path from svgwrite.path import Path
from svgwrite.shapes import Rect from svgwrite.shapes import Rect
from svgwrite.text import Text from svgwrite.text import Text
from typing import Any, Dict, List from typing import Any, Dict, List, Optional
from roentgen import ui from roentgen import ui
from roentgen.address import get_address 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.flinger import Flinger
from roentgen.grid import draw_grid from roentgen.grid import draw_grid
from roentgen.extract_icon import Icon, IconExtractor 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.osm_reader import Map, OSMReader
from roentgen.scheme import Scheme from roentgen.scheme import Scheme
from roentgen.direction import DirectionSet, Sector 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" ICONS_FILE_NAME: str = "icons/icons.svg"
TAGS_FILE_NAME: str = "data/tags.yml" TAGS_FILE_NAME: str = "data/tags.yml"
COLORS_FILE_NAME: str = "data/colors.yml"
MISSING_TAGS_FILE_NAME: str = "missing_tags.yml" MISSING_TAGS_FILE_NAME: str = "missing_tags.yml"
AUTHOR_MODE = "user-coloring" AUTHOR_MODE = "user-coloring"
CREATION_TIME_MODE = "time" CREATION_TIME_MODE = "time"
DEFAULT_FONT = "Roboto"
class Painter: class Painter:
""" """
Map drawing. Map drawing.
""" """
def __init__( def __init__(
self, show_missing_tags: bool, overlap: int, draw_nodes: bool, self, show_missing_tags: bool, overlap: int, draw_nodes: bool,
mode: str, draw_captions: str, map_: Map, flinger: Flinger, 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) write_tags = self.construct_text(node.tags, node.icon_set.processed)
for text_struct in write_tags: for text_struct in write_tags: # type: TextStruct
fill = text_struct["fill"] if "fill" in text_struct else "#444444" text_y += text_struct.size + 1
size = text_struct["size"] if "size" in text_struct else 10 text = text_struct.text
text_y += size + 1
text = text_struct["text"]
text = text.replace(""", '"') text = text.replace(""", '"')
text = text.replace("&", '&') text = text.replace("&", '&')
text = text[:26] + ("..." if len(text) > 26 else "") text = text[:26] + ("..." if len(text) > 26 else "")
self.draw_text( self.draw_text(
text, (node.point[0], node.point[1] + text_y + 8), 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: if self.show_missing_tags:
for tag in node.tags: # type: str for tag in node.tags: # type: str
@ -116,12 +117,13 @@ class Painter:
text = f"{tag}: {node.tags[tag]}" text = f"{tag}: {node.tags[tag]}"
self.draw_text( self.draw_text(
text, (node.point[0], node.point[1] + text_y + 18), text, (node.point[0], node.point[1] + text_y + 18),
"#734A08") Color("#734A08"))
text_y += 10 text_y += 10
def draw_text( def draw_text(
self, text: str, point, fill, size=10, out_fill="#FFFFFF", self, text: str, point, fill: Color, size: float = 10,
out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0): out_fill=Color("white"), out_opacity=1.0,
out_fill_2: Optional[Color] = None, out_opacity_2=1.0):
""" """
Drawing text. Drawing text.
@ -134,24 +136,25 @@ class Painter:
if out_fill_2: if out_fill_2:
self.svg.add(Text( self.svg.add(Text(
text, point, font_size=size, text_anchor="middle", 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_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: if out_fill:
self.svg.add(Text( self.svg.add(Text(
text, point, font_size=size, text_anchor="middle", 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_linejoin="round", stroke_width=3,
stroke=out_fill, opacity=out_opacity)) stroke=out_fill.hex, opacity=out_opacity))
self.svg.add(Text( self.svg.add(Text(
text, point, font_size=size, text_anchor="middle", 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): def construct_text(self, tags, processed):
""" """
Construct labels for not processed tags. Construct labels for not processed tags.
""" """
texts = [] texts: List[TextStruct] = []
name = None name = None
alt_name = None alt_name = None
if "name" in tags: if "name" in tags:
@ -184,20 +187,20 @@ class Painter:
address = get_address(tags, self.draw_captions) address = get_address(tags, self.draw_captions)
if name: if name:
texts.append({"text": name, "fill": "#000000"}) texts.append(TextStruct(name, Color("black")))
if alt_name: if alt_name:
texts.append({"text": "(" + alt_name + ")"}) texts.append(TextStruct("(" + alt_name + ")"))
if address: if address:
texts.append({"text": ", ".join(address)}) texts.append(TextStruct(", ".join(address)))
if self.draw_captions == "main": if self.draw_captions == "main":
return texts return texts
if "route_ref" in tags: if "route_ref" in tags:
texts.append({"text": tags["route_ref"].replace(";", " ")}) texts.append(TextStruct(tags["route_ref"].replace(";", " ")))
tags.pop("route_ref", None) tags.pop("route_ref", None)
if "cladr:code" in tags: 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) tags.pop("cladr:code", None)
if "website" in tags: if "website" in tags:
link = tags["website"] link = tags["website"]
@ -210,18 +213,18 @@ class Painter:
if link[-1] == "/": if link[-1] == "/":
link = link[:-1] link = link[:-1]
link = link[:25] + ("..." if len(tags["website"]) > 25 else "") 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) tags.pop("website", None)
for k in ["phone"]: for k in ["phone"]:
if k in tags: if k in tags:
texts.append({"text": tags[k], "fill": "#444444"}) texts.append(TextStruct(tags[k], Color("#444444")))
tags.pop(k) tags.pop(k)
for tag in tags: for tag in tags:
if self.scheme.is_writable(tag) and not (tag in processed): if self.scheme.is_writable(tag) and not (tag in processed):
texts.append({"text": tags[tag]}) texts.append(TextStruct(tags[tag]))
return texts 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. 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", d=("M", np.add(flung_1, shift_1), "L",
np.add(flung_2, shift_1), np.add(flung_2, shift_2), np.add(flung_2, shift_1), np.add(flung_2, shift_2),
np.add(flung_1, shift_2), "Z"), 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): def draw(self, nodes: List[Node], ways: List[Way], points):
""" """
@ -289,9 +292,9 @@ class Painter:
# Building walls # Building walls
self.draw_building_walls(1, "#AAAAAA", ways) self.draw_building_walls(1, Color("#AAAAAA"), ways)
self.draw_building_walls(2, "#C3C3C3", ways) self.draw_building_walls(2, Color("#C3C3C3"), ways)
self.draw_building_walls(3, "#DDDDDD", ways) self.draw_building_walls(3, Color("#DDDDDD"), ways)
# Building roof # Building roof
@ -321,7 +324,7 @@ class Painter:
# Trees # Trees
for node in nodes: 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 ("diameter_crown" in node.tags or
"circumference" in node.tags)): "circumference" in node.tags)):
continue continue
@ -359,7 +362,7 @@ class Painter:
direction = node.get_tag("direction") direction = node.get_tag("direction")
direction_radius: float = \ direction_radius: float = \
25 * self.flinger.get_scale(node.coordinates) 25 * self.flinger.get_scale(node.coordinates)
direction_color: str = "#FF0000" direction_color: str = Color("red")
else: else:
direction = node.get_tag("direction") direction = node.get_tag("direction")
direction_radius: float = \ direction_radius: float = \
@ -384,12 +387,12 @@ class Painter:
gradientUnits="userSpaceOnUse")) gradientUnits="userSpaceOnUse"))
if is_revert_gradient: if is_revert_gradient:
gradient \ gradient \
.add_stop_color(0, direction_color, opacity=0) \ .add_stop_color(0, direction_color.hex, opacity=0) \
.add_stop_color(1, direction_color, opacity=0.7) .add_stop_color(1, direction_color.hex, opacity=0.7)
else: else:
gradient \ gradient \
.add_stop_color(0, direction_color, opacity=0.4) \ .add_stop_color(0, direction_color.hex, opacity=0.4) \
.add_stop_color(1, direction_color, opacity=0) .add_stop_color(1, direction_color.hex, opacity=0)
self.svg.add(self.svg.path( self.svg.add(self.svg.path(
d=["M", point] + path + ["L", point, "Z"], d=["M", point] + path + ["L", point, "Z"],
fill=gradient.get_paint_server())) fill=gradient.get_paint_server()))
@ -413,7 +416,8 @@ class Painter:
if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]: if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]:
self.draw_texts(node) 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. Draw one icon.
""" """
@ -426,37 +430,34 @@ class Painter:
self.draw_point(icon, point, fill, tags=tags) self.draw_point(icon, point, fill, tags=tags)
def draw_point( 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: tags: Dict[str, str] = None) -> None:
point = np.array(list(map(lambda x: int(x), point))) point = np.array(list(map(lambda x: int(x), point)))
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags)) title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
path: svgwrite.path.Path = icon.get_path(self.svg, point) path: svgwrite.path.Path = icon.get_path(self.svg, point)
path.update({"fill": fill}) path.update({"fill": fill.hex})
path.set_desc(title=title) path.set_desc(title=title)
self.svg.add(path) 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))) point = np.array(list(map(lambda x: int(x), point)))
opacity = 0.5 opacity: float = 0.5
stroke_width = 2.2 stroke_width: float = 2.2
outline_fill = self.scheme.get_color("outline_color") outline_fill: Color = self.scheme.get_color("outline_color")
if mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
r = int(fill[1:3], 16) if mode not in [AUTHOR_MODE, CREATION_TIME_MODE] and is_bright(fill):
g = int(fill[3:5], 16) outline_fill = Color("black")
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 = 0.7
path = icon.get_path(self.svg, point) path = icon.get_path(self.svg, point)
path.update({ path.update({
"fill": outline_fill, "opacity": opacity, "fill": outline_fill.hex, "opacity": opacity,
"stroke": outline_fill, "stroke-width": stroke_width, "stroke": outline_fill.hex, "stroke-width": stroke_width,
"stroke-linejoin": "round"}) "stroke-linejoin": "round"})
self.svg.add(path) self.svg.add(path)
@ -510,9 +511,9 @@ def main(argv):
if not options: if not options:
sys.exit(1) sys.exit(1)
background_color = "#EEEEEE" background_color: Color = Color("#EEEEEE")
if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]: if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]:
background_color = "#111111" background_color: Color = Color("#111111")
if options.input_file_name: if options.input_file_name:
input_file_name = options.input_file_name input_file_name = options.input_file_name
@ -546,7 +547,7 @@ def main(argv):
missing_tags = {} missing_tags = {}
points = [] 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])) min1: np.array = np.array((boundary_box[1], boundary_box[0]))
max1: np.array = np.array((boundary_box[3], boundary_box[2])) 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): if (0 <= i < lat_number) and (0 <= j < lon_number):
matrix[i][j] += 1 matrix[i][j] += 1
if "tags" in node: 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 for way_id in map_.way_map: # type: int
way = map_.way_map[way_id] way = map_.way_map[way_id]
if "tags" in way: if "tags" in way:
@ -635,7 +636,7 @@ def draw_index(flinger, map_, max1, min1, svg):
i = int((node[0] - min1[0]) / lat_step) i = int((node[0] - min1[0]) / lat_step)
j = int((node[1] - min1[1]) / lon_step) j = int((node[1] - min1[1]) / lon_step)
if (0 <= i < lat_number) and (0 <= j < lon_number): 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)) len(way.nodes))
for i in range(lat_number): for i in range(lat_number):
for j in range(lon_number): for j in range(lon_number):

View file

@ -6,11 +6,12 @@ Author: Sergey Vartanov (me@enzet.ru).
import copy import copy
import yaml import yaml
from colour import Color
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
from roentgen.extract_icon import DEFAULT_SHAPE_ID from roentgen.extract_icon import DEFAULT_SHAPE_ID
DEFAULT_COLOR: str = "#444444" DEFAULT_COLOR: Color = Color("#444444")
class IconSet: class IconSet:
@ -18,7 +19,7 @@ class IconSet:
Node representation: icons and color. Node representation: icons and color.
""" """
def __init__( 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): is_default: bool):
""" """
:param icons: list of lists of shape identifiers :param icons: list of lists of shape identifiers
@ -27,7 +28,7 @@ class IconSet:
tag keys should be displayed by text or ignored) tag keys should be displayed by text or ignored)
""" """
self.icons: List[List[str]] = icons self.icons: List[List[str]] = icons
self.color: str = color self.color: Color = color
self.processed: Set[str] = processed self.processed: Set[str] = processed
self.is_default = is_default self.is_default = is_default
@ -38,23 +39,18 @@ class Scheme:
Specifies map colors and rules to draw icons for OpenStreetMap tags. 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 :param file_name: scheme file name with tags, colors, and tag key
specification specification
:param color_file_name: additional color scheme
""" """
content: Dict[str, Any] = \ content: Dict[str, Any] = \
yaml.load(open(file_name).read(), Loader=yaml.FullLoader) 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.ways: List[Dict[str, Any]] = content["ways"]
self.colors: Dict[str, str] = content["colors"] 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.tags_to_write: List[str] = content["tags_to_write"]
self.prefix_to_write: List[str] = content["prefix_to_write"] self.prefix_to_write: List[str] = content["prefix_to_write"]
@ -64,18 +60,20 @@ class Scheme:
# Storage for created icon sets. # Storage for created icon sets.
self.cache: Dict[str, IconSet] = {} 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 color if the color is in scheme, otherwise return default color.
:return: 6-digit color specification with "#" :return: 6-digit color specification with "#"
""" """
if color in self.colors: if color in self.colors:
return "#" + self.colors[color] return Color("#" + self.colors[color])
if color.lower() in self.colors: if color.lower() in self.colors:
return "#" + self.colors[color.lower()] return Color("#" + self.colors[color.lower()])
if color.startswith("#"): try:
return color return Color(color)
except ValueError:
pass
return DEFAULT_COLOR return DEFAULT_COLOR
@ -124,12 +122,12 @@ class Scheme:
main_icon: Optional[List[str]] = None main_icon: Optional[List[str]] = None
extra_icons: List[List[str]] = [] extra_icons: List[List[str]] = []
processed = set() processed: Set[str] = set()
fill = DEFAULT_COLOR fill: Color = DEFAULT_COLOR
for matcher in self.tags: for matcher in self.nodes: # type: Dict[str, Any]
matched = True matched: bool = True
for key in matcher["tags"]: for key in matcher["tags"]: # type: str
if key not in tags: if key not in tags:
matched = False matched = False
break break
@ -173,10 +171,12 @@ class Scheme:
else: else:
result_set: List[List[str]] = extra_icons 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 is_default: bool = False
if not result_set and \ if not result_set and keys_left:
list(filter(lambda x: x not in processed and
not self.is_no_drawable(x), tags.keys())):
result_set = [[DEFAULT_SHAPE_ID]] result_set = [[DEFAULT_SHAPE_ID]]
is_default = True is_default = True

View file

@ -1,3 +1,6 @@
from colour import Color
class MinMax: class MinMax:
""" """
Minimum and maximum. Minimum and maximum.
@ -21,3 +24,12 @@ class MinMax:
def center(self): def center(self):
return (self.min_ + self.max_) / 2 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)