mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 13:06:25 +02:00
Get use of map configuration.
This commit is contained in:
parent
56fdf9709e
commit
4c5209dabc
15 changed files with 160 additions and 130 deletions
|
@ -36,7 +36,7 @@ Röntgen features\:
|
|||
|
||||
\3 {Isometric building shapes} {levels}
|
||||
|
||||
Isometric shapes for walls and shade in proportion to \osm {building:levels}, \osm {building:min_level}, \osm {height} and \osm {min_height} values.
|
||||
With \m {--buildings isometric} or \m {--buildings isometric-no-parts} (not set by default), buildings are drawn using isometric shapes for walls and shade in proportion to \osm {building:levels}, \osm {building:min_level}, \osm {height} and \osm {min_height} values.
|
||||
|
||||
\image {doc/buildings.png} {3D buildings}
|
||||
|
||||
|
@ -265,5 +265,5 @@ To enable / disable Röntgen map paint style go to \kbd {View} → \kbd {Map Pai
|
|||
|
||||
Example of using Röntgen icons on top of Mapnik style. Map Paint Styles\:
|
||||
\list
|
||||
{✓ Mapnik}
|
||||
{✓ Mapnik (true)}
|
||||
{✓ Röntgen}
|
||||
|
|
|
@ -7,6 +7,9 @@ from dataclasses import dataclass
|
|||
|
||||
import numpy as np
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
__email__ = "me@enzet.ru"
|
||||
|
||||
LATITUDE_MAX_DIFFERENCE: float = 0.5
|
||||
LONGITUDE_MAX_DIFFERENCE: float = 0.5
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Color utility.
|
||||
"""
|
||||
from typing import Any, List
|
||||
from typing import Any
|
||||
|
||||
from colour import Color
|
||||
|
||||
|
@ -22,7 +22,7 @@ def is_bright(color: Color) -> bool:
|
|||
|
||||
|
||||
def get_gradient_color(
|
||||
value: Any, bounds: MinMax, colors: List[Color]
|
||||
value: Any, bounds: MinMax, colors: list[Color]
|
||||
) -> Color:
|
||||
"""
|
||||
Get color from the color scale for the value.
|
||||
|
@ -32,7 +32,7 @@ def get_gradient_color(
|
|||
:param colors: color scale
|
||||
"""
|
||||
color_length: int = len(colors) - 1
|
||||
scale: List[Color] = colors + [Color("black")]
|
||||
scale: list[Color] = colors + [Color("black")]
|
||||
|
||||
range_coefficient: float = (
|
||||
0 if bounds.is_empty() else (value - bounds.min_) / bounds.delta()
|
||||
|
|
|
@ -13,6 +13,7 @@ from roentgen import ui
|
|||
from roentgen.color import get_gradient_color
|
||||
from roentgen.figure import Building, DirectionSector, Road, StyledFigure, Tree
|
||||
from roentgen.flinger import Flinger
|
||||
from roentgen.map_configuration import DrawingMode, MapConfiguration
|
||||
|
||||
# fmt: off
|
||||
from roentgen.icon import (
|
||||
|
@ -21,9 +22,8 @@ from roentgen.icon import (
|
|||
from roentgen.osm_reader import OSMData, OSMNode, OSMRelation, OSMWay
|
||||
from roentgen.point import Point
|
||||
from roentgen.scheme import DEFAULT_COLOR, LineStyle, Scheme
|
||||
from roentgen.ui import AUTHOR_MODE, BuildingMode, TIME_MODE
|
||||
from roentgen.ui import BuildingMode
|
||||
from roentgen.util import MinMax
|
||||
|
||||
# fmt: on
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
|
@ -113,7 +113,7 @@ def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
|
|||
return result
|
||||
|
||||
|
||||
def is_cycle(nodes) -> bool:
|
||||
def is_cycle(nodes: list[OSMNode]) -> bool:
|
||||
"""Is way a cycle way or an area boundary."""
|
||||
return nodes[0] == nodes[-1]
|
||||
|
||||
|
@ -129,22 +129,22 @@ class Constructor:
|
|||
flinger: Flinger,
|
||||
scheme: Scheme,
|
||||
icon_extractor: ShapeExtractor,
|
||||
options,
|
||||
configuration: MapConfiguration,
|
||||
) -> None:
|
||||
self.osm_data: OSMData = osm_data
|
||||
self.flinger: Flinger = flinger
|
||||
self.scheme: Scheme = scheme
|
||||
self.icon_extractor = icon_extractor
|
||||
self.options = options
|
||||
self.configuration = configuration
|
||||
|
||||
if options.level:
|
||||
if options.level == "overground":
|
||||
if self.configuration.level:
|
||||
if self.configuration.level == "overground":
|
||||
self.check_level = check_level_overground
|
||||
elif options.level == "underground":
|
||||
elif self.configuration.level == "underground":
|
||||
self.check_level = lambda x: not check_level_overground(x)
|
||||
else:
|
||||
self.check_level = lambda x: not check_level_number(
|
||||
x, float(options.level)
|
||||
x, float(self.configuration.level)
|
||||
)
|
||||
else:
|
||||
self.check_level = lambda x: True
|
||||
|
@ -203,10 +203,10 @@ class Constructor:
|
|||
return
|
||||
|
||||
center_point, center_coordinates = line_center(outers[0], self.flinger)
|
||||
if self.options.mode in [AUTHOR_MODE, TIME_MODE]:
|
||||
if self.configuration.is_wireframe():
|
||||
color: Color
|
||||
if self.options.mode == AUTHOR_MODE:
|
||||
color = get_user_color(line.user, self.options.seed)
|
||||
if self.configuration.drawing_mode == DrawingMode.AUTHOR:
|
||||
color = get_user_color(line.user, self.configuration.seed)
|
||||
else: # self.mode == TIME_MODE
|
||||
color = get_time_color(line.timestamp, self.osm_data.time)
|
||||
self.draw_special_mode(inners, line, outers, color)
|
||||
|
@ -215,7 +215,7 @@ class Constructor:
|
|||
if not line.tags:
|
||||
return
|
||||
|
||||
building_mode: BuildingMode = BuildingMode(self.options.buildings)
|
||||
building_mode: BuildingMode = self.configuration.building_mode
|
||||
if "building" in line.tags or (
|
||||
building_mode == BuildingMode.ISOMETRIC
|
||||
and "building:part" in line.tags
|
||||
|
@ -352,13 +352,13 @@ class Constructor:
|
|||
icon_set: IconSet
|
||||
draw_outline: bool = True
|
||||
|
||||
if self.options.mode in [TIME_MODE, AUTHOR_MODE]:
|
||||
if self.configuration.is_wireframe():
|
||||
if not tags:
|
||||
return
|
||||
color: Color = DEFAULT_COLOR
|
||||
if self.options.mode == AUTHOR_MODE:
|
||||
color = get_user_color(node.user, self.options.seed)
|
||||
if self.options.mode == TIME_MODE:
|
||||
if self.configuration.drawing_mode == DrawingMode.AUTHOR:
|
||||
color = get_user_color(node.user, self.configuration.seed)
|
||||
if self.configuration.drawing_mode == DrawingMode.TIME:
|
||||
color = get_time_color(node.timestamp, self.osm_data.time)
|
||||
dot = self.icon_extractor.get_shape(DEFAULT_SMALL_SHAPE_ID)
|
||||
icon_set = IconSet(
|
||||
|
|
|
@ -3,7 +3,7 @@ Drawing utility.
|
|||
"""
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Union
|
||||
from typing import Optional, Union
|
||||
|
||||
import cairo
|
||||
import numpy as np
|
||||
|
@ -73,7 +73,7 @@ class Drawing:
|
|||
"""Draw rectangle."""
|
||||
raise NotImplementedError
|
||||
|
||||
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
||||
"""Draw line."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -111,7 +111,7 @@ class SVGDrawing(Drawing):
|
|||
style.update_svg_element(rectangle)
|
||||
self.image.add(rectangle)
|
||||
|
||||
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
||||
"""Draw line."""
|
||||
commands: PathCommands = ["M"]
|
||||
for point in points:
|
||||
|
@ -159,7 +159,7 @@ class PNGDrawing(Drawing):
|
|||
self.context.rectangle(point_1[0], point_1[1], size[0], size[1])
|
||||
style.draw_png_stroke(self.context)
|
||||
|
||||
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
||||
"""Draw line."""
|
||||
if style.fill is not None:
|
||||
self.context.move_to(float(points[0][0]), float(points[0][1]))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"""
|
||||
Drawing separate map elements.
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
@ -13,15 +13,18 @@ from roentgen.point import Point
|
|||
from roentgen.scheme import LineStyle, Scheme
|
||||
from roentgen.workspace import workspace
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
__email__ = "me@enzet.ru"
|
||||
|
||||
def draw_element(options) -> None:
|
||||
|
||||
def draw_element(options: argparse.Namespace) -> None:
|
||||
"""Draw single node, line, or area."""
|
||||
if options.node:
|
||||
target: str = "node"
|
||||
tags_description = options.node
|
||||
else:
|
||||
# Not implemented yet.
|
||||
sys.exit(1)
|
||||
exit(1)
|
||||
|
||||
tags: dict[str, str] = dict(
|
||||
[x.split("=") for x in tags_description.split(",")]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
"""
|
||||
Figures displayed on the map.
|
||||
"""
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
import numpy as np
|
||||
from colour import Color
|
||||
|
@ -25,15 +25,15 @@ class Figure(Tagged):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Dict[str, str],
|
||||
inners: List[List[OSMNode]],
|
||||
outers: List[List[OSMNode]],
|
||||
tags: dict[str, str],
|
||||
inners: list[list[OSMNode]],
|
||||
outers: list[list[OSMNode]],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self.tags: Dict[str, str] = tags
|
||||
self.inners: List[List[OSMNode]] = []
|
||||
self.outers: List[List[OSMNode]] = []
|
||||
self.tags: dict[str, str] = tags
|
||||
self.inners: list[list[OSMNode]] = []
|
||||
self.outers: list[list[OSMNode]] = []
|
||||
|
||||
for inner_nodes in inners:
|
||||
self.inners.append(make_clockwise(inner_nodes))
|
||||
|
@ -67,15 +67,15 @@ class Building(Figure):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Dict[str, str],
|
||||
inners: List[List[OSMNode]],
|
||||
outers: List[List[OSMNode]],
|
||||
tags: dict[str, str],
|
||||
inners: list[list[OSMNode]],
|
||||
outers: list[list[OSMNode]],
|
||||
flinger: Flinger,
|
||||
scheme: Scheme,
|
||||
) -> None:
|
||||
super().__init__(tags, inners, outers)
|
||||
|
||||
style: Dict[str, Any] = {
|
||||
style: dict[str, Any] = {
|
||||
"fill": scheme.get_color("building_color").hex,
|
||||
"stroke": scheme.get_color("building_border_color").hex,
|
||||
}
|
||||
|
@ -195,9 +195,9 @@ class StyledFigure(Figure):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Dict[str, str],
|
||||
inners: List[List[OSMNode]],
|
||||
outers: List[List[OSMNode]],
|
||||
tags: dict[str, str],
|
||||
inners: list[list[OSMNode]],
|
||||
outers: list[list[OSMNode]],
|
||||
line_style: LineStyle,
|
||||
) -> None:
|
||||
super().__init__(tags, inners, outers)
|
||||
|
@ -211,16 +211,16 @@ class Road(Figure):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
tags: Dict[str, str],
|
||||
inners: List[List[OSMNode]],
|
||||
outers: List[List[OSMNode]],
|
||||
tags: dict[str, str],
|
||||
inners: list[list[OSMNode]],
|
||||
outers: list[list[OSMNode]],
|
||||
matcher: RoadMatcher,
|
||||
) -> None:
|
||||
super().__init__(tags, inners, outers)
|
||||
self.matcher: RoadMatcher = matcher
|
||||
|
||||
self.width: Optional[float] = None
|
||||
self.lanes: List[Lane] = []
|
||||
self.lanes: list[Lane] = []
|
||||
|
||||
if "lanes" in tags:
|
||||
try:
|
||||
|
@ -359,7 +359,7 @@ class Segment:
|
|||
) # fmt: skip
|
||||
|
||||
|
||||
def is_clockwise(polygon: List[OSMNode]) -> bool:
|
||||
def is_clockwise(polygon: list[OSMNode]) -> bool:
|
||||
"""
|
||||
Return true if polygon nodes are in clockwise order.
|
||||
|
||||
|
@ -374,7 +374,7 @@ def is_clockwise(polygon: List[OSMNode]) -> bool:
|
|||
return count >= 0
|
||||
|
||||
|
||||
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
||||
def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
||||
"""
|
||||
Make polygon nodes clockwise.
|
||||
|
||||
|
@ -383,7 +383,7 @@ def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
|||
return polygon if is_clockwise(polygon) else list(reversed(polygon))
|
||||
|
||||
|
||||
def make_counter_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
||||
def make_counter_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
||||
"""
|
||||
Make polygon nodes counter-clockwise.
|
||||
|
||||
|
@ -392,7 +392,7 @@ def make_counter_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
|||
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
|
||||
|
||||
|
||||
def get_path(nodes: List[OSMNode], shift: np.array, flinger: Flinger) -> str:
|
||||
def get_path(nodes: list[OSMNode], shift: np.array, flinger: Flinger) -> str:
|
||||
"""Construct SVG path commands from nodes."""
|
||||
path: str = ""
|
||||
prev_node: Optional[OSMNode] = None
|
||||
|
|
|
@ -34,6 +34,7 @@ class IconCollection:
|
|||
background_color: Color = Color("white"),
|
||||
color: Color = Color("black"),
|
||||
add_unused: bool = False,
|
||||
add_all: bool = False,
|
||||
) -> "IconCollection":
|
||||
"""
|
||||
Collect all possible icon combinations in grid.
|
||||
|
@ -44,6 +45,7 @@ class IconCollection:
|
|||
:param color: icon color
|
||||
:param add_unused: create icons from shapes that have no corresponding
|
||||
tags
|
||||
:param add_all: create icons from all possible shapes including parts
|
||||
"""
|
||||
icons: list[Icon] = []
|
||||
|
||||
|
@ -111,6 +113,13 @@ class IconCollection:
|
|||
icon.recolor(color)
|
||||
icons.append(icon)
|
||||
|
||||
if add_all:
|
||||
for shape_id in extractor.shapes.keys():
|
||||
shape: Shape = extractor.get_shape(shape_id)
|
||||
icon: Icon = Icon([ShapeSpecification(shape)])
|
||||
icon.recolor(color)
|
||||
icons.append(icon)
|
||||
|
||||
return cls(icons)
|
||||
|
||||
def draw_icons(
|
||||
|
@ -204,8 +213,9 @@ def draw_icons() -> None:
|
|||
extractor: ShapeExtractor = ShapeExtractor(
|
||||
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
||||
)
|
||||
collection: IconCollection = IconCollection.from_scheme(scheme, extractor)
|
||||
|
||||
collection: IconCollection = IconCollection.from_scheme(
|
||||
scheme, extractor, add_all=True
|
||||
)
|
||||
icon_grid_path: Path = workspace.get_icon_grid_path()
|
||||
collection.draw_grid(icon_grid_path)
|
||||
logging.info(f"Icon grid is written to {icon_grid_path}.")
|
||||
|
|
|
@ -322,7 +322,7 @@ class ShapeSpecification:
|
|||
|
||||
def draw(
|
||||
self,
|
||||
svg,
|
||||
svg: Drawing,
|
||||
point: np.array,
|
||||
tags: dict[str, Any] = None,
|
||||
outline: bool = False,
|
||||
|
@ -372,7 +372,7 @@ class ShapeSpecification:
|
|||
and np.allclose(self.offset, other.offset)
|
||||
)
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
def __lt__(self, other: "ShapeSpecification") -> bool:
|
||||
return self.shape.id_ < other.shape.id_
|
||||
|
||||
|
||||
|
@ -471,12 +471,12 @@ class Icon:
|
|||
"""Add shape specifications to the icon."""
|
||||
self.shape_specifications += specifications
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
def __eq__(self, other: "Icon") -> bool:
|
||||
return sorted(self.shape_specifications) == sorted(
|
||||
other.shape_specifications
|
||||
)
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
def __lt__(self, other: "Icon") -> bool:
|
||||
return "".join(
|
||||
[x.shape.id_ for x in self.shape_specifications]
|
||||
) < "".join([x.shape.id_ for x in other.shape_specifications])
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
MapCSS scheme creation.
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional, TextIO
|
||||
|
@ -175,7 +176,7 @@ class MapCSSWriter:
|
|||
)
|
||||
|
||||
|
||||
def ui(options) -> None:
|
||||
def ui(options: argparse.Namespace) -> None:
|
||||
"""Write MapCSS 0.2 scheme."""
|
||||
directory: Path = workspace.get_mapcss_path()
|
||||
icons_with_outline_path: Path = workspace.get_mapcss_icons_path()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Simple OpenStreetMap renderer.
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterator
|
||||
|
@ -17,12 +18,13 @@ from roentgen.constructor import Constructor
|
|||
from roentgen.figure import Road
|
||||
from roentgen.flinger import Flinger
|
||||
from roentgen.icon import ShapeExtractor
|
||||
from roentgen.map_configuration import LabelMode, MapConfiguration
|
||||
from roentgen.osm_getter import NetworkError, get_osm
|
||||
from roentgen.osm_reader import OSMData, OSMNode, OSMReader, OverpassReader
|
||||
from roentgen.point import Occupied
|
||||
from roentgen.road import Intersection, RoadPart
|
||||
from roentgen.scheme import Scheme
|
||||
from roentgen.ui import AUTHOR_MODE, BuildingMode, TIME_MODE, progress_bar
|
||||
from roentgen.ui import BuildingMode, progress_bar
|
||||
from roentgen.workspace import workspace
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
|
@ -35,15 +37,19 @@ class Map:
|
|||
"""
|
||||
|
||||
def __init__(
|
||||
self, flinger: Flinger, svg: svgwrite.Drawing, scheme: Scheme, options
|
||||
self,
|
||||
flinger: Flinger,
|
||||
svg: svgwrite.Drawing,
|
||||
scheme: Scheme,
|
||||
configuration: MapConfiguration,
|
||||
) -> None:
|
||||
self.flinger: Flinger = flinger
|
||||
self.svg: svgwrite.Drawing = svg
|
||||
self.scheme: Scheme = scheme
|
||||
self.options = options
|
||||
self.configuration = configuration
|
||||
|
||||
self.background_color: Color = self.scheme.get_color("background_color")
|
||||
if self.options.mode in [AUTHOR_MODE, TIME_MODE]:
|
||||
if self.configuration.is_wireframe():
|
||||
self.background_color: Color = Color("#111111")
|
||||
|
||||
def draw(self, constructor: Constructor) -> None:
|
||||
|
@ -80,11 +86,13 @@ class Map:
|
|||
|
||||
# All other points
|
||||
|
||||
if self.options.overlap == 0:
|
||||
if self.configuration.overlap == 0:
|
||||
occupied = None
|
||||
else:
|
||||
occupied = Occupied(
|
||||
self.flinger.size[0], self.flinger.size[1], self.options.overlap
|
||||
self.flinger.size[0],
|
||||
self.flinger.size[1],
|
||||
self.configuration.overlap,
|
||||
)
|
||||
|
||||
nodes = sorted(constructor.points, key=lambda x: -x.priority)
|
||||
|
@ -105,18 +113,18 @@ class Map:
|
|||
steps * 2 + index, steps * 3, step=10, text="Drawing texts"
|
||||
)
|
||||
if (
|
||||
self.options.mode not in [TIME_MODE, AUTHOR_MODE]
|
||||
and self.options.label_mode != "no"
|
||||
not self.configuration.is_wireframe()
|
||||
and self.configuration.label_mode != LabelMode.NO
|
||||
):
|
||||
point.draw_texts(self.svg, occupied, self.options.label_mode)
|
||||
point.draw_texts(
|
||||
self.svg, occupied, self.configuration.label_mode
|
||||
)
|
||||
|
||||
progress_bar(-1, len(nodes), step=10, text="Drawing nodes")
|
||||
|
||||
def draw_buildings(self, constructor: Constructor) -> None:
|
||||
"""Draw buildings: shade, walls, and roof."""
|
||||
building_mode: BuildingMode = BuildingMode(self.options.buildings)
|
||||
|
||||
if building_mode == BuildingMode.FLAT:
|
||||
if self.configuration.building_mode == BuildingMode.FLAT:
|
||||
for building in constructor.buildings:
|
||||
building.draw(self.svg, self.flinger)
|
||||
return
|
||||
|
@ -197,12 +205,14 @@ class Map:
|
|||
intersection.draw(self.svg, True)
|
||||
|
||||
|
||||
def ui(options) -> None:
|
||||
def ui(options: argparse.Namespace) -> None:
|
||||
"""
|
||||
Röntgen entry point.
|
||||
|
||||
:param options: command-line arguments
|
||||
"""
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(options)
|
||||
|
||||
if not options.boundary_box and not options.input_file_name:
|
||||
logging.fatal("Specify either --boundary-box, or --input.")
|
||||
exit(1)
|
||||
|
@ -241,8 +251,7 @@ def ui(options) -> None:
|
|||
osm_data = reader.osm_data
|
||||
view_box = boundary_box
|
||||
else:
|
||||
is_full: bool = options.mode in [AUTHOR_MODE, TIME_MODE]
|
||||
osm_reader = OSMReader(is_full=is_full)
|
||||
osm_reader = OSMReader(is_full=configuration.is_wireframe())
|
||||
|
||||
for file_name in input_file_names:
|
||||
if not file_name.is_file():
|
||||
|
@ -273,11 +282,13 @@ def ui(options) -> None:
|
|||
flinger=flinger,
|
||||
scheme=scheme,
|
||||
icon_extractor=icon_extractor,
|
||||
options=options,
|
||||
configuration=configuration,
|
||||
)
|
||||
constructor.construct()
|
||||
|
||||
painter: Map = Map(flinger=flinger, svg=svg, scheme=scheme, options=options)
|
||||
painter: Map = Map(
|
||||
flinger=flinger, svg=svg, scheme=scheme, configuration=configuration
|
||||
)
|
||||
painter.draw(constructor)
|
||||
|
||||
logging.info(f"Writing output SVG to {options.output_file_name}...")
|
||||
|
|
|
@ -8,6 +8,7 @@ import svgwrite
|
|||
from colour import Color
|
||||
|
||||
from roentgen.icon import Icon, IconSet
|
||||
from roentgen.map_configuration import LabelMode
|
||||
from roentgen.osm_reader import Tagged
|
||||
from roentgen.text import Label
|
||||
|
||||
|
@ -161,14 +162,14 @@ class Point(Tagged):
|
|||
self,
|
||||
svg: svgwrite.Drawing,
|
||||
occupied: Optional[Occupied] = None,
|
||||
label_mode: str = "main",
|
||||
label_mode: LabelMode = LabelMode.MAIN,
|
||||
) -> None:
|
||||
"""Draw all labels."""
|
||||
labels: list[Label]
|
||||
|
||||
if label_mode == "main":
|
||||
if label_mode == LabelMode.MAIN:
|
||||
labels = self.labels[:1]
|
||||
elif label_mode == "all":
|
||||
elif label_mode == LabelMode.ALL:
|
||||
labels = self.labels
|
||||
else:
|
||||
return
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Röntgen tile server for slippy maps.
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||
from pathlib import Path
|
||||
|
@ -62,7 +63,7 @@ class _Handler(SimpleHTTPRequestHandler):
|
|||
return
|
||||
|
||||
|
||||
def ui(options) -> None:
|
||||
def ui(options: argparse.Namespace) -> None:
|
||||
"""Command-line interface for tile server."""
|
||||
server: Optional[HTTPServer] = None
|
||||
try:
|
||||
|
|
|
@ -3,6 +3,7 @@ Tile generation.
|
|||
|
||||
See https://wiki.openstreetmap.org/wiki/Tiles
|
||||
"""
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
|
@ -18,6 +19,7 @@ from roentgen.constructor import Constructor
|
|||
from roentgen.flinger import Flinger
|
||||
from roentgen.icon import ShapeExtractor
|
||||
from roentgen.mapper import Map
|
||||
from roentgen.map_configuration import MapConfiguration
|
||||
from roentgen.osm_getter import NetworkError, get_osm
|
||||
from roentgen.osm_reader import OSMData, OSMReader
|
||||
from roentgen.scheme import Scheme
|
||||
|
@ -115,23 +117,31 @@ class Tile:
|
|||
f"https://tile.openstreetmap.org/{self.scale}/{self.x}/{self.y}.png"
|
||||
)
|
||||
|
||||
def draw(self, directory_name: Path, cache_path: Path, options) -> None:
|
||||
def draw(
|
||||
self,
|
||||
directory_name: Path,
|
||||
cache_path: Path,
|
||||
configuration: MapConfiguration,
|
||||
) -> None:
|
||||
"""
|
||||
Draw tile to SVG and PNG files.
|
||||
|
||||
:param directory_name: output directory to storing tiles
|
||||
:param cache_path: directory to store SVG and PNG tiles
|
||||
:param options: drawing configuration
|
||||
:param configuration: drawing configuration
|
||||
"""
|
||||
try:
|
||||
osm_data: OSMData = self.load_osm_data(cache_path)
|
||||
except NetworkError as e:
|
||||
raise NetworkError(f"Map is not loaded. {e.message}")
|
||||
|
||||
self.draw_with_osm_data(osm_data, directory_name, options)
|
||||
self.draw_with_osm_data(osm_data, directory_name, configuration)
|
||||
|
||||
def draw_with_osm_data(
|
||||
self, osm_data: OSMData, directory_name: Path, options
|
||||
self,
|
||||
osm_data: OSMData,
|
||||
directory_name: Path,
|
||||
configuration: MapConfiguration,
|
||||
) -> None:
|
||||
"""Draw SVG and PNG tile using OpenStreetMap data."""
|
||||
top, left = self.get_coordinates()
|
||||
|
@ -154,12 +164,12 @@ class Tile:
|
|||
)
|
||||
scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
|
||||
constructor: Constructor = Constructor(
|
||||
osm_data, flinger, scheme, icon_extractor, options
|
||||
osm_data, flinger, scheme, icon_extractor, configuration
|
||||
)
|
||||
constructor.construct()
|
||||
|
||||
painter: Map = Map(
|
||||
flinger=flinger, svg=svg, scheme=scheme, options=options
|
||||
flinger=flinger, svg=svg, scheme=scheme, configuration=configuration
|
||||
)
|
||||
painter.draw(constructor)
|
||||
|
||||
|
@ -216,7 +226,7 @@ class Tiles:
|
|||
return cls(tiles, tile_1, tile_2, scale, extended_boundary_box)
|
||||
|
||||
def draw_separately(
|
||||
self, directory: Path, cache_path: Path, options
|
||||
self, directory: Path, cache_path: Path, configuration: MapConfiguration
|
||||
) -> None:
|
||||
"""
|
||||
Draw set of tiles as SVG file separately and rasterize them into a set
|
||||
|
@ -224,7 +234,7 @@ class Tiles:
|
|||
|
||||
:param directory: directory for tiles
|
||||
:param cache_path: directory for temporary OSM files
|
||||
:param options: drawing configuration
|
||||
:param configuration: drawing configuration
|
||||
"""
|
||||
cache_file_path: Path = (
|
||||
cache_path / f"{self.boundary_box.get_format()}.osm"
|
||||
|
@ -235,7 +245,7 @@ class Tiles:
|
|||
for tile in self.tiles:
|
||||
file_path: Path = tile.get_file_name(directory)
|
||||
if not file_path.exists():
|
||||
tile.draw_with_osm_data(osm_data, directory, options)
|
||||
tile.draw_with_osm_data(osm_data, directory, configuration)
|
||||
else:
|
||||
logging.debug(f"File {file_path} already exists.")
|
||||
|
||||
|
@ -253,19 +263,21 @@ class Tiles:
|
|||
"""Check whether all tiles are drawn."""
|
||||
return all(x.exists(directory_name) for x in self.tiles)
|
||||
|
||||
def draw(self, directory: Path, cache_path: Path, options) -> None:
|
||||
def draw(
|
||||
self, directory: Path, cache_path: Path, configuration: MapConfiguration
|
||||
) -> None:
|
||||
"""
|
||||
Draw one PNG image with all tiles and split it into a set of separate
|
||||
PNG file with Pillow.
|
||||
|
||||
:param directory: directory for tiles
|
||||
:param cache_path: directory for temporary OSM files
|
||||
:param options: drawing configuration
|
||||
:param configuration: drawing configuration
|
||||
"""
|
||||
if self.tiles_exist(directory):
|
||||
return
|
||||
|
||||
self.draw_image(cache_path, options)
|
||||
self.draw_image(cache_path, configuration)
|
||||
|
||||
input_path: Path = self.get_file_path(cache_path).with_suffix(".png")
|
||||
with input_path.open("rb") as input_file:
|
||||
|
@ -290,12 +302,14 @@ class Tiles:
|
|||
"""Get path of the output SVG file."""
|
||||
return cache_path / f"{self.boundary_box.get_format()}_{self.scale}.svg"
|
||||
|
||||
def draw_image(self, cache_path: Path, options) -> None:
|
||||
def draw_image(
|
||||
self, cache_path: Path, configuration: MapConfiguration
|
||||
) -> None:
|
||||
"""
|
||||
Draw all tiles as one picture.
|
||||
|
||||
:param cache_path: directory for temporary SVG file and OSM files
|
||||
:param options: drawing configuration
|
||||
:param configuration: drawing configuration
|
||||
"""
|
||||
output_path: Path = self.get_file_path(cache_path)
|
||||
|
||||
|
@ -319,16 +333,14 @@ class Tiles:
|
|||
)
|
||||
scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
|
||||
constructor: Constructor = Constructor(
|
||||
osm_data, flinger, scheme, extractor, options=options
|
||||
osm_data, flinger, scheme, extractor, configuration
|
||||
)
|
||||
constructor.construct()
|
||||
|
||||
svg: svgwrite.Drawing = svgwrite.Drawing(
|
||||
str(output_path), size=flinger.size
|
||||
)
|
||||
map_: Map = Map(
|
||||
flinger=flinger, svg=svg, scheme=scheme, options=options
|
||||
)
|
||||
map_: Map = Map(flinger, svg, scheme, configuration)
|
||||
map_.draw(constructor)
|
||||
|
||||
logging.info(f"Writing output SVG {output_path}...")
|
||||
|
@ -346,9 +358,10 @@ class Tiles:
|
|||
logging.debug(f"File {png_path} already exists.")
|
||||
|
||||
|
||||
def ui(options) -> None:
|
||||
def ui(options: argparse.Namespace) -> None:
|
||||
"""Simple user interface for tile generation."""
|
||||
directory: Path = workspace.get_tile_path()
|
||||
configuration: MapConfiguration = MapConfiguration.from_options(options)
|
||||
|
||||
if options.coordinates:
|
||||
coordinates: list[float] = list(
|
||||
|
@ -356,13 +369,13 @@ def ui(options) -> None:
|
|||
)
|
||||
tile: Tile = Tile.from_coordinates(np.array(coordinates), options.scale)
|
||||
try:
|
||||
tile.draw(directory, Path(options.cache), options)
|
||||
tile.draw(directory, Path(options.cache), configuration)
|
||||
except NetworkError as e:
|
||||
logging.fatal(e.message)
|
||||
elif options.tile:
|
||||
scale, x, y = map(int, options.tile.split("/"))
|
||||
tile: Tile = Tile(x, y, scale)
|
||||
tile.draw(directory, Path(options.cache), options)
|
||||
tile.draw(directory, Path(options.cache), configuration)
|
||||
elif options.boundary_box:
|
||||
boundary_box: Optional[BoundaryBox] = BoundaryBox.from_text(
|
||||
options.boundary_box
|
||||
|
@ -370,7 +383,7 @@ def ui(options) -> None:
|
|||
if boundary_box is None:
|
||||
sys.exit(1)
|
||||
tiles: Tiles = Tiles.from_boundary_box(boundary_box, options.scale)
|
||||
tiles.draw(directory, Path(options.cache), options)
|
||||
tiles.draw(directory, Path(options.cache), configuration)
|
||||
else:
|
||||
logging.fatal(
|
||||
"Specify either --coordinates, --boundary-box, or --tile."
|
||||
|
|
|
@ -4,29 +4,15 @@ Command-line user interface.
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
from roentgen.map_configuration import BuildingMode, DrawingMode, LabelMode
|
||||
from roentgen.osm_reader import STAGES_OF_DECAY
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
__email__ = "me@enzet.ru"
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from roentgen.osm_reader import STAGES_OF_DECAY
|
||||
|
||||
BOXES: str = " ▏▎▍▌▋▊▉"
|
||||
BOXES_LENGTH: int = len(BOXES)
|
||||
|
||||
AUTHOR_MODE: str = "author"
|
||||
TIME_MODE: str = "time"
|
||||
|
||||
|
||||
class BuildingMode(Enum):
|
||||
"""
|
||||
Building drawing mode.
|
||||
"""
|
||||
|
||||
FLAT = "flat"
|
||||
ISOMETRIC = "isometric"
|
||||
ISOMETRIC_NO_PARTS = "isometric-no-parts"
|
||||
|
||||
|
||||
def parse_options(args) -> argparse.Namespace:
|
||||
"""Parse Röntgen command-line options."""
|
||||
|
@ -68,8 +54,9 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
|||
parser.add_argument(
|
||||
"--mode",
|
||||
default="normal",
|
||||
help="map drawing mode",
|
||||
metavar="<string>",
|
||||
choices=(x.value for x in DrawingMode),
|
||||
help="map drawing mode: " + ", ".join(x.value for x in DrawingMode),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--overlap",
|
||||
|
@ -81,10 +68,11 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
|||
)
|
||||
parser.add_argument(
|
||||
"--labels",
|
||||
help="label drawing mode: `no`, `main`, or `all`",
|
||||
dest="label_mode",
|
||||
default="main",
|
||||
metavar="<string>",
|
||||
choices=(x.value for x in LabelMode),
|
||||
help="label drawing mode: " + ", ".join(x.value for x in LabelMode),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
|
@ -99,6 +87,12 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
|||
default="overground",
|
||||
help="display only this floor level",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--seed",
|
||||
default="",
|
||||
help="seed for random",
|
||||
metavar="<string>",
|
||||
)
|
||||
|
||||
|
||||
def add_tile_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
|
@ -179,12 +173,6 @@ def add_render_arguments(parser: argparse.ArgumentParser) -> None:
|
|||
default="cache",
|
||||
metavar="<path>",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--seed",
|
||||
default="",
|
||||
help="seed for random",
|
||||
metavar="<string>",
|
||||
)
|
||||
|
||||
|
||||
def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
|
@ -225,14 +213,13 @@ def progress_bar(
|
|||
:param text: short description
|
||||
"""
|
||||
if number == -1:
|
||||
print(f"100 % {length * '█'}▏{text}")
|
||||
sys.stdout.write(f"100 % {length * '█'}▏{text}\n")
|
||||
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(
|
||||
sys.stdout.write(
|
||||
f"{str(int(int(ratio * 1000) / 10)):>3} % {fill_length * '█'}{box}"
|
||||
f"{int(length - fill_length - 1) * ' '}▏{text}"
|
||||
f"{int(length - fill_length - 1) * ' '}▏{text}\n\033[F"
|
||||
)
|
||||
sys.stdout.write("\033[F")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue