mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-31 18:06:23 +02:00
Support color for icon shape.
* Create icon shape specification with color. * Change icon generation rules. * Add height label processing.
This commit is contained in:
parent
a84e838a1b
commit
bf4db29a1a
10 changed files with 203 additions and 195 deletions
|
@ -3,32 +3,102 @@ Röntgen drawing scheme.
|
|||
|
||||
Author: Sergey Vartanov (me@enzet.ru).
|
||||
"""
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import svgwrite
|
||||
import yaml
|
||||
from colour import Color
|
||||
|
||||
from roentgen.color import is_bright
|
||||
from roentgen.icon import DEFAULT_SHAPE_ID, IconExtractor, Shape
|
||||
from roentgen.text import Label, get_address, get_text
|
||||
|
||||
DEFAULT_COLOR: Color = Color("#444444")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShapeSpecification:
|
||||
"""
|
||||
Specification for shape as a part of an icon.
|
||||
"""
|
||||
|
||||
shape: Shape
|
||||
color: Color = DEFAULT_COLOR
|
||||
|
||||
@classmethod
|
||||
def from_structure(
|
||||
cls, structure: Any, extractor: IconExtractor, scheme: "Scheme",
|
||||
color: Color = DEFAULT_COLOR
|
||||
) -> "ShapeSpecification":
|
||||
"""
|
||||
Parse shape specification from structure.
|
||||
"""
|
||||
shape: Shape
|
||||
shape, _ = extractor.get_path(DEFAULT_SHAPE_ID)
|
||||
color: Color = color
|
||||
if isinstance(structure, str):
|
||||
shape, _ = extractor.get_path(structure)
|
||||
elif isinstance(structure, dict):
|
||||
if "shape" in structure:
|
||||
shape, _ = extractor.get_path(structure["shape"])
|
||||
if "color" in structure:
|
||||
color = scheme.get_color(structure["color"])
|
||||
return cls(shape, color)
|
||||
|
||||
def is_default(self) -> bool:
|
||||
"""
|
||||
Check whether shape is default.
|
||||
"""
|
||||
return self.shape.id_ == DEFAULT_SHAPE_ID
|
||||
|
||||
def draw(
|
||||
self, svg: svgwrite.Drawing, point: np.array,
|
||||
tags: Dict[str, Any] = None, outline: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Draw icon shape into SVG file.
|
||||
|
||||
:param svg: output SVG file
|
||||
:param point: 2D position of the icon centre
|
||||
:param opacity: icon opacity
|
||||
:param tags: tags to be displayed as hint
|
||||
:param outline: draw outline for the icon
|
||||
"""
|
||||
point = np.array(list(map(int, point)))
|
||||
|
||||
path: svgwrite.path.Path = self.shape.get_path(svg, point)
|
||||
path.update({"fill": self.color.hex})
|
||||
if outline:
|
||||
bright: bool = is_bright(self.color)
|
||||
color: Color = Color("black") if bright else Color("white")
|
||||
opacity: float = 0.7 if bright else 0.5
|
||||
|
||||
path.update({
|
||||
"fill": color.hex,
|
||||
"stroke": color.hex,
|
||||
"stroke-width": 2.2,
|
||||
"stroke-linejoin": "round",
|
||||
"opacity": opacity,
|
||||
})
|
||||
if tags:
|
||||
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
|
||||
path.set_desc(title=title)
|
||||
svg.add(path)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Icon:
|
||||
"""
|
||||
Node representation: icons and color.
|
||||
"""
|
||||
main_icon: List[Shape] # list of icons
|
||||
extra_icons: List[List[Shape]] # list of lists of icons
|
||||
color: Color # fill color of all icons
|
||||
main_icon: List[ShapeSpecification] # list of shapes
|
||||
extra_icons: List[List[ShapeSpecification]] # list of lists of shapes
|
||||
# tag keys that were processed to create icon set (other
|
||||
# tag keys should be displayed by text or ignored)
|
||||
processed: Set[str]
|
||||
is_default: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -148,9 +218,7 @@ class Scheme:
|
|||
try:
|
||||
return Color(color)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return DEFAULT_COLOR
|
||||
return DEFAULT_COLOR
|
||||
|
||||
def is_no_drawable(self, key: str) -> bool:
|
||||
"""
|
||||
|
@ -199,72 +267,79 @@ class Scheme:
|
|||
if tags_hash in self.cache:
|
||||
return self.cache[tags_hash]
|
||||
|
||||
main_icon_id: Optional[List[str]] = None
|
||||
extra_icon_ids: List[List[str]] = []
|
||||
main_icon: List[ShapeSpecification] = []
|
||||
extra_icons: List[List[ShapeSpecification]] = []
|
||||
processed: Set[str] = set()
|
||||
fill: Color = DEFAULT_COLOR
|
||||
priority: int = 0
|
||||
|
||||
for index, matcher in enumerate(self.icons):
|
||||
# type: (int, Dict[str, Any])
|
||||
index: int
|
||||
matcher: Dict[str, Any]
|
||||
matched: bool = is_matched(matcher, tags)
|
||||
matcher_tags: Set[str] = matcher["tags"].keys()
|
||||
if not matched:
|
||||
continue
|
||||
priority = len(self.icons) - index
|
||||
if "draw" in matcher and not matcher["draw"]:
|
||||
processed |= set(matcher["tags"].keys())
|
||||
processed |= set(matcher_tags)
|
||||
if "icon" in matcher:
|
||||
main_icon_id = copy.deepcopy(matcher["icon"])
|
||||
processed |= set(matcher["tags"].keys())
|
||||
main_icon = [
|
||||
ShapeSpecification.from_structure(x, icon_extractor, self)
|
||||
for x in matcher["icon"]
|
||||
]
|
||||
processed |= set(matcher_tags)
|
||||
if "over_icon" in matcher:
|
||||
if main_icon_id: # TODO: check main icon in under icons
|
||||
main_icon_id += matcher["over_icon"]
|
||||
for key in matcher["tags"].keys():
|
||||
if main_icon:
|
||||
main_icon += [
|
||||
ShapeSpecification.from_structure(
|
||||
x, icon_extractor, self
|
||||
)
|
||||
for x in matcher["over_icon"]
|
||||
]
|
||||
for key in matcher_tags:
|
||||
processed.add(key)
|
||||
if "add_icon" in matcher:
|
||||
extra_icon_ids += [matcher["add_icon"]]
|
||||
for key in matcher["tags"].keys():
|
||||
extra_icons += [[
|
||||
ShapeSpecification.from_structure(
|
||||
x, icon_extractor, self, Color("#888888")
|
||||
)
|
||||
for x in matcher["add_icon"]
|
||||
]]
|
||||
for key in matcher_tags:
|
||||
processed.add(key)
|
||||
if "color" in matcher:
|
||||
fill = self.get_color(matcher["color"])
|
||||
for key in matcher["tags"].keys():
|
||||
processed.add(key)
|
||||
assert False
|
||||
if "set_main_color" in matcher:
|
||||
for shape in main_icon:
|
||||
shape.color = self.get_color(matcher["set_main_color"])
|
||||
|
||||
color: Optional[Color] = None
|
||||
|
||||
for tag_key in tags: # type: str
|
||||
if (tag_key.endswith(":color") or
|
||||
tag_key.endswith(":colour")):
|
||||
fill = self.get_color(tags[tag_key])
|
||||
color = self.get_color(tags[tag_key])
|
||||
processed.add(tag_key)
|
||||
|
||||
for tag_key in tags: # type: str
|
||||
if tag_key in ["color", "colour"]:
|
||||
fill = self.get_color(tags[tag_key])
|
||||
color = self.get_color(tags[tag_key])
|
||||
processed.add(tag_key)
|
||||
|
||||
keys_left = list(filter(
|
||||
lambda x: x not in processed and
|
||||
not self.is_no_drawable(x), tags.keys()
|
||||
))
|
||||
if color:
|
||||
for shape_specification in main_icon:
|
||||
shape_specification.color = color
|
||||
|
||||
is_default: bool = False
|
||||
if not main_icon_id and not extra_icon_ids and keys_left:
|
||||
main_icon_id = [DEFAULT_SHAPE_ID]
|
||||
is_default = True
|
||||
keys_left = [
|
||||
x for x in tags.keys()
|
||||
if x not in processed and not self.is_no_drawable(x)
|
||||
]
|
||||
|
||||
main_icon: List[Shape] = []
|
||||
if main_icon_id:
|
||||
main_icon = list(map(
|
||||
lambda x: icon_extractor.get_path(x)[0], main_icon_id
|
||||
))
|
||||
default_shape, _ = icon_extractor.get_path(DEFAULT_SHAPE_ID)
|
||||
if not main_icon:
|
||||
main_icon = [ShapeSpecification(default_shape)]
|
||||
|
||||
extra_icons: List[List[Shape]] = []
|
||||
for icon_id in extra_icon_ids:
|
||||
extra_icons.append(list(map(
|
||||
lambda x: icon_extractor.get_path(x)[0], icon_id)))
|
||||
|
||||
returned: Icon = Icon(
|
||||
main_icon, extra_icons, fill, processed, is_default
|
||||
)
|
||||
returned: Icon = Icon(main_icon, extra_icons, processed)
|
||||
self.cache[tags_hash] = returned, priority
|
||||
|
||||
return returned, priority
|
||||
|
@ -283,8 +358,9 @@ class Scheme:
|
|||
priority = element["priority"]
|
||||
for key in element: # type: str
|
||||
if key not in [
|
||||
"tags", "no_tags", "priority", "level", "icon",
|
||||
"r", "r1", "r2"]:
|
||||
"tags", "no_tags", "priority", "level", "icon", "r", "r1",
|
||||
"r2"
|
||||
]:
|
||||
value = element[key]
|
||||
if isinstance(value, str) and value.endswith("_color"):
|
||||
value = self.get_color(value)
|
||||
|
@ -374,6 +450,9 @@ class Scheme:
|
|||
if k in tags:
|
||||
texts.append(Label(tags[k], Color("#444444")))
|
||||
tags.pop(k)
|
||||
if "height" in tags:
|
||||
texts.append(Label(f"↕ {tags['height']} m"))
|
||||
tags.pop("height")
|
||||
for tag in tags:
|
||||
if self.is_writable(tag):
|
||||
texts.append(Label(tags[tag]))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue