diff --git a/requirements.txt b/requirements.txt
index 40987c5..763f986 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,4 @@
+svgwrite
+numpy>=1.18.1
PyYAML>=4.2b1
urllib3>=1.25.6
diff --git a/roentgen/grid.py b/roentgen/grid.py
index 9583062..2bfac0b 100644
--- a/roentgen/grid.py
+++ b/roentgen/grid.py
@@ -4,22 +4,21 @@ Author: Sergey Vartanov (me@enzet.ru).
import os
import random
+import svgwrite
import yaml
from roentgen import extract_icon
-from roentgen import svg
from typing import Any, Dict
def draw_icon(
- output_file, icon: Dict[str, Any], x: float, y: float,
+ svg, icon: Dict[str, Any], x: float, y: float,
color: str = "444444"):
- output_file.write(
- f'\n')
+ svg.add(svg.path(
+ d=icon["path"], fill=f"#{color}", stroke="none",
+ transform=f'translate({icon["x"] + x},{icon["y"] + y})'))
def draw_grid():
@@ -103,16 +102,16 @@ def draw_grid():
height = int(number / (width / step) + 1) * step
- output_file = svg.SVG(open(icon_grid_file_name, "w+"))
- output_file.begin(width, height)
+ svg = svgwrite.Drawing(icon_grid_file_name, (width, height))
- output_file.rect(0, 0, width, height, color="FFFFFF")
+ svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF"))
for icon in icons:
background_color, foreground_color = random.choice(icon_colors)
- output_file.rect(x - 2 - 8, y - 2 - 8, 20, 20, color=background_color)
+ svg.add(svg.rect(
+ (x - 2 - 8, y - 2 - 8), (20, 20), fill=f"#{background_color}"))
for i in icon["icons"]:
- draw_icon(output_file, i, x, y, foreground_color)
+ draw_icon(svg, i, x, y, foreground_color)
x += step
if x > width - 8:
x = step / 2
@@ -121,4 +120,4 @@ def draw_grid():
print(f"Icons: {number}.")
- output_file.end()
+ svg.write(open(icon_grid_file_name, "w"))
diff --git a/roentgen/mapper.py b/roentgen/mapper.py
index 28db619..9309219 100644
--- a/roentgen/mapper.py
+++ b/roentgen/mapper.py
@@ -3,23 +3,25 @@ Simple OpenStreetMap renderer.
Author: Sergey Vartanov (me@enzet.ru).
"""
+import numpy as np
import os
-import random
+import svgwrite
import sys
import yaml
-import numpy as np
+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 roentgen import extract_icon
from roentgen import ui
-from roentgen import svg
from roentgen.constructor import Constructor, get_path
from roentgen.flinger import GeoFlinger, Geo
-from roentgen.osm_reader import OSMReader
-from roentgen.osm_getter import get_osm
from roentgen.grid import draw_grid
-
-from typing import List
+from roentgen.osm_getter import get_osm
+from roentgen.osm_reader import Map, OSMReader
ICONS_FILE_NAME: str = "icons/icons.svg"
TAGS_FILE_NAME: str = "data/tags.yml"
@@ -31,7 +33,7 @@ class Painter:
def __init__(
self, show_missing_tags, overlap, draw_nodes, mode, draw_captions,
- map_, flinger, output_file, icons, scheme):
+ map_, flinger, svg: svgwrite.Drawing, icons, scheme):
self.show_missing_tags = show_missing_tags
self.overlap = overlap
@@ -41,16 +43,10 @@ class Painter:
self.map_ = map_
self.flinger = flinger
- self.output_file = output_file
+ self.svg: svgwrite.Drawing = svg
self.icons = icons
self.scheme = scheme
- def draw_raw_nodes(self):
- for node_id in self.map_.node_map:
- node = self.map_.node_map[node_id]
- flung = self.flinger.fling(node)
- self.output_file.circle(flung[0], flung[1], 0.2, color="FFFFFF")
-
def no_draw(self, key):
if key in self.scheme["tags_to_write"] or \
key in self.scheme["tags_to_skip"]:
@@ -125,27 +121,21 @@ class Painter:
#------#
######
"""
- text = text.replace("&", "and")
if out_fill_2:
- self.output_file.write(
- f'' + text + "")
+ self.svg.add(Text(
+ text, (x, y), 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.output_file.write(
- f'' + text + "")
- self.output_file.write(
- f'' + text + "")
+ self.svg.add(Text(
+ text, (x, y), 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",
+ 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 "")
@@ -268,16 +258,12 @@ class Painter:
node_2 = self.map_.node_map[way.nodes[i + 1]]
flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon))
flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon))
- shifted_1 = np.add(flung_1, shift_1)
- shifted_2 = np.add(flung_2, shift_2)
- self.output_file.write(
- f'\n')
+
+ self.svg.add(self.svg.path(
+ d=("M", np.add(flung_1, shift_1), "L",
+ np.add(flung_2, shift_1), np.add(flung_2, shift_2),
+ np.add(flung_1, shift_2), "Z"),
+ fill=f"#{color}", stroke=f"#{color}", stroke_width=1))
elif way.path:
# TODO: implement
pass
@@ -289,34 +275,31 @@ class Painter:
if way.kind == "way":
if way.nodes:
path = get_path(way.nodes, [0, 0], self.map_, self.flinger)
- self.output_file.write(
- f'\n')
+ self.svg.add(Path(d=path, style=way.style))
else:
- self.output_file.write(
- f'\n')
+ self.svg.add(Path(d=way.path, style=way.style))
# Building shade
- self.output_file.write('\n')
+ building_shade = Group(opacity=0.1)
+
for way in ways:
- if way.kind == "building":
- if way.nodes:
- shift = [-5, 5]
- if way.levels:
- shift = [-5 * way.levels, 5 * way.levels]
- for i in range(len(way.nodes) - 1):
- node_1 = self.map_.node_map[way.nodes[i]]
- node_2 = self.map_.node_map[way.nodes[i + 1]]
- flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon))
- flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon))
- self.output_file.write(
- f'\n')
- self.output_file.write("\n")
+ if way.kind != "building" or not way.nodes:
+ continue
+ shift = [-5, 5]
+ if way.levels:
+ shift = [-5 * way.levels, 5 * way.levels]
+ for i in range(len(way.nodes) - 1):
+ node_1 = self.map_.node_map[way.nodes[i]]
+ node_2 = self.map_.node_map[way.nodes[i + 1]]
+ flung_1 = self.flinger.fling(Geo(node_1.lat, node_1.lon))
+ flung_2 = self.flinger.fling(Geo(node_2.lat, node_2.lon))
+ building_shade.add(Path(
+ ("M", flung_1, "L", flung_2, np.add(flung_2, shift),
+ np.add(flung_1, shift), "Z"),
+ fill="#000000", stroke="#000000", stroke_width=1))
+
+ self.svg.add(building_shade)
# Building walls
@@ -327,18 +310,16 @@ class Painter:
# Building roof
for way in ways:
- if way.kind == "building":
- if way.nodes:
- shift = [0, -3]
- if way.levels:
- shift = [0 * way.levels, min(-3, -1 * way.levels)]
- path = get_path(way.nodes, shift, self.map_, self.flinger)
- self.output_file.write(
- f'\n')
- else:
- self.output_file.write(
- f'\n')
+ if way.kind != "building":
+ continue
+ if way.nodes:
+ shift = [0, -3]
+ 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))
+ else:
+ self.svg.add(Path(d=way.path, style=way.style, opacity=1))
# Trees
@@ -347,11 +328,10 @@ class Painter:
node.tags["natural"] == "tree" and
"diameter_crown" in node.tags):
continue
- self.output_file.circle(
- float(node.x) + (random.random() - 0.5) * 10,
- float(node.y) + (random.random() - 0.5) * 10,
+ self.svg.add(Circle(
+ (float(node.x), float(node.y)),
float(node.tags["diameter_crown"]) * 1.2,
- color='688C44', fill='688C44', opacity=0.3)
+ fill="#688C44", stroke="#688C44", opacity=0.3))
# All other nodes
@@ -386,20 +366,15 @@ class Painter:
def draw_point(self, shape, x, y, fill, size=16, xx=0, yy=0, tags=None):
x = int(float(x))
y = int(float(y))
- self.output_file.write(
- '')
- if tags:
- self.output_file.write("")
- self.output_file.write(
- "\n".join(map(lambda x: x + ": " + tags[x], tags)))
- self.output_file.write("")
- self.output_file.write("\n")
+ 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)))
+ self.svg.add(path)
- def draw_point_outline(self, shape, x, y, fill, mode="default", size=16, xx=0, yy=0):
+ 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))
opacity = 0.5
@@ -413,12 +388,12 @@ class Painter:
if Y > 200:
outline_fill = "000000"
opacity = 0.7
- self.output_file.write(
- '\n')
+ 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})"))
def check_level_number(tags, level):
@@ -453,9 +428,9 @@ def main():
if not options:
sys.exit(1)
- background_color = "EEEEEE"
+ background_color = "#EEEEEE"
if options.mode in ["user-coloring", "time"]:
- background_color = "111111"
+ background_color = "#111111"
if options.input_file_name:
input_file_name = options.input_file_name
@@ -480,18 +455,18 @@ def main():
print("Fatal: no such file: " + file_name + ".")
sys.exit(1)
- map_ = osm_reader.parse_osm_file(
+ osm_reader.parse_osm_file(
file_name, parse_ways=options.draw_ways,
parse_relations=options.draw_ways, full=full)
- output_file = svg.SVG(open(options.output_file_name, "w+"))
+ map_: Map = osm_reader.map_
w, h = list(map(lambda x: float(x), options.size.split(",")))
- output_file.begin(w, h)
- output_file.write(
- "
Rӧntgen\n")
- output_file.rect(0, 0, w, h, color=background_color)
+ svg: svgwrite.Drawing = \
+ svgwrite.Drawing(options.output_file_name, size=(w, h))
+
+ svg.add(Rect((0, 0), (w, h), fill=background_color))
min1 = Geo(boundary_box[1], boundary_box[0])
max1 = Geo(boundary_box[3], boundary_box[2])
@@ -537,18 +512,18 @@ 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, output_file=output_file, icons=icons,
+ map_=map_, flinger=flinger, svg=svg, icons=icons,
scheme=scheme)
painter.draw(constructor.nodes, constructor.ways, points)
if flinger.space[0] == 0:
- output_file.rect(0, 0, w, flinger.space[1], color="FFFFFF")
- output_file.rect(
- 0, h - flinger.space[1], w, flinger.space[1], color="FFFFFF")
+ svg.add(Rect((0, 0), (w, flinger.space[1]), fill="#FFFFFF"))
+ svg.add(Rect(
+ (0, h - flinger.space[1]), (w, flinger.space[1]), fill="#FFFFFF"))
if flinger.space[1] == 0:
- output_file.rect(0, 0, flinger.space[0], h, color="FFFFFF")
- output_file.rect(
- w - flinger.space[0], 0, flinger.space[0], h, color="FFFFFF")
+ svg.add(Rect((0, 0), (flinger.space[0], h), fill="#FFFFFF"))
+ svg.add(Rect(
+ (w - flinger.space[0], 0), (flinger.space[0], h), fill="#FFFFFF"))
if options.show_index:
print(min1.lon, max1.lon)
@@ -595,12 +570,13 @@ def main():
t2 = flinger.fling(Geo(
min1.lat + (i + 1) * lat_step,
min1.lon + (j + 1) * lon_step))
- output_file.text(
- ((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40,
- str(int(matrix[i][j])), size=80, color="440000",
- opacity=0.1, align="center")
+ svg.add(Text(
+ str(int(matrix[i][j])),
+ (((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40),
+ font_size=80, fill="440000",
+ opacity=0.1, align="center"))
- output_file.end()
+ svg.write(open(options.output_file_name, "w"))
top_missing_tags = \
sorted(missing_tags.keys(), key=lambda x: -missing_tags[x])
diff --git a/roentgen/osm_reader.py b/roentgen/osm_reader.py
index fd4e2f6..138a5de 100644
--- a/roentgen/osm_reader.py
+++ b/roentgen/osm_reader.py
@@ -3,7 +3,7 @@ Reading OpenStreetMap data from XML file.
Author: Sergey Vartanov
"""
-from typing import Any, Dict, List, Optional
+from typing import Dict, List, Optional
from roentgen import ui
diff --git a/roentgen/svg.py b/roentgen/svg.py
deleted file mode 100644
index 7de4957..0000000
--- a/roentgen/svg.py
+++ /dev/null
@@ -1,245 +0,0 @@
-"""
-Very simple SVG library.
-
-Author: Sergey Vartanov (me@enzet.ru)
-"""
-
-import math
-
-
-class SVG:
- def __init__(self, file_):
- self.file = file_
- self.index = 0
-
- def begin(self, width, height):
- self.file.write(
- '\n\n')
- self.file.write(
- f'''\n')
-
- def path(
- self, path: str, color: str = "black", width: float = 1,
- fill: str = "none", end: str = "butt", id: str = None,
- color2: str = None, gx1: float = 0, gy1: float = 0, gx2: float = 0,
- gy2: float = 0, dash: str = None, dashoffset: str = None,
- opacity: float = 1, transform: str = None):
-
- if color2:
- self.index += 1
- self.file.write(
- f''
- f''
- f'\n')
-
- self.file.write(' \n')
-
- def line(
- self, x1, y1, x2, y2, width=1, color='black', end='butt', id_=None,
- color2=None, gx1=None, gy1=None, gx2=None, gy2=None, dash=None,
- dashoffset=None, opacity=None):
-
- if color2:
- if not gx1:
- gx1 = x1
- if not gy1:
- gy1 = y1
- if not gx2:
- gx2 = x2
- if not gy2:
- gy2 = y2
- self.index += 1
- self.file.write(
- f'\n'
- f''
- f''
- f'\n')
- self.file.write(
- f' \n')
-
- def rect(
- self, x, y, width, height, color='black', id=None, rx=0, ry=0,
- opacity=1.0, stroke_color='none', stroke_width=1.0):
-
- self.file.write(' \n')
-
- def curve(self, x1, y1, x2, y2, x3, y3, x4, y4, id=None, width=1, color='black'):
- self.file.write(' \n')
-
- def rhombus(self, x, y, width, height, color='black', id=None):
- self.file.write(''' \n')
-
- def circle(self, x, y, d, color='black', color2='white', fill='none',
- opacity=None, width=1, id_=None, gx=0, gy=0, gr=0, fx=0, fy=0):
- is_grad = gx != 0 or gy != 0 or gr != 0
- if is_grad:
- self.index += 1
- self.file.write(
- f''
- f''
- f'\n')
- c = 0.577
- self.file.write(
- f' \n')
-
- def text(self, x, y, text, font='Myriad Pro', size='10', align='left',
- color='black', id=None, weight=None, letter_spacing=None, angle=None,
- opacity=None):
- """
- Drawing SVG element.
- """
- if angle is None:
- self.file.write(f' 0:
- trans = 'transform = "matrix(' + str(math.sin(angle)) + ',' + str(math.cos(angle)) + ',' + \
- str(-math.cos(angle)) + ',' + str(math.sin(angle)) + ',' + str(x) + ',' + str(y) + ')"'
- else:
- trans = 'transform = "matrix(' + str(math.sin(angle + math.pi)) + ',' + str(math.cos(angle + math.pi)) + ',' + \
- str(-math.cos(angle + math.pi)) + ',' + str(math.sin(angle + math.pi)) + ',' + str(x) + ',' + str(y) + ')"'
- self.file.write(' ' + trans)
- self.file.write('>')
- self.file.write(text)
- self.file.write('\n')
-
- @staticmethod
- def get_color(color):
- if color == 'none':
- return 'none'
- if color == 'black':
- return 'black'
- return '#' + str(color)
-
- def begin_layer(self, name):
- self.file.write(f'\n')
-
- def end_layer(self):
- self.file.write('\n')
-
- def write(self, raw_code):
- self.file.write(raw_code)
diff --git a/roentgen/ui.py b/roentgen/ui.py
index cca3265..daef08e 100644
--- a/roentgen/ui.py
+++ b/roentgen/ui.py
@@ -8,7 +8,9 @@ from typing import Optional
def parse_options(args):
-
+ """
+ Parse Röntgen command-line options.
+ """
parser = argparse.ArgumentParser()
parser.add_argument(
@@ -56,9 +58,9 @@ def parse_options(args):
"--show-index", dest="show_index", action="store_true")
parser.add_argument(
"--no-show-index", dest="show_index", action="store_false")
- parser.add_argument("--mode", dest="mode", default="normal")
- parser.add_argument("--seed", dest="seed", default="")
- parser.add_argument("--level", dest="level", default=None)
+ parser.add_argument("--mode", default="normal")
+ parser.add_argument("--seed", default="")
+ parser.add_argument("--level", default=None)
arguments = parser.parse_args(args[1:])