Merge main.

This commit is contained in:
Sergey Vartanov 2022-06-06 23:04:50 +03:00
commit db1c69fb42
18 changed files with 276 additions and 188 deletions

View file

@ -36,6 +36,7 @@
<w>rasterized</w>
<w>röntgen</w>
<w>scoria</w>
<w>skillion</w>
<w>subattributes</w>
<w>subelement</w>
<w>subparser</w>

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
python_files="map_machine setup.py tests data/githooks/commit-msg"

View file

@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env bash
echo "Looking for changes..."
files=`git status --porcelain | wc -l`

View file

@ -11,7 +11,7 @@ __url__ = "https://github.com/enzet/map-machine"
__doc_url__ = f"{__url__}/blob/main/README.md"
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
__version__ = "0.1.4"
__version__ = "0.1.5"
REQUIREMENTS = [
"CairoSVG>=2.5.0",

View file

@ -15,10 +15,8 @@ from map_machine.feature.building import Building, BUILDING_SCALE
from map_machine.feature.crater import Crater
from map_machine.feature.direction import DirectionSector
from map_machine.feature.road import Road, Roads
from map_machine.figure import (
StyledFigure,
)
from map_machine.feature.tree import Tree
from map_machine.figure import StyledFigure
from map_machine.geometry.flinger import Flinger
from map_machine.map_configuration import DrawingMode, MapConfiguration
from map_machine.osm.osm_reader import (

View file

@ -9,8 +9,8 @@ from typing import Any, Optional, List, Dict, Set
import numpy as np
import svgwrite
from svgwrite import Drawing
from svgwrite.text import Text
from svgwrite.shapes import Line, Rect
from svgwrite.text import Text
from map_machine.map_configuration import MapConfiguration
from map_machine.osm.osm_reader import Tags

View file

@ -7,14 +7,21 @@ from typing import Dict, List, Optional, Tuple
import numpy as np
from svgwrite import Drawing
from svgwrite.text import Text
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.constructor import Constructor
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.geometry.flinger import Flinger
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.map_configuration import MapConfiguration
from map_machine.mapper import Map
from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay
from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay, Tags
from map_machine.osm.tags import (
HIGHWAY_VALUES,
AEROWAY_VALUES,
RAILWAY_VALUES,
ROAD_VALUES,
)
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.scheme import Scheme
from map_machine.workspace import Workspace
@ -26,45 +33,6 @@ SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor(
)
DEFAULT_ZOOM: float = 18.0
HIGHWAY_VALUES: List[str] = [
"motorway",
"trunk",
"primary",
"secondary",
"tertiary",
"unclassified",
"residential",
"service",
"service_minor",
"road",
"pedestrian",
"living_street",
"bridleway",
"cycleway",
"footway",
"steps",
"path",
"track",
"raceway",
]
AEROWAY_VALUES: List[str] = [
"runway",
"taxiway",
]
RAILWAY_TAGS: List[Dict[str, str]] = [
{"railway": "rail"},
{"railway": "light_rail"},
{"railway": "monorail"},
{"railway": "funicular"},
{"railway": "narrow_gauge"},
{"railway": "subway"},
{"railway": "subway", "color": "red"},
{"railway": "subway", "color": "blue"},
]
ROAD_WIDTHS_AND_FEATURES: List[Dict[str, str]] = [
{"width": "4"},
{"width": "8"},
@ -79,8 +47,6 @@ ROAD_WIDTHS_AND_FEATURES: List[Dict[str, str]] = [
{"embankment": "yes", "width": "4"},
{"embankment": "yes", "width": "8"},
]
ROAD_LANES_AND_FEATURES: List[Dict[str, str]] = [
{"lanes": "1"},
{"lanes": "2"},
@ -106,7 +72,6 @@ PLACEMENT_FEATURES_1: List[Dict[str, str]] = [
{"placement": "transition"},
{"lanes": "3", "placement": "right_of:1"}, # or placement=left_of:2
]
PLACEMENT_FEATURES_2: List[Dict[str, str]] = [
{"lanes": "2"},
# or placement:backward=left_of:1
@ -120,16 +85,18 @@ PLACEMENT_FEATURES_2: List[Dict[str, str]] = [
class Grid:
"""Creating map with elements ordered in grid."""
def __init__(self) -> None:
self.x_step: float = 0.0002
self.y_step: float = 0.0003
self.x_start: float = 0.0
def __init__(self, x_step: float = 0.0002, y_step: float = 0.0003):
self.x_step: float = x_step
self.y_step: float = y_step
self.index: int = 0
self.nodes: Dict[OSMNode, Tuple[int, int]] = {}
self.max_j: float = 0
self.max_i: float = 0
self.way_id: int = 0
self.osm_data: OSMData = OSMData()
self.texts: list[tuple[str, int, int]] = []
def add_node(self, tags: Dict[str, str], i: int, j: int) -> OSMNode:
def add_node(self, tags: Tags, i: int, j: int) -> OSMNode:
"""Add OSM node to the grid."""
self.index += 1
node: OSMNode = OSMNode(
@ -142,21 +109,79 @@ class Grid:
self.max_i = max(self.max_i, i * self.y_step)
return node
def add_way(self, tags: Tags, nodes: list[OSMNode]) -> None:
"""Add OSM way to the grid."""
osm_way: OSMWay = OSMWay(tags, self.way_id, nodes)
self.osm_data.add_way(osm_way)
self.way_id += 1
def add_text(self, text: str, i: int, j: int) -> None:
self.texts.append((text, i, j))
def get_boundary_box(self) -> BoundaryBox:
"""Compute resulting boundary box with margin of one grid step."""
return BoundaryBox(
-self.x_step,
-self.max_i - self.y_step,
self.max_j + self.x_step,
self.y_step,
-self.x_step * 1.5,
-self.max_i - self.y_step * 1.5,
self.max_j + self.x_step * 1.5,
self.y_step * 1.5,
)
def draw(self, output_path: Path, zoom: float = DEFAULT_ZOOM) -> None:
"""Draw grid."""
configuration: MapConfiguration = MapConfiguration(
level="all", credit=None
)
flinger: Flinger = Flinger(
self.get_boundary_box(), zoom, self.osm_data.equator_length
)
svg: Drawing = Drawing(output_path.name, flinger.size)
constructor: Constructor = Constructor(
self.osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration
)
constructor.construct()
map_: Map = Map(flinger, svg, SCHEME, configuration)
map_.draw(constructor)
def road_features(
for text, i, j in self.texts:
svg.add(
Text(
text,
flinger.fling((-i * self.y_step, j * self.x_step)) + (0, 3),
font_family="JetBrains Mono",
font_size=12,
)
)
with output_path.open("w") as output_file:
svg.write(output_file)
logging.info(f"Map is drawn to {output_path}.")
def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None:
"""
Draw two sets of ways intersecting each other to show how they overlapping.
"""
grid: Grid = Grid(0.00012, 0.00012)
for index, tags in enumerate(types):
node_1: OSMNode = grid.add_node({}, index + 1, 8)
node_2: OSMNode = grid.add_node({}, index + 1, len(types) + 9)
grid.add_way(tags, [node_1, node_2])
grid.add_text(", ".join(f"{k}={tags[k]}" for k in tags), index + 1, 0)
for index, tags in enumerate(types):
node_1: OSMNode = grid.add_node({}, 0, index + 9)
node_2: OSMNode = grid.add_node({}, len(types) + 1, index + 9)
grid.add_way(tags, [node_1, node_2])
grid.draw(path)
def draw_road_features(
types: List[Dict[str, str]], features: List[Dict[str, str]], path: Path
) -> None:
"""Draw test image with different road features."""
osm_data: OSMData = OSMData()
grid: Grid = Grid()
for i, type_ in enumerate(types):
@ -168,58 +193,41 @@ def road_features(
if previous:
tags: Dict[str, str] = dict(type_)
tags |= dict(features[j - 1])
way: OSMWay = OSMWay(
tags, i * (len(features) + 1) + j, [previous, node]
)
osm_data.add_way(way)
grid.add_way(tags, [previous, node])
previous = node
draw(osm_data, path, grid.get_boundary_box())
def draw(
osm_data: OSMData,
output_path: Path,
boundary_box: BoundaryBox,
zoom: float = DEFAULT_ZOOM,
) -> None:
"""Draw map."""
configuration: MapConfiguration = MapConfiguration(level="all")
flinger: Flinger = Flinger(boundary_box, zoom, osm_data.equator_length)
svg: Drawing = Drawing(output_path.name, flinger.size)
constructor: Constructor = Constructor(
osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration
)
constructor.construct()
map_: Map = Map(flinger, svg, SCHEME, configuration)
map_.draw(constructor)
with output_path.open("w") as output_file:
svg.write(output_file)
logging.info(f"Map is drawn to {output_path}.")
grid.draw(path)
if __name__ == "__main__":
logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO)
out_path: Path = Path("out")
road_tags: List[Dict[str, str]] = [
{"highway": value} for value in ROAD_VALUES
]
highway_tags: List[Dict[str, str]] = [
{"highway": value} for value in HIGHWAY_VALUES
]
aeroway_tags: List[Dict[str, str]] = [
{"aeroway": value} for value in AEROWAY_VALUES
]
railway_tags: list[dict[str, str]] = [
{"railway": value} for value in RAILWAY_VALUES
]
road_features(
highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg"
draw_road_features(
highway_tags, ROAD_LANES_AND_FEATURES, out_path / "lanes.svg"
)
road_features(
highway_tags + RAILWAY_TAGS + aeroway_tags,
draw_road_features(
highway_tags + railway_tags + aeroway_tags,
ROAD_WIDTHS_AND_FEATURES,
Path("out") / "width.svg",
out_path / "width.svg",
)
road_features(
draw_road_features(
highway_tags,
PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2,
Path("out") / "placement.svg",
out_path / "placement.svg",
)
draw_overlapped_ways(road_tags + railway_tags, out_path / "overlap.svg")

View file

@ -15,7 +15,6 @@ from map_machine.pictogram.icon import (
from map_machine.pictogram.icon_collection import IconCollection
from map_machine.workspace import workspace
SKIP: bool = True

View file

@ -9,19 +9,19 @@ from typing import Optional
import numpy as np
import svgwrite
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.constructor import Constructor
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.geometry.flinger import Flinger
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.mapper import Map
from map_machine.map_configuration import (
BuildingMode,
DrawingMode,
LabelMode,
MapConfiguration,
)
from map_machine.mapper import Map
from map_machine.osm.osm_getter import get_osm
from map_machine.osm.osm_reader import OSMData
from map_machine.pictogram.icon import ShapeExtractor
from map_machine.scheme import Scheme
doc_path: Path = Path("doc")

View file

@ -6,6 +6,7 @@ from pathlib import Path
from typing import Optional
from map_machine.doc.collections import Collection
from map_machine.map_configuration import MapConfiguration
from map_machine.osm.osm_reader import Tags
from map_machine.pictogram.icon import Icon, ShapeExtractor

View file

@ -395,6 +395,13 @@ class Road(Tagged):
except ValueError:
pass
if "placement" in tags:
value: str = tags["placement"]
if ":" in value and len(parts := value.split(":")) == 2:
_, lane_string = parts
if (lane_number := int(lane_string) - 1) >= len(self.lanes):
self.lanes += [Lane()] * (lane_number + 1 - len(self.lanes))
if "width:lanes" in tags:
try:
widths: List[float] = list(

View file

@ -57,6 +57,7 @@ class MapConfiguration:
draw_roofs: bool = True
use_building_colors: bool = False
show_overlapped: bool = False
credit: Optional[str] = "© OpenStreetMap contributors"
@classmethod
def from_options(

View file

@ -19,6 +19,7 @@ from map_machine.constructor import Constructor
from map_machine.drawing import draw_text
from map_machine.feature.building import Building, draw_walls, BUILDING_SCALE
from map_machine.feature.road import Intersection, Road, RoadPart
from map_machine.figure import StyledFigure
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.geometry.flinger import Flinger
from map_machine.geometry.vector import Segment
@ -34,7 +35,7 @@ from map_machine.workspace import workspace
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
OPENSTREETMAP_CREDIT: str = "© OpenStreetMap contributors"
ROAD_PRIORITY: float = 40.0
class Map:
@ -63,7 +64,16 @@ class Map:
)
logging.info("Drawing ways...")
for figure in constructor.get_sorted_figures():
figures: list[StyledFigure] = constructor.get_sorted_figures()
top_figures: list[StyledFigure] = [
x for x in figures if x.line_style.priority >= ROAD_PRIORITY
]
bottom_figures: list[StyledFigure] = [
x for x in figures if x.line_style.priority < ROAD_PRIORITY
]
for figure in bottom_figures:
path_commands: str = figure.get_path(self.flinger)
if path_commands:
path: SVGPath = SVGPath(d=path_commands)
@ -72,6 +82,13 @@ class Map:
constructor.roads.draw(self.svg, self.flinger)
for figure in top_figures:
path_commands: str = figure.get_path(self.flinger)
if path_commands:
path: SVGPath = SVGPath(d=path_commands)
path.update(figure.line_style.style)
self.svg.add(path)
for tree in constructor.trees:
tree.draw(self.svg, self.flinger, self.scheme)
for crater in constructor.craters:
@ -207,13 +224,17 @@ class Map:
text_color: Color = Color("#888888")
outline_color: Color = Color("#FFFFFF")
for text, point in (
(
f"Data: {OPENSTREETMAP_CREDIT}",
credit_list: list[tuple[str, tuple[float, float]]] = [
(f"Rendering: {__project__}", (right_margin, bottom_margin))
]
if self.configuration.credit:
data_credit: tuple[str, tuple[float, float]] = (
f"Data: {self.configuration.credit}",
(right_margin, bottom_margin + font_size + vertical_spacing),
),
(f"Rendering: {__project__}", (right_margin, bottom_margin)),
):
)
credit_list.append(data_credit)
for text, point in credit_list:
for stroke_width, stroke, opacity in (
(3.0, outline_color, 0.7),
(1.0, None, 1.0),
@ -255,12 +276,19 @@ def render_map(arguments: argparse.Namespace) -> None:
if arguments.boundary_box:
boundary_box = BoundaryBox.from_text(arguments.boundary_box)
elif arguments.coordinates and arguments.size:
coordinates: np.ndarray = np.array(
list(map(float, arguments.coordinates.split(",")))
coordinates: Optional[np.ndarray] = None
for delimiter in ",", "/":
if delimiter in arguments.coordinates:
coordinates = np.array(
list(map(float, arguments.coordinates.split(delimiter)))
)
if len(coordinates) != 2:
fatal("Wrong number of coordinates.")
if coordinates is None or len(coordinates) != 2:
fatal("Wrong coordinates format.")
width, height = np.array(list(map(float, arguments.size.split(","))))
boundary_box = BoundaryBox.from_coordinates(
coordinates, configuration.zoom_level, width, height

41
map_machine/osm/tags.py Normal file
View file

@ -0,0 +1,41 @@
ROAD_VALUES: list[str] = [
"motorway",
"trunk",
"primary",
"secondary",
"tertiary",
"unclassified",
"residential",
"service",
]
HIGHWAY_VALUES: list[str] = ROAD_VALUES + [
"service_minor",
"road",
"pedestrian",
"living_street",
"bridleway",
"cycleway",
"footway",
"steps",
"path",
"track",
"raceway",
]
AEROWAY_VALUES: list[str] = [
"runway",
"taxiway",
]
RAILWAY_VALUES: list[str] = [
"rail",
"subway",
"light_rail",
"monorail",
"narrow_gauge",
"tram",
"funicular",
"miniature",
"preserved",
"construction",
"disused",
"abandoned",
]

View file

@ -1949,7 +1949,7 @@ node_icons:
- tags: {bus: "yes"}
add_shapes: [bus]
- tags: {motocar: "yes"}
- tags: {motorcar: "yes"}
add_shapes: [car]
- tags: {car: "yes"}
add_shapes: [car]
@ -2574,10 +2574,24 @@ ways:
stroke: "#BBBBBB"
priority: 42.0
- tags: {railway: construction}
style:
stroke-width: 2.0
stroke: "#000000"
stroke-dasharray: 6,3
opacity: 0.3
priority: 42.0
- tags: {railway: disused}
style:
stroke-width: 3.0
stroke: "#000000"
stroke-dasharray: 3,3
stroke-dasharray: 6,6
opacity: 0.3
priority: 42.0
- tags: {railway: abandoned}
style:
stroke-width: 3.0
stroke: "#000000"
stroke-dasharray: 6,9
opacity: 0.3
priority: 42.0

View file

@ -2,7 +2,6 @@
Command-line user interface.
"""
import argparse
import sys
from typing import Dict, List
from map_machine import __version__
@ -12,9 +11,6 @@ from map_machine.osm.osm_reader import STAGES_OF_DECAY
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
BOXES: str = " ▏▎▍▌▋▊▉"
BOXES_LENGTH: int = len(BOXES)
COMMAND_LINES: Dict[str, List[str]] = {
"render": ["render", "-b", "10.000,20.000,10.001,20.001"],
"render_with_tooltips": [
@ -392,36 +388,3 @@ def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None:
f"number of node and area selectors by {len(STAGES_OF_DECAY) + 1} "
f"times",
)
parser.add_argument(
"--no-lifecycle",
dest="lifecycle",
action="store_false",
help="don't add icons for lifecycle tags",
)
def progress_bar(
number: int, total: int, length: int = 20, step: int = 1000, text: str = ""
) -> 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)
:param text: short description
"""
if number == -1:
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)]
sys.stdout.write(
f"{str(int(int(ratio * 1000.0) / 10.0)):>3} % "
f"{fill_length * ''}{box}"
f"{int(length - fill_length - 1) * ' '}{text}\n\033[F"
)

View file

@ -1,13 +1,17 @@
"""
Test icon generation for nodes.
Tests check that for the given node described by tags, Map Machine generates
expected icons with expected colors.
"""
from typing import Dict, List, Set, Tuple
from typing import List, Set, Tuple
from pathlib import Path
from typing import Optional
from colour import Color
from map_machine.osm.osm_reader import Tags
from map_machine.pictogram.icon import IconSet, ShapeSpecification, Icon
from map_machine.pictogram.icon_collection import IconCollection
from tests import SCHEME, SHAPE_EXTRACTOR, workspace
@ -43,7 +47,7 @@ def test_icons_by_name() -> None:
assert (path / "LICENSE").is_file()
def get_icon(tags: Dict[str, str]) -> IconSet:
def get_icon(tags: Tags) -> IconSet:
"""Construct icon from tags."""
processed: Set[str] = set()
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
@ -71,11 +75,13 @@ def test_no_icons_but_color() -> None:
def check_icon_set(
icon: IconSet,
tags: Tags,
main_specification: List[Tuple[str, Optional[Color]]],
extra_specifications: List[List[Tuple[str, Optional[Color]]]],
) -> None:
"""Check icon set using simple specification."""
icon: IconSet = get_icon(tags)
if not main_specification:
assert icon.main_icon.is_default()
else:
@ -104,17 +110,17 @@ def test_icon() -> None:
Tags that should be visualized with single main icon and without extra
icons.
"""
icon: IconSet = get_icon({"natural": "tree"})
check_icon_set(icon, [("tree", Color("#98AC64"))], [])
check_icon_set({"natural": "tree"}, [("tree", Color("#98AC64"))], [])
def test_icon_1_extra() -> None:
"""
Tags that should be visualized with single main icon and single extra icon.
"""
icon: IconSet = get_icon({"barrier": "gate", "access": "private"})
check_icon_set(
icon, [("gate", DEFAULT_COLOR)], [[("lock_with_keyhole", EXTRA_COLOR)]]
{"barrier": "gate", "access": "private"},
[("gate", DEFAULT_COLOR)],
[[("lock_with_keyhole", EXTRA_COLOR)]],
)
@ -122,11 +128,8 @@ def test_icon_2_extra() -> None:
"""
Tags that should be visualized with single main icon and two extra icons.
"""
icon: IconSet = get_icon(
{"barrier": "gate", "access": "private", "bicycle": "yes"}
)
check_icon_set(
icon,
{"barrier": "gate", "access": "private", "bicycle": "yes"},
[("gate", DEFAULT_COLOR)],
[
[("bicycle", EXTRA_COLOR)],
@ -139,17 +142,17 @@ def test_no_icon_1_extra() -> None:
"""
Tags that should be visualized with default main icon and single extra icon.
"""
icon: IconSet = get_icon({"access": "private"})
check_icon_set(icon, [], [[("lock_with_keyhole", EXTRA_COLOR)]])
check_icon_set(
{"access": "private"}, [], [[("lock_with_keyhole", EXTRA_COLOR)]]
)
def test_no_icon_2_extra() -> None:
"""
Tags that should be visualized with default main icon and two extra icons.
"""
icon: IconSet = get_icon({"access": "private", "bicycle": "yes"})
check_icon_set(
icon,
{"access": "private", "bicycle": "yes"},
[],
[[("bicycle", EXTRA_COLOR)], [("lock_with_keyhole", EXTRA_COLOR)]],
)
@ -159,9 +162,8 @@ def test_icon_regex() -> None:
"""
Tags that should be visualized with default main icon and single extra icon.
"""
icon: IconSet = get_icon({"traffic_sign": "maxspeed", "maxspeed": "42"})
check_icon_set(
icon,
{"traffic_sign": "maxspeed", "maxspeed": "42"},
[("circle_11", DEFAULT_COLOR), ("digit_4", WHITE), ("digit_2", WHITE)],
[],
)
@ -169,22 +171,22 @@ def test_icon_regex() -> None:
def test_vending_machine() -> None:
"""
Check that specific vending machines doesn't render with generic icon.
Check that specific vending machines aren't rendered with generic icon.
See https://github.com/enzet/map-machine/issues/132
"""
check_icon_set(
get_icon({"amenity": "vending_machine"}),
{"amenity": "vending_machine"},
[("vending_machine", DEFAULT_COLOR)],
[],
)
check_icon_set(
get_icon({"amenity": "vending_machine", "vending": "drinks"}),
{"amenity": "vending_machine", "vending": "drinks"},
[("vending_bottle", DEFAULT_COLOR)],
[],
)
check_icon_set(
get_icon({"vending": "drinks"}),
{"vending": "drinks"},
[("vending_bottle", DEFAULT_COLOR)],
[],
)

View file

@ -1,15 +1,25 @@
"""
Test map generation for ways.
Tests check that for the given ways described by tags, Map Machine generates
expected figures in the expected order.
"""
import numpy as np
from map_machine.constructor import Constructor
from map_machine.figure import Figure
from map_machine.geometry.boundary_box import BoundaryBox
from map_machine.geometry.flinger import Flinger
from map_machine.map_configuration import MapConfiguration
from map_machine.constructor import Constructor
from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode
from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode, Tags
from tests import SCHEME, SHAPE_EXTRACTOR
def get_constructor(osm_data: OSMData) -> Constructor:
"""
Get custom constructor for bounds (-0.01, -0.01, 0.01, 0.01) and zoom level
18.
"""
flinger: Flinger = Flinger(
BoundaryBox(-0.01, -0.01, 0.01, 0.01), 18, osm_data.equator_length
)
@ -20,24 +30,26 @@ def get_constructor(osm_data: OSMData) -> Constructor:
return constructor
def create_way(osm_data: OSMData, tags: Tags, index: int) -> None:
"""Create simple OSM way with two arbitrary nodes."""
nodes: list[OSMNode] = [
OSMNode({}, 1, np.array((-0.01, -0.01))),
OSMNode({}, 2, np.array((0.01, 0.01))),
]
for node in nodes:
osm_data.add_node(node)
osm_data.add_way(OSMWay(tags, index, nodes))
def test_river_and_wood() -> None:
"""
Check that river is above the wood.
See https://github.com/enzet/map-machine/issues/126
"""
nodes_1: list[OSMNode] = [
OSMNode({}, 1, np.array((-0.01, -0.01))),
OSMNode({}, 2, np.array((0.01, 0.01))),
]
nodes_2: list[OSMNode] = [
OSMNode({}, 3, np.array((-0.01, -0.01))),
OSMNode({}, 4, np.array((0.01, 0.01))),
]
osm_data: OSMData = OSMData()
osm_data.add_way(OSMWay({"natural": "wood"}, 1, nodes_1))
osm_data.add_way(OSMWay({"waterway": "river"}, 2, nodes_2))
create_way(osm_data, {"natural": "wood"}, 1)
create_way(osm_data, {"waterway": "river"}, 2)
figures: list[Figure] = get_constructor(osm_data).get_sorted_figures()
@ -46,6 +58,19 @@ def test_river_and_wood() -> None:
assert figures[1].tags["waterway"] == "river"
def test_placement_and_lanes() -> None:
"""
Check that `placement` tag is processed correctly when `lanes` tag is not
specified.
See https://github.com/enzet/map-machine/issues/128
"""
osm_data: OSMData = OSMData()
create_way(osm_data, {"highway": "motorway", "placement": "right_of:2"}, 1)
get_constructor(osm_data)
def test_empty_ways() -> None:
"""Ways without nodes."""
osm_data: OSMData = OSMData()