mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 13:06:25 +02:00
Use colour; add text structure.
This commit is contained in:
parent
54b88f05d2
commit
963fb12496
9 changed files with 139 additions and 259 deletions
147
data/colors.yml
147
data/colors.yml
|
@ -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'
|
|
@ -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: "*"}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"]))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue