Refactor constructor; change code style.

This commit is contained in:
Sergey Vartanov 2020-09-05 19:44:28 +03:00
parent 8509e3c0e2
commit 21be74a8a2
18 changed files with 759 additions and 683 deletions

4
.idea/Roentgen.iml generated
View file

@ -5,10 +5,6 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="pytest" />
</component>

View file

@ -18,6 +18,7 @@ colors:
outline_color: "FFFFFF"
beach_color: "F0E0C0"
boundary_color: "880088"
building_color: "F8F0E8" # "D0D0C0"
building_border_color: "DDDDDD" # "AAAAAA"
construction_color: "CCCCCC"
@ -232,6 +233,8 @@ tags:
icon: [cupcake]
- tags: {shop: mall}
icon: [bag]
- tags: {shop: alcohol}
icon: [bottle]
- tags: {shop: mall, building: 'yes'}
icon: [bag]
- tags: {shop: convenience}
@ -527,6 +530,291 @@ tags:
- tags: {'payment:credit_cards': 'yes'}
add_icon: [credit_card]
ways:
- tags: {indoor: area}
stroke: indoor_border_color
stroke-width: 1
fill: indoor_color
layer: 10
- tags: {indoor: corridor}
stroke: indoor_color
stroke-width: 1
fill: indoor_color
layer: 11
- tags: {indoor: ["yes", room, elevator]}
stroke: indoor_color
stroke-width: 1
fill: indoor_color
layer: 12
- tags: {indoor: column}
stroke: indoor_color
stroke-width: 1
fill: indoor_color
layer: 13
- tags: {natural: wood}
fill: wood_color
layer: 21
- tags: {natural: grassland}
fill: grass_color
stroke: grass_border_color
layer: 20
- tags: {natural: scrub}
fill: wood_color
layer: 21
- tags: {natural: sand}
fill: sand_color
layer: 20
- tags: {natural: beach}
fill: beach_color
layer: 20
- tags: {natural: desert}
fill: desert_color
layer: 20
- tags: {natural: forest}
fill: wood_color
layer: 21
- tags: {natural: tree_row}
layer: 21
stroke: wood_color
stroke-width: 5
- tags: {natural: water}
fill: water_color
stroke: water_border_color
stroke-width: 1
layer: 21
- tags: {landuse: grass}
fill: grass_color
layer: 20
stroke: grass_border_color
- tags: {landuse: conservation}
fill: grass_color
layer: 20
- tags: {landuse: forest}
fill: wood_color
layer: 20
- tags: {landuse: garages}
fill: parking_color
layer: 21
- tags: {landuse: construction}
fill: construction_color
- tags: {landuse: residential}
fill: none
stroke: none
- tags: {landuse: commercial}
fill: none
stroke: none
- tags: {building: "*"}
fill: building_color
stroke: building_border_color
- tags: {amenity: parking}
fill: parking_color
opacity: 0.5
icon: parking
- tags: {waterway: riverbank}
fill: water_color
stroke: water_border_color
stroke-width: 1
- tags: {railway: subway}
stroke-width: 10
stroke: "#DDDDDD"
layer: 41
- tags: {railway: [narrow_gauge, tram]}
stroke-width: 2
stroke: "#000000"
layer: 41
- tags: {railway: platform}
fill: platform_color
stroke-width: 1
stroke: platform_border_color
layer: 41
- tags: {highway: motorway}
stroke-width: 33
stroke: road_border_color
layer: 41
- tags: {highway: trunk}
stroke-width: 31
stroke: road_border_color
layer: 41
- tags: {highway: primary}
stroke-width: 29
stroke: primary_border_color
layer: 41
- tags: {highway: secondary}
stroke-width: 27
stroke: road_border_color
layer: 41
- tags: {highway: tertiary}
stroke-width: 25
stroke: road_border_color
layer: 41
- tags: {highway: unclassified}
stroke-width: 17
stroke: road_border_color
layer: 41
- tags: {highway: residential}
stroke-width: 17
stroke: road_border_color
layer: 41
- tags: {highway: service}
no_tags: {service: parking_aisle}
stroke-width: 11
stroke: road_border_color
layer: 41
- tags: {highway: service, service: parking_aisle}
stroke-width: 7
stroke: road_border_color
layer: 41
- tags: {highway: track}
stroke-width: 3
stroke: road_border_color
layer: 41
- tags: {highway: [footway, pedestrian, cycleway]}
no_tags: {area: "yes"}
stroke-width: 3
stroke: foot_border_color
layer: 41
- tags: {highway: steps}
stroke-width: 6
stroke: foot_border_color
- tags: {highway: motorway}
stroke-width: 31
stroke: "#FFFFFF"
layer: 42
- tags: {highway: trunk}
stroke-width: 29
stroke: "#FFFFFF"
layer: 42
- tags: {highway: primary}
stroke-width: 27
stroke: primary_color
layer: 42
- tags: {highway: secondary}
stroke-width: 25
stroke: "#FFFFFF"
layer: 42
- tags: {highway: tertiary}
stroke-width: 23
stroke: "#FFFFFF"
layer: 42
- tags: {highway: unclassified}
stroke-width: 15
stroke: "#FFFFFF"
layer: 42
- tags: {highway: residential}
stroke-width: 15
stroke: "#FFFFFF"
layer: 42
- tags: {highway: service, service: parking_aisle}
stroke-width: 5
stroke: "#FFFFFF"
layer: 42
- tags: {highway: service}
no_tags: {service: parking_aisle}
stroke-width: 9
stroke: "#FFFFFF"
layer: 42
- tags: {highway: track}
stroke-width: 3
stroke: road_border_color
layer: 42
- tags: {highway: [footway, pedestrian]}
no_tags: {area: "yes"}
stroke-width: 1.5
stroke-dasharray: 7,3
stroke-linecap: round
stroke: foot_color
layer: 42
- tags: {highway: [footway, pedestrian], area: "yes"}
stroke: none
fill: "#DDDDDD"
layer: -55 # FIXME
- tags: {highway: cycleway}
no_tags: {area: "yes"}
stroke-width: 1
stroke: cycle_color
stroke-dasharray: 8,2
stroke-linecap: butt
layer: 42
- tags: {highway: steps, conveying: "*"}
stroke-width: 5
stroke-dasharray: 1.5,2
stroke-linecap: butt
stroke: "#888888"
layer: 42
- tags: {highway: steps}
no_tags: {conveying: "*"}
stroke-width: 5
stroke-dasharray: 1.5,2
stroke-linecap: butt
stroke: foot_color
layer: 42
- tags: {highway: path}
stroke-width: 1
stroke-dasharray: 5,5
stroke-linecap: butt
stroke: foot_color
layer: 42
- tags: {leisure: playground}
fill: playground_color
opacity: 0.2
icon: toy_horse
layer: 21
- tags: {leisure: garden}
fill: grass_color
layer: 21
- tags: {leisure: pitch}
fill: playground_color
opacity: 0.2
layer: 21
- tags: {leisure: park}
fill: grass_color
opacity: 0.5
- tags: {barrier: hedge}
fill: none
stroke: wood_color
stroke-width: 4
layer: 40
- tags: {barrier: [fence, retaining_wall]}
fill: none
stroke: "#000000"
stroke-width: 1
opacity: 0.4
layer: 40
- tags: {barrier: handrail}
fill: none
stroke: "#000000"
stroke-width: 1
opacity: 0.3
layer: 40
- tags: {barrier: kerb}
fill: none
stroke: "#000000"
stroke-width: 1
opacity: 0.2
layer: 40
- tags: {border: "*"}
stroke: "#FF0000"
stroke-width: 0.5
stroke-dasharray: 10,20
- tags: {"area:highway": "*"}
- tags: {boundary: "*"}
stroke: boundary_color
stroke-width: 1
stroke-dasharray: 10,5
layer: 60
tags_to_write: [
"operator", "opening_hours", "cuisine", "network", "website",
"website_2", "STIF:zone", "opening_hours:url", "phone",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 416 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Before After
Before After

View file

@ -5,10 +5,11 @@ from hashlib import sha256
from datetime import datetime
from typing import Any, Dict, List, Optional, Set
from roentgen import process, ui
from roentgen import ui
from roentgen.extract_icon import DEFAULT_SMALL_SHAPE_ID
from roentgen.flinger import Geo, GeoFlinger
from roentgen.osm_reader import OSMMember, OSMRelation, OSMWay
from roentgen.scheme import Scheme
from roentgen.scheme import IconSet, Scheme
class Node:
@ -16,17 +17,16 @@ class Node:
Node in Röntgen terms.
"""
def __init__(
self, shapes, tags: Dict[str, str], x: float, y: float, color: str,
path: Optional[str], processed, priority: int = 0):
self.shapes = shapes
self, icon_set: IconSet, tags: Dict[str, str],
point: (float, float), path: Optional[str],
priority: int = 0, is_for_node: bool = True):
self.icon_set: IconSet = icon_set
self.tags = tags
self.x = x
self.y = y
self.color = color
self.point = point
self.path = path
self.processed = processed
self.priority = priority
self.layer = 0
self.is_for_node = is_for_node
class Way:
@ -34,12 +34,12 @@ class Way:
Way in Röntgen terms.
"""
def __init__(
self, kind: str, nodes, path, style, layer: float = 0.0,
priority: float = 0, levels=None):
self, kind: str, nodes, path, style: Dict[str, Any],
layer: float = 0.0, priority: float = 0, levels=None):
self.kind = kind
self.nodes = nodes
self.path = path
self.style = style
self.style: Dict[str, Any] = style
self.layer = layer
self.priority = priority
self.levels = levels
@ -219,8 +219,10 @@ class Constructor:
nodes = None
center_point = None
if way:
c = line_center(
center_point = line_center(
map(lambda x: self.map_.node_map[x], way.nodes), self.flinger)
nodes = way.nodes
@ -230,8 +232,8 @@ class Constructor:
user_color = get_user_color(way.user, self.seed)
self.ways.append(
Way("way", nodes, path,
f"fill:none;stroke:#{user_color};"
f"stroke-width:1;"))
{"fill": "none", "stroke": "#" + user_color,
"stroke-width": 1}))
return
if self.mode == "time":
@ -240,339 +242,69 @@ class Constructor:
time_color = get_time_color(way.timestamp)
self.ways.append(
Way("way", nodes, path,
f"fill:none;stroke:#{time_color};"
f"stroke-width:1;"))
{"fill": "none", "stroke": "#" + time_color,
"stroke-width": 1}))
return
# Indoor features
if not tags:
return
if "indoor" in tags:
v = tags["indoor"]
style = \
f"stroke:#{self.color('indoor_border_color')};" \
f"stroke-width:1;"
if v == "area":
style += f"fill:#{self.color('indoor_color')};"
layer += 10
elif v == "corridor":
style += f"fill:#{self.color('indoor_color')};"
layer += 11
elif v in ["yes", "room", "elevator"]:
style += f"fill:#{self.color('indoor_color')};"
layer += 12
elif v == "column":
style += f"fill:#{self.color('indoor_border_color')};"
layer += 13
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Natural
if "natural" in tags:
v = tags["natural"]
style = "stroke:none;"
if v == "wood":
style += f"fill:#{self.color('wood_color')};"
layer += 21
elif v == "grassland":
style = \
f"fill:#{self.color('grass_color')};" \
f"stroke:#{self.color('grass_border_color')};"
layer += 20
elif v == "scrub":
style += f"fill:#{self.color('wood_color')};"
layer += 21
elif v == "sand":
style += f"fill:#{self.color('sand_color')};"
layer += 20
elif v == "beach":
style += f"fill:#{self.color('beach_color')};"
layer += 20
elif v == "desert":
style += f"fill:#{self.color('desert_color')};"
layer += 20
elif v == "forest":
style += f"fill:#{self.color('wood_color')};"
layer += 21
elif v == "tree_row":
style += \
f"fill:none;stroke:#{self.color('wood_color')};" \
f"stroke-width:5;"
layer += 21
elif v == "water":
style = \
f"fill:#{self.color('water_color')};" \
f"stroke:#{self.color('water_border_color')};" \
f"stroke-width:1.0;"
layer += 21
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Landuse
if "landuse" in tags:
style = "fill:none;stroke:none;"
if tags["landuse"] == "grass":
style = \
f"fill:#{self.color('grass_color')};" \
f"stroke:#{self.color('grass_border_color')};"
layer += 20
elif tags["landuse"] == "conservation":
style = f"fill:#{self.color('grass_color')};stroke:none;"
layer += 20
elif tags["landuse"] == "forest":
style = f"fill:#{self.color('wood_color')};stroke:none;"
layer += 20
elif tags["landuse"] == "garages":
style = f"fill:#{self.color('parking_color')};stroke:none;"
layer += 21
shapes, fill, processed = self.scheme.get_icon(tags)
if way:
self.nodes.append(Node(
shapes, tags, c[0], c[1], fill, path, processed))
elif tags["landuse"] == "construction":
layer += 20
style = f"fill:#{self.color('construction_color')};stroke:none;"
elif tags["landuse"] in ["residential", "commercial"]:
return
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Building
appended = False
kind: str = "way"
levels = None
if "building" in tags:
layer += 40
levels = 1
if "building:levels" in tags:
levels = float(tags["building:levels"])
style = \
f"fill:#{self.color('building_color')};" \
f"stroke:#{self.color('building_border_color')};" \
f"opacity:1.0;"
shapes, fill, processed = self.scheme.get_icon(tags)
if "height" in tags:
try:
layer += float(tags["height"])
except ValueError:
pass
if way:
self.nodes.append(
Node(shapes, tags, c[0], c[1], fill, path, processed, 1))
self.ways.append(Way(
"building", nodes, path, style, layer, 50, levels))
kind = "building"
if "building:levels" in tags:
levels = float(tags["building:levels"])
# Amenity
if "amenity" in tags:
style = "fill:none;stroke:none;"
layer += 21
if tags["amenity"] == "parking":
style = \
f"fill:#{self.color('parking_color')};" \
f"stroke:none;opacity:0.5;"
shapes, fill, processed = self.scheme.get_icon(tags)
if way:
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 "layer" in element:
layer += element["layer"]
for key in element: # type: str
if key not in ["tags", "no_tags", "layer", "level", "icon"]:
value = element[key]
if isinstance(value, str) and value.endswith("_color"):
value = "#" + self.scheme.get_color(value)
style[key] = value
self.ways.append(
Way(kind, nodes, path, style, layer, 50, levels))
if center_point and way.is_cycle() or \
"area" in tags and tags["area"]:
icon_set: IconSet = self.scheme.get_icon(tags)
self.nodes.append(Node(
shapes, tags, c[0], c[1], fill, path, processed, 1))
self.ways.append(Way("way", nodes, path, style, layer, 50))
icon_set, tags, center_point, path, is_for_node=False))
appended = True
# Waterway
if not appended:
style: Dict[str, Any] = {
"fill": "none", "stroke": "#FF0000", "stroke-width": 1}
self.ways.append(Way(kind, nodes, path, style, layer, 50, levels))
if center_point and way.is_cycle() or \
"area" in tags and tags["area"]:
icon_set: IconSet = self.scheme.get_icon(tags)
self.nodes.append(Node(
icon_set, tags, center_point, path, is_for_node=False))
if "waterway" in tags:
style = "fill:none;stroke:none;"
layer += 21
if tags["waterway"] == "riverbank":
style = \
f"fill:#{self.color('water_color')};" \
f"stroke:#{self.color('water_border_color')};" \
f"stroke-width:1.0;"
elif tags["waterway"] == "river":
style = \
f"fill:none;stroke:#{self.color('water_color')};" \
f"stroke-width:10.0;"
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Railway
if "railway" in tags:
layer += 41
v = tags["railway"]
style = \
"fill:none;stroke-dasharray:none;stroke-linejoin:round;" \
"stroke-linecap:round;stroke-width:"
if v == "subway":
style += "10;stroke:#DDDDDD;"
if v in ["narrow_gauge", "tram"]:
style += "2;stroke:#000000;"
if v == "platform":
style = \
f"fill:#{self.color('platform_color')};" \
f"stroke:#{self.color('platform_border_color')};" \
f"stroke-width:1;"
else:
return
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Highway
if "highway" in tags:
layer += 42
v = tags["highway"]
style = \
f"fill:none;stroke:#{self.color('road_border_color')};" \
f"stroke-dasharray:none;stroke-linejoin:round;" \
f"stroke-linecap:round;stroke-width:"
# Highway outline
if v == "motorway":
style += "33"
elif v == "trunk":
style += "31"
elif v == "primary":
style += f"29;stroke:#{self.color('primary_border_color')};"
elif v == "secondary":
style += "27"
elif v == "tertiary":
style += "25"
elif v == "unclassified":
style += "17"
elif v == "residential":
style += "17"
elif v == "service":
if "service" in tags and tags["service"] == "parking_aisle":
style += "7"
else:
style += "11"
elif v == "track":
style += "3"
elif v in ["footway", "pedestrian", "cycleway"]:
if not ("area" in tags and tags["area"] == "yes"):
style += f"3;stroke:#{self.color('foot_border_color')};"
elif v in ["steps"]:
style += \
f"6;stroke:#{self.color('foot_border_color')};" \
f"stroke-linecap:butt;"
else:
style = None
if style:
style += ";"
self.ways.append(Way(
"way", nodes, path, style, layer + 41, 50))
# Highway main shape
style = "fill:none;stroke:#FFFFFF;stroke-linecap:round;" + \
"stroke-linejoin:round;stroke-width:"
if v == "motorway":
style += "31"
elif v == "trunk":
style += "29"
elif v == "primary":
style += "27;stroke:#" + self.color('primary_color')
elif v == "secondary":
style += "25"
elif v == "tertiary":
style += "23"
elif v == "unclassified":
style += "15"
elif v == "residential":
style += "15"
elif v == "service":
if "service" in tags and tags["service"] == "parking_aisle":
style += "5"
else:
style += "9"
elif v == "cycleway":
style += \
f"1;stroke-dasharray:8,2;istroke-linecap:butt;" \
f"stroke:#{self.color('cycle_color')}"
elif v in ["footway", "pedestrian"]:
if "area" in tags and tags["area"] == "yes":
style += "1;stroke:none;fill:#DDDDDD"
layer -= 55 # FIXME!
else:
style += \
"1.5;stroke-dasharray:7,3;stroke-linecap:round;stroke:#"
if "guide_strips" in tags and tags["guide_strips"] == "yes":
style += self.color('guide_strips_color')
else:
style += self.color('foot_color')
elif v == "steps":
style += "5;stroke-dasharray:1.5,2;stroke-linecap:butt;" + \
"stroke:#"
if "conveying" in tags:
style += "888888"
else:
style += self.color('foot_color')
elif v == "path":
style += "1;stroke-dasharray:5,5;stroke-linecap:butt;" + \
"stroke:#" + self.color('foot_color')
style += ";"
self.ways.append(Way("way", nodes, path, style, layer + 42, 50))
if "oneway" in tags and tags["oneway"] == "yes" or \
"conveying" in tags and tags["conveying"] == "forward":
for k in range(7):
self.ways.append(Way(
"way", nodes, path,
f"fill:none;stroke:#EEEEEE;stroke-linecap:butt;"
f"stroke-width:{7 - k};stroke-dasharray:{k},{40 - k};",
layer + 43, 50))
if "access" in tags and tags["access"] == "private":
self.ways.append(Way(
"way", nodes, path,
f"fill:none;stroke:#{self.color('private_access_color')};"
f"stroke-linecap:butt;stroke-width:10;stroke-dasharray:1,5;"
f"opacity:0.4;", layer + 0.1, 50))
# Leisure
if "leisure" in tags:
layer += 21
if tags["leisure"] == "playground":
style = f"fill:#{self.color('playground_color')};opacity:0.2;"
# FIXME!!!!!!!!!!!!!!!!!!!!!
# if nodes:
# self.draw_point_shape("toy_horse", c[0], c[1], "444444")
elif tags["leisure"] == "garden":
style = f"fill:#{self.color('grass_color')};"
elif tags["leisure"] == "pitch":
style = f"fill:#{self.color('playground_color')};opacity:0.2;"
elif tags["leisure"] == "park":
return
else:
style = "fill:#FF0000;opacity:0.2;"
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Barrier
if "barrier" in tags:
style = "fill:none;stroke:none;"
layer += 40
if tags["barrier"] == "hedge":
style += \
f"fill:none;stroke:#{self.color('wood_color')};" \
f"stroke-width:4;"
elif tags["barrier"] == "fense":
style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.4;"
elif tags["barrier"] == "kerb":
style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.2;"
else:
style += "fill:none;stroke:#000000;stroke-width:1;opacity:0.3;"
self.ways.append(Way("way", nodes, path, style, layer, 50))
# Border
if "border" in tags:
style = "fill:none;stroke:none;"
style += "fill:none;stroke:#FF0000;stroke-width:0.5;" + \
"stroke-dahsarray:10,20;"
self.ways.append(Way("way", nodes, path, style, layer, 50))
if "area:highway" in tags:
style = "fill:none;stroke:none;"
if tags["area:highway"] == "yes":
style += "fill:#FFFFFF;stroke:#DDDDDD;stroke-width:1;"
self.ways.append(Way("way", nodes, path, style, layer, 50))
def construct_relations(self):
def construct_relations(self) -> None:
"""
Construct Röntgen ways from OSM relations.
"""
@ -603,7 +335,7 @@ class Constructor:
p += path + " "
self.construct_way(None, tags, p)
def construct_nodes(self):
def construct_nodes(self) -> None:
"""
Draw nodes.
"""
@ -618,33 +350,27 @@ class Constructor:
for node_id in s: # type: int
node_number += 1
ui.write_line(node_number, len(self.map_.node_map))
ui.progress_bar(node_number, len(self.map_.node_map))
node = self.map_.node_map[node_id]
flung = self.flinger.fling(Geo(node.lat, node.lon))
x = flung[0]
y = flung[1]
tags = node.tags
if not self.check_level(tags):
continue
shapes, fill, processed = self.scheme.get_icon(tags)
icon_set: IconSet = self.scheme.get_icon(tags)
if self.mode in ["time", "user-coloring"]:
if not tags:
continue
shapes = ["small"]
icon_set.icons = [[DEFAULT_SMALL_SHAPE_ID]]
if self.mode == "user-coloring":
fill = get_user_color(node.user, self.seed)
icon_set.color = get_user_color(node.user, self.seed)
if self.mode == "time":
fill = get_time_color(node.timestamp)
icon_set.color = get_time_color(node.timestamp)
if shapes == [] and tags != {}:
shapes = [["no"]]
self.nodes.append(Node(icon_set, tags, flung, None))
self.nodes.append(Node(
shapes, tags, x, y, fill, None, processed))
ui.write_line(-1, len(self.map_.node_map))
ui.progress_bar(-1, len(self.map_.node_map))
print("Nodes painted in " + str(datetime.now() - start_time) + ".")

View file

@ -5,11 +5,52 @@ Author: Sergey Vartanov (me@enzet.ru).
"""
import re
import xml.dom.minidom
from typing import Dict
import numpy as np
from svgwrite import Drawing
from roentgen import ui
DEFAULT_SHAPE_ID: str = "default"
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
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
"""
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:
"""
@ -22,7 +63,7 @@ class IconExtractor:
:param svg_file_name: input SVG file name with icons. File may contain
any other irrelevant graphics.
"""
self.icons: Dict[str, (str, float, float)] = {}
self.icons: Dict[str, Icon] = {}
with open(svg_file_name) as input_file:
content = xml.dom.minidom.parse(input_file)
@ -38,35 +79,39 @@ class IconExtractor:
:param node: XML node that contains icon
"""
if node.nodeName == "path":
if "id" in node.attributes.keys() and \
"d" in node.attributes.keys() and \
node.attributes["id"].value:
path = node.attributes["d"].value
m = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
if not m:
ui.error(f"invalid path: {path}")
else:
x = int(float(m.group(1)) / 16)
y = int(float(m.group(2)) / 16)
self.icons[node.attributes["id"].value] = \
(node.attributes["d"].value, x, y)
else:
if node.nodeName != "path":
for sub_node in node.childNodes:
self.parse(sub_node)
return
def get_path(self, id_: str) -> (str, float, float, bool):
if "id" in node.attributes.keys() and \
"d" in node.attributes.keys() and \
node.attributes["id"].value:
path = 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
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 ID
:param id_: string icon identifier
"""
if id_ in self.icons:
return list(self.icons[id_]) + [True]
else:
if id_ == "no":
return "M 4,4 L 4,10 10,10 10,4 z", 0, 0, False
if id_ == "small":
return "M 6,6 L 6,8 8,8 8,6 z", 0, 0, False
ui.error(f"no such icon ID {id_}")
return "M 4,4 L 4,10 10,10 10,4 z", 0, 0, False
return self.icons[id_], True
ui.error(f"no such icon ID {id_}")
return self.icons[DEFAULT_SHAPE_ID], False

View file

@ -1,98 +1,46 @@
#!/usr/bin/env python
"""
Author: Sergey Vartanov (me@enzet.ru)
"""
import math
import numpy as np
from typing import Optional
def get_ratio(maximum, minimum, ratio: float = 1):
return (maximum[0] - minimum[0]) * ratio / (maximum[1] - minimum[1])
class Flinger:
def map_(
value: float, current_min: float, current_max: float, target_min: float,
target_max: float):
"""
Flinger. Coordinates repositioning.
Map current value in bounds of current_min and current_max to bounds of
target_min and target_max.
"""
def __init__(
self, minimum, maximum, target_minimum=None, target_maximum=None,
ratio=None):
self.minimum = minimum
self.maximum = maximum
if not target_minimum:
target_minimum = [0, 0]
if not target_maximum:
target_maximum = maximum - minimum
space = [0, 0]
if ratio:
if ratio == "geo":
ratio = math.sin(
(90.0 - ((self.maximum[1] + self.minimum[1]) / 2.0))
/ 180.0 * math.pi)
current_ratio = get_ratio(self.maximum, self.minimum, ratio)
target_ratio = get_ratio(target_maximum, target_minimum)
if current_ratio >= target_ratio:
n = (target_maximum[0] - target_minimum[0]) / \
(maximum[0] - minimum[0]) / ratio
space[1] = \
((target_maximum[1] - target_minimum[1]) -
(maximum[1] - minimum[1]) * n) / 2.0
space[0] = 0
else:
n = (target_maximum[1] - target_minimum[1]) / \
(maximum[1] - minimum[1])
space[0] = \
((target_maximum[0] - target_minimum[0]) -
(maximum[0] - minimum[0]) * n) / 2.0
space[1] = 0
target_minimum[0] += space
target_maximum[0] += space
self.target_minimum = target_minimum
self.target_maximum = target_maximum
def fling(self, current):
"""
Fling current point to the surface.
:param current: vector to fling
"""
x = map_(
current[0], self.minimum[0], self.maximum[0],
self.target_minimum[0], self.target_maximum[0])
y = map_(
current[1], self.minimum[1], self.maximum[1],
self.target_minimum[1], self.target_maximum[1])
return [x, y]
return \
target_min + (value - current_min) / (current_max - current_min) * \
(target_max - target_min)
class Geo:
def __init__(self, lat, lon):
self.lat = lat
self.lon = lon
def __init__(self, lat: float, lon: float):
self.lat: float = lat
self.lon: float = lon
def __getitem__(self, item):
def __getitem__(self, item) -> Optional[float]:
if item == 0:
return self.lon
if item == 1:
return self.lat
return None
def __add__(self, other):
def __add__(self, other: "Geo") -> "Geo":
return Geo(self.lat + other.lat, self.lon + other.lon)
def __sub__(self, other):
def __sub__(self, other: "Geo") -> "Geo":
return Geo(self.lat - other.lat, self.lon - other.lon)
def __repr__(self):
def __repr__(self) -> str:
return f"{self.lat}, {self.lon}"
@ -157,15 +105,3 @@ class GeoFlinger:
self.minimum.lat, self.maximum.lat,
self.target_minimum[1], self.target_maximum[1])
return [x, y]
def map_(
value: float, current_min: float, current_max: float, target_min: float,
target_max: float):
"""
Map current value in bounds of current_min and current_max to bounds of
target_min and target_max.
"""
return \
target_min + (value - current_min) / (current_max - current_min) * \
(target_max - target_min)

View file

@ -1,24 +1,15 @@
"""
Author: Sergey Vartanov (me@enzet.ru).
"""
import numpy as np
import os
import random
import svgwrite
import yaml
from roentgen import extract_icon
from roentgen.extract_icon import Icon, IconExtractor
from typing import Any, Dict
def draw_icon(
svg, icon: Dict[str, Any], x: float, y: float,
color: str = "444444"):
svg.add(svg.path(
d=icon["path"], fill=f"#{color}", stroke="none",
transform=f'translate({icon["x"] + x},{icon["y"] + y})'))
from typing import List
def draw_grid():
@ -47,8 +38,7 @@ def draw_grid():
width: float = 24 * 16
x: float = step / 2
y: float = step / 2
point: np.array = np.array((step / 2, step / 2))
to_draw = []
@ -83,20 +73,19 @@ def draw_grid():
number: int = 0
icons = []
icons: List[List[Icon]] = []
extractor = extract_icon.IconExtractor(icons_file_name)
extractor: IconExtractor = IconExtractor(icons_file_name)
for icons_to_draw in to_draw:
drawed = False
icon_set = {"icons": []}
for icon in icons_to_draw:
path, xx, yy, _ = extractor.get_path(icon)
icon_set["icons"].append({"path": path,
"x": (- 8.0 - xx * 16),
"y": (- 8.0 - yy * 16)})
drawed = True
if drawed:
found: bool = False
icon_set: List[Icon] = []
for icon_id in icons_to_draw: # type: str
icon, got = extractor.get_path(icon_id)
assert got
icon_set.append(icon)
found = True
if found:
icons.append(icon_set)
number += 1
@ -109,13 +98,16 @@ def draw_grid():
for icon in icons:
background_color, foreground_color = random.choice(icon_colors)
svg.add(svg.rect(
(x - 2 - 8, y - 2 - 8), (20, 20), fill=f"#{background_color}"))
for i in icon["icons"]:
draw_icon(svg, i, x, y, foreground_color)
x += step
if x > width - 8:
x = step / 2
y += step
point - np.array((-10, -10)), (20, 20),
fill=f"#{background_color}"))
for i in icon: # type: Icon
path = i.get_path(svg, point)
path.update({"fill": f"#{foreground_color}"})
svg.add(path)
point += np.array((step, 0))
if point[0] > width - 8:
point[0] = step / 2
point += np.array((0, step))
height += step
print(f"Icons: {number}.")

View file

@ -7,19 +7,18 @@ import numpy as np
import os
import svgwrite
import sys
import yaml
from svgwrite.container import Group
from svgwrite.path import Path
from svgwrite.shapes import Circle, Rect
from svgwrite.text import Text
from typing import List
from typing import Dict, List
from roentgen import extract_icon
from roentgen import ui
from roentgen.constructor import Constructor, get_path
from roentgen.constructor import Constructor, get_path, Node, Way
from roentgen.flinger import GeoFlinger, Geo
from roentgen.grid import draw_grid
from roentgen.extract_icon import Icon, IconExtractor
from roentgen.osm_getter import get_osm
from roentgen.osm_reader import Map, OSMReader
from roentgen.scheme import Scheme
@ -29,70 +28,97 @@ TAGS_FILE_NAME: str = "data/tags.yml"
COLORS_FILE_NAME: str = "data/colors.yml"
MISSING_TAGS_FILE_NAME: str = "missing_tags.yml"
AUTHOR_MODE = "user-coloring"
CREATION_TIME_MODE = "time"
class Painter:
"""
Map drawing.
"""
def __init__(
self, show_missing_tags, overlap, draw_nodes, mode, draw_captions,
map_, flinger, svg: svgwrite.Drawing, icons, scheme: Scheme):
self, show_missing_tags: bool, overlap: int, draw_nodes: bool,
mode: str, draw_captions: bool, map_: Map, flinger: GeoFlinger,
svg: svgwrite.Drawing, icon_extractor: IconExtractor,
scheme: Scheme):
self.show_missing_tags = show_missing_tags
self.overlap = overlap
self.draw_nodes = draw_nodes
self.mode = mode
self.show_missing_tags: bool = show_missing_tags
self.overlap: int = overlap
self.draw_nodes: bool = draw_nodes
self.mode: str = mode
self.draw_captions = draw_captions
self.map_ = map_
self.flinger = flinger
self.map_: Map = map_
self.flinger: GeoFlinger = flinger
self.svg: svgwrite.Drawing = svg
self.icons = icons
self.icon_extractor = icon_extractor
self.scheme: Scheme = scheme
def draw_shapes(self, shapes, points, x, y, fill, tags, processed):
def draw_shapes(self, node: Node, points: List[List[float]]):
"""
Draw shapes for one node.
"""
if node.icon_set.is_default and not node.is_for_node:
return
xxx = -(len(shapes) - 1) * 8
left: float = -(len(node.icon_set.icons) - 1) * 8
if self.overlap != 0:
for shape in shapes:
for shape_ids in node.icon_set.icons:
has_space = True
for p in points[-1000:]:
if x + xxx - self.overlap <= p[0] <= x + xxx + self.overlap and \
y - self.overlap <= p[1] <= y + self.overlap:
if node.point[0] + left - self.overlap <= p[0] \
<= node.point[0] + left + self.overlap and \
node.point[1] - self.overlap <= p[1] \
<= node.point[1] + self.overlap:
has_space = False
break
if has_space:
self.draw_point_shape(shape, x + xxx, y, fill, tags=tags)
points.append([x + xxx, y])
xxx += 16
self.draw_point_shape(
shape_ids, (node.point[0] + left, node.point[1]),
node.icon_set.color, tags=node.tags)
points.append([node.point[0] + left, node.point[1]])
left += 16
else:
for shape in shapes:
self.draw_point_shape(shape, x + xxx, y, fill, tags=tags)
xxx += 16
def draw_texts(self, shapes, points, x, y, fill, tags, processed):
if self.draw_captions == "no":
return
for shape_ids in node.icon_set.icons:
self.draw_point_shape(
shape_ids, (node.point[0] + left, node.point[1]),
node.icon_set.color, tags=node.tags)
left += 16
def draw_texts(self, node: Node):
"""
Draw all labels.
"""
text_y: float = 0
write_tags = self.construct_text(tags, processed)
write_tags = self.construct_text(node.tags, node.icon_set.processed)
for text_struct in write_tags:
fill = text_struct["fill"] if "fill" in text_struct else "444444"
size = text_struct["size"] if "size" in text_struct else 10
text_y += size + 1
self.wr(text_struct["text"], x, y, fill, text_y, size=size)
text = text_struct["text"]
text = text.replace("&quot;", '"')
text = text.replace("&amp;", '&')
text = text[:26] + ("..." if len(text) > 26 else "")
self.draw_text(
text, (node.point[0], node.point[1] + text_y + 8),
fill, size=size)
if self.show_missing_tags:
for k in tags:
if not self.no_draw(k) and k not in processed:
text = k + ": " + tags[k]
self.draw_text(text, x, float(y) + text_y + 18, "734A08")
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),
"734A08")
text_y += 10
def draw_text(self, text: str, x, y, fill, size=10, out_fill="FFFFFF",
out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0):
def draw_text(
self, text: str, point, fill, size=10, out_fill="FFFFFF",
out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0):
"""
Drawing text.
@ -104,27 +130,24 @@ class Painter:
"""
if out_fill_2:
self.svg.add(Text(
text, (x, y), font_size=size, text_anchor="middle",
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{out_fill_2}",
stroke_linejoin="round", stroke_width=5,
stroke=f"#{out_fill_2}", opacity=out_opacity_2))
if out_fill:
self.svg.add(Text(
text, (x, y), font_size=size, text_anchor="middle",
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{out_fill}",
stroke_linejoin="round", stroke_width=3,
stroke=f"#{out_fill}", opacity=out_opacity))
self.svg.add(Text(
text, (x, y), font_size=size, text_anchor="middle",
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{fill}"))
def wr(self, text, x, y, fill, text_y, size=10):
text = text[:26] + ("..." if len(text) > 26 else "")
self.draw_text(text, x, float(y) + text_y + 8, fill, size=size)
def construct_text(self, tags, processed):
for key in tags:
tags[key] = tags[key].replace("&quot;", '"')
"""
Construct labels for not processed tags.
"""
texts = []
address: List[str] = []
name = None
@ -207,8 +230,7 @@ class Painter:
texts.append({"text": tags[k], "fill": "444444"})
tags.pop(k)
for tag in tags:
if self.to_write(tag) and not (tag in processed):
# texts.append({"text": tag + ": " + tags[tag]})
if self.scheme.is_writable(tag) and not (tag in processed):
texts.append({"text": tags[tag]})
return texts
@ -250,21 +272,27 @@ class Painter:
pass
def draw(self, nodes, ways, points):
"""
Draw map.
"""
ways = sorted(ways, key=lambda x: x.layer)
for way in ways:
if way.kind == "way":
if way.nodes:
path = get_path(way.nodes, [0, 0], self.map_, self.flinger)
self.svg.add(Path(d=path, style=way.style))
p = Path(d=path)
p.update(way.style)
self.svg.add(p)
else:
self.svg.add(Path(d=way.path, style=way.style))
p = Path(d=way.path)
p.update(way.style)
self.svg.add(p)
# Building shade
building_shade = Group(opacity=0.1)
for way in ways:
for way in ways: # type: Way
if way.kind != "building" or not way.nodes:
continue
shift = [-5, 5]
@ -290,7 +318,7 @@ class Painter:
# Building roof
for way in ways:
for way in ways: # type: Way
if way.kind != "building":
continue
if way.nodes:
@ -298,9 +326,13 @@ class Painter:
if way.levels:
shift = [0 * way.levels, min(-3, -1 * way.levels)]
path = get_path(way.nodes, shift, self.map_, self.flinger)
self.svg.add(Path(d=path, style=way.style, opacity=1))
p = Path(d=path, opacity=1)
p.update(way.style)
self.svg.add(p)
else:
self.svg.add(Path(d=way.path, style=way.style, opacity=1))
p = Path(d=way.path, opacity=1)
p.update(way.style)
self.svg.add(p)
# Trees
@ -310,58 +342,59 @@ class Painter:
"diameter_crown" in node.tags):
continue
self.svg.add(Circle(
(float(node.x), float(node.y)),
float(node.tags["diameter_crown"]) * 1.2,
node.point, float(node.tags["diameter_crown"]) * 1.2,
fill="#688C44", stroke="#688C44", opacity=0.3))
# All other nodes
nodes = sorted(nodes, key=lambda x: x.layer)
for node in nodes:
for node in nodes: # type: Node
if "natural" in node.tags and \
node.tags["natural"] == "tree" and \
"diameter_crown" in node.tags:
continue
self.draw_shapes(
node.shapes, points, node.x, node.y, node.color, node.tags,
node.processed)
self.draw_shapes(node, points)
for node in nodes:
if self.mode not in ["time", "user-coloring"]:
self.draw_texts(
node.shapes, points, node.x, node.y, node.color,
node.tags, node.processed)
if self.draw_captions == "no":
return
def draw_point_shape(self, name, x, y, fill, tags=None):
if not isinstance(name, list):
name = [name]
if self.mode not in ["time", "user-coloring"]:
for one_name in name:
shape, xx, yy, _ = self.icons.get_path(one_name)
self.draw_point_outline(
shape, x, y, fill, mode=self.mode, size=16, xx=xx, yy=yy)
for one_name in name:
shape, xx, yy, _ = self.icons.get_path(one_name)
self.draw_point(shape, x, y, fill, size=16, xx=xx, yy=yy, tags=tags)
for node in nodes: # type: Node
if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]:
self.draw_texts(node)
def draw_point(self, shape, x, y, fill, size=16, xx=0, yy=0, tags=None):
x = int(float(x))
y = int(float(y))
path = self.svg.path(
d=shape, fill=f"#{fill}", fill_opacity=1,
transform=f"translate({x - size / 2.0 - xx * 16},"
f"{y - size / 2.0 - yy * 16})")
path.set_desc(title="\n".join(map(lambda x: x + ": " + tags[x], tags)))
def draw_point_shape(self, shape_ids: List[str], point, fill, tags=None):
"""
Draw one icon.
"""
if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]:
for shape_id in shape_ids: # type: str
icon, _ = self.icon_extractor.get_path(shape_id)
self.draw_point_outline(icon, point, fill, mode=self.mode)
for shape_id in shape_ids: # type: str
icon, _ = self.icon_extractor.get_path(shape_id)
self.draw_point(icon, point, fill, tags=tags)
def draw_point(
self, icon: Icon, point: (float, float), fill: str,
tags: Dict[str, str] = None) -> None:
point = np.array(list(map(lambda x: int(x), point)))
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
path = icon.get_path(self.svg, point)
path.update({"fill": f"#{fill}"})
path.set_desc(title=title)
self.svg.add(path)
def draw_point_outline(
self, shape, x, y, fill, mode="default", size=16, xx=0, yy=0):
x = int(float(x))
y = int(float(y))
self, icon: Icon, point, fill, mode="default", size=16):
point = np.array(list(map(lambda x: int(x), point)))
opacity = 0.5
stroke_width = 2.2
outline_fill = self.scheme.get_color("outline_color")
if mode not in ["user-coloring", "time"]:
if mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
r = int(fill[0:2], 16)
g = int(fill[2:4], 16)
b = int(fill[4:6], 16)
@ -369,12 +402,13 @@ class Painter:
if Y > 200:
outline_fill = "000000"
opacity = 0.7
self.svg.add(self.svg.path(
d=shape, fill=f"#{outline_fill}", opacity=opacity,
stroke=f"#{outline_fill}", stroke_width=stroke_width,
stroke_linejoin="round",
transform=f"translate({x - size / 2.0 - xx * 16},"
f"{y - size / 2.0 - yy * 16})"))
path = icon.get_path(self.svg, point)
path.update({
"fill": f"#{outline_fill}", "opacity": opacity,
"stroke": f"#{outline_fill}", "stroke-width": stroke_width,
"stroke-linejoin": "round"})
self.svg.add(path)
def check_level_number(tags, level):
@ -395,6 +429,14 @@ def check_level_overground(tags):
for level in levels:
if level <= 0:
return False
if "layer" in tags:
levels = \
map(lambda x: float(x), tags["layer"].replace(",", ".").split(";"))
for level in levels:
if level <= 0:
return False
if "parking" in tags and tags["parking"] == "underground":
return False
return True
@ -410,7 +452,7 @@ def main():
sys.exit(1)
background_color = "#EEEEEE"
if options.mode in ["user-coloring", "time"]:
if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]:
background_color = "#111111"
if options.input_file_name:
@ -426,7 +468,7 @@ def main():
full = False # Full keys getting
if options.mode in ["user-coloring", "time"]:
if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]:
full = True
osm_reader = OSMReader()
@ -460,7 +502,7 @@ def main():
flinger = GeoFlinger(min1, max1, [0, 0], [w, h])
icons = extract_icon.IconExtractor(ICONS_FILE_NAME)
icon_extractor = IconExtractor(ICONS_FILE_NAME)
def check_level(x):
""" Draw objects on all levels. """
@ -489,7 +531,7 @@ def main():
show_missing_tags=options.show_missing_tags, overlap=options.overlap,
draw_nodes=options.draw_nodes, mode=options.mode,
draw_captions=options.draw_captions,
map_=map_, flinger=flinger, svg=svg, icons=icons,
map_=map_, flinger=flinger, svg=svg, icon_extractor=icon_extractor,
scheme=scheme)
painter.draw(constructor.nodes, constructor.ways, points)

