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. MapCSS scheme creation.
""" """
from pathlib import Path from pathlib import Path
from typing import Dict, List from typing import Dict, List, Optional
import logging import logging
from colour import Color from colour import Color
from roentgen.grid import IconCollection from roentgen.grid import IconCollection
from roentgen.icon import ShapeExtractor 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: class MapCSSWriter:
""" def __init__(
Construct icon selectors for MapCSS 0.2 scheme. self,
""" scheme: Scheme,
selectors: Dict[str, List[str]] = {} icon_directory_name: str,
for matcher in scheme.node_matchers: add_icons_for_lifecycle: bool,
if matcher.shapes and not matcher.location_restrictions: ):
# TODO: support location restrictions self.add_icons_for_lifecycle = add_icons_for_lifecycle
selectors[matcher.get_mapcss_selector()] = [ self.icon_directory_name = icon_directory_name
(x if isinstance(x, str) else x["shape"])
for x in matcher.shapes
]
s: str = "" self.matchers: Dict[Matcher, List[str]] = {}
for selector in selectors: for matcher in scheme.node_matchers:
for target in ["node", "area"]: if matcher.shapes and not matcher.location_restrictions:
s += ( self.matchers[matcher] = [
target + selector + " {\n" (x if isinstance(x, str) else x["shape"])
f' icon-image: "{icon_directory_name}/' for x in matcher.shapes
+ "___".join(selectors[selector]) ]
+ '.svg";\n}\n'
) def add_selector(
return s 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: def write_mapcss() -> None:
@ -58,12 +99,10 @@ def write_mapcss() -> None:
collection.draw_icons( collection.draw_icons(
icons_with_outline_path, color=Color("black"), outline=True icons_with_outline_path, color=Color("black"), outline=True
) )
with Path("data/roentgen_icons_part.mapcss").open() as input_file: mapcss_writer: MapCSSWriter = MapCSSWriter(
header = input_file.read() scheme, icon_directory_name, True
)
with (directory / "roentgen_icons.mapcss").open("w+") as output_file: with (directory / "roentgen_icons.mapcss").open("w+") as output_file:
output_file.write(header) mapcss_writer.write(output_file)
output_file.write("\n")
output_file.write(construct_selectors(scheme, icon_directory_name))
logging.info(f"MapCSS 0.2 scheme is written to {directory}.") 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$") 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]: def parse_float(string: str) -> Optional[float]:
""" """
Parse string representation of a float or integer value. Parse string representation of a float or integer value.

View file

@ -69,6 +69,17 @@ def is_matched_tag(
return MatchingType.NOT_MATCHED 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: class Matcher:
""" """
Tag matching. Tag matching.
@ -120,21 +131,13 @@ class Matcher:
return matched 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. Construct MapCSS 0.2 selector from the node matcher.
See https://wiki.openstreetmap.org/wiki/MapCSS/0.2 See https://wiki.openstreetmap.org/wiki/MapCSS/0.2
""" """
def get_selector(key: str, value: str) -> str: return "".join([get_selector(x, y, prefix) for (x, y) in self.tags.items()])
"""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()])
class NodeMatcher(Matcher): class NodeMatcher(Matcher):