Support color for icon shape.

* Create icon shape specification with color.
  * Change icon generation rules.
  * Add height label processing.
This commit is contained in:
Sergey Vartanov 2021-05-09 05:04:38 +03:00
parent a84e838a1b
commit bf4db29a1a
10 changed files with 203 additions and 195 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Before After
Before After

View file

@ -19,7 +19,8 @@ from roentgen.osm_reader import (
Map, OSMMember, OSMNode, OSMRelation, OSMWay, Tagged Map, OSMMember, OSMNode, OSMRelation, OSMWay, Tagged
) )
from roentgen.point import Point from roentgen.point import Point
from roentgen.scheme import Icon, LineStyle, Scheme from roentgen.scheme import Icon, LineStyle, Scheme, ShapeSpecification, \
DEFAULT_COLOR
from roentgen.util import MinMax from roentgen.util import MinMax
DEBUG: bool = False DEBUG: bool = False
@ -445,34 +446,35 @@ class Constructor:
continue continue
priority: int priority: int
icon_set: Icon icon: Icon
draw_outline: bool = True draw_outline: bool = True
if self.mode in ["time", "user-coloring"]: if self.mode in ["time", "user-coloring"]:
if not tags: if not tags:
continue continue
color = DEFAULT_COLOR
if self.mode == "user-coloring": if self.mode == "user-coloring":
color = get_user_color(node.user, self.seed) color = get_user_color(node.user, self.seed)
if self.mode == "time": if self.mode == "time":
color = get_time_color(node.timestamp, self.map_.time) color = get_time_color(node.timestamp, self.map_.time)
dot, _ = self.icon_extractor.get_path(DEFAULT_SMALL_SHAPE_ID) dot, _ = self.icon_extractor.get_path(DEFAULT_SMALL_SHAPE_ID)
icon_set = Icon([dot], [], color, set(), True) icon = Icon([ShapeSpecification(dot, color)], [], set())
priority = 0 priority = 0
draw_outline = False draw_outline = False
labels = [] labels = []
else: else:
icon_set, priority = self.scheme.get_icon( icon, priority = self.scheme.get_icon(
self.icon_extractor, tags self.icon_extractor, tags
) )
labels = self.scheme.construct_text(tags, True) labels = self.scheme.construct_text(tags, True)
self.nodes.append(Point( self.nodes.append(Point(
icon_set, labels, tags, flung, node.coordinates, icon, labels, tags, flung, node.coordinates,
priority=priority, draw_outline=draw_outline priority=priority, draw_outline=draw_outline
)) ))
missing_tags.update( missing_tags.update(
f"{key}: {tags[key]}" for key in tags f"{key}: {tags[key]}" for key in tags
if key not in icon_set.processed) if key not in icon.processed)
ui.progress_bar(-1, len(self.map_.node_map), text="Constructing nodes") ui.progress_bar(-1, len(self.map_.node_map), text="Constructing nodes")

View file

