mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-10 23:01:53 +02:00
Merge main.
This commit is contained in:
commit
155b1e21c7
112 changed files with 17854 additions and 5686 deletions
|
@ -2,6 +2,7 @@
|
|||
Map Machine drawing scheme.
|
||||
"""
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
@ -11,18 +12,18 @@ import numpy as np
|
|||
import yaml
|
||||
from colour import Color
|
||||
|
||||
from map_machine.direction import DirectionSet
|
||||
from map_machine.icon import (
|
||||
DEFAULT_COLOR,
|
||||
from map_machine.feature.direction import DirectionSet
|
||||
from map_machine.map_configuration import MapConfiguration
|
||||
from map_machine.osm.osm_reader import Tagged, Tags
|
||||
from map_machine.pictogram.icon import (
|
||||
DEFAULT_SHAPE_ID,
|
||||
Icon,
|
||||
IconSet,
|
||||
Shape,
|
||||
ShapeExtractor,
|
||||
ShapeSpecification,
|
||||
DEFAULT_SMALL_SHAPE_ID,
|
||||
)
|
||||
from map_machine.map_configuration import MapConfiguration
|
||||
from map_machine.text import Label, get_address, get_text
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
__email__ = "me@enzet.ru"
|
||||
|
@ -32,30 +33,28 @@ IconDescription = List[Union[str, Dict[str, str]]]
|
|||
|
||||
@dataclass
|
||||
class LineStyle:
|
||||
"""
|
||||
SVG line style and its priority.
|
||||
"""
|
||||
"""SVG line style and its priority."""
|
||||
|
||||
style: Dict[str, Union[int, float, str]]
|
||||
parallel_offset: float = 0.0
|
||||
priority: float = 0.0
|
||||
|
||||
|
||||
class MatchingType(Enum):
|
||||
"""
|
||||
Description on how tag was matched.
|
||||
"""
|
||||
"""Description on how tag was matched."""
|
||||
|
||||
NOT_MATCHED = 0
|
||||
MATCHED_BY_SET = 1
|
||||
MATCHED_BY_WILDCARD = 2
|
||||
MATCHED = 3
|
||||
MATCHED_BY_REGEX = 4
|
||||
|
||||
|
||||
def is_matched_tag(
|
||||
matcher_tag_key: str,
|
||||
matcher_tag_value: Union[str, list],
|
||||
tags: Dict[str, str],
|
||||
) -> MatchingType:
|
||||
tags: Tags,
|
||||
) -> Tuple[MatchingType, List[str]]:
|
||||
"""
|
||||
Check whether element tags contradict tag matcher.
|
||||
|
||||
|
@ -63,20 +62,21 @@ def is_matched_tag(
|
|||
:param matcher_tag_value: tag value, tag value list, or "*"
|
||||
:param tags: element tags to check
|
||||
"""
|
||||
if matcher_tag_key in tags:
|
||||
if matcher_tag_value == "*":
|
||||
return MatchingType.MATCHED_BY_WILDCARD
|
||||
if (
|
||||
isinstance(matcher_tag_value, str)
|
||||
and tags[matcher_tag_key] == matcher_tag_value
|
||||
):
|
||||
return MatchingType.MATCHED
|
||||
if (
|
||||
isinstance(matcher_tag_value, list)
|
||||
and tags[matcher_tag_key] in matcher_tag_value
|
||||
):
|
||||
return MatchingType.MATCHED_BY_SET
|
||||
return MatchingType.NOT_MATCHED
|
||||
if matcher_tag_key not in tags:
|
||||
return MatchingType.NOT_MATCHED, []
|
||||
|
||||
if matcher_tag_value == "*":
|
||||
return MatchingType.MATCHED_BY_WILDCARD, []
|
||||
if tags[matcher_tag_key] == matcher_tag_value:
|
||||
return MatchingType.MATCHED, []
|
||||
if matcher_tag_value.startswith("^"):
|
||||
matcher: Optional[re.Match] = re.match(
|
||||
matcher_tag_value, tags[matcher_tag_key]
|
||||
)
|
||||
if matcher:
|
||||
return MatchingType.MATCHED_BY_REGEX, list(matcher.groups())
|
||||
|
||||
return MatchingType.NOT_MATCHED, []
|
||||
|
||||
|
||||
def get_selector(key: str, value: str, prefix: str = "") -> str:
|
||||
|
@ -103,15 +103,13 @@ def match_location(restrictions: Dict[str, str], country: str) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
class Matcher:
|
||||
"""
|
||||
Tag matching.
|
||||
"""
|
||||
class Matcher(Tagged):
|
||||
"""Tag matching."""
|
||||
|
||||
def __init__(
|
||||
self, structure: Dict[str, Any], group: Optional[Dict[str, Any]] = None
|
||||
) -> None:
|
||||
self.tags: Dict[str, str] = structure["tags"]
|
||||
super().__init__(structure["tags"])
|
||||
|
||||
self.exception: Dict[str, str] = {}
|
||||
if "exception" in structure:
|
||||
|
@ -129,6 +127,8 @@ class Matcher:
|
|||
if "location_restrictions" in structure:
|
||||
self.location_restrictions = structure["location_restrictions"]
|
||||
|
||||
self.verify()
|
||||
|
||||
def check_zoom_level(self, zoom_level: float) -> bool:
|
||||
"""Check whether zoom level is matching."""
|
||||
return (
|
||||
|
@ -136,16 +136,16 @@ class Matcher:
|
|||
)
|
||||
|
||||
def is_matched(
|
||||
self,
|
||||
tags: Dict[str, str],
|
||||
configuration: Optional[MapConfiguration] = None,
|
||||
) -> bool:
|
||||
self, tags: Tags, configuration: Optional[MapConfiguration] = None
|
||||
) -> Tuple[bool, Dict[str, str]]:
|
||||
"""
|
||||
Check whether element tags matches tag matcher.
|
||||
|
||||
:param tags: element tags to be matched
|
||||
:param configuration: current map configuration to be matched
|
||||
"""
|
||||
groups: Dict[str, str] = {}
|
||||
|
||||
if (
|
||||
configuration is not None
|
||||
and self.location_restrictions
|
||||
|
@ -153,28 +153,30 @@ class Matcher:
|
|||
self.location_restrictions, configuration.country
|
||||
)
|
||||
):
|
||||
return False
|
||||
return False, {}
|
||||
|
||||
for config_tag_key in self.tags:
|
||||
config_tag_key: str
|
||||
tag_matcher = self.tags[config_tag_key]
|
||||
if (
|
||||
is_matched_tag(config_tag_key, tag_matcher, tags)
|
||||
== MatchingType.NOT_MATCHED
|
||||
):
|
||||
return False
|
||||
is_matched, matched_groups = is_matched_tag(
|
||||
config_tag_key, self.tags[config_tag_key], tags
|
||||
)
|
||||
if is_matched == MatchingType.NOT_MATCHED:
|
||||
return False, {}
|
||||
|
||||
if matched_groups:
|
||||
for index, element in enumerate(matched_groups):
|
||||
groups[f"#{config_tag_key}{index}"] = element
|
||||
|
||||
if self.exception:
|
||||
for config_tag_key in self.exception:
|
||||
config_tag_key: str
|
||||
tag_matcher = self.exception[config_tag_key]
|
||||
if (
|
||||
is_matched_tag(config_tag_key, tag_matcher, tags)
|
||||
!= MatchingType.NOT_MATCHED
|
||||
):
|
||||
return False
|
||||
is_matched, matched_groups = is_matched_tag(
|
||||
config_tag_key, self.exception[config_tag_key], tags
|
||||
)
|
||||
if is_matched != MatchingType.NOT_MATCHED:
|
||||
return False, {}
|
||||
|
||||
return True
|
||||
return True, groups
|
||||
|
||||
def get_mapcss_selector(self, prefix: str = "") -> str:
|
||||
"""
|
||||
|
@ -195,10 +197,21 @@ class Matcher:
|
|||
return {}
|
||||
|
||||
|
||||
def get_shape_specifications(
|
||||
structure: List[Union[str, Dict[str, Any]]]
|
||||
) -> List[dict]:
|
||||
"""Parse shape specification from scheme."""
|
||||
shapes: List[dict] = []
|
||||
for shape_specification in structure:
|
||||
if isinstance(shape_specification, str):
|
||||
shapes.append({"shape": shape_specification})
|
||||
else:
|
||||
shapes.append(shape_specification)
|
||||
return shapes
|
||||
|
||||
|
||||
class NodeMatcher(Matcher):
|
||||
"""
|
||||
Tag specification matcher.
|
||||
"""
|
||||
"""Tag specification matcher."""
|
||||
|
||||
def __init__(
|
||||
self, structure: Dict[str, Any], group: Dict[str, Any]
|
||||
|
@ -212,15 +225,15 @@ class NodeMatcher(Matcher):
|
|||
|
||||
self.shapes: Optional[IconDescription] = None
|
||||
if "shapes" in structure:
|
||||
self.shapes = structure["shapes"]
|
||||
self.shapes = get_shape_specifications(structure["shapes"])
|
||||
|
||||
self.over_icon: Optional[IconDescription] = None
|
||||
if "over_icon" in structure:
|
||||
self.over_icon = structure["over_icon"]
|
||||
self.over_icon = get_shape_specifications(structure["over_icon"])
|
||||
|
||||
self.add_shapes: Optional[IconDescription] = None
|
||||
if "add_shapes" in structure:
|
||||
self.add_shapes = structure["add_shapes"]
|
||||
self.add_shapes = get_shape_specifications(structure["add_shapes"])
|
||||
|
||||
self.set_main_color: Optional[str] = None
|
||||
if "set_main_color" in structure:
|
||||
|
@ -232,23 +245,21 @@ class NodeMatcher(Matcher):
|
|||
|
||||
self.under_icon: Optional[IconDescription] = None
|
||||
if "under_icon" in structure:
|
||||
self.under_icon = structure["under_icon"]
|
||||
self.under_icon = get_shape_specifications(structure["under_icon"])
|
||||
|
||||
self.with_icon: Optional[IconDescription] = None
|
||||
if "with_icon" in structure:
|
||||
self.with_icon = structure["with_icon"]
|
||||
self.with_icon = get_shape_specifications(structure["with_icon"])
|
||||
|
||||
def get_clean_shapes(self) -> Optional[List[str]]:
|
||||
"""Get list of shape identifiers for shapes."""
|
||||
if not self.shapes:
|
||||
return None
|
||||
return [(x if isinstance(x, str) else x["shape"]) for x in self.shapes]
|
||||
return [x["shape"] for x in self.shapes]
|
||||
|
||||
|
||||
class WayMatcher(Matcher):
|
||||
"""
|
||||
Special tag matcher for ways.
|
||||
"""
|
||||
"""Special tag matcher for ways."""
|
||||
|
||||
def __init__(self, structure: Dict[str, Any], scheme: "Scheme") -> None:
|
||||
super().__init__(structure)
|
||||
|
@ -260,38 +271,42 @@ class WayMatcher(Matcher):
|
|||
self.style[key] = scheme.get_color(style[key]).hex.upper()
|
||||
else:
|
||||
self.style[key] = style[key]
|
||||
self.priority: int = 0
|
||||
|
||||
self.priority: float = 0.0
|
||||
if "priority" in structure:
|
||||
self.priority = structure["priority"]
|
||||
|
||||
self.parallel_offset: float = 0.0
|
||||
if parallel_offset := structure.get("parallel_offset"):
|
||||
self.parallel_offset = parallel_offset
|
||||
|
||||
def get_style(self) -> Dict[str, Any]:
|
||||
"""Return way SVG style."""
|
||||
return self.style
|
||||
|
||||
|
||||
class RoadMatcher(Matcher):
|
||||
"""
|
||||
Special tag matcher for highways.
|
||||
"""
|
||||
"""Special tag matcher for highways."""
|
||||
|
||||
def __init__(self, structure: Dict[str, Any], scheme: "Scheme") -> None:
|
||||
super().__init__(structure)
|
||||
self.border_color: Color = Color(
|
||||
scheme.get_color(structure["border_color"])
|
||||
)
|
||||
self.color: Color = Color("white")
|
||||
self.color: Color = scheme.get_color("road_color")
|
||||
if "color" in structure:
|
||||
self.color = Color(scheme.get_color(structure["color"]))
|
||||
self.default_width: float = structure["default_width"]
|
||||
self.priority: float = 0
|
||||
self.priority: float = 0.0
|
||||
if "priority" in structure:
|
||||
self.priority = structure["priority"]
|
||||
|
||||
def get_priority(self, tags: Dict[str, str]) -> float:
|
||||
layer: float = 0
|
||||
def get_priority(self, tags: Tags) -> float:
|
||||
"""Get priority for drawing order."""
|
||||
layer: float = 0.0
|
||||
if "layer" in tags:
|
||||
layer = float(tags.get("layer"))
|
||||
return 1000 * layer + self.priority
|
||||
return 1000.0 * layer + self.priority
|
||||
|
||||
|
||||
class Scheme:
|
||||
|
@ -301,7 +316,56 @@ class Scheme:
|
|||
Specifies map colors and rules to draw icons for OpenStreetMap tags.
|
||||
"""
|
||||
|
||||
def __init__(self, file_name: Path) -> None:
|
||||
def __init__(self, content: Dict[str, Any]) -> None:
|
||||
self.node_matchers: List[NodeMatcher] = []
|
||||
if "node_icons" in content:
|
||||
for group in content["node_icons"]:
|
||||
for element in group["tags"]:
|
||||
self.node_matchers.append(NodeMatcher(element, group))
|
||||
|
||||
self.colors: Dict[str, str] = (
|
||||
content["colors"] if "colors" in content else {}
|
||||
)
|
||||
self.material_colors: Dict[str, str] = (
|
||||
content["material_colors"] if "material_colors" in content else {}
|
||||
)
|
||||
|
||||
self.way_matchers: List[WayMatcher] = (
|
||||
[WayMatcher(x, self) for x in content["ways"]]
|
||||
if "ways" in content
|
||||
else []
|
||||
)
|
||||
self.road_matchers: List[RoadMatcher] = (
|
||||
[RoadMatcher(x, self) for x in content["roads"]]
|
||||
if "roads" in content
|
||||
else []
|
||||
)
|
||||
self.area_matchers: List[Matcher] = (
|
||||
[Matcher(x) for x in content["area_tags"]]
|
||||
if "area_tags" in content
|
||||
else []
|
||||
)
|
||||
self.keys_to_write: List[str] = (
|
||||
content["keys_to_write"] if "keys_to_write" in content else []
|
||||
)
|
||||
self.prefix_to_write: List[str] = (
|
||||
content["prefix_to_write"] if "prefix_to_write" in content else []
|
||||
)
|
||||
self.keys_to_skip: List[str] = (
|
||||
content["keys_to_skip"] if "keys_to_skip" in content else []
|
||||
)
|
||||
self.prefix_to_skip: List[str] = (
|
||||
content["prefix_to_skip"] if "prefix_to_skip" in content else []
|
||||
)
|
||||
self.tags_to_skip: Dict[str, str] = (
|
||||
content["tags_to_skip"] if "tags_to_skip" in content else {}
|
||||
)
|
||||
|
||||
# Storage for created icon sets.
|
||||
self.cache: Dict[str, Tuple[IconSet, int]] = {}
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, file_name: Path) -> "Scheme":
|
||||
"""
|
||||
:param file_name: name of the scheme file with tags, colors, and tag key
|
||||
specification
|
||||
|
@ -310,30 +374,7 @@ class Scheme:
|
|||
content: Dict[str, Any] = yaml.load(
|
||||
input_file.read(), Loader=yaml.FullLoader
|
||||
)
|
||||
self.node_matchers: List[NodeMatcher] = []
|
||||
for group in content["node_icons"]:
|
||||
for element in group["tags"]:
|
||||
self.node_matchers.append(NodeMatcher(element, group))
|
||||
|
||||
self.colors: Dict[str, str] = content["colors"]
|
||||
self.material_colors: Dict[str, str] = content["material_colors"]
|
||||
|
||||
self.way_matchers: List[WayMatcher] = [
|
||||
WayMatcher(x, self) for x in content["ways"]
|
||||
]
|
||||
self.road_matchers: List[RoadMatcher] = [
|
||||
RoadMatcher(x, self) for x in content["roads"]
|
||||
]
|
||||
self.area_matchers: List[Matcher] = [
|
||||
Matcher(x) for x in content["area_tags"]
|
||||
]
|
||||
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"]
|
||||
|
||||
# Storage for created icon sets.
|
||||
self.cache: Dict[str, Tuple[IconSet, int]] = {}
|
||||
return cls(content)
|
||||
|
||||
def get_color(self, color: str) -> Color:
|
||||
"""
|
||||
|
@ -343,42 +384,91 @@ class Scheme:
|
|||
:return: color specification
|
||||
"""
|
||||
if color in self.colors:
|
||||
return Color(self.colors[color])
|
||||
specification: Union[str, dict] = self.colors[color]
|
||||
if isinstance(specification, str):
|
||||
return Color(self.colors[color])
|
||||
|
||||
color: Color = self.get_color(specification["color"])
|
||||
if "darken" in specification:
|
||||
percent: float = float(specification["darken"])
|
||||
color.set_luminance(color.get_luminance() * (1 - percent))
|
||||
return color
|
||||
|
||||
if color.lower() in self.colors:
|
||||
return Color(self.colors[color.lower()])
|
||||
|
||||
try:
|
||||
return Color(color)
|
||||
except (ValueError, AttributeError):
|
||||
return DEFAULT_COLOR
|
||||
logging.debug(f"Unknown color `{color}`.")
|
||||
return Color(self.colors["default"])
|
||||
|
||||
def is_no_drawable(self, key: str) -> bool:
|
||||
def get_default_color(self) -> Color:
|
||||
"""Get default color for a main icon."""
|
||||
return self.get_color("default")
|
||||
|
||||
def get_extra_color(self) -> Color:
|
||||
"""Get default color for an extra icon."""
|
||||
return self.get_color("extra")
|
||||
|
||||
def get(self, variable_name: str):
|
||||
"""
|
||||
FIXME: colors should be variables.
|
||||
"""
|
||||
if variable_name in self.colors:
|
||||
return self.colors[variable_name]
|
||||
return 0.0
|
||||
|
||||
def is_no_drawable(self, key: str, value: 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
|
||||
:param value: OpenStreetMap tag value
|
||||
"""
|
||||
if key in self.tags_to_write or key in self.tags_to_skip:
|
||||
if (
|
||||
key in self.keys_to_write + self.keys_to_skip
|
||||
or key in self.tags_to_skip
|
||||
and self.tags_to_skip[key] == value
|
||||
):
|
||||
return True
|
||||
for prefix in self.prefix_to_write + self.prefix_to_skip:
|
||||
if key[: len(prefix) + 1] == f"{prefix}:":
|
||||
|
||||
if ":" in key:
|
||||
prefix: str = key.split(":")[0]
|
||||
if prefix in self.prefix_to_write + self.prefix_to_skip:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_writable(self, key: str) -> bool:
|
||||
def is_writable(self, key: str, value: 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
|
||||
:param value: OpenStreetMap tag value
|
||||
"""
|
||||
if key in self.tags_to_skip:
|
||||
if (
|
||||
key in self.keys_to_skip
|
||||
or key in self.tags_to_skip
|
||||
and self.tags_to_skip[key] == value
|
||||
):
|
||||
return False
|
||||
if key in self.tags_to_write:
|
||||
|
||||
if key in self.keys_to_write:
|
||||
return True
|
||||
for prefix in self.prefix_to_write:
|
||||
if key[: len(prefix) + 1] == f"{prefix}:":
|
||||
return True
|
||||
|
||||
prefix: Optional[str] = None
|
||||
if ":" in key:
|
||||
prefix = key.split(":")[0]
|
||||
|
||||
if prefix in self.prefix_to_skip:
|
||||
return False
|
||||
|
||||
if prefix in self.prefix_to_write:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_icon(
|
||||
|
@ -406,11 +496,13 @@ class Scheme:
|
|||
main_icon: Optional[Icon] = None
|
||||
extra_icons: List[Icon] = []
|
||||
priority: int = 0
|
||||
color: Optional[Color] = None
|
||||
|
||||
for index, matcher in enumerate(self.node_matchers):
|
||||
if not matcher.replace_shapes and main_icon:
|
||||
continue
|
||||
if not matcher.is_matched(tags, configuration):
|
||||
matching, groups = matcher.is_matched(tags, configuration)
|
||||
if not matching:
|
||||
continue
|
||||
if (
|
||||
not configuration.ignore_level_matching
|
||||
|
@ -423,7 +515,7 @@ class Scheme:
|
|||
processed |= matcher_tags
|
||||
if matcher.shapes:
|
||||
specifications = [
|
||||
self.get_shape_specification(x, extractor)
|
||||
self.get_shape_specification(x, extractor, groups)
|
||||
for x in matcher.shapes
|
||||
]
|
||||
main_icon = Icon(specifications)
|
||||
|
@ -437,18 +529,18 @@ class Scheme:
|
|||
processed |= matcher_tags
|
||||
if matcher.add_shapes:
|
||||
specifications = [
|
||||
self.get_shape_specification(x, extractor, Color("#888888"))
|
||||
self.get_shape_specification(
|
||||
x, extractor, color=self.get_extra_color()
|
||||
)
|
||||
for x in matcher.add_shapes
|
||||
]
|
||||
extra_icons += [Icon(specifications)]
|
||||
processed |= matcher_tags
|
||||
if matcher.set_main_color and main_icon:
|
||||
main_icon.recolor(self.get_color(matcher.set_main_color))
|
||||
color = self.get_color(matcher.set_main_color)
|
||||
if matcher.set_opacity and main_icon:
|
||||
main_icon.opacity = matcher.set_opacity
|
||||
|
||||
color: Optional[Color] = None
|
||||
|
||||
if "material" in tags:
|
||||
value: str = tags["material"]
|
||||
if value in self.material_colors:
|
||||
|
@ -465,24 +557,36 @@ class Scheme:
|
|||
color = self.get_color(tags[color_tag_key])
|
||||
processed.add(color_tag_key)
|
||||
|
||||
if not main_icon:
|
||||
dot_spec: ShapeSpecification = ShapeSpecification(
|
||||
extractor.get_shape(DEFAULT_SHAPE_ID), self.get_color("default")
|
||||
)
|
||||
main_icon: Icon = Icon([dot_spec])
|
||||
|
||||
if main_icon and color:
|
||||
main_icon.recolor(color)
|
||||
|
||||
default_shape = extractor.get_shape(DEFAULT_SHAPE_ID)
|
||||
if not main_icon:
|
||||
main_icon = Icon([ShapeSpecification(default_shape)])
|
||||
default_icon: Optional[Icon] = None
|
||||
if configuration.show_overlapped:
|
||||
small_dot_spec: ShapeSpecification = ShapeSpecification(
|
||||
extractor.get_shape(DEFAULT_SMALL_SHAPE_ID),
|
||||
color if color else self.get_color("default"),
|
||||
)
|
||||
default_icon = Icon([small_dot_spec])
|
||||
|
||||
returned: IconSet = IconSet(main_icon, extra_icons, processed)
|
||||
returned: IconSet = IconSet(
|
||||
main_icon, extra_icons, default_icon, processed
|
||||
)
|
||||
self.cache[tags_hash] = returned, priority
|
||||
|
||||
for key in ["direction", "camera:direction"]:
|
||||
for key in "direction", "camera:direction":
|
||||
if key in tags:
|
||||
for specification in main_icon.shape_specifications:
|
||||
if (
|
||||
DirectionSet(tags[key]).is_right() is False
|
||||
and specification.shape.is_right_directed is True
|
||||
or specification.shape.is_right_directed is True
|
||||
and specification.shape.is_right_directed is False
|
||||
DirectionSet(tags[key]).is_right() is not None
|
||||
and specification.shape.is_right_directed is not None
|
||||
and DirectionSet(tags[key]).is_right()
|
||||
!= specification.shape.is_right_directed
|
||||
):
|
||||
specification.flip_horizontally = True
|
||||
|
||||
|
@ -493,119 +597,51 @@ class Scheme:
|
|||
line_styles = []
|
||||
|
||||
for matcher in self.way_matchers:
|
||||
if not matcher.is_matched(tags):
|
||||
matching, _ = matcher.is_matched(tags)
|
||||
if not matching:
|
||||
continue
|
||||
|
||||
line_styles.append(LineStyle(matcher.style, matcher.priority))
|
||||
line_style: LineStyle = LineStyle(
|
||||
matcher.style, matcher.parallel_offset, matcher.priority
|
||||
)
|
||||
line_styles.append(line_style)
|
||||
|
||||
return line_styles
|
||||
|
||||
def get_road(self, tags: Dict[str, Any]) -> Optional[RoadMatcher]:
|
||||
"""Get road matcher if tags are matched."""
|
||||
for matcher in self.road_matchers:
|
||||
if not matcher.is_matched(tags):
|
||||
matching, _ = matcher.is_matched(tags)
|
||||
if not matching:
|
||||
continue
|
||||
return matcher
|
||||
return None
|
||||
|
||||
def construct_text(
|
||||
self, tags: Dict[str, str], draw_captions: str, processed: Set[str]
|
||||
) -> List[Label]:
|
||||
"""Construct labels for not processed tags."""
|
||||
texts: List[Label] = []
|
||||
|
||||
name = None
|
||||
alt_name = None
|
||||
if "name" in tags:
|
||||
name = tags["name"]
|
||||
processed.add("name")
|
||||
elif "name:en" in tags:
|
||||
if not name:
|
||||
name = tags["name:en"]
|
||||
processed.add("name:en")
|
||||
processed.add("name:en")
|
||||
if "alt_name" in tags:
|
||||
if alt_name:
|
||||
alt_name += ", "
|
||||
else:
|
||||
alt_name = ""
|
||||
alt_name += tags["alt_name"]
|
||||
processed.add("alt_name")
|
||||
if "old_name" in tags:
|
||||
if alt_name:
|
||||
alt_name += ", "
|
||||
else:
|
||||
alt_name = ""
|
||||
alt_name += "ex " + tags["old_name"]
|
||||
|
||||
address: List[str] = get_address(tags, draw_captions, processed)
|
||||
|
||||
if name:
|
||||
texts.append(Label(name, Color("black")))
|
||||
if alt_name:
|
||||
texts.append(Label(f"({alt_name})"))
|
||||
if address:
|
||||
texts.append(Label(", ".join(address)))
|
||||
|
||||
if draw_captions == "main":
|
||||
return texts
|
||||
|
||||
texts += get_text(tags, processed)
|
||||
|
||||
if "route_ref" in tags:
|
||||
texts.append(Label(tags["route_ref"].replace(";", " ")))
|
||||
processed.add("route_ref")
|
||||
if "cladr:code" in tags:
|
||||
texts.append(Label(tags["cladr:code"], size=7))
|
||||
processed.add("cladr:code")
|
||||
if "website" in tags:
|
||||
link = tags["website"]
|
||||
if link[:7] == "http://":
|
||||
link = link[7:]
|
||||
if link[:8] == "https://":
|
||||
link = link[8:]
|
||||
if link[:4] == "www.":
|
||||
link = link[4:]
|
||||
if link[-1] == "/":
|
||||
link = link[:-1]
|
||||
link = link[:25] + ("..." if len(tags["website"]) > 25 else "")
|
||||
texts.append(Label(link, Color("#000088")))
|
||||
processed.add("website")
|
||||
for key in ["phone"]:
|
||||
if key in tags:
|
||||
texts.append(Label(tags[key], Color("#444444")))
|
||||
processed.add(key)
|
||||
if "height" in tags:
|
||||
texts.append(Label(f"↕ {tags['height']} m"))
|
||||
processed.add("height")
|
||||
for tag in tags:
|
||||
if self.is_writable(tag) and tag not in processed:
|
||||
texts.append(Label(tags[tag]))
|
||||
return texts
|
||||
|
||||
def is_area(self, tags: Dict[str, str]) -> bool:
|
||||
def is_area(self, tags: Tags) -> bool:
|
||||
"""Check whether way described by tags is area."""
|
||||
for matcher in self.area_matchers:
|
||||
if matcher.is_matched(tags):
|
||||
matching, _ = matcher.is_matched(tags)
|
||||
if matching:
|
||||
return True
|
||||
return False
|
||||
|
||||
def process_ignored(
|
||||
self, tags: Dict[str, str], processed: Set[str]
|
||||
) -> None:
|
||||
def process_ignored(self, tags: Tags, processed: Set[str]) -> None:
|
||||
"""
|
||||
Mark all ignored tag as processed.
|
||||
|
||||
:param tags: input tag dictionary
|
||||
:param processed: processed set
|
||||
"""
|
||||
[processed.add(tag) for tag in tags if self.is_no_drawable(tag)]
|
||||
processed.update(
|
||||
set(tag for tag in tags if self.is_no_drawable(tag, tags[tag]))
|
||||
)
|
||||
|
||||
def get_shape_specification(
|
||||
self,
|
||||
structure: Union[str, Dict[str, Any]],
|
||||
extractor: ShapeExtractor,
|
||||
color: Color = DEFAULT_COLOR,
|
||||
groups: Dict[str, str] = None,
|
||||
color: Optional[Color] = None,
|
||||
) -> ShapeSpecification:
|
||||
"""
|
||||
Parse shape specification from structure, that is just shape string
|
||||
|
@ -613,31 +649,33 @@ class Scheme:
|
|||
and offset (optional).
|
||||
"""
|
||||
shape: Shape = extractor.get_shape(DEFAULT_SHAPE_ID)
|
||||
color: Color = color
|
||||
offset: np.ndarray = np.array((0, 0))
|
||||
color: Color = (
|
||||
color if color is not None else Color(self.colors["default"])
|
||||
)
|
||||
offset: np.ndarray = np.array((0.0, 0.0))
|
||||
flip_horizontally: bool = False
|
||||
flip_vertically: bool = False
|
||||
use_outline: bool = True
|
||||
|
||||
if isinstance(structure, str):
|
||||
shape = extractor.get_shape(structure)
|
||||
elif isinstance(structure, dict):
|
||||
if "shape" in structure:
|
||||
shape = extractor.get_shape(structure["shape"])
|
||||
else:
|
||||
logging.error(
|
||||
"Invalid shape specification: `shape` key expected."
|
||||
)
|
||||
if "color" in structure:
|
||||
color = self.get_color(structure["color"])
|
||||
if "offset" in structure:
|
||||
offset = np.array(structure["offset"])
|
||||
if "flip_horizontally" in structure:
|
||||
flip_horizontally = structure["flip_horizontally"]
|
||||
if "flip_vertically" in structure:
|
||||
flip_vertically = structure["flip_vertically"]
|
||||
if "outline" in structure:
|
||||
use_outline = structure["outline"]
|
||||
structure: Dict[str, Any]
|
||||
if "shape" in structure:
|
||||
shape_id: str = structure["shape"]
|
||||
if groups:
|
||||
for key in groups:
|
||||
shape_id = shape_id.replace(key, groups[key])
|
||||
shape = extractor.get_shape(shape_id)
|
||||
else:
|
||||
logging.error("Invalid shape specification: `shape` key expected.")
|
||||
if "color" in structure:
|
||||
color = self.get_color(structure["color"])
|
||||
if "offset" in structure:
|
||||
offset = np.array(structure["offset"])
|
||||
if "flip_horizontally" in structure:
|
||||
flip_horizontally = structure["flip_horizontally"]
|
||||
if "flip_vertically" in structure:
|
||||
flip_vertically = structure["flip_vertically"]
|
||||
if "outline" in structure:
|
||||
use_outline = structure["outline"]
|
||||
|
||||
return ShapeSpecification(
|
||||
shape,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue