Issue #63: add ghost icons.

Add keys with lifecycle prefixes to MapCSS with ghost icons (icons with
low opacity).
This commit is contained in:
Sergey Vartanov 2021-07-27 03:29:17 +03:00
parent 071164efe0
commit afaeb6f37a
3 changed files with 94 additions and 40 deletions

View file

@ -2,39 +2,80 @@
MapCSS scheme creation.
"""
from pathlib import Path
from typing import Dict, List
from typing import Dict, List, Optional
import logging
from colour import Color
from roentgen.grid import IconCollection
from roentgen.icon import ShapeExtractor
from roentgen.scheme import Scheme
from roentgen.osm_reader import STAGES_OF_DECAY
from roentgen.scheme import Scheme, Matcher
def construct_selectors(scheme: Scheme, icon_directory_name: str) -> str:
"""
Construct icon selectors for MapCSS 0.2 scheme.
"""
selectors: Dict[str, List[str]] = {}
for matcher in scheme.node_matchers:
if matcher.shapes and not matcher.location_restrictions:
# TODO: support location restrictions
selectors[matcher.get_mapcss_selector()] = [
(x if isinstance(x, str) else x["shape"])
for x in matcher.shapes
]
class MapCSSWriter:
def __init__(
self,
scheme: Scheme,
icon_directory_name: str,
add_icons_for_lifecycle: bool,
):
self.add_icons_for_lifecycle = add_icons_for_lifecycle
self.icon_directory_name = icon_directory_name
s: str = ""
for selector in selectors:
for target in ["node", "area"]:
s += (
target + selector + " {\n"
f' icon-image: "{icon_directory_name}/'
+ "___".join(selectors[selector])
+ '.svg";\n}\n'
)
return s
self.matchers: Dict[Matcher, List[str]] = {}
for matcher in scheme.node_matchers:
if matcher.shapes and not matcher.location_restrictions:
self.matchers[matcher] = [
(x if isinstance(x, str) else x["shape"])
for x in matcher.shapes
]
def add_selector(
self,
target: str,
matcher: Matcher,
prefix: str = "",
opacity: Optional[float] = None,
) -> str:
selector = (
target + matcher.get_mapcss_selector(prefix) + " {\n"
f' icon-image: "{self.icon_directory_name}/'
+ "___".join(self.matchers[matcher])
+ '.svg";\n'
)
if opacity is not None:
selector += f" icon-opacity: {opacity:.2f};\n"
selector += "}\n"
return selector
def write(self, output_file) -> None:
"""
Construct icon selectors for MapCSS 0.2 scheme.
"""
with Path("data/roentgen_icons_part.mapcss").open() as input_file:
output_file.write(input_file.read())
output_file.write("\n")
for matcher in self.matchers:
for target in ["node", "area"]:
output_file.write(self.add_selector(target, matcher))
if not self.add_icons_for_lifecycle:
return
for index, stage_of_decay in enumerate(STAGES_OF_DECAY):
opacity: float = 0.6 - 0.4 * index / (len(STAGES_OF_DECAY) - 1)
for matcher in self.matchers:
if len(matcher.tags) > 1:
continue
for target in ["node", "area"]:
output_file.write(
self.add_selector(
target, matcher, stage_of_decay, opacity
)
)
def write_mapcss() -> None:
@ -58,12 +99,10 @@ def write_mapcss() -> None:
collection.draw_icons(
icons_with_outline_path, color=Color("black"), outline=True
)
with Path("data/roentgen_icons_part.mapcss").open() as input_file:
header = input_file.read()
mapcss_writer: MapCSSWriter = MapCSSWriter(
scheme, icon_directory_name, True
)
with (directory / "roentgen_icons.mapcss").open("w+") as output_file:
output_file.write(header)
output_file.write("\n")
output_file.write(construct_selectors(scheme, icon_directory_name))
mapcss_writer.write(output_file)
logging.info(f"MapCSS 0.2 scheme is written to {directory}.")

View file

@ -23,6 +23,18 @@ KILOMETERS_PATTERN = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*km$")
MILES_PATTERN = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*mi$")
STAGES_OF_DECAY: List[str] = [
"disused",
"abandoned",
"ruins",
"demolished",
"removed",
"razed",
"destroyed",
"was", # is not actually a stage of decay
]
def parse_float(string: str) -> Optional[float]:
"""
Parse string representation of a float or integer value.

View file

@ -69,6 +69,17 @@ def is_matched_tag(
return MatchingType.NOT_MATCHED
def get_selector(key: str, value: str, prefix: str = "") -> str:
"""Get MapCSS 0.2 selector for one key."""
if prefix:
key = f"{prefix}:{key}"
if value == "*":
return f"[{key}]"
if '"' in value:
return f"[{key}='{value}']"
return f'[{key}="{value}"]'
class Matcher:
"""
Tag matching.
@ -120,21 +131,13 @@ class Matcher:
return matched
def get_mapcss_selector(self) -> str:
def get_mapcss_selector(self, prefix: str = "") -> str:
"""
Construct MapCSS 0.2 selector from the node matcher.
See https://wiki.openstreetmap.org/wiki/MapCSS/0.2
"""
def get_selector(key: str, value: str) -> str:
"""Get MapCSS 0.2 selector for one key."""
if value == "*":
return f"[{key}]"
if '"' in value:
return f"[{key}='{value}']"
return f'[{key}="{value}"]'
return "".join([get_selector(x, y) for (x, y) in self.tags.items()])
return "".join([get_selector(x, y, prefix) for (x, y) in self.tags.items()])
class NodeMatcher(Matcher):