@ -11,7 +11,7 @@ from colour import Color
from svgwrite import Drawing from svgwrite import Drawing
from roentgen.icon import IconExtractor, Shape from roentgen.icon import IconExtractor, Shape
from roentgen.scheme import Scheme from roentgen.scheme import Scheme, ShapeSpecification
def draw_all_icons( def draw_all_icons(
@ -35,9 +35,18 @@ def draw_all_icons(
to_draw: List[Set[str]] = [] to_draw: List[Set[str]] = []
icons_file_name: str = "icons/icons.svg"
extractor: IconExtractor = IconExtractor(icons_file_name)
for element in scheme.icons: # type: Dict[str, Any] for element in scheme.icons: # type: Dict[str, Any]
if "icon" in element and set(element["icon"]) not in to_draw: if "icon" in element:
to_draw.append(set(element["icon"])) specifications = [
ShapeSpecification.from_structure(x, extractor, scheme)
for x in element["icon"]
]
ids = set(x.shape.id_ for x in specifications)
if ids not in to_draw:
to_draw.append(ids)
if "add_icon" in element and set(element["add_icon"]) not in to_draw: if "add_icon" in element and set(element["add_icon"]) not in to_draw:
to_draw.append(set(element["add_icon"])) to_draw.append(set(element["add_icon"]))
if "over_icon" not in element: if "over_icon" not in element:
@ -65,16 +74,13 @@ def draw_all_icons(
current_set not in to_draw): current_set not in to_draw):
to_draw.append(current_set) to_draw.append(current_set)
icons_file_name: str = "icons/icons.svg"
extractor: IconExtractor = IconExtractor(icons_file_name)
specified_ids: Set[str] = set() specified_ids: Set[str] = set()
for icons_to_draw in to_draw: # type: List[str] for icons_to_draw in to_draw: # type: List[str]
specified_ids |= icons_to_draw specified_ids |= icons_to_draw
print( print(
"Icons with no tag specification: \n " + "Icons with no tag specification: \n " +
", ".join(sorted(extractor.icons.keys() - specified_ids)) + "." ", ".join(sorted(extractor.shapes.keys() - specified_ids)) + "."
) )
draw_grid( draw_grid(

View file

@ -5,12 +5,10 @@ Author: Sergey Vartanov (me@enzet.ru).
""" """
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Dict, Optional from typing import Dict, Optional
from xml.dom.minidom import Document, Element, Node, parse from xml.dom.minidom import Document, Element, Node, parse
import numpy as np import numpy as np
import svgwrite
from colour import Color
from svgwrite import Drawing from svgwrite import Drawing
from roentgen import ui from roentgen import ui
@ -55,56 +53,6 @@ class Shape:
d=self.path, transform=f"translate({shift[0]},{shift[1]})" d=self.path, transform=f"translate({shift[0]},{shift[1]})"
) )
def draw(
self, svg: svgwrite.Drawing, point: np.array, color: Color,
opacity: float = 1.0, 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 color: fill color
: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.get_path(svg, point)
path.update({"fill": color.hex})
if outline:
opacity: float = 0.5
path.update({
"fill": color.hex,
"stroke": color.hex,
"stroke-width": 2.2,
"stroke-linejoin": "round",
})
if opacity != 1.0:
path.update({"opacity": opacity})
if tags:
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
path.set_desc(title=title)
svg.add(path)
def is_standard(id_: str):
"""
Check whether SVG object ID is standard Inkscape ID.
"""
matcher = re.match(STANDARD_INKSCAPE_ID, id_)
return matcher is not None
if id_ == "base":
return True
for prefix in [
]:
matcher = re.match(prefix + "\\d+", id_)
if matcher:
return True
return False
class IconExtractor: class IconExtractor:
""" """
@ -118,10 +66,10 @@ class IconExtractor:
:param svg_file_name: input SVG file name with icons. File may contain :param svg_file_name: input SVG file name with icons. File may contain
any other irrelevant graphics. any other irrelevant graphics.
""" """
self.icons: Dict[str, Shape] = {} self.shapes: Dict[str, Shape] = {}
with open(svg_file_name) as input_file: with open(svg_file_name) as input_file:
content = parse(input_file) # type: Document content: Document = parse(input_file)
for element in content.childNodes: # type: Element for element in content.childNodes: # type: Element
if element.nodeName != "svg": if element.nodeName != "svg":
continue continue
@ -145,7 +93,7 @@ class IconExtractor:
return return
id_: str = node.getAttribute("id") id_: str = node.getAttribute("id")
if is_standard(id_): if re.match(STANDARD_INKSCAPE_ID, id_) is not None:
return return
if node.hasAttribute("d"): if node.hasAttribute("d"):
@ -172,7 +120,7 @@ class IconExtractor:
name = child_node.childNodes[0].nodeValue name = child_node.childNodes[0].nodeValue
break break
self.icons[id_] = Shape(path, point, id_, name) self.shapes[id_] = Shape(path, point, id_, name)
else: else:
ui.error(f"not standard ID {id_}") ui.error(f"not standard ID {id_}")
@ -182,8 +130,8 @@ class IconExtractor:
:param id_: string icon identifier :param id_: string icon identifier
""" """
if id_ in self.icons: if id_ in self.shapes:
return self.icons[id_], True return self.shapes[id_], True
ui.error(f"no such icon ID {id_}") ui.error(f"no such shape ID {id_}")
return self.icons[DEFAULT_SHAPE_ID], False return self.shapes[DEFAULT_SHAPE_ID], False

View file

@ -7,7 +7,7 @@ from colour import Color
from roentgen.color import is_bright from roentgen.color import is_bright
from roentgen.icon import Shape from roentgen.icon import Shape
from roentgen.osm_reader import Tagged from roentgen.osm_reader import Tagged
from roentgen.scheme import Icon from roentgen.scheme import Icon, ShapeSpecification
from roentgen.text import Label from roentgen.text import Label
DEFAULT_FONT: str = "Roboto" DEFAULT_FONT: str = "Roboto"
@ -71,17 +71,17 @@ class Point(Tagged):
Draw main shape for one node. Draw main shape for one node.
""" """
if ( if (
self.icon.main_icon and self.icon.main_icon[0].is_default() and
(not self.icon.main_icon[0].is_default() or not self.icon.extra_icons
self.is_for_node)
): ):
position = self.point + np.array((0, self.y)) return
self.main_icon_painted: bool = self.draw_point_shape(
svg, self.icon.main_icon, position = self.point + np.array((0, self.y))
position, self.icon.color, occupied, self.main_icon_painted: bool = self.draw_point_shape(
tags=self.tags) svg, self.icon.main_icon, position, occupied, tags=self.tags
if self.main_icon_painted: )
self.y += 16 if self.main_icon_painted:
self.y += 16
def draw_extra_shapes( def draw_extra_shapes(
self, svg: svgwrite.Drawing, occupied: Optional[Occupied] = None self, svg: svgwrite.Drawing, occupied: Optional[Occupied] = None
@ -108,14 +108,14 @@ class Point(Tagged):
for shape_ids in self.icon.extra_icons: for shape_ids in self.icon.extra_icons:
self.draw_point_shape( self.draw_point_shape(
svg, shape_ids, self.point + np.array((left, self.y)), svg, shape_ids, self.point + np.array((left, self.y)),
Color("#888888"), occupied) occupied=occupied)
left += 16 left += 16
if self.icon.extra_icons: if self.icon.extra_icons:
self.y += 16 self.y += 16
def draw_point_shape( def draw_point_shape(
self, svg: svgwrite.Drawing, shapes: List[Shape], position, self, svg: svgwrite.Drawing, shapes: List[ShapeSpecification], position,
fill: Color, occupied, tags: Optional[Dict[str, str]] = None occupied, tags: Optional[Dict[str, str]] = None
) -> bool: ) -> bool:
""" """
Draw one combined icon and its outline. Draw one combined icon and its outline.
@ -129,16 +129,13 @@ class Point(Tagged):
# Draw outlines. # Draw outlines.
if self.draw_outline: if self.draw_outline:
for icon in shapes: # type: Shape for shape in shapes:
bright: bool = is_bright(fill) shape.draw(svg, position, outline=True)
color: Color = Color("black") if bright else Color("white")
opacity: float = 0.7 if bright else 0.5
icon.draw(svg, position, color, opacity=opacity, outline=True)
# Draw icons. # Draw icons.
for icon in shapes: # type: Shape for shape in shapes: # type: ShapeSpecification
icon.draw(svg, position, fill, tags=tags) shape.draw(svg, position, tags=tags)
if occupied: if occupied:
overlap: int = occupied.overlap overlap: int = occupied.overlap

View file

@ -3,32 +3,102 @@ Röntgen drawing scheme.
Author: Sergey Vartanov (me@enzet.ru). Author: Sergey Vartanov (me@enzet.ru).
""" """
import copy
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional, Set, Tuple, Union from typing import Any, Dict, List, Optional, Set, Tuple, Union
import numpy as np
import svgwrite
import yaml import yaml
from colour import Color from colour import Color
from roentgen.color import is_bright
from roentgen.icon import DEFAULT_SHAPE_ID, IconExtractor, Shape from roentgen.icon import DEFAULT_SHAPE_ID, IconExtractor, Shape
from roentgen.text import Label, get_address, get_text from roentgen.text import Label, get_address, get_text
DEFAULT_COLOR: Color = Color("#444444") 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 @dataclass
class Icon: class Icon:
""" """
Node representation: icons and color. Node representation: icons and color.
""" """
main_icon: List[Shape] # list of icons main_icon: List[ShapeSpecification] # list of shapes
extra_icons: List[List[Shape]] # list of lists of icons extra_icons: List[List[ShapeSpecification]] # list of lists of shapes
color: Color # fill color of all icons
# tag keys that were processed to create icon set (other # tag keys that were processed to create icon set (other
# tag keys should be displayed by text or ignored) # tag keys should be displayed by text or ignored)
processed: Set[str] processed: Set[str]
is_default: bool
@dataclass @dataclass
@ -148,9 +218,7 @@ class Scheme:
try: try:
return Color(color) return Color(color)
except ValueError: except ValueError:
pass return DEFAULT_COLOR
return DEFAULT_COLOR
def is_no_drawable(self, key: str) -> bool: def is_no_drawable(self, key: str) -> bool:
""" """
@ -199,72 +267,79 @@ class Scheme:
if tags_hash in self.cache: if tags_hash in self.cache:
return self.cache[tags_hash] return self.cache[tags_hash]
main_icon_id: Optional[List[str]] = None main_icon: List[ShapeSpecification] = []
extra_icon_ids: List[List[str]] = [] extra_icons: List[List[ShapeSpecification]] = []
processed: Set[str] = set() processed: Set[str] = set()
fill: Color = DEFAULT_COLOR
priority: int = 0 priority: int = 0
for index, matcher in enumerate(self.icons): for index, matcher in enumerate(self.icons):
# type: (int, Dict[str, Any]) index: int
matcher: Dict[str, Any]
matched: bool = is_matched(matcher, tags) matched: bool = is_matched(matcher, tags)
matcher_tags: Set[str] = matcher["tags"].keys()
if not matched: if not matched:
continue continue
priority = len(self.icons) - index priority = len(self.icons) - index
if "draw" in matcher and not matcher["draw"]: if "draw" in matcher and not matcher["draw"]:
processed |= set(matcher["tags"].keys()) processed |= set(matcher_tags)
if "icon" in matcher: if "icon" in matcher:
main_icon_id = copy.deepcopy(matcher["icon"]) main_icon = [
processed |= set(matcher["tags"].keys()) ShapeSpecification.from_structure(x, icon_extractor, self)
for x in matcher["icon"]
]
processed |= set(matcher_tags)
if "over_icon" in matcher: if "over_icon" in matcher:
if main_icon_id: # TODO: check main icon in under icons if main_icon:
main_icon_id += matcher["over_icon"] main_icon += [
for key in matcher["tags"].keys(): ShapeSpecification.from_structure(
x, icon_extractor, self
)
for x in matcher["over_icon"]
]
for key in matcher_tags:
processed.add(key) processed.add(key)
if "add_icon" in matcher: if "add_icon" in matcher:
extra_icon_ids += [matcher["add_icon"]] extra_icons += [[
for key in matcher["tags"].keys(): ShapeSpecification.from_structure(
x, icon_extractor, self, Color("#888888")
)
for x in matcher["add_icon"]
]]
for key in matcher_tags:
processed.add(key) processed.add(key)
if "color" in matcher: if "color" in matcher:
fill = self.get_color(matcher["color"]) assert False
for key in matcher["tags"].keys(): if "set_main_color" in matcher:
processed.add(key) 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 for tag_key in tags: # type: str
if (tag_key.endswith(":color") or if (tag_key.endswith(":color") or
tag_key.endswith(":colour")): tag_key.endswith(":colour")):
fill = self.get_color(tags[tag_key]) color = self.get_color(tags[tag_key])
processed.add(tag_key) processed.add(tag_key)
for tag_key in tags: # type: str for tag_key in tags: # type: str
if tag_key in ["color", "colour"]: if tag_key in ["color", "colour"]:
fill = self.get_color(tags[tag_key]) color = self.get_color(tags[tag_key])
processed.add(tag_key) processed.add(tag_key)
keys_left = list(filter( if color:
lambda x: x not in processed and for shape_specification in main_icon:
not self.is_no_drawable(x), tags.keys() shape_specification.color = color
))
is_default: bool = False keys_left = [
if not main_icon_id and not extra_icon_ids and keys_left: x for x in tags.keys()
main_icon_id = [DEFAULT_SHAPE_ID] if x not in processed and not self.is_no_drawable(x)
is_default = True ]
main_icon: List[Shape] = [] default_shape, _ = icon_extractor.get_path(DEFAULT_SHAPE_ID)
if main_icon_id: if not main_icon:
main_icon = list(map( main_icon = [ShapeSpecification(default_shape)]
lambda x: icon_extractor.get_path(x)[0], main_icon_id
))
extra_icons: List[List[Shape]] = [] returned: Icon = Icon(main_icon, extra_icons, processed)
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
)
self.cache[tags_hash] = returned, priority self.cache[tags_hash] = returned, priority
return returned, priority return returned, priority
@ -283,8 +358,9 @@ class Scheme:
priority = element["priority"] priority = element["priority"]
for key in element: # type: str for key in element: # type: str
if key not in [ if key not in [
"tags", "no_tags", "priority", "level", "icon", "tags", "no_tags", "priority", "level", "icon", "r", "r1",
"r", "r1", "r2"]: "r2"
]:
value = element[key] value = element[key]
if isinstance(value, str) and value.endswith("_color"): if isinstance(value, str) and value.endswith("_color"):
value = self.get_color(value) value = self.get_color(value)
@ -374,6 +450,9 @@ class Scheme:
if k in tags: if k in tags:
texts.append(Label(tags[k], Color("#444444"))) texts.append(Label(tags[k], Color("#444444")))
tags.pop(k) tags.pop(k)
if "height" in tags:
texts.append(Label(f"{tags['height']} m"))
tags.pop("height")
for tag in tags: for tag in tags:
if self.is_writable(tag): if self.is_writable(tag):
texts.append(Label(tags[tag])) texts.append(Label(tags[tag]))

View file

@ -141,31 +141,24 @@ node_icons:
icon: [electricity] icon: [electricity]
# plant=* # plant=*
- tags: {plant: christmas_trees} - tags: {plant: christmas_trees}
icon: [christmas_tree] icon: [{shape: christmas_tree, color: orchard_border_color}]
color: orchard_border_color
# produce=* # produce=*
- tags: {produce: apple} - tags: {produce: apple}
icon: [apple] icon: [{shape: apple, color: orchard_border_color}]
color: orchard_border_color
- tags: {produce: christmas_trees} - tags: {produce: christmas_trees}
icon: [christmas_tree] icon: [{shape: christmas_tree, color: orchard_border_color}]
color: orchard_border_color
- tags: {produce: pear} - tags: {produce: pear}
icon: [pear] icon: [{shape: pear, color: orchard_border_color}]
color: orchard_border_color
# trees=* # trees=*
- tags: {trees: apple_trees} - tags: {trees: apple_trees}
icon: [apple] icon: [{shape: apple, color: orchard_border_color}]
color: orchard_border_color
- tags: {trees: pear_trees} - tags: {trees: pear_trees}
icon: [pear] icon: [{shape: pear, color: orchard_border_color}]
color: orchard_border_color
# Bigger objects # Bigger objects
- tags: {waterway: waterfall} - tags: {waterway: waterfall}
icon: [waterfall] icon: [{shape: waterfall, color: water_border_color}]
color: water_border_color
- tags: {natural: cliff} - tags: {natural: cliff}
icon: [cliff] icon: [cliff]
- tags: {natural: peak} - tags: {natural: peak}
@ -361,19 +354,15 @@ node_icons:
# Emergency # Emergency
- tags: {emergency: defibrillator} - tags: {emergency: defibrillator}
icon: [defibrillator] icon: [{shape: defibrillator, color: emergency_color}]
color: emergency_color
- tags: {emergency: fire_extinguisher} - tags: {emergency: fire_extinguisher}
icon: [fire_extinguisher] icon: [{shape: fire_extinguisher, color: emergency_color}]
color: emergency_color
- tags: {emergency: fire_hydrant} - tags: {emergency: fire_hydrant}
icon: [fire_hydrant] icon: [fire_hydrant]
- tags: {emergency: life_ring} - tags: {emergency: life_ring}
icon: [life_ring] icon: [{shape: life_ring, color: emergency_color}]
color: emergency_color
- tags: {emergency: phone} - tags: {emergency: phone}
icon: [sos_phone] icon: [{shape: sos_phone, color: emergency_color}]
color: emergency_color
# Transport-important middle objects # Transport-important middle objects
@ -628,8 +617,7 @@ node_icons:
- tags: {amenity: clock} - tags: {amenity: clock}
icon: [clock] icon: [clock]
- tags: {amenity: fountain} - tags: {amenity: fountain}
icon: [fountain] icon: [{shape: fountain, color: water_border_color}]
color: water_border_color
- tags: {amenity: waste_basket} - tags: {amenity: waste_basket}
icon: [waste_basket] icon: [waste_basket]
- tags: {highway: street_lamp} - tags: {highway: street_lamp}
@ -666,50 +654,38 @@ node_icons:
icon: [lowered_kerb] icon: [lowered_kerb]
# Trees # Trees
- tags: {natural: tree} - tags: {natural: tree}
icon: [tree] icon: [{shape: tree, color: tree_color}]
color: tree_color
- tags: {leaf_type: broadleaved} - tags: {leaf_type: broadleaved}
icon: [tree_with_leaf] icon: [{shape: tree_with_leaf, color: tree_color}]
color: tree_color
- tags: {leaf_type: needleleaved} - tags: {leaf_type: needleleaved}
icon: [needleleaved_tree] icon: [{shape: needleleaved_tree, color: tree_color}]
color: tree_color
- tags: {leaf_type: palm} - tags: {leaf_type: palm}
icon: [palm] icon: [{shape: palm, color: tree_color}]
color: tree_color
- tags: {natural: tree, leaf_type: broadleaved} - tags: {natural: tree, leaf_type: broadleaved}
icon: [tree_with_leaf] icon: [{shape: tree_with_leaf, color: tree_color}]
color: tree_color
- tags: {natural: tree, leaf_type: needleleaved} - tags: {natural: tree, leaf_type: needleleaved}
icon: [needleleaved_tree] icon: [{shape: needleleaved_tree, color: tree_color}]
color: tree_color
- tags: {natural: tree, leaf_type: palm} - tags: {natural: tree, leaf_type: palm}
icon: [palm] icon: [{shape: palm, color: tree_color}]
color: tree_color
- tags: {natural: tree, type: conifer} - tags: {natural: tree, type: conifer}
icon: [needleleaved_tree] icon: [{shape: needleleaved_tree, color: tree_color}]
color: tree_color
- tags: {leaf_cycle: deciduous} - tags: {leaf_cycle: deciduous}
color: decidious_color set_main_color: decidious_color
- tags: {leaf_cycle: evergreen} - tags: {leaf_cycle: evergreen}
color: evergreen_color set_main_color: evergreen_color
- tags: {natural: tree, leaf_cycle: deciduous} - tags: {natural: tree, leaf_cycle: deciduous}
color: decidious_color set_main_color: decidious_color
- tags: {natural: tree, leaf_cycle: evergreen} - tags: {natural: tree, leaf_cycle: evergreen}
color: evergreen_color set_main_color: evergreen_color
- tags: {natural: bush} - tags: {natural: bush}
icon: [bush] icon: [{shape: bush, color: tree_color}]
color: tree_color
# Tree genus # Tree genus
- tags: {natural: tree, genus: Betula} - tags: {natural: tree, genus: Betula}
icon: [betula] icon: [{shape: betula, color: tree_color}]
color: tree_color
- tags: {natural: tree, "genus:en": Birch} - tags: {natural: tree, "genus:en": Birch}
icon: [betula] icon: [{shape: betula, color: tree_color}]
color: tree_color
- tags: {natural: tree, "genus:ru": Берёза} - tags: {natural: tree, "genus:ru": Берёза}
icon: [betula] icon: [{shape: betula, color: tree_color}]
color: tree_color
- tags: {railway: buffer_stop} - tags: {railway: buffer_stop}
icon: [buffer_stop] icon: [buffer_stop]