View file

@ -1,3 +1,8 @@
"""
Getting OpenStreetMap data from the web.
Author: Sergey Vartanov (me@enzet.ru).
"""
import os
import re
import time

View file

@ -83,9 +83,15 @@ class OSMWay:
return self
def is_cycle(self) -> bool:
"""
Is way a cycle way or an area boundary.
"""
return self.nodes[0] == self.nodes[-1]
def try_to_glue(self, other: "OSMWay"):
"""
Create new combined way if ways share endpoints.
"""
if self.nodes[0] == other.nodes[0]:
return OSMWay(nodes=list(reversed(other.nodes[1:])) + self.nodes)
elif self.nodes[0] == other.nodes[-1]:
@ -142,6 +148,9 @@ def get_value(key: str, text: str):
class Map:
"""
The whole OpenStreetMap information about nodes, ways, and relations.
"""
def __init__(self):
self.node_map: Dict[int, OSMNode] = {}
self.way_map: Dict[int, OSMWay] = {}
@ -162,19 +171,18 @@ class OSMReader:
"""
Parse OSM XML representation.
"""
print(f"Line number counting for {file_name}...")
with open(file_name) as f:
for lines_number, _ in enumerate(f):
pass
print("Done.")
lines_number: int = sum(1 for _ in open(file_name))
print(f"Parsing OSM file {file_name}...")
input_file = open(file_name)
line = input_file.readline()
line_number = 0
element = None
while line != "":
line_number += 1
ui.write_line(line_number, lines_number)
ui.progress_bar(line_number, lines_number)
# Node parsing.
@ -237,6 +245,6 @@ class OSMReader:
line = input_file.readline()
input_file.close()
ui.write_line(-1, lines_number) # Complete progress bar.
ui.progress_bar(-1, lines_number) # Complete progress bar.
return self.map_

View file

@ -1,11 +1,37 @@
"""
Röntgen drawing scheme.
Author: Sergey Vartanov (me@enzet.ru).
"""
import copy
import yaml
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional, Set
from roentgen.extract_icon import DEFAULT_SHAPE_ID
DEFAULT_COLOR: str = "444444"
class IconSet:
"""
Node representation: icons and color.
"""
def __init__(
self, icons: List[List[str]], color: str, processed: Set[str],
is_default: bool):
"""
:param icons: list of lists of shape identifiers
:param color: fill color of all icons
:param processed: tag keys that were processed to create icon set (other
tag keys should be displayed by text or ignored)
"""
self.icons: List[List[str]] = icons
self.color: str = color
self.processed: Set[str] = processed
self.is_default = is_default
class Scheme:
"""
Map style.
@ -22,6 +48,7 @@ class Scheme:
yaml.load(open(file_name).read(), Loader=yaml.FullLoader)
self.tags: List[Dict[str, Any]] = content["tags"]
self.ways: List[Dict[str, Any]] = content["ways"]
self.colors: Dict[str, str] = content["colors"]
w3c_colors: Dict[str, str] = \
@ -34,7 +61,8 @@ class Scheme:
self.tags_to_skip: List[str] = content["tags_to_skip"]
self.prefix_to_skip: List[str] = content["prefix_to_skip"]
self.cache = {}
# Storage for created icon sets.
self.cache: Dict[str, IconSet] = {}
def get_color(self, color: str) -> str:
"""
@ -81,16 +109,24 @@ class Scheme:
return True
return False
def get_icon(self, tags: Dict[str, Any]):
def get_icon(self, tags: Dict[str, Any]) -> 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 = ",".join(tags.keys()) + ":" + \
",".join(map(lambda x: str(x), tags.values()))
if tags_hash in self.cache:
return self.cache[tags_hash]
main_icon = None
extra_icons = []
main_icon: Optional[List[str]] = None
extra_icons: List[List[str]] = []
processed = set()
fill = DEFAULT_COLOR
for matcher in self.tags:
matched = True
for key in matcher["tags"]:
@ -118,7 +154,7 @@ class Scheme:
for key in matcher["tags"].keys():
processed.add(key)
if "add_icon" in matcher:
extra_icons += matcher["add_icon"]
extra_icons += [matcher["add_icon"]]
for key in matcher["tags"].keys():
processed.add(key)
if "color" in matcher:
@ -126,17 +162,24 @@ class Scheme:
for key in matcher["tags"].keys():
processed.add(key)
for color_name in ["color", "colour", "building:colour"]:
if color_name in tags:
fill = self.get_color(tags[color_name])
processed.add(color_name)
for tag_key in tags: # type: str
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)
if main_icon:
returned = [main_icon] + extra_icons, fill, processed
result_set: List[List[str]] = [main_icon] + extra_icons
else:
returned = extra_icons, fill, processed
result_set: List[List[str]] = extra_icons
is_default: bool = False
if not result_set and tags:
result_set = [[DEFAULT_SHAPE_ID]]
is_default = True
returned: IconSet = IconSet(result_set, fill, processed, is_default)
self.cache[tags_hash] = returned
return returned

View file

@ -4,7 +4,10 @@ Author: Sergey Vartanov (me@enzet.ru).
import argparse
import sys
from typing import Optional
from typing import List, Optional
BOXES: List[str] = [" ", "", "", "", "", "", "", ""]
BOXES_LENGTH: int = len(BOXES)
def parse_options(args):
@ -43,24 +46,44 @@ def parse_options(args):
action="store_false",
default=True)
parser.add_argument(
"-nw", "--no-draw-ways", dest="draw_ways", action="store_false",
"-nw", "--no-draw-ways",
dest="draw_ways",
action="store_false",
default=True)
parser.add_argument(
"--captions", "--no-draw-captions", dest="draw_captions",
"--captions", "--no-draw-captions",
dest="draw_captions",
default="main")
parser.add_argument(
"--show-missing-tags", dest="show_missing_tags", action="store_true")
"--show-missing-tags",
dest="show_missing_tags",
action="store_true")
parser.add_argument(
"--no-show-missing-tags", dest="show_missing_tags",
"--no-show-missing-tags",
dest="show_missing_tags",
action="store_false")
parser.add_argument("--overlap", dest="overlap", default=12, type=int)
parser.add_argument(
"--show-index", dest="show_index", action="store_true")
"--overlap",
dest="overlap",
default=12,
type=int)
parser.add_argument(
"--no-show-index", dest="show_index", action="store_false")
parser.add_argument("--mode", default="normal")
parser.add_argument("--seed", default="")
parser.add_argument("--level", default=None)
"--show-index",
dest="show_index",
action="store_true")
parser.add_argument(
"--no-show-index",
dest="show_index",
action="store_false")
parser.add_argument(
"--mode",
default="normal")
parser.add_argument(
"--seed",
default="")
parser.add_argument(
"--level",
default=None)
arguments = parser.parse_args(args[1:])
@ -70,20 +93,27 @@ def parse_options(args):
return arguments
def write_line(number, total):
length = 20
parts = length * 8
boxes = [" ", "", "", "", "", "", "", ""]
def progress_bar(
number: int, total: int, length: int = 20, step: int = 1000) -> None:
"""
Draw progress bar using Unicode symbols.
:param number: current value
:param total: maximum value
:param length: progress bar length.
:param step: frequency of progress bar updating (assuming that numbers go
subsequently)
"""
if number == -1:
print("%3s" % "100" + " %" + (length * "") + "")
elif number % 1000 == 0:
p = number / float(total)
l = int(p * parts)
fl = int(l / 8)
pr = int(l - fl * 8)
print(("%3s" % str(int(p * 1000) / 10)) + " %" + (fl * "") +
boxes[pr] + ((length - fl - 1) * " ") + "")
print(f"100 % {length * ''}")
elif number % step == 0:
ratio: float = number / total
parts: int = int(ratio * length * BOXES_LENGTH)
fill_length: int = int(parts / BOXES_LENGTH)
box: str = BOXES[int(parts - fill_length * BOXES_LENGTH)]
print(
f"{str(int(int(ratio * 1000) / 10)):>3} % {fill_length * ''}{box}"
f"{int(length - fill_length - 1) * ' '}")
sys.stdout.write("\033[F")

5
run.py
View file

@ -1,3 +1,8 @@
"""
Röntgen entry point.
Author: Sergey Vartanov (me@enzet.ru).
"""
from roentgen.mapper import main
if __name__ == "__main__":

44
test.py
View file

@ -6,8 +6,8 @@ import os
import random
import yaml
from roentgen import extract_icon
from roentgen.flinger import map_
from roentgen.grid import draw_grid
def test_flinger_map():
@ -15,44 +15,4 @@ def test_flinger_map():
def test_icons():
tags_file_name = 'data/tags.yml'
icons_file_name = 'icons/icons.svg'
scheme = yaml.load(open(tags_file_name))
extracter = extract_icon.IconExtractor(icons_file_name)
to_draw = []
for element in scheme['tags']:
if 'icon' in element:
if not (set(element['icon']) in to_draw):
to_draw.append(set(element['icon']))
if 'add_icon' in element:
if not (set(element['add_icon']) in to_draw):
to_draw.append(set(element['add_icon']))
if 'over_icon' in element:
with_icons = []
if 'under_icon' in element:
for icon in element['under_icon']:
if not (set([icon] + element['over_icon']) in to_draw):
to_draw.append(set([icon] + element['over_icon']))
if 'under_icon' in element and 'with_icon' in element:
for icon in element['under_icon']:
for icon2 in element['with_icon']:
if not (set([icon] + [icon2] + element['over_icon']) in to_draw):
to_draw.append(set([icon] + [icon2] + element['over_icon']))
for icon2 in element['with_icon']:
for icon3 in element['with_icon']:
if icon2 != icon3 and icon2 != icon and icon3 != icon:
if not (set([icon] + [icon2] + [icon3] + element['over_icon']) in to_draw):
to_draw.append(set([icon] + [icon2] + [icon3] + element['over_icon']))
for icons_to_draw in to_draw:
icon_set = {'icons': []}
for icon in icons_to_draw:
path, xx, yy, is_shape = extracter.get_path(icon)
assert is_shape, icon
icon_set['icons'].append({'path': path,
'x': (- 8.0 - xx * 16),
'y': (- 8.0 - yy * 16)})
draw_grid()