mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-01 10:21:54 +02:00
Refactor way and relation processing.
This commit is contained in:
parent
bbc28b4c61
commit
5506eb3082
7 changed files with 256 additions and 310 deletions
109
data/tags.yml
109
data/tags.yml
|
@ -19,6 +19,7 @@ colors:
|
|||
direction_view_color: "E0F0FF"
|
||||
direction_camera_color: "0088FF"
|
||||
|
||||
allotments_color: "D0E0D0"
|
||||
beach_color: "F0E0C0"
|
||||
boundary_color: "880088"
|
||||
building_border_color: "E0D0C0" # "AAAAAA"
|
||||
|
@ -28,11 +29,13 @@ colors:
|
|||
desert_color: "F0E0D0"
|
||||
emergency_color: "DD2222"
|
||||
farmland_color: "FFEEBB"
|
||||
ferry_terminal_color: "AABBDD"
|
||||
foot_border_color: "FFFFFF"
|
||||
foot_color: "B89A74"
|
||||
grass_border_color: "BFD098"
|
||||
grass_color: "CFE0A8"
|
||||
guide_strips_color: "228833"
|
||||
hidden_color: "000000"
|
||||
indoor_border_color: "C0B8B0"
|
||||
indoor_color: "E8E4E0"
|
||||
meadow_border_color: "BFD078"
|
||||
|
@ -40,6 +43,8 @@ colors:
|
|||
orchard_color: "B8DCA4"
|
||||
outline_color: "FFFFFF"
|
||||
parking_color: "DDCC99"
|
||||
pitch_color: "AADDCC"
|
||||
pitch_border_color: "88BBAA"
|
||||
platform_border_color: "AAAAAA"
|
||||
platform_color: "CCCCCC"
|
||||
playground_border_color: "663300"
|
||||
|
@ -69,7 +74,7 @@ colors:
|
|||
"rose": "FF007F" # Wikipedia
|
||||
"slate_blue": "6A5ACD" # W3C slateblue
|
||||
|
||||
nodes:
|
||||
node_icons:
|
||||
|
||||
# No draw
|
||||
|
||||
|
@ -281,6 +286,8 @@ nodes:
|
|||
icon: [garages]
|
||||
- tags: {tourism: hotel}
|
||||
icon: [bed]
|
||||
- tags: {building: hotel}
|
||||
icon: [bed]
|
||||
- tags: {tourism: hostel}
|
||||
icon: [two_beds]
|
||||
- tags: {tourism: motel} # Tourism?
|
||||
|
@ -295,12 +302,14 @@ nodes:
|
|||
icon: [toy_horse]
|
||||
- tags: {building: kindergarten, amenity: kindergarten}
|
||||
icon: [toy_horse]
|
||||
- tags: {amenity: school, __country: Japan}
|
||||
icon: [japan_elementary_school]
|
||||
- tags: {building: office}
|
||||
icon: [briefcase]
|
||||
- tags: {amenity: post_office}
|
||||
icon: [letter]
|
||||
- tags: {amenity: post_office, __country: Japan}
|
||||
icon: [japan_post]
|
||||
- tags: {amenity: school, __country: Japan}
|
||||
icon: [japan_elementary_school]
|
||||
- tags: {office: telecommunication}
|
||||
icon: [telephone]
|
||||
|
||||
|
@ -692,6 +701,22 @@ nodes:
|
|||
- tags: {"payment:credit_cards": "yes"}
|
||||
add_icon: [credit_card]
|
||||
|
||||
line_icons:
|
||||
- tags: {building: hotel}
|
||||
icon: [bed]
|
||||
- tags: {building: office}
|
||||
icon: [briefcase]
|
||||
- tags: {leisure: playground}
|
||||
icon: [toy_horse]
|
||||
- tags: {amenity: ferry_terminal}
|
||||
icon: [anchor]
|
||||
- tags: {amenity: parking}
|
||||
icon: [parking]
|
||||
- tags: {aeroway: landingpad}
|
||||
icon: [booster_landing]
|
||||
- tags: {aeroway: helipad}
|
||||
icon: [h]
|
||||
|
||||
ways:
|
||||
- tags: {indoor: area}
|
||||
stroke: indoor_border_color
|
||||
|
@ -753,6 +778,8 @@ ways:
|
|||
priority: 21
|
||||
stroke: wood_color
|
||||
stroke-width: 5
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
- tags: {natural: water}
|
||||
fill: water_color
|
||||
# stroke: water_border_color
|
||||
|
@ -773,6 +800,9 @@ ways:
|
|||
- tags: {natural: scree}
|
||||
fill: scree_color
|
||||
|
||||
- tags: {landuse: allotments}
|
||||
fill: allotments_color
|
||||
priority: 20
|
||||
- tags: {landuse: conservation}
|
||||
fill: grass_color
|
||||
priority: 20
|
||||
|
@ -803,17 +833,20 @@ ways:
|
|||
# Hidden land use
|
||||
|
||||
- tags: {landuse: residential}
|
||||
fill: none
|
||||
stroke: none
|
||||
fill: hidden_color
|
||||
opacity: 0.05
|
||||
- tags: {landuse: commercial}
|
||||
fill: none
|
||||
stroke: none
|
||||
fill: hidden_color
|
||||
opacity: 0.05
|
||||
- tags: {landuse: military}
|
||||
fill: none
|
||||
stroke: none
|
||||
fill: hidden_color
|
||||
opacity: 0.05
|
||||
- tags: {landuse: railway}
|
||||
fill: none
|
||||
stroke: none
|
||||
fill: hidden_color
|
||||
opacity: 0.05
|
||||
- tags: {landuse: industrial}
|
||||
fill: hidden_color
|
||||
opacity: 0.05
|
||||
|
||||
- tags: {building: "*"}
|
||||
fill: building_color
|
||||
|
@ -822,15 +855,19 @@ ways:
|
|||
# fill: building_color
|
||||
# stroke: building_border_color
|
||||
|
||||
- tags: {amenity: ferry_terminal}
|
||||
fill: ferry_terminal_color
|
||||
priority: 50
|
||||
- tags: {amenity: parking}
|
||||
fill: parking_color
|
||||
opacity: 0.5
|
||||
icon: parking
|
||||
|
||||
- tags: {aeroway: landingpad}
|
||||
fill: "#000000"
|
||||
opacity: 0.1
|
||||
icon: parking
|
||||
- tags: {aeroway: helipad}
|
||||
fill: "#440044"
|
||||
opacity: 0.1
|
||||
|
||||
- tags: {waterway: riverbank}
|
||||
fill: none # water_color
|
||||
|
@ -859,68 +896,81 @@ ways:
|
|||
r2: 15
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: trunk}
|
||||
r2: 13
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: primary}
|
||||
r2: 11
|
||||
stroke: primary_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41.9
|
||||
- tags: {highway: motorway_link}
|
||||
r2: 9
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: secondary}
|
||||
r2: 9
|
||||
stroke: secondary_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41.8
|
||||
- tags: {highway: tertiary}
|
||||
r2: 7
|
||||
stroke: tertiary_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41.7
|
||||
- tags: {highway: unclassified}
|
||||
r2: 5
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: residential}
|
||||
r2: 5
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: living_street}
|
||||
r2: 4
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: service}
|
||||
no_tags: {service: parking_aisle}
|
||||
r2: 3
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: service, service: parking_aisle}
|
||||
r2: 2
|
||||
stroke: road_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: track}
|
||||
stroke-width: 1.5
|
||||
stroke: track_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: [footway, pedestrian, cycleway]}
|
||||
no_tags: {area: "yes"}
|
||||
stroke-width: 3
|
||||
stroke: foot_border_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 41
|
||||
- tags: {highway: steps}
|
||||
stroke-width: 6
|
||||
|
@ -935,69 +985,82 @@ ways:
|
|||
r: 15
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: trunk}
|
||||
r: 13
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: primary}
|
||||
r: 11
|
||||
stroke: primary_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42.9
|
||||
- tags: {highway: secondary}
|
||||
r: 9
|
||||
stroke: secondary_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42.8
|
||||
- tags: {highway: motorway_link}
|
||||
r: 9
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: tertiary}
|
||||
r: 7
|
||||
stroke: tertiary_color
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42.7
|
||||
- tags: {highway: unclassified}
|
||||
r: 5
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: residential}
|
||||
r: 5
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: living_street}
|
||||
r: 4
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: service}
|
||||
no_tags: {service: parking_aisle}
|
||||
r: 3
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: service, service: parking_aisle}
|
||||
r: 2
|
||||
stroke: "#FFFFFF"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: 42
|
||||
- tags: {highway: [footway, pedestrian]}
|
||||
no_tags: {area: "yes"}
|
||||
stroke-width: 1.5
|
||||
stroke-dasharray: 7,3
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
stroke: foot_color
|
||||
priority: 42
|
||||
- tags: {highway: [footway, pedestrian], area: "yes"}
|
||||
stroke: none
|
||||
fill: "#DDDDDD"
|
||||
stroke-linecap: round
|
||||
stroke-linejoin: round
|
||||
priority: -55 # FIXME
|
||||
- tags: {highway: cycleway}
|
||||
no_tags: {area: "yes"}
|
||||
|
@ -1040,15 +1103,13 @@ ways:
|
|||
fill: grass_color
|
||||
opacity: 0.5
|
||||
- tags: {leisure: pitch}
|
||||
fill: playground_color
|
||||
stroke: playground_border_color
|
||||
fill: pitch_color
|
||||
stroke: pitch_border_color
|
||||
stroke-width: 1
|
||||
opacity: 0.2
|
||||
priority: 21
|
||||
- tags: {leisure: playground}
|
||||
fill: playground_color
|
||||
opacity: 0.2
|
||||
icon: toy_horse
|
||||
priority: 21
|
||||
- tags: {leisure: swimming_pool}
|
||||
fill: water_color
|
||||
|
@ -1064,31 +1125,31 @@ ways:
|
|||
fill: none
|
||||
stroke: "#000000"
|
||||
stroke-width: 1
|
||||
opacity: 0.6
|
||||
opacity: 0.35
|
||||
priority: 40
|
||||
- tags: {barrier: wall}
|
||||
fill: none
|
||||
stroke: "#000000"
|
||||
stroke-width: 1
|
||||
opacity: 0.5
|
||||
opacity: 0.3
|
||||
priority: 40
|
||||
- tags: {barrier: [fence, retaining_wall]}
|
||||
fill: none
|
||||
stroke: "#000000"
|
||||
stroke-width: 1
|
||||
opacity: 0.4
|
||||
opacity: 0.25
|
||||
priority: 40
|
||||
- tags: {barrier: handrail}
|
||||
fill: none
|
||||
stroke: "#000000"
|
||||
stroke-width: 1
|
||||
opacity: 0.3
|
||||
opacity: 0.2
|
||||
priority: 40
|
||||
- tags: {barrier: kerb}
|
||||
fill: none
|
||||
stroke: "#000000"
|
||||
stroke-width: 1
|
||||
opacity: 0.2
|
||||
opacity: 0.15
|
||||
priority: 40
|
||||
|
||||
- tags: {border: "*"}
|
||||
|
@ -1138,9 +1199,7 @@ tags_to_skip: [
|
|||
"FIXME", "source_ref", "naptan:verified:note", "fixme",
|
||||
"building:levels", "ref:opendataparis:adresse", "indoor",
|
||||
"level:ref", "ref:opendataparis:geo_point_2d", "created_by",
|
||||
"mapillary", "diameter_crown", "attribution"]
|
||||
"mapillary", "diameter_crown", "attribution", "curve_geometry"]
|
||||
|
||||
prefix_to_skip: [
|
||||
"source", "mapillary"]
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ Construct Röntgen nodes and ways.
|
|||
|
||||
Author: Sergey Vartanov (me@enzet.ru).
|
||||
"""
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from hashlib import sha256
|
||||
|
@ -13,14 +14,14 @@ import numpy as np
|
|||
|
||||
from roentgen import ui
|
||||
from roentgen.color import get_gradient_color
|
||||
from roentgen.extract_icon import DEFAULT_SMALL_SHAPE_ID
|
||||
from roentgen.icon import DEFAULT_SMALL_SHAPE_ID
|
||||
from roentgen.flinger import Flinger
|
||||
from roentgen.osm_reader import (
|
||||
Map, OSMMember, OSMRelation, OSMWay, OSMNode, Tagged)
|
||||
from roentgen.scheme import IconSet, Scheme
|
||||
from roentgen.scheme import IconSet, Scheme, LineStyle
|
||||
from roentgen.util import MinMax
|
||||
|
||||
DEBUG: bool = False
|
||||
DEBUG: bool = True
|
||||
TIME_COLOR_SCALE: List[Color] = [
|
||||
Color("#581845"), Color("#900C3F"), Color("#C70039"), Color("#FF5733"),
|
||||
Color("#FFC300"), Color("#DAF7A6")]
|
||||
|
@ -86,16 +87,14 @@ class Figure(Tagged):
|
|||
"""
|
||||
def __init__(
|
||||
self, tags: Dict[str, str], inners: List[List[OSMNode]],
|
||||
outers: List[List[OSMNode]], style: Dict[str, Any],
|
||||
layer: float = 0.0):
|
||||
outers: List[List[OSMNode]], line_style: LineStyle):
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.tags: Dict[str, str] = tags
|
||||
self.inners: List[List[OSMNode]] = []
|
||||
self.outers: List[List[OSMNode]] = []
|
||||
self.style: Dict[str, Any] = style
|
||||
self.layer: float = layer
|
||||
self.line_style = line_style
|
||||
|
||||
for inner_nodes in inners:
|
||||
self.inners.append(make_clockwise(inner_nodes))
|
||||
|
@ -145,9 +144,9 @@ class Building(Figure):
|
|||
"""
|
||||
def __init__(
|
||||
self, tags: Dict[str, str], inners, outers, flinger: Flinger,
|
||||
style: Dict[str, Any], layer: float):
|
||||
line_style: LineStyle):
|
||||
|
||||
super().__init__(tags, inners, outers, style, layer)
|
||||
super().__init__(tags, inners, outers, line_style)
|
||||
|
||||
self.parts = []
|
||||
|
||||
|
@ -268,6 +267,13 @@ def get_path(nodes: List[OSMNode], shift: np.array, flinger: Flinger) -> str:
|
|||
return path
|
||||
|
||||
|
||||
def is_cycle(nodes) -> bool:
|
||||
"""
|
||||
Is way a cycle way or an area boundary.
|
||||
"""
|
||||
return nodes[0] == nodes[-1]
|
||||
|
||||
|
||||
class Constructor:
|
||||
"""
|
||||
Röntgen node and way constructor.
|
||||
|
@ -309,120 +315,74 @@ class Constructor:
|
|||
way: OSMWay = self.map_.way_map[way_id]
|
||||
if not self.check_level(way.tags):
|
||||
continue
|
||||
self.construct_way(way, way.tags, [], [way.nodes])
|
||||
self.construct_line(way, [], [way.nodes])
|
||||
|
||||
ui.progress_bar(-1, len(self.map_.way_map), text="Constructing ways")
|
||||
|
||||
def construct_way(
|
||||
self, way: Optional[OSMWay], tags: Dict[str, Any],
|
||||
def construct_line(
|
||||
self, line: Optional[Tagged],
|
||||
inners: List[List[OSMNode]], outers: List[List[OSMNode]]) -> None:
|
||||
"""
|
||||
Way construction.
|
||||
Way or relation construction.
|
||||
|
||||
:param way: OSM way
|
||||
:param tags: way tag dictionary
|
||||
:param line: OpenStreetMap way or relation
|
||||
:param inners: list of polygons that compose inner boundary
|
||||
:param outers: list of polygons that compose outer boundary
|
||||
"""
|
||||
layer: float = 0
|
||||
assert len(outers) >= 1
|
||||
|
||||
center_point, center_coordinates = None, None
|
||||
line_is_cycle: bool = is_cycle(outers[0])
|
||||
|
||||
if way is not None:
|
||||
center_point, center_coordinates = (
|
||||
line_center(way.nodes, self.flinger))
|
||||
line_center(outers[0], self.flinger))
|
||||
|
||||
if self.mode == "user-coloring":
|
||||
if not way:
|
||||
return
|
||||
user_color = get_user_color(way.user, self.seed)
|
||||
self.figures.append(
|
||||
Figure(
|
||||
way.tags, inners, outers,
|
||||
{"fill": "none", "stroke": user_color.hex,
|
||||
"stroke-width": 1}))
|
||||
user_color = get_user_color(line.user, self.seed)
|
||||
self.figures.append(Figure(
|
||||
line.tags, inners, outers,
|
||||
LineStyle({
|
||||
"fill": "none", "stroke": user_color.hex,
|
||||
"stroke-width": 1})))
|
||||
return
|
||||
|
||||
if self.mode == "time":
|
||||
if not way:
|
||||
return
|
||||
time_color = get_time_color(way.timestamp, self.map_.time)
|
||||
self.figures.append(
|
||||
Figure(
|
||||
way.tags, inners, outers,
|
||||
{"fill": "none", "stroke": time_color.hex,
|
||||
"stroke-width": 1}))
|
||||
time_color = get_time_color(line.timestamp, self.map_.time)
|
||||
self.figures.append(Figure(
|
||||
line.tags, inners, outers,
|
||||
LineStyle({
|
||||
"fill": "none", "stroke": time_color.hex,
|
||||
"stroke-width": 1})))
|
||||
return
|
||||
|
||||
if not tags:
|
||||
if not line.tags:
|
||||
return
|
||||
|
||||
appended: bool = False
|
||||
scale: float = self.flinger.get_scale(center_coordinates)
|
||||
|
||||
for element in self.scheme.ways: # type: Dict[str, Any]
|
||||
matched: bool = True
|
||||
for config_tag_key in element["tags"]: # type: str
|
||||
matcher = element["tags"][config_tag_key]
|
||||
if config_tag_key not in tags or \
|
||||
(matcher != "*" and
|
||||
tags[config_tag_key] != matcher and
|
||||
tags[config_tag_key] not in matcher):
|
||||
matched = False
|
||||
break
|
||||
if "no_tags" in element:
|
||||
for config_tag_key in element["no_tags"]: # type: str
|
||||
if (config_tag_key in tags and
|
||||
tags[config_tag_key] ==
|
||||
element["no_tags"][config_tag_key]):
|
||||
matched = False
|
||||
break
|
||||
if matched:
|
||||
style: Dict[str, Any] = {"fill": "none"}
|
||||
if "priority" in element:
|
||||
layer = element["priority"]
|
||||
for key in element: # type: str
|
||||
if key not in [
|
||||
"tags", "no_tags", "priority", "level", "icon",
|
||||
"r", "r2"]:
|
||||
value = element[key]
|
||||
if isinstance(value, str) and value.endswith("_color"):
|
||||
value = self.scheme.get_color(value)
|
||||
style[key] = value
|
||||
if center_coordinates is not None:
|
||||
if "r" in element:
|
||||
style["stroke-width"] = \
|
||||
element["r"] * \
|
||||
self.flinger.get_scale(center_coordinates)
|
||||
if "r2" in element:
|
||||
style["stroke-width"] = \
|
||||
element["r2"] * \
|
||||
self.flinger.get_scale(center_coordinates) + 2
|
||||
if "building" in tags:
|
||||
line_styles: List[LineStyle] = self.scheme.get_style(line.tags, scale)
|
||||
|
||||
for line_style in line_styles: # type: LineStyle
|
||||
if "building" in line.tags:
|
||||
self.add_building(Building(
|
||||
tags, inners, outers, self.flinger, style, layer))
|
||||
line.tags, inners, outers, self.flinger, line_style))
|
||||
else:
|
||||
self.figures.append(
|
||||
Figure(tags, inners, outers, style, layer))
|
||||
if center_point is not None and \
|
||||
(way.is_cycle() and "area" in tags and tags["area"]):
|
||||
icon_set: IconSet = self.scheme.get_icon(tags)
|
||||
Figure(line.tags, inners, outers, line_style))
|
||||
icon_set: IconSet = self.scheme.get_icon(line.tags, for_="line")
|
||||
self.nodes.append(Point(
|
||||
icon_set, tags, center_point, center_coordinates,
|
||||
icon_set, line.tags, center_point, center_coordinates,
|
||||
is_for_node=False))
|
||||
appended = True
|
||||
|
||||
if not appended:
|
||||
if not line_styles:
|
||||
if DEBUG:
|
||||
style: Dict[str, Any] = {
|
||||
"fill": "none", "stroke": Color("red").hex,
|
||||
"stroke-width": 1}
|
||||
self.figures.append(Figure(
|
||||
tags, inners, outers, style, layer))
|
||||
if (center_point is not None and
|
||||
way.is_cycle() and "area" in tags and tags["area"]):
|
||||
icon_set: IconSet = self.scheme.get_icon(tags)
|
||||
line.tags, inners, outers, LineStyle(style, 1000)))
|
||||
icon_set: IconSet = self.scheme.get_icon(line.tags)
|
||||
self.nodes.append(Point(
|
||||
icon_set, tags, center_point, center_coordinates,
|
||||
icon_set, line.tags, center_point, center_coordinates,
|
||||
is_for_node=False))
|
||||
|
||||
def construct_relations(self) -> None:
|
||||
|
@ -447,7 +407,7 @@ class Constructor:
|
|||
outer_ways.append(self.map_.way_map[member.ref])
|
||||
inners_path: List[List[OSMNode]] = glue(inner_ways)
|
||||
outers_path: List[List[OSMNode]] = glue(outer_ways)
|
||||
self.construct_way(None, tags, inners_path, outers_path)
|
||||
self.construct_line(relation, inners_path, outers_path)
|
||||
|
||||
def construct_nodes(self) -> None:
|
||||
"""
|
||||
|
@ -459,6 +419,8 @@ class Constructor:
|
|||
self.map_.node_map.keys(),
|
||||
key=lambda x: -self.map_.node_map[x].coordinates[0])
|
||||
|
||||
missing_tags = Counter()
|
||||
|
||||
for node_id in s: # type: int
|
||||
node_number += 1
|
||||
ui.progress_bar(
|
||||
|
@ -477,7 +439,6 @@ class Constructor:
|
|||
if not tags:
|
||||
continue
|
||||
icon_set.icons = [[DEFAULT_SMALL_SHAPE_ID]]
|
||||
break
|
||||
if self.mode == "user-coloring":
|
||||
icon_set.color = get_user_color(node.user, self.seed)
|
||||
if self.mode == "time":
|
||||
|
@ -485,4 +446,11 @@ class Constructor:
|
|||
|
||||
self.nodes.append(Point(icon_set, tags, flung, node.coordinates))
|
||||
|
||||
missing_tags.update(
|
||||
f"{key}: {tags[key]}" for key in tags
|
||||
if key not in icon_set.processed)
|
||||
|
||||
for t in missing_tags.most_common():
|
||||
print(t)
|
||||
|
||||
ui.progress_bar(-1, len(self.map_.node_map), text="Constructing nodes")
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
"""
|
||||
Extract icons from SVG file.
|
||||
|
||||
Author: Sergey Vartanov (me@enzet.ru).
|
||||
"""
|
||||
import re
|
||||
import xml.dom.minidom
|
||||
from typing import Dict
|
||||
from xml.dom.minidom import Element, Node
|
||||
|
||||
import numpy as np
|
||||
from svgwrite import Drawing
|
||||
|
||||
from roentgen import ui
|
||||
|
||||
DEFAULT_SHAPE_ID: str = "default"
|
||||
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
|
||||
STANDARD_INKSCAPE_ID: str = "(path|rect)\\d*"
|
||||
|
||||
GRID_STEP: int = 16
|
||||
|
||||
|
||||
class Icon:
|
||||
"""
|
||||
SVG icon path description.
|
||||
"""
|
||||
def __init__(self, path: str, offset: np.array, id_: str):
|
||||
"""
|
||||
:param path: SVG icon path
|
||||
:param offset: vector that should be used to shift the path
|
||||
:param id_: shape identifier
|
||||
"""
|
||||
assert path
|
||||
|
||||
self.path: str = path
|
||||
self.offset: np.array = offset
|
||||
self.id_: str = id_
|
||||
|
||||
def is_default(self) -> bool:
|
||||
"""
|
||||
Return true if icon is has a default shape that doesn't represent
|
||||
anything.
|
||||
"""
|
||||
return self.id_ in [DEFAULT_SHAPE_ID, DEFAULT_SMALL_SHAPE_ID]
|
||||
|
||||
def get_path(self, svg: Drawing, point: np.array):
|
||||
"""
|
||||
Draw icon into SVG file.
|
||||
|
||||
:param svg: SVG file to draw to
|
||||
:param point: icon position
|
||||
"""
|
||||
shift: np.array = self.offset + point
|
||||
|
||||
return svg.path(
|
||||
d=self.path, transform=f"translate({shift[0]},{shift[1]})")
|
||||
|
||||
|
||||
class IconExtractor:
|
||||
"""
|
||||
Extract icons from SVG file.
|
||||
|
||||
Icon is a single path with "id" attribute that aligned to 16×16 grid.
|
||||
"""
|
||||
def __init__(self, svg_file_name: str):
|
||||
"""
|
||||
:param svg_file_name: input SVG file name with icons. File may contain
|
||||
any other irrelevant graphics.
|
||||
"""
|
||||
self.icons: Dict[str, Icon] = {}
|
||||
|
||||
with open(svg_file_name) as input_file:
|
||||
content = xml.dom.minidom.parse(input_file)
|
||||
for element in content.childNodes:
|
||||
if element.nodeName == "svg":
|
||||
for node in element.childNodes: # type: Node
|
||||
if isinstance(node, Element):
|
||||
self.parse(node)
|
||||
|
||||
def parse(self, node: Element) -> None:
|
||||
"""
|
||||
Extract icon paths into a map.
|
||||
|
||||
:param node: XML node that contains icon
|
||||
"""
|
||||
if node.nodeName == "g":
|
||||
for sub_node in node.childNodes:
|
||||
if isinstance(sub_node, Element):
|
||||
self.parse(sub_node)
|
||||
return
|
||||
|
||||
if ("id" in node.attributes.keys() and
|
||||
"d" in node.attributes.keys() and
|
||||
node.attributes["id"].value):
|
||||
path: str = node.attributes["d"].value
|
||||
matcher = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
|
||||
if not matcher:
|
||||
ui.error(f"invalid path: {path}")
|
||||
return
|
||||
|
||||
def get_offset(value: float):
|
||||
""" Get negated icon offset from the origin. """
|
||||
return -int(value / GRID_STEP) * GRID_STEP - GRID_STEP / 2
|
||||
|
||||
point: np.array = np.array((
|
||||
get_offset(float(matcher.group(1))),
|
||||
get_offset(float(matcher.group(2)))))
|
||||
|
||||
id_: str = node.attributes["id"].value
|
||||
matcher = re.match(STANDARD_INKSCAPE_ID, id_)
|
||||
if not matcher:
|
||||
self.icons[id_] = Icon(node.attributes["d"].value, point, id_)
|
||||
|
||||
def get_path(self, id_: str) -> (Icon, bool):
|
||||
"""
|
||||
Get SVG path of the icon.
|
||||
|
||||
:param id_: string icon identifier
|
||||
"""
|
||||
if id_ in self.icons:
|
||||
return self.icons[id_], True
|
||||
|
||||
ui.error(f"no such icon ID {id_}")
|
||||
return self.icons[DEFAULT_SHAPE_ID], False
|
|
@ -7,7 +7,7 @@ import numpy as np
|
|||
from svgwrite import Drawing
|
||||
from typing import List, Dict, Any, Set
|
||||
|
||||
from roentgen.extract_icon import Icon, IconExtractor
|
||||
from roentgen.icon import Icon, IconExtractor
|
||||
from roentgen.scheme import Scheme
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@ def draw_grid(step: float = 24, columns: int = 16):
|
|||
|
||||
to_draw: List[Set[str]] = []
|
||||
|
||||
for element in scheme.nodes: # type: Dict[str, Any]
|
||||
for element in scheme.node_icons + scheme.line_icons: # type: Dict[str, Any]
|
||||
if "icon" in element:
|
||||
if set(element["icon"]) not in to_draw:
|
||||
to_draw.append(set(element["icon"]))
|
||||
|
|
|
@ -23,7 +23,7 @@ from roentgen.constructor import (
|
|||
Constructor, Point, Figure, TextStruct, Building, Segment)
|
||||
from roentgen.flinger import Flinger
|
||||
from roentgen.grid import draw_grid
|
||||
from roentgen.extract_icon import Icon, IconExtractor
|
||||
from roentgen.icon import Icon, IconExtractor
|
||||
from roentgen.osm_getter import get_osm
|
||||
from roentgen.osm_reader import Map, OSMReader
|
||||
from roentgen.scheme import Scheme
|
||||
|
@ -114,16 +114,6 @@ class Painter:
|
|||
text, (node.point[0], node.point[1] + text_y + 8),
|
||||
text_struct.fill, size=text_struct.size)
|
||||
|
||||
if self.show_missing_tags:
|
||||
for tag in node.tags: # type: str
|
||||
if not self.scheme.is_no_drawable(tag) and \
|
||||
tag not in node.icon_set.processed:
|
||||
text = f"{tag}: {node.tags[tag]}"
|
||||
self.draw_text(
|
||||
text, (node.point[0], node.point[1] + text_y + 18),
|
||||
Color("#734A08"))
|
||||
text_y += 10
|
||||
|
||||
def draw_text(
|
||||
self, text: str, point, fill: Color, size: float = 10,
|
||||
out_fill=Color("white"), out_opacity=1.0,
|
||||
|
@ -232,14 +222,14 @@ class Painter:
|
|||
"""
|
||||
Draw map.
|
||||
"""
|
||||
ways = sorted(constructor.figures, key=lambda x: x.layer)
|
||||
ways = sorted(constructor.figures, key=lambda x: x.line_style.priority)
|
||||
ways_length: int = len(ways)
|
||||
for index, way in enumerate(ways): # type: Figure
|
||||
ui.progress_bar(index, ways_length, step=10, text="Drawing ways")
|
||||
path_commands: str = way.get_path(self.flinger)
|
||||
if path_commands:
|
||||
path = Path(d=path_commands)
|
||||
path.update(way.style)
|
||||
path.update(way.line_style.style)
|
||||
self.svg.add(path)
|
||||
ui.progress_bar(-1, 0, text="Drawing ways")
|
||||
|
||||
|
@ -301,7 +291,7 @@ class Painter:
|
|||
shift = np.array([0, -way.get_levels() * height])
|
||||
path_commands: str = way.get_path(self.flinger, shift)
|
||||
path = Path(d=path_commands, opacity=1)
|
||||
path.update(way.style)
|
||||
path.update(way.line_style.style)
|
||||
path.update({"stroke-linejoin": "round"})
|
||||
self.svg.add(path)
|
||||
|
||||
|
@ -463,7 +453,7 @@ def check_level_number(tags: Dict[str, Any], level: float):
|
|||
return True
|
||||
|
||||
|
||||
def check_level_overground(tags: Dict[str, Any]):
|
||||
def check_level_overground(tags: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Check if element described by tags is overground.
|
||||
"""
|
||||
|
@ -548,7 +538,7 @@ def main(argv) -> None:
|
|||
|
||||
icon_extractor: IconExtractor = IconExtractor(ICONS_FILE_NAME)
|
||||
|
||||
def check_level(x):
|
||||
def check_level(x) -> bool:
|
||||
""" Draw objects on all levels. """
|
||||
return True
|
||||
|
||||
|
@ -556,11 +546,11 @@ def main(argv) -> None:
|
|||
if options.level == "overground":
|
||||
check_level = check_level_overground
|
||||
elif options.level == "underground":
|
||||
def check_level(x):
|
||||
def check_level(x) -> bool:
|
||||
""" Draw underground objects. """
|
||||
return not check_level_overground(x)
|
||||
else:
|
||||
def check_level(x):
|
||||
def check_level(x) -> bool:
|
||||
""" Draw objects on the specified level. """
|
||||
return not check_level_number(x, float(options.level))
|
||||
|
||||
|
@ -582,11 +572,3 @@ def main(argv) -> None:
|
|||
print("Writing output SVG...")
|
||||
svg.write(open(options.output_file_name, "w"))
|
||||
print("Done.")
|
||||
|
||||
top_missing_tags = \
|
||||
sorted(missing_tags.keys(), key=lambda x: -missing_tags[x])
|
||||
missing_tags_file = open(MISSING_TAGS_FILE_NAME, "w+")
|
||||
for tag in top_missing_tags:
|
||||
missing_tags_file.write(
|
||||
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
|
||||
missing_tags_file.close()
|
||||
|
|
|
@ -146,6 +146,8 @@ class OSMRelation(Tagged):
|
|||
|
||||
self.id_: int = id_
|
||||
self.members: List["OSMMember"] = []
|
||||
self.user: Optional[str] = None
|
||||
self.timestamp: Optional[datetime] = None
|
||||
|
||||
def parse_from_xml(self, text: str) -> "OSMRelation":
|
||||
"""
|
||||
|
@ -155,6 +157,10 @@ class OSMRelation(Tagged):
|
|||
"""
|
||||
self.id_ = int(get_value("id", text))
|
||||
|
||||
self.user = get_value("user", text)
|
||||
self.timestamp = datetime.strptime(
|
||||
get_value("timestamp", text), OSM_TIME_PATTERN)
|
||||
|
||||
return self
|
||||
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ import yaml
|
|||
|
||||
from colour import Color
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
from typing import Any, Dict, List, Optional, Set, Union
|
||||
|
||||
from roentgen.extract_icon import DEFAULT_SHAPE_ID
|
||||
from roentgen.icon import DEFAULT_SHAPE_ID
|
||||
|
||||
DEFAULT_COLOR: Color = Color("#444444")
|
||||
|
||||
|
@ -28,6 +28,12 @@ class IconSet:
|
|||
is_default: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class LineStyle:
|
||||
style: Dict[str, Union[int, float, str]]
|
||||
priority: float = 0.0
|
||||
|
||||
|
||||
class Scheme:
|
||||
"""
|
||||
Map style.
|
||||
|
@ -43,7 +49,9 @@ class Scheme:
|
|||
content: Dict[str, Any] = yaml.load(
|
||||
input_file.read(), Loader=yaml.FullLoader)
|
||||
|
||||
self.nodes: List[Dict[str, Any]] = content["nodes"]
|
||||
self.node_icons: List[Dict[str, Any]] = content["node_icons"]
|
||||
self.line_icons: List[Dict[str, Any]] = content["line_icons"]
|
||||
|
||||
self.ways: List[Dict[str, Any]] = content["ways"]
|
||||
|
||||
self.colors: Dict[str, str] = content["colors"]
|
||||
|
@ -83,7 +91,7 @@ class Scheme:
|
|||
if key in self.tags_to_write or key in self.tags_to_skip:
|
||||
return True
|
||||
for prefix in self.prefix_to_write + self.prefix_to_skip: # type: str
|
||||
if key[:len(prefix) + 1] == prefix + ":":
|
||||
if key[:len(prefix) + 1] == f"{prefix}:":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -94,24 +102,23 @@ class Scheme:
|
|||
|
||||
:param key: OpenStreetMap tag key
|
||||
"""
|
||||
if key in self.tags_to_skip:
|
||||
if key in self.tags_to_skip: # type: str
|
||||
return False
|
||||
if key in self.tags_to_write:
|
||||
if key in self.tags_to_write: # type: str
|
||||
return True
|
||||
for prefix in self.prefix_to_write:
|
||||
if key[:len(prefix) + 1] == prefix + ":":
|
||||
for prefix in self.prefix_to_write: # type: str
|
||||
if key[:len(prefix) + 1] == f"{prefix}:":
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_icon(self, tags: Dict[str, Any]) -> IconSet:
|
||||
def get_icon(self, tags: Dict[str, Any], for_: str = "node") -> IconSet:
|
||||
"""
|
||||
Construct icon set.
|
||||
|
||||
:param tags: OpenStreetMap element tags dictionary
|
||||
"""
|
||||
tags_hash: str = \
|
||||
",".join(tags.keys()) + ":" + \
|
||||
",".join(map(lambda x: str(x), tags.values()))
|
||||
tags_hash: str = (
|
||||
",".join(tags.keys()) + ":" + ",".join(map(str, tags.values())))
|
||||
|
||||
if tags_hash in self.cache:
|
||||
return self.cache[tags_hash]
|
||||
|
@ -121,14 +128,16 @@ class Scheme:
|
|||
processed: Set[str] = set()
|
||||
fill: Color = DEFAULT_COLOR
|
||||
|
||||
for matcher in self.nodes: # type: Dict[str, Any]
|
||||
rules = self.node_icons if for_ == "node" else self.line_icons
|
||||
|
||||
for matcher in rules: # type: Dict[str, Any]
|
||||
matched: bool = True
|
||||
for key in matcher["tags"]: # type: str
|
||||
if key not in tags:
|
||||
matched = False
|
||||
break
|
||||
if matcher["tags"][key] != "*" and \
|
||||
matcher["tags"][key] != tags[key]:
|
||||
if (matcher["tags"][key] != "*" and
|
||||
matcher["tags"][key] != tags[key]):
|
||||
matched = False
|
||||
break
|
||||
if "no_tags" in matcher:
|
||||
|
@ -136,7 +145,8 @@ class Scheme:
|
|||
if no_tag in tags.keys():
|
||||
matched = False
|
||||
break
|
||||
if matched:
|
||||
if not matched:
|
||||
continue
|
||||
if "draw" in matcher and not matcher["draw"]:
|
||||
processed |= set(matcher["tags"].keys())
|
||||
if "icon" in matcher:
|
||||
|
@ -157,8 +167,8 @@ class Scheme:
|
|||
processed.add(key)
|
||||
|
||||
for tag_key in tags: # type: str
|
||||
if tag_key in ["color", "colour"] or tag_key.endswith(":color") or \
|
||||
tag_key.endswith(":colour"):
|
||||
if (tag_key in ["color", "colour"] or tag_key.endswith(":color") or
|
||||
tag_key.endswith(":colour")):
|
||||
fill = self.get_color(tags[tag_key])
|
||||
processed.add(tag_key)
|
||||
|
||||
|
@ -181,3 +191,48 @@ class Scheme:
|
|||
self.cache[tags_hash] = returned
|
||||
|
||||
return returned
|
||||
|
||||
def get_style(self, tags: Dict[str, Any], scale):
|
||||
|
||||
line_styles = []
|
||||
|
||||
for element in self.ways: # type: Dict[str, Any]
|
||||
priority = 0
|
||||
matched: bool = True
|
||||
for config_tag_key in element["tags"]: # type: str
|
||||
matcher = element["tags"][config_tag_key]
|
||||
if (config_tag_key not in tags or
|
||||
(matcher != "*" and
|
||||
tags[config_tag_key] != matcher and
|
||||
tags[config_tag_key] not in matcher)):
|
||||
matched = False
|
||||
break
|
||||
if "no_tags" in element:
|
||||
for config_tag_key in element["no_tags"]: # type: str
|
||||
if (config_tag_key in tags and
|
||||
tags[config_tag_key] ==
|
||||
element["no_tags"][config_tag_key]):
|
||||
matched = False
|
||||
break
|
||||
if not matched:
|
||||
continue
|
||||
style: Dict[str, Any] = {"fill": "none"}
|
||||
if "priority" in element:
|
||||
priority = element["priority"]
|
||||
for key in element: # type: str
|
||||
if key not in [
|
||||
"tags", "no_tags", "priority", "level", "icon",
|
||||
"r", "r2"]:
|
||||
value = element[key]
|
||||
if isinstance(value, str) and value.endswith("_color"):
|
||||
value = self.get_color(value)
|
||||
style[key] = value
|
||||
if "r" in element:
|
||||
style["stroke-width"] = (element["r"] * scale)
|
||||
if "r2" in element:
|
||||
style["stroke-width"] = (element["r2"] * scale + 2)
|
||||
|
||||
line_styles.append(LineStyle(style, priority))
|
||||
|
||||
return line_styles
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue